need to add the files too
This commit is contained in:
parent
12797498bc
commit
daa2b78a9c
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2014 Google Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,438 @@
|
||||||
|
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package metadata provides access to Google Compute Engine (GCE)
|
||||||
|
// metadata and API service accounts.
|
||||||
|
//
|
||||||
|
// This package is a wrapper around the GCE metadata service,
|
||||||
|
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||||
|
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/net/context/ctxhttp"
|
||||||
|
|
||||||
|
"cloud.google.com/go/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// metadataIP is the documented metadata server IP address.
|
||||||
|
metadataIP = "169.254.169.254"
|
||||||
|
|
||||||
|
// metadataHostEnv is the environment variable specifying the
|
||||||
|
// GCE metadata hostname. If empty, the default value of
|
||||||
|
// metadataIP ("169.254.169.254") is used instead.
|
||||||
|
// This is variable name is not defined by any spec, as far as
|
||||||
|
// I know; it was made up for the Go package.
|
||||||
|
metadataHostEnv = "GCE_METADATA_HOST"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cachedValue struct {
|
||||||
|
k string
|
||||||
|
trim bool
|
||||||
|
mu sync.Mutex
|
||||||
|
v string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||||
|
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||||
|
instID = &cachedValue{k: "instance/id", trim: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
metaClient = &http.Client{
|
||||||
|
Transport: &internal.Transport{
|
||||||
|
Base: &http.Transport{
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 2 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).Dial,
|
||||||
|
ResponseHeaderTimeout: 2 * time.Second,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subscribeClient = &http.Client{
|
||||||
|
Transport: &internal.Transport{
|
||||||
|
Base: &http.Transport{
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 2 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).Dial,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotDefinedError is returned when requested metadata is not defined.
|
||||||
|
//
|
||||||
|
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||||
|
//
|
||||||
|
// This error is not returned if the value is defined to be the empty
|
||||||
|
// string.
|
||||||
|
type NotDefinedError string
|
||||||
|
|
||||||
|
func (suffix NotDefinedError) Error() string {
|
||||||
|
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a value from the metadata service.
|
||||||
|
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||||
|
//
|
||||||
|
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||||
|
// 169.254.169.254 will be used instead.
|
||||||
|
//
|
||||||
|
// If the requested metadata is not defined, the returned error will
|
||||||
|
// be of type NotDefinedError.
|
||||||
|
func Get(suffix string) (string, error) {
|
||||||
|
val, _, err := getETag(metaClient, suffix)
|
||||||
|
return val, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getETag returns a value from the metadata service as well as the associated
|
||||||
|
// ETag using the provided client. This func is otherwise equivalent to Get.
|
||||||
|
func getETag(client *http.Client, suffix string) (value, etag string, err error) {
|
||||||
|
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||||
|
// a container, which is an important use-case for local testing of cloud
|
||||||
|
// deployments. To enable spoofing of the metadata service, the environment
|
||||||
|
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||||
|
// requests shall go.
|
||||||
|
host := os.Getenv(metadataHostEnv)
|
||||||
|
if host == "" {
|
||||||
|
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||||
|
// binaries built with the "netgo" tag and without cgo won't
|
||||||
|
// know the search suffix for "metadata" is
|
||||||
|
// ".google.internal", and this IP address is documented as
|
||||||
|
// being stable anyway.
|
||||||
|
host = metadataIP
|
||||||
|
}
|
||||||
|
url := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
req.Header.Set("Metadata-Flavor", "Google")
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode == http.StatusNotFound {
|
||||||
|
return "", "", NotDefinedError(suffix)
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
|
||||||
|
}
|
||||||
|
all, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return string(all), res.Header.Get("Etag"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTrimmed(suffix string) (s string, err error) {
|
||||||
|
s, err = Get(suffix)
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cachedValue) get() (v string, err error) {
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.mu.Lock()
|
||||||
|
if c.v != "" {
|
||||||
|
return c.v, nil
|
||||||
|
}
|
||||||
|
if c.trim {
|
||||||
|
v, err = getTrimmed(c.k)
|
||||||
|
} else {
|
||||||
|
v, err = Get(c.k)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
c.v = v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
onGCEOnce sync.Once
|
||||||
|
onGCE bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||||
|
func OnGCE() bool {
|
||||||
|
onGCEOnce.Do(initOnGCE)
|
||||||
|
return onGCE
|
||||||
|
}
|
||||||
|
|
||||||
|
func initOnGCE() {
|
||||||
|
onGCE = testOnGCE()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOnGCE() bool {
|
||||||
|
// The user explicitly said they're on GCE, so trust them.
|
||||||
|
if os.Getenv(metadataHostEnv) != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resc := make(chan bool, 2)
|
||||||
|
|
||||||
|
// Try two strategies in parallel.
|
||||||
|
// See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194
|
||||||
|
go func() {
|
||||||
|
res, err := ctxhttp.Get(ctx, metaClient, "http://"+metadataIP)
|
||||||
|
if err != nil {
|
||||||
|
resc <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
addrs, err := net.LookupHost("metadata.google.internal")
|
||||||
|
if err != nil || len(addrs) == 0 {
|
||||||
|
resc <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resc <- strsContains(addrs, metadataIP)
|
||||||
|
}()
|
||||||
|
|
||||||
|
tryHarder := systemInfoSuggestsGCE()
|
||||||
|
if tryHarder {
|
||||||
|
res := <-resc
|
||||||
|
if res {
|
||||||
|
// The first strategy succeeded, so let's use it.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Wait for either the DNS or metadata server probe to
|
||||||
|
// contradict the other one and say we are running on
|
||||||
|
// GCE. Give it a lot of time to do so, since the system
|
||||||
|
// info already suggests we're running on a GCE BIOS.
|
||||||
|
timer := time.NewTimer(5 * time.Second)
|
||||||
|
defer timer.Stop()
|
||||||
|
select {
|
||||||
|
case res = <-resc:
|
||||||
|
return res
|
||||||
|
case <-timer.C:
|
||||||
|
// Too slow. Who knows what this system is.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's no hint from the system info that we're running on
|
||||||
|
// GCE, so use the first probe's result as truth, whether it's
|
||||||
|
// true or false. The goal here is to optimize for speed for
|
||||||
|
// users who are NOT running on GCE. We can't assume that
|
||||||
|
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||||
|
// address is fast. Worst case this should return when the
|
||||||
|
// metaClient's Transport.ResponseHeaderTimeout or
|
||||||
|
// Transport.Dial.Timeout fires (in two seconds).
|
||||||
|
return <-resc
|
||||||
|
}
|
||||||
|
|
||||||
|
// systemInfoSuggestsGCE reports whether the local system (without
|
||||||
|
// doing network requests) suggests that we're running on GCE. If this
|
||||||
|
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||||
|
// server.
|
||||||
|
func systemInfoSuggestsGCE() bool {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
// We don't have any non-Linux clues available, at least yet.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
|
||||||
|
name := strings.TrimSpace(string(slurp))
|
||||||
|
return name == "Google" || name == "Google Compute Engine"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe subscribes to a value from the metadata service.
|
||||||
|
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||||
|
// The suffix may contain query parameters.
|
||||||
|
//
|
||||||
|
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||||
|
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||||
|
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||||
|
// is deleted. Subscribe returns the error value returned from the last call to
|
||||||
|
// fn, which may be nil when ok == false.
|
||||||
|
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||||
|
const failedSubscribeSleep = time.Second * 5
|
||||||
|
|
||||||
|
// First check to see if the metadata value exists at all.
|
||||||
|
val, lastETag, err := getETag(subscribeClient, suffix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fn(val, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := true
|
||||||
|
if strings.ContainsRune(suffix, '?') {
|
||||||
|
suffix += "&wait_for_change=true&last_etag="
|
||||||
|
} else {
|
||||||
|
suffix += "?wait_for_change=true&last_etag="
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag))
|
||||||
|
if err != nil {
|
||||||
|
if _, deleted := err.(NotDefinedError); !deleted {
|
||||||
|
time.Sleep(failedSubscribeSleep)
|
||||||
|
continue // Retry on other errors.
|
||||||
|
}
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
lastETag = etag
|
||||||
|
|
||||||
|
if err := fn(val, ok); err != nil || !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectID returns the current instance's project ID string.
|
||||||
|
func ProjectID() (string, error) { return projID.get() }
|
||||||
|
|
||||||
|
// NumericProjectID returns the current instance's numeric project ID.
|
||||||
|
func NumericProjectID() (string, error) { return projNum.get() }
|
||||||
|
|
||||||
|
// InternalIP returns the instance's primary internal IP address.
|
||||||
|
func InternalIP() (string, error) {
|
||||||
|
return getTrimmed("instance/network-interfaces/0/ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalIP returns the instance's primary external (public) IP address.
|
||||||
|
func ExternalIP() (string, error) {
|
||||||
|
return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hostname returns the instance's hostname. This will be of the form
|
||||||
|
// "<instanceID>.c.<projID>.internal".
|
||||||
|
func Hostname() (string, error) {
|
||||||
|
return getTrimmed("instance/hostname")
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceTags returns the list of user-defined instance tags,
|
||||||
|
// assigned when initially creating a GCE instance.
|
||||||
|
func InstanceTags() ([]string, error) {
|
||||||
|
var s []string
|
||||||
|
j, err := Get("instance/tags")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceID returns the current VM's numeric instance ID.
|
||||||
|
func InstanceID() (string, error) {
|
||||||
|
return instID.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceName returns the current VM's instance ID string.
|
||||||
|
func InstanceName() (string, error) {
|
||||||
|
host, err := Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.Split(host, ".")[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||||
|
func Zone() (string, error) {
|
||||||
|
zone, err := getTrimmed("instance/zone")
|
||||||
|
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceAttributes returns the list of user-defined attributes,
|
||||||
|
// assigned when initially creating a GCE VM instance. The value of an
|
||||||
|
// attribute can be obtained with InstanceAttributeValue.
|
||||||
|
func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }
|
||||||
|
|
||||||
|
// ProjectAttributes returns the list of user-defined attributes
|
||||||
|
// applying to the project as a whole, not just this VM. The value of
|
||||||
|
// an attribute can be obtained with ProjectAttributeValue.
|
||||||
|
func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
|
||||||
|
|
||||||
|
func lines(suffix string) ([]string, error) {
|
||||||
|
j, err := Get(suffix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||||
|
for i := range s {
|
||||||
|
s[i] = strings.TrimSpace(s[i])
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstanceAttributeValue returns the value of the provided VM
|
||||||
|
// instance attribute.
|
||||||
|
//
|
||||||
|
// If the requested attribute is not defined, the returned error will
|
||||||
|
// be of type NotDefinedError.
|
||||||
|
//
|
||||||
|
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||||
|
// defined to be the empty string.
|
||||||
|
func InstanceAttributeValue(attr string) (string, error) {
|
||||||
|
return Get("instance/attributes/" + attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectAttributeValue returns the value of the provided
|
||||||
|
// project attribute.
|
||||||
|
//
|
||||||
|
// If the requested attribute is not defined, the returned error will
|
||||||
|
// be of type NotDefinedError.
|
||||||
|
//
|
||||||
|
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||||
|
// defined to be the empty string.
|
||||||
|
func ProjectAttributeValue(attr string) (string, error) {
|
||||||
|
return Get("project/attributes/" + attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scopes returns the service account scopes for the given account.
|
||||||
|
// The account may be empty or the string "default" to use the instance's
|
||||||
|
// main account.
|
||||||
|
func Scopes(serviceAccount string) ([]string, error) {
|
||||||
|
if serviceAccount == "" {
|
||||||
|
serviceAccount = "default"
|
||||||
|
}
|
||||||
|
return lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||||
|
}
|
||||||
|
|
||||||
|
func strsContains(ss []string, s string) bool {
|
||||||
|
for _, v := range ss {
|
||||||
|
if v == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package internal provides support for the cloud packages.
|
||||||
|
//
|
||||||
|
// Users should not import this package directly.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const userAgent = "gcloud-golang/0.1"
|
||||||
|
|
||||||
|
// Transport is an http.RoundTripper that appends Google Cloud client's
|
||||||
|
// user-agent to the original request's user-agent header.
|
||||||
|
type Transport struct {
|
||||||
|
// TODO(bradfitz): delete internal.Transport. It's too wrappy for what it does.
|
||||||
|
// Do User-Agent some other way.
|
||||||
|
|
||||||
|
// Base is the actual http.RoundTripper
|
||||||
|
// requests will use. It must not be nil.
|
||||||
|
Base http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip appends a user-agent to the existing user-agent
|
||||||
|
// header and delegates the request to the base http.RoundTripper.
|
||||||
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
req = cloneRequest(req)
|
||||||
|
ua := req.Header.Get("User-Agent")
|
||||||
|
if ua == "" {
|
||||||
|
ua = userAgent
|
||||||
|
} else {
|
||||||
|
ua = fmt.Sprintf("%s %s", ua, userAgent)
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", ua)
|
||||||
|
return t.Base.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cloneRequest returns a clone of the provided *http.Request.
|
||||||
|
// The clone is a shallow copy of the struct and its Header map.
|
||||||
|
func cloneRequest(r *http.Request) *http.Request {
|
||||||
|
// shallow copy of the struct
|
||||||
|
r2 := new(http.Request)
|
||||||
|
*r2 = *r
|
||||||
|
// deep copy of the Header
|
||||||
|
r2.Header = make(http.Header)
|
||||||
|
for k, s := range r.Header {
|
||||||
|
r2.Header[k] = s
|
||||||
|
}
|
||||||
|
return r2
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
gax "github.com/googleapis/gax-go"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Retry calls the supplied function f repeatedly according to the provided
|
||||||
|
// backoff parameters. It returns when one of the following occurs:
|
||||||
|
// When f's first return value is true, Retry immediately returns with f's second
|
||||||
|
// return value.
|
||||||
|
// When the provided context is done, Retry returns with ctx.Err().
|
||||||
|
func Retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error)) error {
|
||||||
|
return retry(ctx, bo, f, gax.Sleep)
|
||||||
|
}
|
||||||
|
|
||||||
|
func retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error),
|
||||||
|
sleep func(context.Context, time.Duration) error) error {
|
||||||
|
var lastErr error
|
||||||
|
for {
|
||||||
|
stop, err := f()
|
||||||
|
if stop {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Remember the last "real" error from f.
|
||||||
|
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
p := bo.Pause()
|
||||||
|
if cerr := sleep(ctx, p); cerr != nil {
|
||||||
|
if lastErr != nil {
|
||||||
|
return fmt.Errorf("%v; last function err: %v", cerr, lastErr)
|
||||||
|
}
|
||||||
|
return cerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Microsoft Corporation
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,12 @@
|
||||||
|
# go-ansiterm
|
||||||
|
|
||||||
|
This is a cross platform Ansi Terminal Emulation library. It reads a stream of Ansi characters and produces the appropriate function calls. The results of the function calls are platform dependent.
|
||||||
|
|
||||||
|
For example the parser might receive "ESC, [, A" as a stream of three characters. This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU). The parser then calls the cursor up function (CUU()) on an event handler. The event handler determines what platform specific work must be done to cause the cursor to move up one position.
|
||||||
|
|
||||||
|
The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go).
|
||||||
|
|
||||||
|
See parser_test.go for examples exercising the state machine and generating appropriate function calls.
|
||||||
|
|
||||||
|
-----
|
||||||
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
@ -0,0 +1,188 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
const LogEnv = "DEBUG_TERMINAL"
|
||||||
|
|
||||||
|
// ANSI constants
|
||||||
|
// References:
|
||||||
|
// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm
|
||||||
|
// -- http://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||||
|
// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
|
||||||
|
// -- http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
// -- http://vt100.net/emu/dec_ansi_parser
|
||||||
|
// -- http://vt100.net/emu/vt500_parser.svg
|
||||||
|
// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
|
// -- http://www.inwap.com/pdp10/ansicode.txt
|
||||||
|
const (
|
||||||
|
// ECMA-48 Set Graphics Rendition
|
||||||
|
// Note:
|
||||||
|
// -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved
|
||||||
|
// -- Fonts could possibly be supported via SetCurrentConsoleFontEx
|
||||||
|
// -- Windows does not expose the per-window cursor (i.e., caret) blink times
|
||||||
|
ANSI_SGR_RESET = 0
|
||||||
|
ANSI_SGR_BOLD = 1
|
||||||
|
ANSI_SGR_DIM = 2
|
||||||
|
_ANSI_SGR_ITALIC = 3
|
||||||
|
ANSI_SGR_UNDERLINE = 4
|
||||||
|
_ANSI_SGR_BLINKSLOW = 5
|
||||||
|
_ANSI_SGR_BLINKFAST = 6
|
||||||
|
ANSI_SGR_REVERSE = 7
|
||||||
|
_ANSI_SGR_INVISIBLE = 8
|
||||||
|
_ANSI_SGR_LINETHROUGH = 9
|
||||||
|
_ANSI_SGR_FONT_00 = 10
|
||||||
|
_ANSI_SGR_FONT_01 = 11
|
||||||
|
_ANSI_SGR_FONT_02 = 12
|
||||||
|
_ANSI_SGR_FONT_03 = 13
|
||||||
|
_ANSI_SGR_FONT_04 = 14
|
||||||
|
_ANSI_SGR_FONT_05 = 15
|
||||||
|
_ANSI_SGR_FONT_06 = 16
|
||||||
|
_ANSI_SGR_FONT_07 = 17
|
||||||
|
_ANSI_SGR_FONT_08 = 18
|
||||||
|
_ANSI_SGR_FONT_09 = 19
|
||||||
|
_ANSI_SGR_FONT_10 = 20
|
||||||
|
_ANSI_SGR_DOUBLEUNDERLINE = 21
|
||||||
|
ANSI_SGR_BOLD_DIM_OFF = 22
|
||||||
|
_ANSI_SGR_ITALIC_OFF = 23
|
||||||
|
ANSI_SGR_UNDERLINE_OFF = 24
|
||||||
|
_ANSI_SGR_BLINK_OFF = 25
|
||||||
|
_ANSI_SGR_RESERVED_00 = 26
|
||||||
|
ANSI_SGR_REVERSE_OFF = 27
|
||||||
|
_ANSI_SGR_INVISIBLE_OFF = 28
|
||||||
|
_ANSI_SGR_LINETHROUGH_OFF = 29
|
||||||
|
ANSI_SGR_FOREGROUND_BLACK = 30
|
||||||
|
ANSI_SGR_FOREGROUND_RED = 31
|
||||||
|
ANSI_SGR_FOREGROUND_GREEN = 32
|
||||||
|
ANSI_SGR_FOREGROUND_YELLOW = 33
|
||||||
|
ANSI_SGR_FOREGROUND_BLUE = 34
|
||||||
|
ANSI_SGR_FOREGROUND_MAGENTA = 35
|
||||||
|
ANSI_SGR_FOREGROUND_CYAN = 36
|
||||||
|
ANSI_SGR_FOREGROUND_WHITE = 37
|
||||||
|
_ANSI_SGR_RESERVED_01 = 38
|
||||||
|
ANSI_SGR_FOREGROUND_DEFAULT = 39
|
||||||
|
ANSI_SGR_BACKGROUND_BLACK = 40
|
||||||
|
ANSI_SGR_BACKGROUND_RED = 41
|
||||||
|
ANSI_SGR_BACKGROUND_GREEN = 42
|
||||||
|
ANSI_SGR_BACKGROUND_YELLOW = 43
|
||||||
|
ANSI_SGR_BACKGROUND_BLUE = 44
|
||||||
|
ANSI_SGR_BACKGROUND_MAGENTA = 45
|
||||||
|
ANSI_SGR_BACKGROUND_CYAN = 46
|
||||||
|
ANSI_SGR_BACKGROUND_WHITE = 47
|
||||||
|
_ANSI_SGR_RESERVED_02 = 48
|
||||||
|
ANSI_SGR_BACKGROUND_DEFAULT = 49
|
||||||
|
// 50 - 65: Unsupported
|
||||||
|
|
||||||
|
ANSI_MAX_CMD_LENGTH = 4096
|
||||||
|
|
||||||
|
MAX_INPUT_EVENTS = 128
|
||||||
|
DEFAULT_WIDTH = 80
|
||||||
|
DEFAULT_HEIGHT = 24
|
||||||
|
|
||||||
|
ANSI_BEL = 0x07
|
||||||
|
ANSI_BACKSPACE = 0x08
|
||||||
|
ANSI_TAB = 0x09
|
||||||
|
ANSI_LINE_FEED = 0x0A
|
||||||
|
ANSI_VERTICAL_TAB = 0x0B
|
||||||
|
ANSI_FORM_FEED = 0x0C
|
||||||
|
ANSI_CARRIAGE_RETURN = 0x0D
|
||||||
|
ANSI_ESCAPE_PRIMARY = 0x1B
|
||||||
|
ANSI_ESCAPE_SECONDARY = 0x5B
|
||||||
|
ANSI_OSC_STRING_ENTRY = 0x5D
|
||||||
|
ANSI_COMMAND_FIRST = 0x40
|
||||||
|
ANSI_COMMAND_LAST = 0x7E
|
||||||
|
DCS_ENTRY = 0x90
|
||||||
|
CSI_ENTRY = 0x9B
|
||||||
|
OSC_STRING = 0x9D
|
||||||
|
ANSI_PARAMETER_SEP = ";"
|
||||||
|
ANSI_CMD_G0 = '('
|
||||||
|
ANSI_CMD_G1 = ')'
|
||||||
|
ANSI_CMD_G2 = '*'
|
||||||
|
ANSI_CMD_G3 = '+'
|
||||||
|
ANSI_CMD_DECPNM = '>'
|
||||||
|
ANSI_CMD_DECPAM = '='
|
||||||
|
ANSI_CMD_OSC = ']'
|
||||||
|
ANSI_CMD_STR_TERM = '\\'
|
||||||
|
|
||||||
|
KEY_CONTROL_PARAM_2 = ";2"
|
||||||
|
KEY_CONTROL_PARAM_3 = ";3"
|
||||||
|
KEY_CONTROL_PARAM_4 = ";4"
|
||||||
|
KEY_CONTROL_PARAM_5 = ";5"
|
||||||
|
KEY_CONTROL_PARAM_6 = ";6"
|
||||||
|
KEY_CONTROL_PARAM_7 = ";7"
|
||||||
|
KEY_CONTROL_PARAM_8 = ";8"
|
||||||
|
KEY_ESC_CSI = "\x1B["
|
||||||
|
KEY_ESC_N = "\x1BN"
|
||||||
|
KEY_ESC_O = "\x1BO"
|
||||||
|
|
||||||
|
FILL_CHARACTER = ' '
|
||||||
|
)
|
||||||
|
|
||||||
|
func getByteRange(start byte, end byte) []byte {
|
||||||
|
bytes := make([]byte, 0, 32)
|
||||||
|
for i := start; i <= end; i++ {
|
||||||
|
bytes = append(bytes, byte(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
var toGroundBytes = getToGroundBytes()
|
||||||
|
var executors = getExecuteBytes()
|
||||||
|
|
||||||
|
// SPACE 20+A0 hex Always and everywhere a blank space
|
||||||
|
// Intermediate 20-2F hex !"#$%&'()*+,-./
|
||||||
|
var intermeds = getByteRange(0x20, 0x2F)
|
||||||
|
|
||||||
|
// Parameters 30-3F hex 0123456789:;<=>?
|
||||||
|
// CSI Parameters 30-39, 3B hex 0123456789;
|
||||||
|
var csiParams = getByteRange(0x30, 0x3F)
|
||||||
|
|
||||||
|
var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...)
|
||||||
|
|
||||||
|
// Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
|
||||||
|
var upperCase = getByteRange(0x40, 0x5F)
|
||||||
|
|
||||||
|
// Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
|
||||||
|
var lowerCase = getByteRange(0x60, 0x7E)
|
||||||
|
|
||||||
|
// Alphabetics 40-7E hex (all of upper and lower case)
|
||||||
|
var alphabetics = append(upperCase, lowerCase...)
|
||||||
|
|
||||||
|
var printables = getByteRange(0x20, 0x7F)
|
||||||
|
|
||||||
|
var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E)
|
||||||
|
var escapeToGroundBytes = getEscapeToGroundBytes()
|
||||||
|
|
||||||
|
// See http://www.vt100.net/emu/vt500_parser.png for description of the complex
|
||||||
|
// byte ranges below
|
||||||
|
|
||||||
|
func getEscapeToGroundBytes() []byte {
|
||||||
|
escapeToGroundBytes := getByteRange(0x30, 0x4F)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, 0x59)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, 0x5A)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, 0x5C)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...)
|
||||||
|
return escapeToGroundBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExecuteBytes() []byte {
|
||||||
|
executeBytes := getByteRange(0x00, 0x17)
|
||||||
|
executeBytes = append(executeBytes, 0x19)
|
||||||
|
executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...)
|
||||||
|
return executeBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func getToGroundBytes() []byte {
|
||||||
|
groundBytes := []byte{0x18}
|
||||||
|
groundBytes = append(groundBytes, 0x1A)
|
||||||
|
groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...)
|
||||||
|
groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...)
|
||||||
|
groundBytes = append(groundBytes, 0x99)
|
||||||
|
groundBytes = append(groundBytes, 0x9A)
|
||||||
|
groundBytes = append(groundBytes, 0x9C)
|
||||||
|
return groundBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 7F hex Always and everywhere ignored
|
||||||
|
// C1 Control 80-9F hex 32 additional control characters
|
||||||
|
// G1 Displayable A1-FE hex 94 additional displayable characters
|
||||||
|
// Special A0+FF hex Same as SPACE and DELETE
|
|
@ -0,0 +1,7 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type ansiContext struct {
|
||||||
|
currentChar byte
|
||||||
|
paramBuffer []byte
|
||||||
|
interBuffer []byte
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type csiEntryState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiEntryState) Handle(b byte) (s state, e error) {
|
||||||
|
logger.Infof("CsiEntry::Handle %#x", b)
|
||||||
|
|
||||||
|
nextState, err := csiState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(alphabetics, b):
|
||||||
|
return csiState.parser.ground, nil
|
||||||
|
case sliceContains(csiCollectables, b):
|
||||||
|
return csiState.parser.csiParam, nil
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return csiState, csiState.parser.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return csiState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiEntryState) Transition(s state) error {
|
||||||
|
logger.Infof("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name())
|
||||||
|
csiState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case csiState.parser.ground:
|
||||||
|
return csiState.parser.csiDispatch()
|
||||||
|
case csiState.parser.csiParam:
|
||||||
|
switch {
|
||||||
|
case sliceContains(csiParams, csiState.parser.context.currentChar):
|
||||||
|
csiState.parser.collectParam()
|
||||||
|
case sliceContains(intermeds, csiState.parser.context.currentChar):
|
||||||
|
csiState.parser.collectInter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiEntryState) Enter() error {
|
||||||
|
csiState.parser.clear()
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type csiParamState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiParamState) Handle(b byte) (s state, e error) {
|
||||||
|
logger.Infof("CsiParam::Handle %#x", b)
|
||||||
|
|
||||||
|
nextState, err := csiState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(alphabetics, b):
|
||||||
|
return csiState.parser.ground, nil
|
||||||
|
case sliceContains(csiCollectables, b):
|
||||||
|
csiState.parser.collectParam()
|
||||||
|
return csiState, nil
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return csiState, csiState.parser.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return csiState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiParamState) Transition(s state) error {
|
||||||
|
logger.Infof("CsiParam::Transition %s --> %s", csiState.Name(), s.Name())
|
||||||
|
csiState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case csiState.parser.ground:
|
||||||
|
return csiState.parser.csiDispatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type escapeIntermediateState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeIntermediateState) Handle(b byte) (s state, e error) {
|
||||||
|
logger.Infof("escapeIntermediateState::Handle %#x", b)
|
||||||
|
nextState, err := escState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(intermeds, b):
|
||||||
|
return escState, escState.parser.collectInter()
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return escState, escState.parser.execute()
|
||||||
|
case sliceContains(escapeIntermediateToGroundBytes, b):
|
||||||
|
return escState.parser.ground, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return escState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeIntermediateState) Transition(s state) error {
|
||||||
|
logger.Infof("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name())
|
||||||
|
escState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case escState.parser.ground:
|
||||||
|
return escState.parser.escDispatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type escapeState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeState) Handle(b byte) (s state, e error) {
|
||||||
|
logger.Infof("escapeState::Handle %#x", b)
|
||||||
|
nextState, err := escState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case b == ANSI_ESCAPE_SECONDARY:
|
||||||
|
return escState.parser.csiEntry, nil
|
||||||
|
case b == ANSI_OSC_STRING_ENTRY:
|
||||||
|
return escState.parser.oscString, nil
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return escState, escState.parser.execute()
|
||||||
|
case sliceContains(escapeToGroundBytes, b):
|
||||||
|
return escState.parser.ground, nil
|
||||||
|
case sliceContains(intermeds, b):
|
||||||
|
return escState.parser.escapeIntermediate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return escState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeState) Transition(s state) error {
|
||||||
|
logger.Infof("Escape::Transition %s --> %s", escState.Name(), s.Name())
|
||||||
|
escState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case escState.parser.ground:
|
||||||
|
return escState.parser.escDispatch()
|
||||||
|
case escState.parser.escapeIntermediate:
|
||||||
|
return escState.parser.collectInter()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeState) Enter() error {
|
||||||
|
escState.parser.clear()
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type AnsiEventHandler interface {
|
||||||
|
// Print
|
||||||
|
Print(b byte) error
|
||||||
|
|
||||||
|
// Execute C0 commands
|
||||||
|
Execute(b byte) error
|
||||||
|
|
||||||
|
// CUrsor Up
|
||||||
|
CUU(int) error
|
||||||
|
|
||||||
|
// CUrsor Down
|
||||||
|
CUD(int) error
|
||||||
|
|
||||||
|
// CUrsor Forward
|
||||||
|
CUF(int) error
|
||||||
|
|
||||||
|
// CUrsor Backward
|
||||||
|
CUB(int) error
|
||||||
|
|
||||||
|
// Cursor to Next Line
|
||||||
|
CNL(int) error
|
||||||
|
|
||||||
|
// Cursor to Previous Line
|
||||||
|
CPL(int) error
|
||||||
|
|
||||||
|
// Cursor Horizontal position Absolute
|
||||||
|
CHA(int) error
|
||||||
|
|
||||||
|
// Vertical line Position Absolute
|
||||||
|
VPA(int) error
|
||||||
|
|
||||||
|
// CUrsor Position
|
||||||
|
CUP(int, int) error
|
||||||
|
|
||||||
|
// Horizontal and Vertical Position (depends on PUM)
|
||||||
|
HVP(int, int) error
|
||||||
|
|
||||||
|
// Text Cursor Enable Mode
|
||||||
|
DECTCEM(bool) error
|
||||||
|
|
||||||
|
// Origin Mode
|
||||||
|
DECOM(bool) error
|
||||||
|
|
||||||
|
// 132 Column Mode
|
||||||
|
DECCOLM(bool) error
|
||||||
|
|
||||||
|
// Erase in Display
|
||||||
|
ED(int) error
|
||||||
|
|
||||||
|
// Erase in Line
|
||||||
|
EL(int) error
|
||||||
|
|
||||||
|
// Insert Line
|
||||||
|
IL(int) error
|
||||||
|
|
||||||
|
// Delete Line
|
||||||
|
DL(int) error
|
||||||
|
|
||||||
|
// Insert Character
|
||||||
|
ICH(int) error
|
||||||
|
|
||||||
|
// Delete Character
|
||||||
|
DCH(int) error
|
||||||
|
|
||||||
|
// Set Graphics Rendition
|
||||||
|
SGR([]int) error
|
||||||
|
|
||||||
|
// Pan Down
|
||||||
|
SU(int) error
|
||||||
|
|
||||||
|
// Pan Up
|
||||||
|
SD(int) error
|
||||||
|
|
||||||
|
// Device Attributes
|
||||||
|
DA([]string) error
|
||||||
|
|
||||||
|
// Set Top and Bottom Margins
|
||||||
|
DECSTBM(int, int) error
|
||||||
|
|
||||||
|
// Index
|
||||||
|
IND() error
|
||||||
|
|
||||||
|
// Reverse Index
|
||||||
|
RI() error
|
||||||
|
|
||||||
|
// Flush updates from previous commands
|
||||||
|
Flush() error
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type groundState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gs groundState) Handle(b byte) (s state, e error) {
|
||||||
|
gs.parser.context.currentChar = b
|
||||||
|
|
||||||
|
nextState, err := gs.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(printables, b):
|
||||||
|
return gs, gs.parser.print()
|
||||||
|
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return gs, gs.parser.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return gs, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type oscStringState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oscState oscStringState) Handle(b byte) (s state, e error) {
|
||||||
|
logger.Infof("OscString::Handle %#x", b)
|
||||||
|
nextState, err := oscState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case isOscStringTerminator(b):
|
||||||
|
return oscState.parser.ground, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return oscState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// See below for OSC string terminators for linux
|
||||||
|
// http://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||||
|
func isOscStringTerminator(b byte) bool {
|
||||||
|
|
||||||
|
if b == ANSI_BEL || b == 0x5C {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger *logrus.Logger
|
||||||
|
|
||||||
|
type AnsiParser struct {
|
||||||
|
currState state
|
||||||
|
eventHandler AnsiEventHandler
|
||||||
|
context *ansiContext
|
||||||
|
csiEntry state
|
||||||
|
csiParam state
|
||||||
|
dcsEntry state
|
||||||
|
escape state
|
||||||
|
escapeIntermediate state
|
||||||
|
error state
|
||||||
|
ground state
|
||||||
|
oscString state
|
||||||
|
stateMap []state
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateParser(initialState string, evtHandler AnsiEventHandler) *AnsiParser {
|
||||||
|
logFile := ioutil.Discard
|
||||||
|
|
||||||
|
if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
|
||||||
|
logFile, _ = os.Create("ansiParser.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = &logrus.Logger{
|
||||||
|
Out: logFile,
|
||||||
|
Formatter: new(logrus.TextFormatter),
|
||||||
|
Level: logrus.InfoLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := &AnsiParser{
|
||||||
|
eventHandler: evtHandler,
|
||||||
|
context: &ansiContext{},
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: parser}}
|
||||||
|
parser.csiParam = csiParamState{baseState{name: "CsiParam", parser: parser}}
|
||||||
|
parser.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: parser}}
|
||||||
|
parser.escape = escapeState{baseState{name: "Escape", parser: parser}}
|
||||||
|
parser.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: parser}}
|
||||||
|
parser.error = errorState{baseState{name: "Error", parser: parser}}
|
||||||
|
parser.ground = groundState{baseState{name: "Ground", parser: parser}}
|
||||||
|
parser.oscString = oscStringState{baseState{name: "OscString", parser: parser}}
|
||||||
|
|
||||||
|
parser.stateMap = []state{
|
||||||
|
parser.csiEntry,
|
||||||
|
parser.csiParam,
|
||||||
|
parser.dcsEntry,
|
||||||
|
parser.escape,
|
||||||
|
parser.escapeIntermediate,
|
||||||
|
parser.error,
|
||||||
|
parser.ground,
|
||||||
|
parser.oscString,
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.currState = getState(initialState, parser.stateMap)
|
||||||
|
|
||||||
|
logger.Infof("CreateParser: parser %p", parser)
|
||||||
|
return parser
|
||||||
|
}
|
||||||
|
|
||||||
|
func getState(name string, states []state) state {
|
||||||
|
for _, el := range states {
|
||||||
|
if el.Name() == name {
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
|
||||||
|
for i, b := range bytes {
|
||||||
|
if err := ap.handle(b); err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(bytes), ap.eventHandler.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) handle(b byte) error {
|
||||||
|
ap.context.currentChar = b
|
||||||
|
newState, err := ap.currState.Handle(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newState == nil {
|
||||||
|
logger.Warning("newState is nil")
|
||||||
|
return errors.New("New state of 'nil' is invalid.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if newState != ap.currState {
|
||||||
|
if err := ap.changeState(newState); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) changeState(newState state) error {
|
||||||
|
logger.Infof("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
|
||||||
|
|
||||||
|
// Exit old state
|
||||||
|
if err := ap.currState.Exit(); err != nil {
|
||||||
|
logger.Infof("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform transition action
|
||||||
|
if err := ap.currState.Transition(newState); err != nil {
|
||||||
|
logger.Infof("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter new state
|
||||||
|
if err := newState.Enter(); err != nil {
|
||||||
|
logger.Infof("Enter state '%s' failed with: '%v'", newState.Name(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ap.currState = newState
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseParams(bytes []byte) ([]string, error) {
|
||||||
|
paramBuff := make([]byte, 0, 0)
|
||||||
|
params := []string{}
|
||||||
|
|
||||||
|
for _, v := range bytes {
|
||||||
|
if v == ';' {
|
||||||
|
if len(paramBuff) > 0 {
|
||||||
|
// Completed parameter, append it to the list
|
||||||
|
s := string(paramBuff)
|
||||||
|
params = append(params, s)
|
||||||
|
paramBuff = make([]byte, 0, 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paramBuff = append(paramBuff, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last parameter may not be terminated with ';'
|
||||||
|
if len(paramBuff) > 0 {
|
||||||
|
s := string(paramBuff)
|
||||||
|
params = append(params, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("Parsed params: %v with length: %d", params, len(params))
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCmd(context ansiContext) (string, error) {
|
||||||
|
return string(context.currentChar), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInt(params []string, dflt int) int {
|
||||||
|
i := getInts(params, 1, dflt)[0]
|
||||||
|
logger.Infof("getInt: %v", i)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInts(params []string, minCount int, dflt int) []int {
|
||||||
|
ints := []int{}
|
||||||
|
|
||||||
|
for _, v := range params {
|
||||||
|
i, _ := strconv.Atoi(v)
|
||||||
|
// Zero is mapped to the default value in VT100.
|
||||||
|
if i == 0 {
|
||||||
|
i = dflt
|
||||||
|
}
|
||||||
|
ints = append(ints, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ints) < minCount {
|
||||||
|
remaining := minCount - len(ints)
|
||||||
|
for i := 0; i < remaining; i++ {
|
||||||
|
ints = append(ints, dflt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("getInts: %v", ints)
|
||||||
|
|
||||||
|
return ints
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) modeDispatch(param string, set bool) error {
|
||||||
|
switch param {
|
||||||
|
case "?3":
|
||||||
|
return ap.eventHandler.DECCOLM(set)
|
||||||
|
case "?6":
|
||||||
|
return ap.eventHandler.DECOM(set)
|
||||||
|
case "?25":
|
||||||
|
return ap.eventHandler.DECTCEM(set)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) hDispatch(params []string) error {
|
||||||
|
if len(params) == 1 {
|
||||||
|
return ap.modeDispatch(params[0], true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) lDispatch(params []string) error {
|
||||||
|
if len(params) == 1 {
|
||||||
|
return ap.modeDispatch(params[0], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEraseParam(params []string) int {
|
||||||
|
param := getInt(params, 0)
|
||||||
|
if param < 0 || 3 < param {
|
||||||
|
param = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return param
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ap *AnsiParser) collectParam() error {
|
||||||
|
currChar := ap.context.currentChar
|
||||||
|
logger.Infof("collectParam %#x", currChar)
|
||||||
|
ap.context.paramBuffer = append(ap.context.paramBuffer, currChar)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) collectInter() error {
|
||||||
|
currChar := ap.context.currentChar
|
||||||
|
logger.Infof("collectInter %#x", currChar)
|
||||||
|
ap.context.paramBuffer = append(ap.context.interBuffer, currChar)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) escDispatch() error {
|
||||||
|
cmd, _ := parseCmd(*ap.context)
|
||||||
|
intermeds := ap.context.interBuffer
|
||||||
|
logger.Infof("escDispatch currentChar: %#x", ap.context.currentChar)
|
||||||
|
logger.Infof("escDispatch: %v(%v)", cmd, intermeds)
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "D": // IND
|
||||||
|
return ap.eventHandler.IND()
|
||||||
|
case "E": // NEL, equivalent to CRLF
|
||||||
|
err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN)
|
||||||
|
if err == nil {
|
||||||
|
err = ap.eventHandler.Execute(ANSI_LINE_FEED)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
case "M": // RI
|
||||||
|
return ap.eventHandler.RI()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) csiDispatch() error {
|
||||||
|
cmd, _ := parseCmd(*ap.context)
|
||||||
|
params, _ := parseParams(ap.context.paramBuffer)
|
||||||
|
|
||||||
|
logger.Infof("csiDispatch: %v(%v)", cmd, params)
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "@":
|
||||||
|
return ap.eventHandler.ICH(getInt(params, 1))
|
||||||
|
case "A":
|
||||||
|
return ap.eventHandler.CUU(getInt(params, 1))
|
||||||
|
case "B":
|
||||||
|
return ap.eventHandler.CUD(getInt(params, 1))
|
||||||
|
case "C":
|
||||||
|
return ap.eventHandler.CUF(getInt(params, 1))
|
||||||
|
case "D":
|
||||||
|
return ap.eventHandler.CUB(getInt(params, 1))
|
||||||
|
case "E":
|
||||||
|
return ap.eventHandler.CNL(getInt(params, 1))
|
||||||
|
case "F":
|
||||||
|
return ap.eventHandler.CPL(getInt(params, 1))
|
||||||
|
case "G":
|
||||||
|
return ap.eventHandler.CHA(getInt(params, 1))
|
||||||
|
case "H":
|
||||||
|
ints := getInts(params, 2, 1)
|
||||||
|
x, y := ints[0], ints[1]
|
||||||
|
return ap.eventHandler.CUP(x, y)
|
||||||
|
case "J":
|
||||||
|
param := getEraseParam(params)
|
||||||
|
return ap.eventHandler.ED(param)
|
||||||
|
case "K":
|
||||||
|
param := getEraseParam(params)
|
||||||
|
return ap.eventHandler.EL(param)
|
||||||
|
case "L":
|
||||||
|
return ap.eventHandler.IL(getInt(params, 1))
|
||||||
|
case "M":
|
||||||
|
return ap.eventHandler.DL(getInt(params, 1))
|
||||||
|
case "P":
|
||||||
|
return ap.eventHandler.DCH(getInt(params, 1))
|
||||||
|
case "S":
|
||||||
|
return ap.eventHandler.SU(getInt(params, 1))
|
||||||
|
case "T":
|
||||||
|
return ap.eventHandler.SD(getInt(params, 1))
|
||||||
|
case "c":
|
||||||
|
return ap.eventHandler.DA(params)
|
||||||
|
case "d":
|
||||||
|
return ap.eventHandler.VPA(getInt(params, 1))
|
||||||
|
case "f":
|
||||||
|
ints := getInts(params, 2, 1)
|
||||||
|
x, y := ints[0], ints[1]
|
||||||
|
return ap.eventHandler.HVP(x, y)
|
||||||
|
case "h":
|
||||||
|
return ap.hDispatch(params)
|
||||||
|
case "l":
|
||||||
|
return ap.lDispatch(params)
|
||||||
|
case "m":
|
||||||
|
return ap.eventHandler.SGR(getInts(params, 1, 0))
|
||||||
|
case "r":
|
||||||
|
ints := getInts(params, 2, 1)
|
||||||
|
top, bottom := ints[0], ints[1]
|
||||||
|
return ap.eventHandler.DECSTBM(top, bottom)
|
||||||
|
default:
|
||||||
|
logger.Errorf(fmt.Sprintf("Unsupported CSI command: '%s', with full context: %v", cmd, ap.context))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) print() error {
|
||||||
|
return ap.eventHandler.Print(ap.context.currentChar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) clear() error {
|
||||||
|
ap.context = &ansiContext{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) execute() error {
|
||||||
|
return ap.eventHandler.Execute(ap.context.currentChar)
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type stateID int
|
||||||
|
|
||||||
|
type state interface {
|
||||||
|
Enter() error
|
||||||
|
Exit() error
|
||||||
|
Handle(byte) (state, error)
|
||||||
|
Name() string
|
||||||
|
Transition(state) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseState struct {
|
||||||
|
name string
|
||||||
|
parser *AnsiParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Enter() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Exit() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Handle(b byte) (s state, e error) {
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case b == CSI_ENTRY:
|
||||||
|
return base.parser.csiEntry, nil
|
||||||
|
case b == DCS_ENTRY:
|
||||||
|
return base.parser.dcsEntry, nil
|
||||||
|
case b == ANSI_ESCAPE_PRIMARY:
|
||||||
|
return base.parser.escape, nil
|
||||||
|
case b == OSC_STRING:
|
||||||
|
return base.parser.oscString, nil
|
||||||
|
case sliceContains(toGroundBytes, b):
|
||||||
|
return base.parser.ground, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Name() string {
|
||||||
|
return base.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Transition(s state) error {
|
||||||
|
if s == base.parser.ground {
|
||||||
|
execBytes := []byte{0x18}
|
||||||
|
execBytes = append(execBytes, 0x1A)
|
||||||
|
execBytes = append(execBytes, getByteRange(0x80, 0x8F)...)
|
||||||
|
execBytes = append(execBytes, getByteRange(0x91, 0x97)...)
|
||||||
|
execBytes = append(execBytes, 0x99)
|
||||||
|
execBytes = append(execBytes, 0x9A)
|
||||||
|
|
||||||
|
if sliceContains(execBytes, base.parser.context.currentChar) {
|
||||||
|
return base.parser.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dcsEntryState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorState struct {
|
||||||
|
baseState
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sliceContains(bytes []byte, b byte) bool {
|
||||||
|
for _, v := range bytes {
|
||||||
|
if v == b {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertBytesToInteger(bytes []byte) int {
|
||||||
|
s := string(bytes)
|
||||||
|
i, _ := strconv.Atoi(s)
|
||||||
|
return i
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Azure/go-ansiterm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows keyboard constants
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx.
|
||||||
|
const (
|
||||||
|
VK_PRIOR = 0x21 // PAGE UP key
|
||||||
|
VK_NEXT = 0x22 // PAGE DOWN key
|
||||||
|
VK_END = 0x23 // END key
|
||||||
|
VK_HOME = 0x24 // HOME key
|
||||||
|
VK_LEFT = 0x25 // LEFT ARROW key
|
||||||
|
VK_UP = 0x26 // UP ARROW key
|
||||||
|
VK_RIGHT = 0x27 // RIGHT ARROW key
|
||||||
|
VK_DOWN = 0x28 // DOWN ARROW key
|
||||||
|
VK_SELECT = 0x29 // SELECT key
|
||||||
|
VK_PRINT = 0x2A // PRINT key
|
||||||
|
VK_EXECUTE = 0x2B // EXECUTE key
|
||||||
|
VK_SNAPSHOT = 0x2C // PRINT SCREEN key
|
||||||
|
VK_INSERT = 0x2D // INS key
|
||||||
|
VK_DELETE = 0x2E // DEL key
|
||||||
|
VK_HELP = 0x2F // HELP key
|
||||||
|
VK_F1 = 0x70 // F1 key
|
||||||
|
VK_F2 = 0x71 // F2 key
|
||||||
|
VK_F3 = 0x72 // F3 key
|
||||||
|
VK_F4 = 0x73 // F4 key
|
||||||
|
VK_F5 = 0x74 // F5 key
|
||||||
|
VK_F6 = 0x75 // F6 key
|
||||||
|
VK_F7 = 0x76 // F7 key
|
||||||
|
VK_F8 = 0x77 // F8 key
|
||||||
|
VK_F9 = 0x78 // F9 key
|
||||||
|
VK_F10 = 0x79 // F10 key
|
||||||
|
VK_F11 = 0x7A // F11 key
|
||||||
|
VK_F12 = 0x7B // F12 key
|
||||||
|
|
||||||
|
RIGHT_ALT_PRESSED = 0x0001
|
||||||
|
LEFT_ALT_PRESSED = 0x0002
|
||||||
|
RIGHT_CTRL_PRESSED = 0x0004
|
||||||
|
LEFT_CTRL_PRESSED = 0x0008
|
||||||
|
SHIFT_PRESSED = 0x0010
|
||||||
|
NUMLOCK_ON = 0x0020
|
||||||
|
SCROLLLOCK_ON = 0x0040
|
||||||
|
CAPSLOCK_ON = 0x0080
|
||||||
|
ENHANCED_KEY = 0x0100
|
||||||
|
)
|
||||||
|
|
||||||
|
type ansiCommand struct {
|
||||||
|
CommandBytes []byte
|
||||||
|
Command string
|
||||||
|
Parameters []string
|
||||||
|
IsSpecial bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAnsiCommand(command []byte) *ansiCommand {
|
||||||
|
|
||||||
|
if isCharacterSelectionCmdChar(command[1]) {
|
||||||
|
// Is Character Set Selection commands
|
||||||
|
return &ansiCommand{
|
||||||
|
CommandBytes: command,
|
||||||
|
Command: string(command),
|
||||||
|
IsSpecial: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// last char is command character
|
||||||
|
lastCharIndex := len(command) - 1
|
||||||
|
|
||||||
|
ac := &ansiCommand{
|
||||||
|
CommandBytes: command,
|
||||||
|
Command: string(command[lastCharIndex]),
|
||||||
|
IsSpecial: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// more than a single escape
|
||||||
|
if lastCharIndex != 0 {
|
||||||
|
start := 1
|
||||||
|
// skip if double char escape sequence
|
||||||
|
if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
// convert this to GetNextParam method
|
||||||
|
ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ac
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 {
|
||||||
|
if index < 0 || index >= len(ac.Parameters) {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
param, err := strconv.ParseInt(ac.Parameters[index], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return int16(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ansiCommand) String() string {
|
||||||
|
return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
|
||||||
|
bytesToHex(ac.CommandBytes),
|
||||||
|
ac.Command,
|
||||||
|
strings.Join(ac.Parameters, "\",\""))
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands.
|
||||||
|
// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html.
|
||||||
|
func isAnsiCommandChar(b byte) bool {
|
||||||
|
switch {
|
||||||
|
case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY:
|
||||||
|
return true
|
||||||
|
case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM:
|
||||||
|
// non-CSI escape sequence terminator
|
||||||
|
return true
|
||||||
|
case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL:
|
||||||
|
// String escape sequence terminator
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isXtermOscSequence(command []byte, current byte) bool {
|
||||||
|
return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCharacterSelectionCmdChar(b byte) bool {
|
||||||
|
return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytesToHex converts a slice of bytes to a human-readable string.
|
||||||
|
func bytesToHex(b []byte) string {
|
||||||
|
hex := make([]string, len(b))
|
||||||
|
for i, ch := range b {
|
||||||
|
hex[i] = fmt.Sprintf("%X", ch)
|
||||||
|
}
|
||||||
|
return strings.Join(hex, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureInRange adjusts the passed value, if necessary, to ensure it is within
|
||||||
|
// the passed min / max range.
|
||||||
|
func ensureInRange(n int16, min int16, max int16) int16 {
|
||||||
|
if n < min {
|
||||||
|
return min
|
||||||
|
} else if n > max {
|
||||||
|
return max
|
||||||
|
} else {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStdFile(nFile int) (*os.File, uintptr) {
|
||||||
|
var file *os.File
|
||||||
|
switch nFile {
|
||||||
|
case syscall.STD_INPUT_HANDLE:
|
||||||
|
file = os.Stdin
|
||||||
|
case syscall.STD_OUTPUT_HANDLE:
|
||||||
|
file = os.Stdout
|
||||||
|
case syscall.STD_ERROR_HANDLE:
|
||||||
|
file = os.Stderr
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := syscall.GetStdHandle(nFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Invalid standard handle indentifier: %v -- %v", nFile, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, uintptr(fd)
|
||||||
|
}
|
|
@ -0,0 +1,322 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//===========================================================================================================
|
||||||
|
// IMPORTANT NOTE:
|
||||||
|
//
|
||||||
|
// The methods below make extensive use of the "unsafe" package to obtain the required pointers.
|
||||||
|
// Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack
|
||||||
|
// variables) the pointers reference *before* the API completes.
|
||||||
|
//
|
||||||
|
// As a result, in those cases, the code must hint that the variables remain in active by invoking the
|
||||||
|
// dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer
|
||||||
|
// require unsafe pointers.
|
||||||
|
//
|
||||||
|
// If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform
|
||||||
|
// the garbage collector the variables remain in use if:
|
||||||
|
//
|
||||||
|
// -- The value is not a pointer (e.g., int32, struct)
|
||||||
|
// -- The value is not referenced by the method after passing the pointer to Windows
|
||||||
|
//
|
||||||
|
// See http://golang.org/doc/go1.3.
|
||||||
|
//===========================================================================================================
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo")
|
||||||
|
setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo")
|
||||||
|
setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition")
|
||||||
|
setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
|
||||||
|
getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
|
||||||
|
scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA")
|
||||||
|
setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute")
|
||||||
|
setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo")
|
||||||
|
writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW")
|
||||||
|
readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW")
|
||||||
|
waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows Console constants
|
||||||
|
const (
|
||||||
|
// Console modes
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||||
|
ENABLE_PROCESSED_INPUT = 0x0001
|
||||||
|
ENABLE_LINE_INPUT = 0x0002
|
||||||
|
ENABLE_ECHO_INPUT = 0x0004
|
||||||
|
ENABLE_WINDOW_INPUT = 0x0008
|
||||||
|
ENABLE_MOUSE_INPUT = 0x0010
|
||||||
|
ENABLE_INSERT_MODE = 0x0020
|
||||||
|
ENABLE_QUICK_EDIT_MODE = 0x0040
|
||||||
|
ENABLE_EXTENDED_FLAGS = 0x0080
|
||||||
|
|
||||||
|
ENABLE_PROCESSED_OUTPUT = 0x0001
|
||||||
|
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
|
||||||
|
|
||||||
|
// Character attributes
|
||||||
|
// Note:
|
||||||
|
// -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
|
||||||
|
// Clearing all foreground or background colors results in black; setting all creates white.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
|
||||||
|
FOREGROUND_BLUE uint16 = 0x0001
|
||||||
|
FOREGROUND_GREEN uint16 = 0x0002
|
||||||
|
FOREGROUND_RED uint16 = 0x0004
|
||||||
|
FOREGROUND_INTENSITY uint16 = 0x0008
|
||||||
|
FOREGROUND_MASK uint16 = 0x000F
|
||||||
|
|
||||||
|
BACKGROUND_BLUE uint16 = 0x0010
|
||||||
|
BACKGROUND_GREEN uint16 = 0x0020
|
||||||
|
BACKGROUND_RED uint16 = 0x0040
|
||||||
|
BACKGROUND_INTENSITY uint16 = 0x0080
|
||||||
|
BACKGROUND_MASK uint16 = 0x00F0
|
||||||
|
|
||||||
|
COMMON_LVB_MASK uint16 = 0xFF00
|
||||||
|
COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000
|
||||||
|
COMMON_LVB_UNDERSCORE uint16 = 0x8000
|
||||||
|
|
||||||
|
// Input event types
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||||
|
KEY_EVENT = 0x0001
|
||||||
|
MOUSE_EVENT = 0x0002
|
||||||
|
WINDOW_BUFFER_SIZE_EVENT = 0x0004
|
||||||
|
MENU_EVENT = 0x0008
|
||||||
|
FOCUS_EVENT = 0x0010
|
||||||
|
|
||||||
|
// WaitForSingleObject return codes
|
||||||
|
WAIT_ABANDONED = 0x00000080
|
||||||
|
WAIT_FAILED = 0xFFFFFFFF
|
||||||
|
WAIT_SIGNALED = 0x0000000
|
||||||
|
WAIT_TIMEOUT = 0x00000102
|
||||||
|
|
||||||
|
// WaitForSingleObject wait duration
|
||||||
|
WAIT_INFINITE = 0xFFFFFFFF
|
||||||
|
WAIT_ONE_SECOND = 1000
|
||||||
|
WAIT_HALF_SECOND = 500
|
||||||
|
WAIT_QUARTER_SECOND = 250
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows API Console types
|
||||||
|
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD)
|
||||||
|
// -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment
|
||||||
|
type (
|
||||||
|
CHAR_INFO struct {
|
||||||
|
UnicodeChar uint16
|
||||||
|
Attributes uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSOLE_CURSOR_INFO struct {
|
||||||
|
Size uint32
|
||||||
|
Visible int32
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||||
|
Size COORD
|
||||||
|
CursorPosition COORD
|
||||||
|
Attributes uint16
|
||||||
|
Window SMALL_RECT
|
||||||
|
MaximumWindowSize COORD
|
||||||
|
}
|
||||||
|
|
||||||
|
COORD struct {
|
||||||
|
X int16
|
||||||
|
Y int16
|
||||||
|
}
|
||||||
|
|
||||||
|
SMALL_RECT struct {
|
||||||
|
Left int16
|
||||||
|
Top int16
|
||||||
|
Right int16
|
||||||
|
Bottom int16
|
||||||
|
}
|
||||||
|
|
||||||
|
// INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||||
|
INPUT_RECORD struct {
|
||||||
|
EventType uint16
|
||||||
|
KeyEvent KEY_EVENT_RECORD
|
||||||
|
}
|
||||||
|
|
||||||
|
KEY_EVENT_RECORD struct {
|
||||||
|
KeyDown int32
|
||||||
|
RepeatCount uint16
|
||||||
|
VirtualKeyCode uint16
|
||||||
|
VirtualScanCode uint16
|
||||||
|
UnicodeChar uint16
|
||||||
|
ControlKeyState uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
WINDOW_BUFFER_SIZE struct {
|
||||||
|
Size COORD
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// boolToBOOL converts a Go bool into a Windows int32.
|
||||||
|
func boolToBOOL(f bool) int32 {
|
||||||
|
if f {
|
||||||
|
return int32(1)
|
||||||
|
} else {
|
||||||
|
return int32(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx.
|
||||||
|
func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
|
||||||
|
r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleCursorInfo sets the size and visiblity of the console cursor.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx.
|
||||||
|
func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
|
||||||
|
r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleCursorPosition location of the console cursor.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx.
|
||||||
|
func SetConsoleCursorPosition(handle uintptr, coord COORD) error {
|
||||||
|
r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord))
|
||||||
|
use(coord)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsoleMode gets the console mode for given file descriptor
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx.
|
||||||
|
func GetConsoleMode(handle uintptr) (mode uint32, err error) {
|
||||||
|
err = syscall.GetConsoleMode(syscall.Handle(handle), &mode)
|
||||||
|
return mode, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleMode sets the console mode for given file descriptor
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||||
|
func SetConsoleMode(handle uintptr, mode uint32) error {
|
||||||
|
r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0)
|
||||||
|
use(mode)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx.
|
||||||
|
func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||||
|
info := CONSOLE_SCREEN_BUFFER_INFO{}
|
||||||
|
err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error {
|
||||||
|
r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char)))
|
||||||
|
use(scrollRect)
|
||||||
|
use(clipRect)
|
||||||
|
use(destOrigin)
|
||||||
|
use(char)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleScreenBufferSize sets the size of the console screen buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx.
|
||||||
|
func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
|
||||||
|
r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord))
|
||||||
|
use(coord)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleTextAttribute sets the attributes of characters written to the
|
||||||
|
// console screen buffer by the WriteFile or WriteConsole function.
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
|
||||||
|
func SetConsoleTextAttribute(handle uintptr, attribute uint16) error {
|
||||||
|
r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
|
||||||
|
use(attribute)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleWindowInfo sets the size and position of the console screen buffer's window.
|
||||||
|
// Note that the size and location must be within and no larger than the backing console screen buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx.
|
||||||
|
func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error {
|
||||||
|
r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect)))
|
||||||
|
use(isAbsolute)
|
||||||
|
use(rect)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx.
|
||||||
|
func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error {
|
||||||
|
r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))
|
||||||
|
use(buffer)
|
||||||
|
use(bufferSize)
|
||||||
|
use(bufferCoord)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadConsoleInput reads (and removes) data from the console input buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx.
|
||||||
|
func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error {
|
||||||
|
r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count)))
|
||||||
|
use(buffer)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForSingleObject waits for the passed handle to be signaled.
|
||||||
|
// It returns true if the handle was signaled; false otherwise.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx.
|
||||||
|
func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) {
|
||||||
|
r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait)))
|
||||||
|
switch r1 {
|
||||||
|
case WAIT_ABANDONED, WAIT_TIMEOUT:
|
||||||
|
return false, nil
|
||||||
|
case WAIT_SIGNALED:
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
use(msWait)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String helpers
|
||||||
|
func (info CONSOLE_SCREEN_BUFFER_INFO) String() string {
|
||||||
|
return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coord COORD) String() string {
|
||||||
|
return fmt.Sprintf("%v,%v", coord.X, coord.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rect SMALL_RECT) String() string {
|
||||||
|
return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkError evaluates the results of a Windows API call and returns the error if it failed.
|
||||||
|
func checkError(r1, r2 uintptr, err error) error {
|
||||||
|
// Windows APIs return non-zero to indicate success
|
||||||
|
if r1 != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the error if provided, otherwise default to EINVAL
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// coordToPointer converts a COORD into a uintptr (by fooling the type system).
|
||||||
|
func coordToPointer(c COORD) uintptr {
|
||||||
|
// Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass.
|
||||||
|
return uintptr(*((*uint32)(unsafe.Pointer(&c))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// use is a no-op, but the compiler cannot see that it is.
|
||||||
|
// Calling use(p) ensures that p is kept live until that point.
|
||||||
|
func use(p interface{}) {}
|
|
@ -0,0 +1,100 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import "github.com/Azure/go-ansiterm"
|
||||||
|
|
||||||
|
const (
|
||||||
|
FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||||
|
BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||||
|
)
|
||||||
|
|
||||||
|
// collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the
|
||||||
|
// request represented by the passed ANSI mode.
|
||||||
|
func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) {
|
||||||
|
switch ansiMode {
|
||||||
|
|
||||||
|
// Mode styles
|
||||||
|
case ansiterm.ANSI_SGR_BOLD:
|
||||||
|
windowsMode = windowsMode | FOREGROUND_INTENSITY
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF:
|
||||||
|
windowsMode &^= FOREGROUND_INTENSITY
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_UNDERLINE:
|
||||||
|
windowsMode = windowsMode | COMMON_LVB_UNDERSCORE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_REVERSE:
|
||||||
|
inverted = true
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_REVERSE_OFF:
|
||||||
|
inverted = false
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_UNDERLINE_OFF:
|
||||||
|
windowsMode &^= COMMON_LVB_UNDERSCORE
|
||||||
|
|
||||||
|
// Foreground colors
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_BLACK:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_RED:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_GREEN:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_YELLOW:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_BLUE:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_CYAN:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_WHITE:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
// Background colors
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT:
|
||||||
|
// Black with no intensity
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_BLACK:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_RED:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_GREEN:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_YELLOW:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_BLUE:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_CYAN:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_WHITE:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||||
|
}
|
||||||
|
|
||||||
|
return windowsMode, inverted
|
||||||
|
}
|
||||||
|
|
||||||
|
// invertAttributes inverts the foreground and background colors of a Windows attributes value
|
||||||
|
func invertAttributes(windowsMode uint16) uint16 {
|
||||||
|
return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
const (
|
||||||
|
horizontal = iota
|
||||||
|
vertical
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT {
|
||||||
|
if h.originMode {
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
return SMALL_RECT{
|
||||||
|
Top: sr.top,
|
||||||
|
Bottom: sr.bottom,
|
||||||
|
Left: 0,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return SMALL_RECT{
|
||||||
|
Top: info.Window.Top,
|
||||||
|
Bottom: info.Window.Bottom,
|
||||||
|
Left: 0,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setCursorPosition sets the cursor to the specified position, bounded to the screen size
|
||||||
|
func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error {
|
||||||
|
position.X = ensureInRange(position.X, window.Left, window.Right)
|
||||||
|
position.Y = ensureInRange(position.Y, window.Top, window.Bottom)
|
||||||
|
err := SetConsoleCursorPosition(h.fd, position)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("Cursor position set: (%d, %d)", position.X, position.Y)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error {
|
||||||
|
return h.moveCursor(vertical, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error {
|
||||||
|
return h.moveCursor(horizontal, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
position := info.CursorPosition
|
||||||
|
switch moveMode {
|
||||||
|
case horizontal:
|
||||||
|
position.X += int16(param)
|
||||||
|
case vertical:
|
||||||
|
position.Y += int16(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorLine(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
position := info.CursorPosition
|
||||||
|
position.X = 0
|
||||||
|
position.Y += int16(param)
|
||||||
|
|
||||||
|
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
position := info.CursorPosition
|
||||||
|
position.X = int16(param) - 1
|
||||||
|
|
||||||
|
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import "github.com/Azure/go-ansiterm"
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error {
|
||||||
|
// Ignore an invalid (negative area) request
|
||||||
|
if toCoord.Y < fromCoord.Y {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var coordStart = COORD{}
|
||||||
|
var coordEnd = COORD{}
|
||||||
|
|
||||||
|
xCurrent, yCurrent := fromCoord.X, fromCoord.Y
|
||||||
|
xEnd, yEnd := toCoord.X, toCoord.Y
|
||||||
|
|
||||||
|
// Clear any partial initial line
|
||||||
|
if xCurrent > 0 {
|
||||||
|
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||||
|
coordEnd.X, coordEnd.Y = xEnd, yCurrent
|
||||||
|
|
||||||
|
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xCurrent = 0
|
||||||
|
yCurrent += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear intervening rectangular section
|
||||||
|
if yCurrent < yEnd {
|
||||||
|
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||||
|
coordEnd.X, coordEnd.Y = xEnd, yEnd-1
|
||||||
|
|
||||||
|
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xCurrent = 0
|
||||||
|
yCurrent = yEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear remaining partial ending line
|
||||||
|
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||||
|
coordEnd.X, coordEnd.Y = xEnd, yEnd
|
||||||
|
|
||||||
|
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error {
|
||||||
|
region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X}
|
||||||
|
width := toCoord.X - fromCoord.X + 1
|
||||||
|
height := toCoord.Y - fromCoord.Y + 1
|
||||||
|
size := uint32(width) * uint32(height)
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]CHAR_INFO, size)
|
||||||
|
|
||||||
|
char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes}
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
buffer[i] = char
|
||||||
|
}
|
||||||
|
|
||||||
|
err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, ®ion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
// effectiveSr gets the current effective scroll region in buffer coordinates
|
||||||
|
func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion {
|
||||||
|
top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom)
|
||||||
|
bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom)
|
||||||
|
if top >= bottom {
|
||||||
|
top = window.Top
|
||||||
|
bottom = window.Bottom
|
||||||
|
}
|
||||||
|
return scrollRegion{top: top, bottom: bottom}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) scrollUp(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
return h.scroll(param, sr, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) scrollDown(param int) error {
|
||||||
|
return h.scrollUp(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) deleteLines(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
start := info.CursorPosition.Y
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
// Lines cannot be inserted or deleted outside the scrolling region.
|
||||||
|
if start >= sr.top && start <= sr.bottom {
|
||||||
|
sr.top = start
|
||||||
|
return h.scroll(param, sr, info)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) insertLines(param int) error {
|
||||||
|
return h.deleteLines(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates.
|
||||||
|
func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error {
|
||||||
|
logger.Infof("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom)
|
||||||
|
logger.Infof("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
|
||||||
|
|
||||||
|
// Copy from and clip to the scroll region (full buffer width)
|
||||||
|
scrollRect := SMALL_RECT{
|
||||||
|
Top: sr.top,
|
||||||
|
Bottom: sr.bottom,
|
||||||
|
Left: 0,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin to which area should be copied
|
||||||
|
destOrigin := COORD{
|
||||||
|
X: 0,
|
||||||
|
Y: sr.top - int16(param),
|
||||||
|
}
|
||||||
|
|
||||||
|
char := CHAR_INFO{
|
||||||
|
UnicodeChar: ' ',
|
||||||
|
Attributes: h.attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) deleteCharacters(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.scrollLine(param, info.CursorPosition, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) insertCharacters(param int) error {
|
||||||
|
return h.deleteCharacters(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scrollLine scrolls a line horizontally starting at the provided position by a number of columns.
|
||||||
|
func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error {
|
||||||
|
// Copy from and clip to the scroll region (full buffer width)
|
||||||
|
scrollRect := SMALL_RECT{
|
||||||
|
Top: position.Y,
|
||||||
|
Bottom: position.Y,
|
||||||
|
Left: position.X,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin to which area should be copied
|
||||||
|
destOrigin := COORD{
|
||||||
|
X: position.X - int16(columns),
|
||||||
|
Y: position.Y,
|
||||||
|
}
|
||||||
|
|
||||||
|
char := CHAR_INFO{
|
||||||
|
UnicodeChar: ' ',
|
||||||
|
Attributes: h.attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
// AddInRange increments a value by the passed quantity while ensuring the values
|
||||||
|
// always remain within the supplied min / max range.
|
||||||
|
func addInRange(n int16, increment int16, min int16, max int16) int16 {
|
||||||
|
return ensureInRange(n+increment, min, max)
|
||||||
|
}
|
|
@ -0,0 +1,726 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Azure/go-ansiterm"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger *logrus.Logger
|
||||||
|
|
||||||
|
type windowsAnsiEventHandler struct {
|
||||||
|
fd uintptr
|
||||||
|
file *os.File
|
||||||
|
infoReset *CONSOLE_SCREEN_BUFFER_INFO
|
||||||
|
sr scrollRegion
|
||||||
|
buffer bytes.Buffer
|
||||||
|
attributes uint16
|
||||||
|
inverted bool
|
||||||
|
wrapNext bool
|
||||||
|
drewMarginByte bool
|
||||||
|
originMode bool
|
||||||
|
marginByte byte
|
||||||
|
curInfo *CONSOLE_SCREEN_BUFFER_INFO
|
||||||
|
curPos COORD
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateWinEventHandler(fd uintptr, file *os.File) ansiterm.AnsiEventHandler {
|
||||||
|
logFile := ioutil.Discard
|
||||||
|
|
||||||
|
if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
|
||||||
|
logFile, _ = os.Create("winEventHandler.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = &logrus.Logger{
|
||||||
|
Out: logFile,
|
||||||
|
Formatter: new(logrus.TextFormatter),
|
||||||
|
Level: logrus.DebugLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
infoReset, err := GetConsoleScreenBufferInfo(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &windowsAnsiEventHandler{
|
||||||
|
fd: fd,
|
||||||
|
file: file,
|
||||||
|
infoReset: infoReset,
|
||||||
|
attributes: infoReset.Attributes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type scrollRegion struct {
|
||||||
|
top int16
|
||||||
|
bottom int16
|
||||||
|
}
|
||||||
|
|
||||||
|
// simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the
|
||||||
|
// current cursor position and scroll region settings, in which case it returns
|
||||||
|
// true. If no special handling is necessary, then it does nothing and returns
|
||||||
|
// false.
|
||||||
|
//
|
||||||
|
// In the false case, the caller should ensure that a carriage return
|
||||||
|
// and line feed are inserted or that the text is otherwise wrapped.
|
||||||
|
func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) {
|
||||||
|
if h.wrapNext {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
h.clearWrap()
|
||||||
|
}
|
||||||
|
pos, info, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
if pos.Y == sr.bottom {
|
||||||
|
// Scrolling is necessary. Let Windows automatically scroll if the scrolling region
|
||||||
|
// is the full window.
|
||||||
|
if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom {
|
||||||
|
if includeCR {
|
||||||
|
pos.X = 0
|
||||||
|
h.updatePos(pos)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A custom scroll region is active. Scroll the window manually to simulate
|
||||||
|
// the LF.
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
logger.Info("Simulating LF inside scroll region")
|
||||||
|
if err := h.scrollUp(1); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if includeCR {
|
||||||
|
pos.X = 0
|
||||||
|
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
|
||||||
|
} else if pos.Y < info.Window.Bottom {
|
||||||
|
// Let Windows handle the LF.
|
||||||
|
pos.Y++
|
||||||
|
if includeCR {
|
||||||
|
pos.X = 0
|
||||||
|
}
|
||||||
|
h.updatePos(pos)
|
||||||
|
return false, nil
|
||||||
|
} else {
|
||||||
|
// The cursor is at the bottom of the screen but outside the scroll
|
||||||
|
// region. Skip the LF.
|
||||||
|
logger.Info("Simulating LF outside scroll region")
|
||||||
|
if includeCR {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
pos.X = 0
|
||||||
|
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeLF executes a LF without a CR.
|
||||||
|
func (h *windowsAnsiEventHandler) executeLF() error {
|
||||||
|
handled, err := h.simulateLF(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !handled {
|
||||||
|
// Windows LF will reset the cursor column position. Write the LF
|
||||||
|
// and restore the cursor position.
|
||||||
|
pos, _, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
|
||||||
|
if pos.X != 0 {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info("Resetting cursor position for LF without CR")
|
||||||
|
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) Print(b byte) error {
|
||||||
|
if h.wrapNext {
|
||||||
|
h.buffer.WriteByte(h.marginByte)
|
||||||
|
h.clearWrap()
|
||||||
|
if _, err := h.simulateLF(true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos, info, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pos.X == info.Size.X-1 {
|
||||||
|
h.wrapNext = true
|
||||||
|
h.marginByte = b
|
||||||
|
} else {
|
||||||
|
pos.X++
|
||||||
|
h.updatePos(pos)
|
||||||
|
h.buffer.WriteByte(b)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) Execute(b byte) error {
|
||||||
|
switch b {
|
||||||
|
case ansiterm.ANSI_TAB:
|
||||||
|
logger.Info("Execute(TAB)")
|
||||||
|
// Move to the next tab stop, but preserve auto-wrap if already set.
|
||||||
|
if !h.wrapNext {
|
||||||
|
pos, info, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pos.X = (pos.X + 8) - pos.X%8
|
||||||
|
if pos.X >= info.Size.X {
|
||||||
|
pos.X = info.Size.X - 1
|
||||||
|
}
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case ansiterm.ANSI_BEL:
|
||||||
|
h.buffer.WriteByte(ansiterm.ANSI_BEL)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case ansiterm.ANSI_BACKSPACE:
|
||||||
|
if h.wrapNext {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.clearWrap()
|
||||||
|
}
|
||||||
|
pos, _, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pos.X > 0 {
|
||||||
|
pos.X--
|
||||||
|
h.updatePos(pos)
|
||||||
|
h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED:
|
||||||
|
// Treat as true LF.
|
||||||
|
return h.executeLF()
|
||||||
|
|
||||||
|
case ansiterm.ANSI_LINE_FEED:
|
||||||
|
// Simulate a CR and LF for now since there is no way in go-ansiterm
|
||||||
|
// to tell if the LF should include CR (and more things break when it's
|
||||||
|
// missing than when it's incorrectly added).
|
||||||
|
handled, err := h.simulateLF(true)
|
||||||
|
if handled || err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_CARRIAGE_RETURN:
|
||||||
|
if h.wrapNext {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.clearWrap()
|
||||||
|
}
|
||||||
|
pos, _, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pos.X != 0 {
|
||||||
|
pos.X = 0
|
||||||
|
h.updatePos(pos)
|
||||||
|
h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CUU(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("CUU: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorVertical(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CUD(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("CUD: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorVertical(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CUF(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("CUF: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorHorizontal(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CUB(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("CUB: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorHorizontal(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CNL(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("CNL: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorLine(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CPL(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("CPL: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorLine(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CHA(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("CHA: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorColumn(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) VPA(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("VPA: [[%d]]", param)
|
||||||
|
h.clearWrap()
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
window := h.getCursorWindow(info)
|
||||||
|
position := info.CursorPosition
|
||||||
|
position.Y = window.Top + int16(param) - 1
|
||||||
|
return h.setCursorPosition(position, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("CUP: [[%d %d]]", row, col)
|
||||||
|
h.clearWrap()
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
window := h.getCursorWindow(info)
|
||||||
|
position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
|
||||||
|
return h.setCursorPosition(position, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) HVP(row int, col int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("HVP: [[%d %d]]", row, col)
|
||||||
|
h.clearWrap()
|
||||||
|
return h.CUP(row, col)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
|
||||||
|
h.clearWrap()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DECOM(enable bool) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("DECOM: [%v]", []string{strconv.FormatBool(enable)})
|
||||||
|
h.clearWrap()
|
||||||
|
h.originMode = enable
|
||||||
|
return h.CUP(1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("DECCOLM: [%v]", []string{strconv.FormatBool(use132)})
|
||||||
|
h.clearWrap()
|
||||||
|
if err := h.ED(2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
targetWidth := int16(80)
|
||||||
|
if use132 {
|
||||||
|
targetWidth = 132
|
||||||
|
}
|
||||||
|
if info.Size.X < targetWidth {
|
||||||
|
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
|
||||||
|
logger.Info("set buffer failed:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window := info.Window
|
||||||
|
window.Left = 0
|
||||||
|
window.Right = targetWidth - 1
|
||||||
|
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
|
||||||
|
logger.Info("set window failed:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.Size.X > targetWidth {
|
||||||
|
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
|
||||||
|
logger.Info("set buffer failed:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SetConsoleCursorPosition(h.fd, COORD{0, 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) ED(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("ED: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
|
||||||
|
// [J -- Erases from the cursor to the end of the screen, including the cursor position.
|
||||||
|
// [1J -- Erases from the beginning of the screen to the cursor, including the cursor position.
|
||||||
|
// [2J -- Erases the complete display. The cursor does not move.
|
||||||
|
// Notes:
|
||||||
|
// -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
|
||||||
|
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var start COORD
|
||||||
|
var end COORD
|
||||||
|
|
||||||
|
switch param {
|
||||||
|
case 0:
|
||||||
|
start = info.CursorPosition
|
||||||
|
end = COORD{info.Size.X - 1, info.Size.Y - 1}
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
start = COORD{0, 0}
|
||||||
|
end = info.CursorPosition
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
start = COORD{0, 0}
|
||||||
|
end = COORD{info.Size.X - 1, info.Size.Y - 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.clearRange(h.attributes, start, end)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the whole buffer was cleared, move the window to the top while preserving
|
||||||
|
// the window-relative cursor position.
|
||||||
|
if param == 2 {
|
||||||
|
pos := info.CursorPosition
|
||||||
|
window := info.Window
|
||||||
|
pos.Y -= window.Top
|
||||||
|
window.Bottom -= window.Top
|
||||||
|
window.Top = 0
|
||||||
|
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) EL(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("EL: [%v]", strconv.Itoa(param))
|
||||||
|
h.clearWrap()
|
||||||
|
|
||||||
|
// [K -- Erases from the cursor to the end of the line, including the cursor position.
|
||||||
|
// [1K -- Erases from the beginning of the line to the cursor, including the cursor position.
|
||||||
|
// [2K -- Erases the complete line.
|
||||||
|
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var start COORD
|
||||||
|
var end COORD
|
||||||
|
|
||||||
|
switch param {
|
||||||
|
case 0:
|
||||||
|
start = info.CursorPosition
|
||||||
|
end = COORD{info.Size.X, info.CursorPosition.Y}
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
start = COORD{0, info.CursorPosition.Y}
|
||||||
|
end = info.CursorPosition
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
start = COORD{0, info.CursorPosition.Y}
|
||||||
|
end = COORD{info.Size.X, info.CursorPosition.Y}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.clearRange(h.attributes, start, end)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) IL(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("IL: [%v]", strconv.Itoa(param))
|
||||||
|
h.clearWrap()
|
||||||
|
return h.insertLines(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DL(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("DL: [%v]", strconv.Itoa(param))
|
||||||
|
h.clearWrap()
|
||||||
|
return h.deleteLines(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) ICH(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("ICH: [%v]", strconv.Itoa(param))
|
||||||
|
h.clearWrap()
|
||||||
|
return h.insertCharacters(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DCH(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("DCH: [%v]", strconv.Itoa(param))
|
||||||
|
h.clearWrap()
|
||||||
|
return h.deleteCharacters(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) SGR(params []int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
strings := []string{}
|
||||||
|
for _, v := range params {
|
||||||
|
strings = append(strings, strconv.Itoa(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("SGR: [%v]", strings)
|
||||||
|
|
||||||
|
if len(params) <= 0 {
|
||||||
|
h.attributes = h.infoReset.Attributes
|
||||||
|
h.inverted = false
|
||||||
|
} else {
|
||||||
|
for _, attr := range params {
|
||||||
|
|
||||||
|
if attr == ansiterm.ANSI_SGR_RESET {
|
||||||
|
h.attributes = h.infoReset.Attributes
|
||||||
|
h.inverted = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes := h.attributes
|
||||||
|
if h.inverted {
|
||||||
|
attributes = invertAttributes(attributes)
|
||||||
|
}
|
||||||
|
err := SetConsoleTextAttribute(h.fd, attributes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) SU(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("SU: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.scrollUp(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) SD(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("SD: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.scrollDown(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DA(params []string) error {
|
||||||
|
logger.Infof("DA: [%v]", params)
|
||||||
|
// DA cannot be implemented because it must send data on the VT100 input stream,
|
||||||
|
// which is not available to go-ansiterm.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("DECSTBM: [%d, %d]", top, bottom)
|
||||||
|
|
||||||
|
// Windows is 0 indexed, Linux is 1 indexed
|
||||||
|
h.sr.top = int16(top - 1)
|
||||||
|
h.sr.bottom = int16(bottom - 1)
|
||||||
|
|
||||||
|
// This command also moves the cursor to the origin.
|
||||||
|
h.clearWrap()
|
||||||
|
return h.CUP(1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) RI() error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info("RI: []")
|
||||||
|
h.clearWrap()
|
||||||
|
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
if info.CursorPosition.Y == sr.top {
|
||||||
|
return h.scrollDown(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.moveCursorVertical(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) IND() error {
|
||||||
|
logger.Info("IND: []")
|
||||||
|
return h.executeLF()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) Flush() error {
|
||||||
|
h.curInfo = nil
|
||||||
|
if h.buffer.Len() > 0 {
|
||||||
|
logger.Infof("Flush: [%s]", h.buffer.Bytes())
|
||||||
|
if _, err := h.buffer.WriteTo(h.file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.wrapNext && !h.drewMarginByte {
|
||||||
|
logger.Infof("Flush: drawing margin byte '%c'", h.marginByte)
|
||||||
|
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}}
|
||||||
|
size := COORD{1, 1}
|
||||||
|
position := COORD{0, 0}
|
||||||
|
region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
|
||||||
|
if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.drewMarginByte = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheConsoleInfo ensures that the current console screen information has been queried
|
||||||
|
// since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos.
|
||||||
|
func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||||
|
if h.curInfo == nil {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return COORD{}, nil, err
|
||||||
|
}
|
||||||
|
h.curInfo = info
|
||||||
|
h.curPos = info.CursorPosition
|
||||||
|
}
|
||||||
|
return h.curPos, h.curInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) updatePos(pos COORD) {
|
||||||
|
if h.curInfo == nil {
|
||||||
|
panic("failed to call getCurrentInfo before calling updatePos")
|
||||||
|
}
|
||||||
|
h.curPos = pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearWrap clears the state where the cursor is in the margin
|
||||||
|
// waiting for the next character before wrapping the line. This must
|
||||||
|
// be done before most operations that act on the cursor.
|
||||||
|
func (h *windowsAnsiEventHandler) clearWrap() {
|
||||||
|
h.wrapNext = false
|
||||||
|
h.drewMarginByte = false
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
FROM google/golang:stable
|
||||||
|
MAINTAINER Guillaume J. Charmes <guillaume@charmes.net>
|
||||||
|
CMD /tmp/a.out
|
||||||
|
ADD . /src
|
||||||
|
RUN cd /src && go build -o /tmp/a.out
|
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Guillaume J. Charmes
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# go-select
|
||||||
|
|
||||||
|
select(2) implementation in Go
|
||||||
|
|
||||||
|
## Supported platforms
|
||||||
|
|
||||||
|
| | 386 | amd64 | arm | arm64 |
|
||||||
|
|---------------|-----|-------|-----|-------|
|
||||||
|
| **linux** | yes | yes | yes | yes |
|
||||||
|
| **darwin** | yes | yes | n/a | ?? |
|
||||||
|
| **freebsd** | yes | yes | yes | ?? |
|
||||||
|
| **openbsd** | yes | yes | yes | ?? |
|
||||||
|
| **netbsd** | yes | yes | yes | ?? |
|
||||||
|
| **dragonfly** | n/a | yes | n/a | ?? |
|
||||||
|
| **solaris** | n/a | no | n/a | ?? |
|
||||||
|
| **plan9** | no | no | n/a | ?? |
|
||||||
|
| **windows** | yes | yes | n/a | ?? |
|
||||||
|
| **android** | n/a | n/a | no | ?? |
|
||||||
|
|
||||||
|
*n/a: platform not supported by Go
|
||||||
|
|
||||||
|
Go on `plan9` and `solaris` do not implement `syscall.Select` not `syscall.SYS_SELECT`.
|
||||||
|
|
||||||
|
## Cross compile
|
||||||
|
|
||||||
|
Using davecheney's https://github.com/davecheney/golang-crosscompile
|
||||||
|
|
||||||
|
```
|
||||||
|
export PLATFORMS="darwin/386 darwin/amd64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm windows/386 windows/amd64 openbsd/386 openbsd/amd64 netbsd/386 netbsd/amd64 dragonfly/amd64 plan9/386 plan9/amd64 solaris/amd64"
|
||||||
|
```
|
|
@ -0,0 +1,33 @@
|
||||||
|
// +build !freebsd,!windows,!plan9
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const FD_SETSIZE = syscall.FD_SETSIZE
|
||||||
|
|
||||||
|
// FDSet wraps syscall.FdSet with convenience methods
|
||||||
|
type FDSet syscall.FdSet
|
||||||
|
|
||||||
|
// Set adds the fd to the set
|
||||||
|
func (fds *FDSet) Set(fd uintptr) {
|
||||||
|
fds.Bits[fd/NFDBITS] |= (1 << (fd % NFDBITS))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear remove the fd from the set
|
||||||
|
func (fds *FDSet) Clear(fd uintptr) {
|
||||||
|
fds.Bits[fd/NFDBITS] &^= (1 << (fd % NFDBITS))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet check if the given fd is set
|
||||||
|
func (fds *FDSet) IsSet(fd uintptr) bool {
|
||||||
|
return fds.Bits[fd/NFDBITS]&(1<<(fd%NFDBITS)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep a null set to avoid reinstatiation
|
||||||
|
var nullFdSet = &FDSet{}
|
||||||
|
|
||||||
|
// Zero empties the Set
|
||||||
|
func (fds *FDSet) Zero() {
|
||||||
|
copy(fds.Bits[:], (nullFdSet).Bits[:])
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// +build darwin openbsd netbsd 386 arm
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
// darwin, netbsd and openbsd uses uint32 on both amd64 and 386
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NFDBITS is the amount of bits per mask
|
||||||
|
NFDBITS = 4 * 8
|
||||||
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
// +build !darwin,!netbsd,!openbsd
|
||||||
|
// +build amd64 arm64
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
// darwin, netbsd and openbsd uses uint32 on both amd64 and 386
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NFDBITS is the amount of bits per mask
|
||||||
|
NFDBITS = 8 * 8
|
||||||
|
)
|
|
@ -0,0 +1,93 @@
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
/**
|
||||||
|
From: XCode's MacOSX10.10.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/select.h
|
||||||
|
--
|
||||||
|
// darwin/amd64 / 386
|
||||||
|
sizeof(__int32_t) == 4
|
||||||
|
--
|
||||||
|
|
||||||
|
typedef __int32_t __fd_mask;
|
||||||
|
|
||||||
|
#define FD_SETSIZE 1024
|
||||||
|
#define __NFDBITS (sizeof(__fd_mask) * 8)
|
||||||
|
#define __howmany(x, y) ((((x) % (y)) == 0) ? ((x) / (y)) : (((x) / (y)) + 1))
|
||||||
|
|
||||||
|
typedef struct fd_set {
|
||||||
|
__fd_mask fds_bits[__howmany(__FD_SETSIZE, __NFDBITS)];
|
||||||
|
} fd_set;
|
||||||
|
|
||||||
|
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
|
||||||
|
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
|
||||||
|
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
|
||||||
|
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
From: /usr/include/i386-linux-gnu/sys/select.h
|
||||||
|
--
|
||||||
|
// linux/i686
|
||||||
|
sizeof(long int) == 4
|
||||||
|
--
|
||||||
|
|
||||||
|
typedef long int __fd_mask;
|
||||||
|
|
||||||
|
#define FD_SETSIZE 1024
|
||||||
|
#define __NFDBITS (sizeof(__fd_mask) * 8)
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct fd_set {
|
||||||
|
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
|
||||||
|
} fd_set;
|
||||||
|
|
||||||
|
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
|
||||||
|
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
|
||||||
|
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
|
||||||
|
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
From: /usr/include/x86_64-linux-gnu/sys/select.h
|
||||||
|
--
|
||||||
|
// linux/amd64
|
||||||
|
sizeof(long int) == 8
|
||||||
|
--
|
||||||
|
|
||||||
|
typedef long int __fd_mask;
|
||||||
|
|
||||||
|
#define FD_SETSIZE 1024
|
||||||
|
#define __NFDBITS (sizeof(__fd_mask) * 8)
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct fd_set {
|
||||||
|
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
|
||||||
|
} fd_set;
|
||||||
|
|
||||||
|
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
|
||||||
|
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
|
||||||
|
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
|
||||||
|
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
From: /usr/include/sys/select.h
|
||||||
|
--
|
||||||
|
// freebsd/amd64
|
||||||
|
sizeof(unsigned long) == 8
|
||||||
|
--
|
||||||
|
|
||||||
|
typedef unsigned long __fd_mask;
|
||||||
|
|
||||||
|
#define FD_SETSIZE 1024U
|
||||||
|
#define __NFDBITS (sizeof(__fd_mask) * 8)
|
||||||
|
#define _howmany(x, y) (((x) + ((y) - 1)) / (y))
|
||||||
|
|
||||||
|
typedef struct fd_set {
|
||||||
|
__fd_mask fds_bits[_howmany(FD_SETSIZE, __NFDBITS)];
|
||||||
|
} fd_set;
|
||||||
|
|
||||||
|
#define __FD_MASK(n) ((__fd_mask)1 << ((n) % __NFDBITS))
|
||||||
|
#define FD_SET(n, p) ((p)->fds_bits[(n)/__NFDBITS] |= __FD_MASK(n))
|
||||||
|
#define FD_CLR(n, p) ((p)->fds_bits[(n)/__NFDBITS] &= ~__FD_MASK(n))
|
||||||
|
#define FD_ISSET(n, p) (((p)->fds_bits[(n)/__NFDBITS] & __FD_MASK(n)) != 0)
|
||||||
|
*/
|
|
@ -0,0 +1,33 @@
|
||||||
|
// +build freebsd
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const FD_SETSIZE = syscall.FD_SETSIZE
|
||||||
|
|
||||||
|
// FDSet wraps syscall.FdSet with convenience methods
|
||||||
|
type FDSet syscall.FdSet
|
||||||
|
|
||||||
|
// Set adds the fd to the set
|
||||||
|
func (fds *FDSet) Set(fd uintptr) {
|
||||||
|
fds.X__fds_bits[fd/NFDBITS] |= (1 << (fd % NFDBITS))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear remove the fd from the set
|
||||||
|
func (fds *FDSet) Clear(fd uintptr) {
|
||||||
|
fds.X__fds_bits[fd/NFDBITS] &^= (1 << (fd % NFDBITS))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet check if the given fd is set
|
||||||
|
func (fds *FDSet) IsSet(fd uintptr) bool {
|
||||||
|
return fds.X__fds_bits[fd/NFDBITS]&(1<<(fd%NFDBITS)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep a null set to avoid reinstatiation
|
||||||
|
var nullFdSet = &FDSet{}
|
||||||
|
|
||||||
|
// Zero empties the Set
|
||||||
|
func (fds *FDSet) Zero() {
|
||||||
|
copy(fds.X__fds_bits[:], (nullFdSet).X__fds_bits[:])
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// +build plan9
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
const FD_SETSIZE = 0
|
||||||
|
|
||||||
|
// FDSet wraps syscall.FdSet with convenience methods
|
||||||
|
type FDSet struct{}
|
||||||
|
|
||||||
|
// Set adds the fd to the set
|
||||||
|
func (fds *FDSet) Set(fd uintptr) {}
|
||||||
|
|
||||||
|
// Clear remove the fd from the set
|
||||||
|
func (fds *FDSet) Clear(fd uintptr) {}
|
||||||
|
|
||||||
|
// IsSet check if the given fd is set
|
||||||
|
func (fds *FDSet) IsSet(fd uintptr) bool { return false }
|
||||||
|
|
||||||
|
// Zero empties the Set
|
||||||
|
func (fds *FDSet) Zero() {}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const FD_SETSIZE = 64
|
||||||
|
|
||||||
|
// FDSet extracted from mingw libs source code
|
||||||
|
type FDSet struct {
|
||||||
|
fd_count uint
|
||||||
|
fd_array [FD_SETSIZE]uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set adds the fd to the set
|
||||||
|
func (fds *FDSet) Set(fd uintptr) {
|
||||||
|
var i uint
|
||||||
|
for i = 0; i < fds.fd_count; i++ {
|
||||||
|
if fds.fd_array[i] == fd {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i == fds.fd_count {
|
||||||
|
if fds.fd_count < FD_SETSIZE {
|
||||||
|
fds.fd_array[i] = fd
|
||||||
|
fds.fd_count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear remove the fd from the set
|
||||||
|
func (fds *FDSet) Clear(fd uintptr) {
|
||||||
|
var i uint
|
||||||
|
for i = 0; i < fds.fd_count; i++ {
|
||||||
|
if fds.fd_array[i] == fd {
|
||||||
|
for i < fds.fd_count-1 {
|
||||||
|
fds.fd_array[i] = fds.fd_array[i+1]
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
fds.fd_count--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet check if the given fd is set
|
||||||
|
func (fds *FDSet) IsSet(fd uintptr) bool {
|
||||||
|
if isset, err := __WSAFDIsSet(syscall.Handle(fd), fds); err == nil && isset != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero empties the Set
|
||||||
|
func (fds *FDSet) Zero() {
|
||||||
|
fds.fd_count = 0
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Select wraps syscall.Select with Go types
|
||||||
|
func Select(n int, r, w, e *FDSet, timeout time.Duration) error {
|
||||||
|
var timeval *syscall.Timeval
|
||||||
|
if timeout >= 0 {
|
||||||
|
t := syscall.NsecToTimeval(timeout.Nanoseconds())
|
||||||
|
timeval = &t
|
||||||
|
}
|
||||||
|
|
||||||
|
return sysSelect(n, r, w, e, timeval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetrySelect wraps syscall.Select with Go types, and retries a number of times, with a given retryDelay.
|
||||||
|
func RetrySelect(n int, r, w, e *FDSet, timeout time.Duration, retries int, retryDelay time.Duration) (err error) {
|
||||||
|
for i := 0; i < retries; i++ {
|
||||||
|
if err = Select(n, r, w, e, timeout); err != syscall.EINTR {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
time.Sleep(retryDelay)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
|
||||||
|
_, err := syscall.Select(n, (*syscall.FdSet)(r), (*syscall.FdSet)(w), (*syscall.FdSet)(e), timeout)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
// +build !linux,!windows,!plan9,!solaris
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
|
||||||
|
return syscall.Select(n, (*syscall.FdSet)(r), (*syscall.FdSet)(w), (*syscall.FdSet)(e), timeout)
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// +build plan9 solaris
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrUnsupported .
|
||||||
|
var ErrUnsupported = fmt.Errorf("Platofrm %s/%s unsupported", runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
|
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
|
||||||
|
return ErrUnsupported
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
//sys _select(nfds int, readfds *FDSet, writefds *FDSet, exceptfds *FDSet, timeout *syscall.Timeval) (total int, err error) = ws2_32.select
|
||||||
|
//sys __WSAFDIsSet(handle syscall.Handle, fdset *FDSet) (isset int, err error) = ws2_32.__WSAFDIsSet
|
||||||
|
|
||||||
|
func sysSelect(n int, r, w, e *FDSet, timeout *syscall.Timeval) error {
|
||||||
|
_, err := _select(n, r, w, e, timeout)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
export GOOS=linux; export GOARCH=arm; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=linux; export GOARCH=arm64; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=linux; export GOARCH=amd64; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=linux; export GOARCH=386; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=darwin; export GOARCH=arm; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=darwin; export GOARCH=arm64; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=darwin; export GOARCH=amd64; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=darwin; export GOARCH=386; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=freebsd; export GOARCH=arm; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=freebsd; export GOARCH=amd64; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=freebsd; export GOARCH=386; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=openbsd; export GOARCH=arm; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=openbsd; export GOARCH=amd64; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=openbsd; export GOARCH=386; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=netbsd; export GOARCH=arm; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=netbsd; export GOARCH=amd64; echo $GOOS/$GOARCH; go build
|
||||||
|
export GOOS=netbsd; export GOARCH=386; echo $GOOS/$GOARCH; go build
|
|
@ -0,0 +1,41 @@
|
||||||
|
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||||
|
|
||||||
|
package goselect
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
var (
|
||||||
|
modws2_32 = syscall.NewLazyDLL("ws2_32.dll")
|
||||||
|
|
||||||
|
procselect = modws2_32.NewProc("select")
|
||||||
|
proc__WSAFDIsSet = modws2_32.NewProc("__WSAFDIsSet")
|
||||||
|
)
|
||||||
|
|
||||||
|
func _select(nfds int, readfds *FDSet, writefds *FDSet, exceptfds *FDSet, timeout *syscall.Timeval) (total int, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall6(procselect.Addr(), 5, uintptr(nfds), uintptr(unsafe.Pointer(readfds)), uintptr(unsafe.Pointer(writefds)), uintptr(unsafe.Pointer(exceptfds)), uintptr(unsafe.Pointer(timeout)), 0)
|
||||||
|
total = int(r0)
|
||||||
|
if total == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func __WSAFDIsSet(handle syscall.Handle, fdset *FDSet) (isset int, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall(proc__WSAFDIsSet.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(fdset)), 0)
|
||||||
|
isset = int(r0)
|
||||||
|
if isset == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package jsonlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSONLog represents a log message, typically a single entry from a given log stream.
|
||||||
|
// JSONLogs can be easily serialized to and from JSON and support custom formatting.
|
||||||
|
type JSONLog struct {
|
||||||
|
// Log is the log message
|
||||||
|
Log string `json:"log,omitempty"`
|
||||||
|
// Stream is the log source
|
||||||
|
Stream string `json:"stream,omitempty"`
|
||||||
|
// Created is the created timestamp of log
|
||||||
|
Created time.Time `json:"time"`
|
||||||
|
// Attrs is the list of extra attributes provided by the user
|
||||||
|
Attrs map[string]string `json:"attrs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format returns the log formatted according to format
|
||||||
|
// If format is nil, returns the log message
|
||||||
|
// If format is json, returns the log marshaled in json format
|
||||||
|
// By default, returns the log with the log time formatted according to format.
|
||||||
|
func (jl *JSONLog) Format(format string) (string, error) {
|
||||||
|
if format == "" {
|
||||||
|
return jl.Log, nil
|
||||||
|
}
|
||||||
|
if format == "json" {
|
||||||
|
m, err := json.Marshal(jl)
|
||||||
|
return string(m), err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %s", jl.Created.Format(format), jl.Log), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the log to nil.
|
||||||
|
func (jl *JSONLog) Reset() {
|
||||||
|
jl.Log = ""
|
||||||
|
jl.Stream = ""
|
||||||
|
jl.Created = time.Time{}
|
||||||
|
}
|
178
vendor/github.com/docker/docker/pkg/jsonlog/jsonlog_marshalling.go
generated
vendored
Normal file
178
vendor/github.com/docker/docker/pkg/jsonlog/jsonlog_marshalling.go
generated
vendored
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
// This code was initially generated by ffjson <https://github.com/pquerna/ffjson>
|
||||||
|
// This code was generated via the following steps:
|
||||||
|
// $ go get -u github.com/pquerna/ffjson
|
||||||
|
// $ make BIND_DIR=. shell
|
||||||
|
// $ ffjson pkg/jsonlog/jsonlog.go
|
||||||
|
// $ mv pkg/jsonglog/jsonlog_ffjson.go pkg/jsonlog/jsonlog_marshalling.go
|
||||||
|
//
|
||||||
|
// It has been modified to improve the performance of time marshalling to JSON
|
||||||
|
// and to clean it up.
|
||||||
|
// Should this code need to be regenerated when the JSONLog struct is changed,
|
||||||
|
// the relevant changes which have been made are:
|
||||||
|
// import (
|
||||||
|
// "bytes"
|
||||||
|
//-
|
||||||
|
// "unicode/utf8"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func (mj *JSONLog) MarshalJSON() ([]byte, error) {
|
||||||
|
//@@ -20,13 +16,13 @@ func (mj *JSONLog) MarshalJSON() ([]byte, error) {
|
||||||
|
// }
|
||||||
|
// return buf.Bytes(), nil
|
||||||
|
// }
|
||||||
|
//+
|
||||||
|
// func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error {
|
||||||
|
//- var err error
|
||||||
|
//- var obj []byte
|
||||||
|
//- var first bool = true
|
||||||
|
//- _ = obj
|
||||||
|
//- _ = err
|
||||||
|
//- _ = first
|
||||||
|
//+ var (
|
||||||
|
//+ err error
|
||||||
|
//+ timestamp string
|
||||||
|
//+ first bool = true
|
||||||
|
//+ )
|
||||||
|
// buf.WriteString(`{`)
|
||||||
|
// if len(mj.Log) != 0 {
|
||||||
|
// if first == true {
|
||||||
|
//@@ -52,11 +48,11 @@ func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error {
|
||||||
|
// buf.WriteString(`,`)
|
||||||
|
// }
|
||||||
|
// buf.WriteString(`"time":`)
|
||||||
|
//- obj, err = mj.Created.MarshalJSON()
|
||||||
|
//+ timestamp, err = FastTimeMarshalJSON(mj.Created)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//- buf.Write(obj)
|
||||||
|
//+ buf.WriteString(timestamp)
|
||||||
|
// buf.WriteString(`}`)
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
// @@ -81,9 +81,10 @@ func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error {
|
||||||
|
// if len(mj.Log) != 0 {
|
||||||
|
// - if first == true {
|
||||||
|
// - first = false
|
||||||
|
// - } else {
|
||||||
|
// - buf.WriteString(`,`)
|
||||||
|
// - }
|
||||||
|
// + first = false
|
||||||
|
// buf.WriteString(`"log":`)
|
||||||
|
// ffjsonWriteJSONString(buf, mj.Log)
|
||||||
|
// }
|
||||||
|
|
||||||
|
package jsonlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalJSON marshals the JSONLog.
|
||||||
|
func (mj *JSONLog) MarshalJSON() ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.Grow(1024)
|
||||||
|
if err := mj.MarshalJSONBuf(&buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSONBuf marshals the JSONLog and stores the result to a bytes.Buffer.
|
||||||
|
func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
timestamp string
|
||||||
|
first = true
|
||||||
|
)
|
||||||
|
buf.WriteString(`{`)
|
||||||
|
if len(mj.Log) != 0 {
|
||||||
|
first = false
|
||||||
|
buf.WriteString(`"log":`)
|
||||||
|
ffjsonWriteJSONString(buf, mj.Log)
|
||||||
|
}
|
||||||
|
if len(mj.Stream) != 0 {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
buf.WriteString(`,`)
|
||||||
|
}
|
||||||
|
buf.WriteString(`"stream":`)
|
||||||
|
ffjsonWriteJSONString(buf, mj.Stream)
|
||||||
|
}
|
||||||
|
if !first {
|
||||||
|
buf.WriteString(`,`)
|
||||||
|
}
|
||||||
|
buf.WriteString(`"time":`)
|
||||||
|
timestamp, err = FastTimeMarshalJSON(mj.Created)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf.WriteString(timestamp)
|
||||||
|
buf.WriteString(`}`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ffjsonWriteJSONString(buf *bytes.Buffer, s string) {
|
||||||
|
const hex = "0123456789abcdef"
|
||||||
|
|
||||||
|
buf.WriteByte('"')
|
||||||
|
start := 0
|
||||||
|
for i := 0; i < len(s); {
|
||||||
|
if b := s[i]; b < utf8.RuneSelf {
|
||||||
|
if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if start < i {
|
||||||
|
buf.WriteString(s[start:i])
|
||||||
|
}
|
||||||
|
switch b {
|
||||||
|
case '\\', '"':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte(b)
|
||||||
|
case '\n':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('n')
|
||||||
|
case '\r':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('r')
|
||||||
|
default:
|
||||||
|
|
||||||
|
buf.WriteString(`\u00`)
|
||||||
|
buf.WriteByte(hex[b>>4])
|
||||||
|
buf.WriteByte(hex[b&0xF])
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, size := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if c == utf8.RuneError && size == 1 {
|
||||||
|
if start < i {
|
||||||
|
buf.WriteString(s[start:i])
|
||||||
|
}
|
||||||
|
buf.WriteString(`\ufffd`)
|
||||||
|
i += size
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == '\u2028' || c == '\u2029' {
|
||||||
|
if start < i {
|
||||||
|
buf.WriteString(s[start:i])
|
||||||
|
}
|
||||||
|
buf.WriteString(`\u202`)
|
||||||
|
buf.WriteByte(hex[c&0xF])
|
||||||
|
i += size
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i += size
|
||||||
|
}
|
||||||
|
if start < len(s) {
|
||||||
|
buf.WriteString(s[start:])
|
||||||
|
}
|
||||||
|
buf.WriteByte('"')
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package jsonlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSONLogs is based on JSONLog.
|
||||||
|
// It allows marshalling JSONLog from Log as []byte
|
||||||
|
// and an already marshalled Created timestamp.
|
||||||
|
type JSONLogs struct {
|
||||||
|
Log []byte `json:"log,omitempty"`
|
||||||
|
Stream string `json:"stream,omitempty"`
|
||||||
|
Created string `json:"time"`
|
||||||
|
|
||||||
|
// json-encoded bytes
|
||||||
|
RawAttrs json.RawMessage `json:"attrs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSONBuf is based on the same method from JSONLog
|
||||||
|
// It has been modified to take into account the necessary changes.
|
||||||
|
func (mj *JSONLogs) MarshalJSONBuf(buf *bytes.Buffer) error {
|
||||||
|
var first = true
|
||||||
|
|
||||||
|
buf.WriteString(`{`)
|
||||||
|
if len(mj.Log) != 0 {
|
||||||
|
first = false
|
||||||
|
buf.WriteString(`"log":`)
|
||||||
|
ffjsonWriteJSONBytesAsString(buf, mj.Log)
|
||||||
|
}
|
||||||
|
if len(mj.Stream) != 0 {
|
||||||
|
if first == true {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
buf.WriteString(`,`)
|
||||||
|
}
|
||||||
|
buf.WriteString(`"stream":`)
|
||||||
|
ffjsonWriteJSONString(buf, mj.Stream)
|
||||||
|
}
|
||||||
|
if len(mj.RawAttrs) > 0 {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
buf.WriteString(`,`)
|
||||||
|
}
|
||||||
|
buf.WriteString(`"attrs":`)
|
||||||
|
buf.Write(mj.RawAttrs)
|
||||||
|
}
|
||||||
|
if !first {
|
||||||
|
buf.WriteString(`,`)
|
||||||
|
}
|
||||||
|
buf.WriteString(`"time":`)
|
||||||
|
buf.WriteString(mj.Created)
|
||||||
|
buf.WriteString(`}`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is based on ffjsonWriteJSONBytesAsString. It has been changed
|
||||||
|
// to accept a string passed as a slice of bytes.
|
||||||
|
func ffjsonWriteJSONBytesAsString(buf *bytes.Buffer, s []byte) {
|
||||||
|
const hex = "0123456789abcdef"
|
||||||
|
|
||||||
|
buf.WriteByte('"')
|
||||||
|
start := 0
|
||||||
|
for i := 0; i < len(s); {
|
||||||
|
if b := s[i]; b < utf8.RuneSelf {
|
||||||
|
if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if start < i {
|
||||||
|
buf.Write(s[start:i])
|
||||||
|
}
|
||||||
|
switch b {
|
||||||
|
case '\\', '"':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte(b)
|
||||||
|
case '\n':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('n')
|
||||||
|
case '\r':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('r')
|
||||||
|
default:
|
||||||
|
|
||||||
|
buf.WriteString(`\u00`)
|
||||||
|
buf.WriteByte(hex[b>>4])
|
||||||
|
buf.WriteByte(hex[b&0xF])
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, size := utf8.DecodeRune(s[i:])
|
||||||
|
if c == utf8.RuneError && size == 1 {
|
||||||
|
if start < i {
|
||||||
|
buf.Write(s[start:i])
|
||||||
|
}
|
||||||
|
buf.WriteString(`\ufffd`)
|
||||||
|
i += size
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == '\u2028' || c == '\u2029' {
|
||||||
|
if start < i {
|
||||||
|
buf.Write(s[start:i])
|
||||||
|
}
|
||||||
|
buf.WriteString(`\u202`)
|
||||||
|
buf.WriteByte(hex[c&0xF])
|
||||||
|
i += size
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i += size
|
||||||
|
}
|
||||||
|
if start < len(s) {
|
||||||
|
buf.Write(s[start:])
|
||||||
|
}
|
||||||
|
buf.WriteByte('"')
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Package jsonlog provides helper functions to parse and print time (time.Time) as JSON.
|
||||||
|
package jsonlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RFC3339NanoFixed is our own version of RFC339Nano because we want one
|
||||||
|
// that pads the nano seconds part with zeros to ensure
|
||||||
|
// the timestamps are aligned in the logs.
|
||||||
|
RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
|
||||||
|
// JSONFormat is the format used by FastMarshalJSON
|
||||||
|
JSONFormat = `"` + time.RFC3339Nano + `"`
|
||||||
|
)
|
||||||
|
|
||||||
|
// FastTimeMarshalJSON avoids one of the extra allocations that
|
||||||
|
// time.MarshalJSON is making.
|
||||||
|
func FastTimeMarshalJSON(t time.Time) (string, error) {
|
||||||
|
if y := t.Year(); y < 0 || y >= 10000 {
|
||||||
|
// RFC 3339 is clear that years are 4 digits exactly.
|
||||||
|
// See golang.org/issue/4556#c15 for more discussion.
|
||||||
|
return "", errors.New("time.MarshalJSON: year outside of range [0,9999]")
|
||||||
|
}
|
||||||
|
return t.Format(JSONFormat), nil
|
||||||
|
}
|
|
@ -0,0 +1,225 @@
|
||||||
|
package jsonmessage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/jsonlog"
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSONError wraps a concrete Code and Message, `Code` is
|
||||||
|
// is an integer error code, `Message` is the error message.
|
||||||
|
type JSONError struct {
|
||||||
|
Code int `json:"code,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *JSONError) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONProgress describes a Progress. terminalFd is the fd of the current terminal,
|
||||||
|
// Start is the initial value for the operation. Current is the current status and
|
||||||
|
// value of the progress made towards Total. Total is the end value describing when
|
||||||
|
// we made 100% progress for an operation.
|
||||||
|
type JSONProgress struct {
|
||||||
|
terminalFd uintptr
|
||||||
|
Current int64 `json:"current,omitempty"`
|
||||||
|
Total int64 `json:"total,omitempty"`
|
||||||
|
Start int64 `json:"start,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *JSONProgress) String() string {
|
||||||
|
var (
|
||||||
|
width = 200
|
||||||
|
pbBox string
|
||||||
|
numbersBox string
|
||||||
|
timeLeftBox string
|
||||||
|
)
|
||||||
|
|
||||||
|
ws, err := term.GetWinsize(p.terminalFd)
|
||||||
|
if err == nil {
|
||||||
|
width = int(ws.Width)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Current <= 0 && p.Total <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
current := units.HumanSize(float64(p.Current))
|
||||||
|
if p.Total <= 0 {
|
||||||
|
return fmt.Sprintf("%8v", current)
|
||||||
|
}
|
||||||
|
total := units.HumanSize(float64(p.Total))
|
||||||
|
percentage := int(float64(p.Current)/float64(p.Total)*100) / 2
|
||||||
|
if percentage > 50 {
|
||||||
|
percentage = 50
|
||||||
|
}
|
||||||
|
if width > 110 {
|
||||||
|
// this number can't be negative gh#7136
|
||||||
|
numSpaces := 0
|
||||||
|
if 50-percentage > 0 {
|
||||||
|
numSpaces = 50 - percentage
|
||||||
|
}
|
||||||
|
pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces))
|
||||||
|
}
|
||||||
|
|
||||||
|
numbersBox = fmt.Sprintf("%8v/%v", current, total)
|
||||||
|
|
||||||
|
if p.Current > p.Total {
|
||||||
|
// remove total display if the reported current is wonky.
|
||||||
|
numbersBox = fmt.Sprintf("%8v", current)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Current > 0 && p.Start > 0 && percentage < 50 {
|
||||||
|
fromStart := time.Now().UTC().Sub(time.Unix(p.Start, 0))
|
||||||
|
perEntry := fromStart / time.Duration(p.Current)
|
||||||
|
left := time.Duration(p.Total-p.Current) * perEntry
|
||||||
|
left = (left / time.Second) * time.Second
|
||||||
|
|
||||||
|
if width > 50 {
|
||||||
|
timeLeftBox = " " + left.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pbBox + numbersBox + timeLeftBox
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONMessage defines a message struct. It describes
|
||||||
|
// the created time, where it from, status, ID of the
|
||||||
|
// message. It's used for docker events.
|
||||||
|
type JSONMessage struct {
|
||||||
|
Stream string `json:"stream,omitempty"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
Progress *JSONProgress `json:"progressDetail,omitempty"`
|
||||||
|
ProgressMessage string `json:"progress,omitempty"` //deprecated
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
From string `json:"from,omitempty"`
|
||||||
|
Time int64 `json:"time,omitempty"`
|
||||||
|
TimeNano int64 `json:"timeNano,omitempty"`
|
||||||
|
Error *JSONError `json:"errorDetail,omitempty"`
|
||||||
|
ErrorMessage string `json:"error,omitempty"` //deprecated
|
||||||
|
// Aux contains out-of-band data, such as digests for push signing.
|
||||||
|
Aux *json.RawMessage `json:"aux,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display displays the JSONMessage to `out`. `isTerminal` describes if `out`
|
||||||
|
// is a terminal. If this is the case, it will erase the entire current line
|
||||||
|
// when displaying the progressbar.
|
||||||
|
func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
|
||||||
|
if jm.Error != nil {
|
||||||
|
if jm.Error.Code == 401 {
|
||||||
|
return fmt.Errorf("Authentication is required.")
|
||||||
|
}
|
||||||
|
return jm.Error
|
||||||
|
}
|
||||||
|
var endl string
|
||||||
|
if isTerminal && jm.Stream == "" && jm.Progress != nil {
|
||||||
|
// <ESC>[2K = erase entire current line
|
||||||
|
fmt.Fprintf(out, "%c[2K\r", 27)
|
||||||
|
endl = "\r"
|
||||||
|
} else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if jm.TimeNano != 0 {
|
||||||
|
fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(jsonlog.RFC3339NanoFixed))
|
||||||
|
} else if jm.Time != 0 {
|
||||||
|
fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(jsonlog.RFC3339NanoFixed))
|
||||||
|
}
|
||||||
|
if jm.ID != "" {
|
||||||
|
fmt.Fprintf(out, "%s: ", jm.ID)
|
||||||
|
}
|
||||||
|
if jm.From != "" {
|
||||||
|
fmt.Fprintf(out, "(from %s) ", jm.From)
|
||||||
|
}
|
||||||
|
if jm.Progress != nil && isTerminal {
|
||||||
|
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl)
|
||||||
|
} else if jm.ProgressMessage != "" { //deprecated
|
||||||
|
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl)
|
||||||
|
} else if jm.Stream != "" {
|
||||||
|
fmt.Fprintf(out, "%s%s", jm.Stream, endl)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisplayJSONMessagesStream displays a json message stream from `in` to `out`, `isTerminal`
|
||||||
|
// describes if `out` is a terminal. If this is the case, it will print `\n` at the end of
|
||||||
|
// each line and move the cursor while displaying.
|
||||||
|
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(*json.RawMessage)) error {
|
||||||
|
var (
|
||||||
|
dec = json.NewDecoder(in)
|
||||||
|
ids = make(map[string]int)
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
diff := 0
|
||||||
|
var jm JSONMessage
|
||||||
|
if err := dec.Decode(&jm); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if jm.Aux != nil {
|
||||||
|
if auxCallback != nil {
|
||||||
|
auxCallback(jm.Aux)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if jm.Progress != nil {
|
||||||
|
jm.Progress.terminalFd = terminalFd
|
||||||
|
}
|
||||||
|
if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") {
|
||||||
|
line, ok := ids[jm.ID]
|
||||||
|
if !ok {
|
||||||
|
// NOTE: This approach of using len(id) to
|
||||||
|
// figure out the number of lines of history
|
||||||
|
// only works as long as we clear the history
|
||||||
|
// when we output something that's not
|
||||||
|
// accounted for in the map, such as a line
|
||||||
|
// with no ID.
|
||||||
|
line = len(ids)
|
||||||
|
ids[jm.ID] = line
|
||||||
|
if isTerminal {
|
||||||
|
fmt.Fprintf(out, "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff = len(ids) - line
|
||||||
|
if isTerminal && diff > 0 {
|
||||||
|
fmt.Fprintf(out, "%c[%dA", 27, diff)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// When outputting something that isn't progress
|
||||||
|
// output, clear the history of previous lines. We
|
||||||
|
// don't want progress entries from some previous
|
||||||
|
// operation to be updated (for example, pull -a
|
||||||
|
// with multiple tags).
|
||||||
|
ids = make(map[string]int)
|
||||||
|
}
|
||||||
|
err := jm.Display(out, isTerminal)
|
||||||
|
if jm.ID != "" && isTerminal && diff > 0 {
|
||||||
|
fmt.Fprintf(out, "%c[%dB", 27, diff)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type stream interface {
|
||||||
|
io.Writer
|
||||||
|
FD() uintptr
|
||||||
|
IsTerminal() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisplayJSONMessagesToStream prints json messages to the output stream
|
||||||
|
func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error {
|
||||||
|
return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
|
||||||
|
}
|
590
vendor/github.com/docker/docker/pkg/namesgenerator/names-generator.go
generated
vendored
Normal file
590
vendor/github.com/docker/docker/pkg/namesgenerator/names-generator.go
generated
vendored
Normal file
|
@ -0,0 +1,590 @@
|
||||||
|
package namesgenerator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/random"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
left = [...]string{
|
||||||
|
"admiring",
|
||||||
|
"adoring",
|
||||||
|
"affectionate",
|
||||||
|
"agitated",
|
||||||
|
"amazing",
|
||||||
|
"angry",
|
||||||
|
"awesome",
|
||||||
|
"blissful",
|
||||||
|
"boring",
|
||||||
|
"brave",
|
||||||
|
"clever",
|
||||||
|
"cocky",
|
||||||
|
"compassionate",
|
||||||
|
"competent",
|
||||||
|
"condescending",
|
||||||
|
"confident",
|
||||||
|
"cranky",
|
||||||
|
"dazzling",
|
||||||
|
"determined",
|
||||||
|
"distracted",
|
||||||
|
"dreamy",
|
||||||
|
"eager",
|
||||||
|
"ecstatic",
|
||||||
|
"elastic",
|
||||||
|
"elated",
|
||||||
|
"elegant",
|
||||||
|
"eloquent",
|
||||||
|
"epic",
|
||||||
|
"fervent",
|
||||||
|
"festive",
|
||||||
|
"flamboyant",
|
||||||
|
"focused",
|
||||||
|
"friendly",
|
||||||
|
"frosty",
|
||||||
|
"gallant",
|
||||||
|
"gifted",
|
||||||
|
"goofy",
|
||||||
|
"gracious",
|
||||||
|
"happy",
|
||||||
|
"hardcore",
|
||||||
|
"heuristic",
|
||||||
|
"hopeful",
|
||||||
|
"hungry",
|
||||||
|
"infallible",
|
||||||
|
"inspiring",
|
||||||
|
"jolly",
|
||||||
|
"jovial",
|
||||||
|
"keen",
|
||||||
|
"kickass",
|
||||||
|
"kind",
|
||||||
|
"laughing",
|
||||||
|
"loving",
|
||||||
|
"lucid",
|
||||||
|
"mystifying",
|
||||||
|
"modest",
|
||||||
|
"musing",
|
||||||
|
"naughty",
|
||||||
|
"nervous",
|
||||||
|
"nifty",
|
||||||
|
"nostalgic",
|
||||||
|
"objective",
|
||||||
|
"optimistic",
|
||||||
|
"peaceful",
|
||||||
|
"pedantic",
|
||||||
|
"pensive",
|
||||||
|
"practical",
|
||||||
|
"priceless",
|
||||||
|
"quirky",
|
||||||
|
"quizzical",
|
||||||
|
"relaxed",
|
||||||
|
"reverent",
|
||||||
|
"romantic",
|
||||||
|
"sad",
|
||||||
|
"serene",
|
||||||
|
"sharp",
|
||||||
|
"silly",
|
||||||
|
"sleepy",
|
||||||
|
"stoic",
|
||||||
|
"stupefied",
|
||||||
|
"suspicious",
|
||||||
|
"tender",
|
||||||
|
"thirsty",
|
||||||
|
"trusting",
|
||||||
|
"unruffled",
|
||||||
|
"upbeat",
|
||||||
|
"vibrant",
|
||||||
|
"vigilant",
|
||||||
|
"wizardly",
|
||||||
|
"wonderful",
|
||||||
|
"xenodochial",
|
||||||
|
"youthful",
|
||||||
|
"zealous",
|
||||||
|
"zen",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Docker, starting from 0.7.x, generates names from notable scientists and hackers.
|
||||||
|
// Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa.
|
||||||
|
right = [...]string{
|
||||||
|
// Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. https://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB
|
||||||
|
"albattani",
|
||||||
|
|
||||||
|
// Frances E. Allen, became the first female IBM Fellow in 1989. In 2006, she became the first female recipient of the ACM's Turing Award. https://en.wikipedia.org/wiki/Frances_E._Allen
|
||||||
|
"allen",
|
||||||
|
|
||||||
|
// June Almeida - Scottish virologist who took the first pictures of the rubella virus - https://en.wikipedia.org/wiki/June_Almeida
|
||||||
|
"almeida",
|
||||||
|
|
||||||
|
// Maria Gaetana Agnesi - Italian mathematician, philosopher, theologian and humanitarian. She was the first woman to write a mathematics handbook and the first woman appointed as a Mathematics Professor at a University. https://en.wikipedia.org/wiki/Maria_Gaetana_Agnesi
|
||||||
|
"agnesi",
|
||||||
|
|
||||||
|
// Archimedes was a physicist, engineer and mathematician who invented too many things to list them here. https://en.wikipedia.org/wiki/Archimedes
|
||||||
|
"archimedes",
|
||||||
|
|
||||||
|
// Maria Ardinghelli - Italian translator, mathematician and physicist - https://en.wikipedia.org/wiki/Maria_Ardinghelli
|
||||||
|
"ardinghelli",
|
||||||
|
|
||||||
|
// Aryabhata - Ancient Indian mathematician-astronomer during 476-550 CE https://en.wikipedia.org/wiki/Aryabhata
|
||||||
|
"aryabhata",
|
||||||
|
|
||||||
|
// Wanda Austin - Wanda Austin is the President and CEO of The Aerospace Corporation, a leading architect for the US security space programs. https://en.wikipedia.org/wiki/Wanda_Austin
|
||||||
|
"austin",
|
||||||
|
|
||||||
|
// Charles Babbage invented the concept of a programmable computer. https://en.wikipedia.org/wiki/Charles_Babbage.
|
||||||
|
"babbage",
|
||||||
|
|
||||||
|
// Stefan Banach - Polish mathematician, was one of the founders of modern functional analysis. https://en.wikipedia.org/wiki/Stefan_Banach
|
||||||
|
"banach",
|
||||||
|
|
||||||
|
// John Bardeen co-invented the transistor - https://en.wikipedia.org/wiki/John_Bardeen
|
||||||
|
"bardeen",
|
||||||
|
|
||||||
|
// Jean Bartik, born Betty Jean Jennings, was one of the original programmers for the ENIAC computer. https://en.wikipedia.org/wiki/Jean_Bartik
|
||||||
|
"bartik",
|
||||||
|
|
||||||
|
// Laura Bassi, the world's first female professor https://en.wikipedia.org/wiki/Laura_Bassi
|
||||||
|
"bassi",
|
||||||
|
|
||||||
|
// Hugh Beaver, British engineer, founder of the Guinness Book of World Records https://en.wikipedia.org/wiki/Hugh_Beaver
|
||||||
|
"beaver",
|
||||||
|
|
||||||
|
// Alexander Graham Bell - an eminent Scottish-born scientist, inventor, engineer and innovator who is credited with inventing the first practical telephone - https://en.wikipedia.org/wiki/Alexander_Graham_Bell
|
||||||
|
"bell",
|
||||||
|
|
||||||
|
// Homi J Bhabha - was an Indian nuclear physicist, founding director, and professor of physics at the Tata Institute of Fundamental Research. Colloquially known as "father of Indian nuclear programme"- https://en.wikipedia.org/wiki/Homi_J._Bhabha
|
||||||
|
"bhabha",
|
||||||
|
|
||||||
|
// Bhaskara II - Ancient Indian mathematician-astronomer whose work on calculus predates Newton and Leibniz by over half a millennium - https://en.wikipedia.org/wiki/Bh%C4%81skara_II#Calculus
|
||||||
|
"bhaskara",
|
||||||
|
|
||||||
|
// Elizabeth Blackwell - American doctor and first American woman to receive a medical degree - https://en.wikipedia.org/wiki/Elizabeth_Blackwell
|
||||||
|
"blackwell",
|
||||||
|
|
||||||
|
// Niels Bohr is the father of quantum theory. https://en.wikipedia.org/wiki/Niels_Bohr.
|
||||||
|
"bohr",
|
||||||
|
|
||||||
|
// Kathleen Booth, she's credited with writing the first assembly language. https://en.wikipedia.org/wiki/Kathleen_Booth
|
||||||
|
"booth",
|
||||||
|
|
||||||
|
// Anita Borg - Anita Borg was the founding director of the Institute for Women and Technology (IWT). https://en.wikipedia.org/wiki/Anita_Borg
|
||||||
|
"borg",
|
||||||
|
|
||||||
|
// Satyendra Nath Bose - He provided the foundation for Bose–Einstein statistics and the theory of the Bose–Einstein condensate. - https://en.wikipedia.org/wiki/Satyendra_Nath_Bose
|
||||||
|
"bose",
|
||||||
|
|
||||||
|
// Evelyn Boyd Granville - She was one of the first African-American woman to receive a Ph.D. in mathematics; she earned it in 1949 from Yale University. https://en.wikipedia.org/wiki/Evelyn_Boyd_Granville
|
||||||
|
"boyd",
|
||||||
|
|
||||||
|
// Brahmagupta - Ancient Indian mathematician during 598-670 CE who gave rules to compute with zero - https://en.wikipedia.org/wiki/Brahmagupta#Zero
|
||||||
|
"brahmagupta",
|
||||||
|
|
||||||
|
// Walter Houser Brattain co-invented the transistor - https://en.wikipedia.org/wiki/Walter_Houser_Brattain
|
||||||
|
"brattain",
|
||||||
|
|
||||||
|
// Emmett Brown invented time travel. https://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff)
|
||||||
|
"brown",
|
||||||
|
|
||||||
|
// Rachel Carson - American marine biologist and conservationist, her book Silent Spring and other writings are credited with advancing the global environmental movement. https://en.wikipedia.org/wiki/Rachel_Carson
|
||||||
|
"carson",
|
||||||
|
|
||||||
|
// Subrahmanyan Chandrasekhar - Astrophysicist known for his mathematical theory on different stages and evolution in structures of the stars. He has won nobel prize for physics - https://en.wikipedia.org/wiki/Subrahmanyan_Chandrasekhar
|
||||||
|
"chandrasekhar",
|
||||||
|
|
||||||
|
//Claude Shannon - The father of information theory and founder of digital circuit design theory. (https://en.wikipedia.org/wiki/Claude_Shannon)
|
||||||
|
"shannon",
|
||||||
|
|
||||||
|
// Joan Clarke - Bletchley Park code breaker during the Second World War who pioneered techniques that remained top secret for decades. Also an accomplished numismatist https://en.wikipedia.org/wiki/Joan_Clarke
|
||||||
|
"clarke",
|
||||||
|
|
||||||
|
// Jane Colden - American botanist widely considered the first female American botanist - https://en.wikipedia.org/wiki/Jane_Colden
|
||||||
|
"colden",
|
||||||
|
|
||||||
|
// Gerty Theresa Cori - American biochemist who became the third woman—and first American woman—to win a Nobel Prize in science, and the first woman to be awarded the Nobel Prize in Physiology or Medicine. Cori was born in Prague. https://en.wikipedia.org/wiki/Gerty_Cori
|
||||||
|
"cori",
|
||||||
|
|
||||||
|
// Seymour Roger Cray was an American electrical engineer and supercomputer architect who designed a series of computers that were the fastest in the world for decades. https://en.wikipedia.org/wiki/Seymour_Cray
|
||||||
|
"cray",
|
||||||
|
|
||||||
|
// This entry reflects a husband and wife team who worked together:
|
||||||
|
// Joan Curran was a Welsh scientist who developed radar and invented chaff, a radar countermeasure. https://en.wikipedia.org/wiki/Joan_Curran
|
||||||
|
// Samuel Curran was an Irish physicist who worked alongside his wife during WWII and invented the proximity fuse. https://en.wikipedia.org/wiki/Samuel_Curran
|
||||||
|
"curran",
|
||||||
|
|
||||||
|
// Marie Curie discovered radioactivity. https://en.wikipedia.org/wiki/Marie_Curie.
|
||||||
|
"curie",
|
||||||
|
|
||||||
|
// Charles Darwin established the principles of natural evolution. https://en.wikipedia.org/wiki/Charles_Darwin.
|
||||||
|
"darwin",
|
||||||
|
|
||||||
|
// Leonardo Da Vinci invented too many things to list here. https://en.wikipedia.org/wiki/Leonardo_da_Vinci.
|
||||||
|
"davinci",
|
||||||
|
|
||||||
|
// Edsger Wybe Dijkstra was a Dutch computer scientist and mathematical scientist. https://en.wikipedia.org/wiki/Edsger_W._Dijkstra.
|
||||||
|
"dijkstra",
|
||||||
|
|
||||||
|
// Donna Dubinsky - played an integral role in the development of personal digital assistants (PDAs) serving as CEO of Palm, Inc. and co-founding Handspring. https://en.wikipedia.org/wiki/Donna_Dubinsky
|
||||||
|
"dubinsky",
|
||||||
|
|
||||||
|
// Annie Easley - She was a leading member of the team which developed software for the Centaur rocket stage and one of the first African-Americans in her field. https://en.wikipedia.org/wiki/Annie_Easley
|
||||||
|
"easley",
|
||||||
|
|
||||||
|
// Thomas Alva Edison, prolific inventor https://en.wikipedia.org/wiki/Thomas_Edison
|
||||||
|
"edison",
|
||||||
|
|
||||||
|
// Albert Einstein invented the general theory of relativity. https://en.wikipedia.org/wiki/Albert_Einstein
|
||||||
|
"einstein",
|
||||||
|
|
||||||
|
// Gertrude Elion - American biochemist, pharmacologist and the 1988 recipient of the Nobel Prize in Medicine - https://en.wikipedia.org/wiki/Gertrude_Elion
|
||||||
|
"elion",
|
||||||
|
|
||||||
|
// Douglas Engelbart gave the mother of all demos: https://en.wikipedia.org/wiki/Douglas_Engelbart
|
||||||
|
"engelbart",
|
||||||
|
|
||||||
|
// Euclid invented geometry. https://en.wikipedia.org/wiki/Euclid
|
||||||
|
"euclid",
|
||||||
|
|
||||||
|
// Leonhard Euler invented large parts of modern mathematics. https://de.wikipedia.org/wiki/Leonhard_Euler
|
||||||
|
"euler",
|
||||||
|
|
||||||
|
// Pierre de Fermat pioneered several aspects of modern mathematics. https://en.wikipedia.org/wiki/Pierre_de_Fermat
|
||||||
|
"fermat",
|
||||||
|
|
||||||
|
// Enrico Fermi invented the first nuclear reactor. https://en.wikipedia.org/wiki/Enrico_Fermi.
|
||||||
|
"fermi",
|
||||||
|
|
||||||
|
// Richard Feynman was a key contributor to quantum mechanics and particle physics. https://en.wikipedia.org/wiki/Richard_Feynman
|
||||||
|
"feynman",
|
||||||
|
|
||||||
|
// Benjamin Franklin is famous for his experiments in electricity and the invention of the lightning rod.
|
||||||
|
"franklin",
|
||||||
|
|
||||||
|
// Galileo was a founding father of modern astronomy, and faced politics and obscurantism to establish scientific truth. https://en.wikipedia.org/wiki/Galileo_Galilei
|
||||||
|
"galileo",
|
||||||
|
|
||||||
|
// William Henry "Bill" Gates III is an American business magnate, philanthropist, investor, computer programmer, and inventor. https://en.wikipedia.org/wiki/Bill_Gates
|
||||||
|
"gates",
|
||||||
|
|
||||||
|
// Adele Goldberg, was one of the designers and developers of the Smalltalk language. https://en.wikipedia.org/wiki/Adele_Goldberg_(computer_scientist)
|
||||||
|
"goldberg",
|
||||||
|
|
||||||
|
// Adele Goldstine, born Adele Katz, wrote the complete technical description for the first electronic digital computer, ENIAC. https://en.wikipedia.org/wiki/Adele_Goldstine
|
||||||
|
"goldstine",
|
||||||
|
|
||||||
|
// Shafi Goldwasser is a computer scientist known for creating theoretical foundations of modern cryptography. Winner of 2012 ACM Turing Award. https://en.wikipedia.org/wiki/Shafi_Goldwasser
|
||||||
|
"goldwasser",
|
||||||
|
|
||||||
|
// James Golick, all around gangster.
|
||||||
|
"golick",
|
||||||
|
|
||||||
|
// Jane Goodall - British primatologist, ethologist, and anthropologist who is considered to be the world's foremost expert on chimpanzees - https://en.wikipedia.org/wiki/Jane_Goodall
|
||||||
|
"goodall",
|
||||||
|
|
||||||
|
// Lois Haibt - American computer scientist, part of the team at IBM that developed FORTRAN - https://en.wikipedia.org/wiki/Lois_Haibt
|
||||||
|
"haibt",
|
||||||
|
|
||||||
|
// Margaret Hamilton - Director of the Software Engineering Division of the MIT Instrumentation Laboratory, which developed on-board flight software for the Apollo space program. https://en.wikipedia.org/wiki/Margaret_Hamilton_(scientist)
|
||||||
|
"hamilton",
|
||||||
|
|
||||||
|
// Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics. https://en.wikipedia.org/wiki/Stephen_Hawking
|
||||||
|
"hawking",
|
||||||
|
|
||||||
|
// Werner Heisenberg was a founding father of quantum mechanics. https://en.wikipedia.org/wiki/Werner_Heisenberg
|
||||||
|
"heisenberg",
|
||||||
|
|
||||||
|
// Jaroslav Heyrovský was the inventor of the polarographic method, father of the electroanalytical method, and recipient of the Nobel Prize in 1959. His main field of work was polarography. https://en.wikipedia.org/wiki/Jaroslav_Heyrovsk%C3%BD
|
||||||
|
"heyrovsky",
|
||||||
|
|
||||||
|
// Dorothy Hodgkin was a British biochemist, credited with the development of protein crystallography. She was awarded the Nobel Prize in Chemistry in 1964. https://en.wikipedia.org/wiki/Dorothy_Hodgkin
|
||||||
|
"hodgkin",
|
||||||
|
|
||||||
|
// Erna Schneider Hoover revolutionized modern communication by inventing a computerized telephone switching method. https://en.wikipedia.org/wiki/Erna_Schneider_Hoover
|
||||||
|
"hoover",
|
||||||
|
|
||||||
|
// Grace Hopper developed the first compiler for a computer programming language and is credited with popularizing the term "debugging" for fixing computer glitches. https://en.wikipedia.org/wiki/Grace_Hopper
|
||||||
|
"hopper",
|
||||||
|
|
||||||
|
// Frances Hugle, she was an American scientist, engineer, and inventor who contributed to the understanding of semiconductors, integrated circuitry, and the unique electrical principles of microscopic materials. https://en.wikipedia.org/wiki/Frances_Hugle
|
||||||
|
"hugle",
|
||||||
|
|
||||||
|
// Hypatia - Greek Alexandrine Neoplatonist philosopher in Egypt who was one of the earliest mothers of mathematics - https://en.wikipedia.org/wiki/Hypatia
|
||||||
|
"hypatia",
|
||||||
|
|
||||||
|
// Yeong-Sil Jang was a Korean scientist and astronomer during the Joseon Dynasty; he invented the first metal printing press and water gauge. https://en.wikipedia.org/wiki/Jang_Yeong-sil
|
||||||
|
"jang",
|
||||||
|
|
||||||
|
// Betty Jennings - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Jean_Bartik
|
||||||
|
"jennings",
|
||||||
|
|
||||||
|
// Mary Lou Jepsen, was the founder and chief technology officer of One Laptop Per Child (OLPC), and the founder of Pixel Qi. https://en.wikipedia.org/wiki/Mary_Lou_Jepsen
|
||||||
|
"jepsen",
|
||||||
|
|
||||||
|
// Irène Joliot-Curie - French scientist who was awarded the Nobel Prize for Chemistry in 1935. Daughter of Marie and Pierre Curie. https://en.wikipedia.org/wiki/Ir%C3%A8ne_Joliot-Curie
|
||||||
|
"joliot",
|
||||||
|
|
||||||
|
// Karen Spärck Jones came up with the concept of inverse document frequency, which is used in most search engines today. https://en.wikipedia.org/wiki/Karen_Sp%C3%A4rck_Jones
|
||||||
|
"jones",
|
||||||
|
|
||||||
|
// A. P. J. Abdul Kalam - is an Indian scientist aka Missile Man of India for his work on the development of ballistic missile and launch vehicle technology - https://en.wikipedia.org/wiki/A._P._J._Abdul_Kalam
|
||||||
|
"kalam",
|
||||||
|
|
||||||
|
// Susan Kare, created the icons and many of the interface elements for the original Apple Macintosh in the 1980s, and was an original employee of NeXT, working as the Creative Director. https://en.wikipedia.org/wiki/Susan_Kare
|
||||||
|
"kare",
|
||||||
|
|
||||||
|
// Mary Kenneth Keller, Sister Mary Kenneth Keller became the first American woman to earn a PhD in Computer Science in 1965. https://en.wikipedia.org/wiki/Mary_Kenneth_Keller
|
||||||
|
"keller",
|
||||||
|
|
||||||
|
// Har Gobind Khorana - Indian-American biochemist who shared the 1968 Nobel Prize for Physiology - https://en.wikipedia.org/wiki/Har_Gobind_Khorana
|
||||||
|
"khorana",
|
||||||
|
|
||||||
|
// Jack Kilby invented silicone integrated circuits and gave Silicon Valley its name. - https://en.wikipedia.org/wiki/Jack_Kilby
|
||||||
|
"kilby",
|
||||||
|
|
||||||
|
// Maria Kirch - German astronomer and first woman to discover a comet - https://en.wikipedia.org/wiki/Maria_Margarethe_Kirch
|
||||||
|
"kirch",
|
||||||
|
|
||||||
|
// Donald Knuth - American computer scientist, author of "The Art of Computer Programming" and creator of the TeX typesetting system. https://en.wikipedia.org/wiki/Donald_Knuth
|
||||||
|
"knuth",
|
||||||
|
|
||||||
|
// Sophie Kowalevski - Russian mathematician responsible for important original contributions to analysis, differential equations and mechanics - https://en.wikipedia.org/wiki/Sofia_Kovalevskaya
|
||||||
|
"kowalevski",
|
||||||
|
|
||||||
|
// Marie-Jeanne de Lalande - French astronomer, mathematician and cataloguer of stars - https://en.wikipedia.org/wiki/Marie-Jeanne_de_Lalande
|
||||||
|
"lalande",
|
||||||
|
|
||||||
|
// Hedy Lamarr - Actress and inventor. The principles of her work are now incorporated into modern Wi-Fi, CDMA and Bluetooth technology. https://en.wikipedia.org/wiki/Hedy_Lamarr
|
||||||
|
"lamarr",
|
||||||
|
|
||||||
|
// Leslie B. Lamport - American computer scientist. Lamport is best known for his seminal work in distributed systems and was the winner of the 2013 Turing Award. https://en.wikipedia.org/wiki/Leslie_Lamport
|
||||||
|
"lamport",
|
||||||
|
|
||||||
|
// Mary Leakey - British paleoanthropologist who discovered the first fossilized Proconsul skull - https://en.wikipedia.org/wiki/Mary_Leakey
|
||||||
|
"leakey",
|
||||||
|
|
||||||
|
// Henrietta Swan Leavitt - she was an American astronomer who discovered the relation between the luminosity and the period of Cepheid variable stars. https://en.wikipedia.org/wiki/Henrietta_Swan_Leavitt
|
||||||
|
"leavitt",
|
||||||
|
|
||||||
|
//Daniel Lewin - Mathematician, Akamai co-founder, soldier, 9/11 victim-- Developed optimization techniques for routing traffic on the internet. Died attempting to stop the 9-11 hijackers. https://en.wikipedia.org/wiki/Daniel_Lewin
|
||||||
|
"lewin",
|
||||||
|
|
||||||
|
// Ruth Lichterman - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Ruth_Teitelbaum
|
||||||
|
"lichterman",
|
||||||
|
|
||||||
|
// Barbara Liskov - co-developed the Liskov substitution principle. Liskov was also the winner of the Turing Prize in 2008. - https://en.wikipedia.org/wiki/Barbara_Liskov
|
||||||
|
"liskov",
|
||||||
|
|
||||||
|
// Ada Lovelace invented the first algorithm. https://en.wikipedia.org/wiki/Ada_Lovelace (thanks James Turnbull)
|
||||||
|
"lovelace",
|
||||||
|
|
||||||
|
// Auguste and Louis Lumière - the first filmmakers in history - https://en.wikipedia.org/wiki/Auguste_and_Louis_Lumi%C3%A8re
|
||||||
|
"lumiere",
|
||||||
|
|
||||||
|
// Mahavira - Ancient Indian mathematician during 9th century AD who discovered basic algebraic identities - https://en.wikipedia.org/wiki/Mah%C4%81v%C4%ABra_(mathematician)
|
||||||
|
"mahavira",
|
||||||
|
|
||||||
|
// Maria Mayer - American theoretical physicist and Nobel laureate in Physics for proposing the nuclear shell model of the atomic nucleus - https://en.wikipedia.org/wiki/Maria_Mayer
|
||||||
|
"mayer",
|
||||||
|
|
||||||
|
// John McCarthy invented LISP: https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)
|
||||||
|
"mccarthy",
|
||||||
|
|
||||||
|
// Barbara McClintock - a distinguished American cytogeneticist, 1983 Nobel Laureate in Physiology or Medicine for discovering transposons. https://en.wikipedia.org/wiki/Barbara_McClintock
|
||||||
|
"mcclintock",
|
||||||
|
|
||||||
|
// Malcolm McLean invented the modern shipping container: https://en.wikipedia.org/wiki/Malcom_McLean
|
||||||
|
"mclean",
|
||||||
|
|
||||||
|
// Kay McNulty - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Kathleen_Antonelli
|
||||||
|
"mcnulty",
|
||||||
|
|
||||||
|
// Lise Meitner - Austrian/Swedish physicist who was involved in the discovery of nuclear fission. The element meitnerium is named after her - https://en.wikipedia.org/wiki/Lise_Meitner
|
||||||
|
"meitner",
|
||||||
|
|
||||||
|
// Carla Meninsky, was the game designer and programmer for Atari 2600 games Dodge 'Em and Warlords. https://en.wikipedia.org/wiki/Carla_Meninsky
|
||||||
|
"meninsky",
|
||||||
|
|
||||||
|
// Johanna Mestorf - German prehistoric archaeologist and first female museum director in Germany - https://en.wikipedia.org/wiki/Johanna_Mestorf
|
||||||
|
"mestorf",
|
||||||
|
|
||||||
|
// Marvin Minsky - Pioneer in Artificial Intelligence, co-founder of the MIT's AI Lab, won the Turing Award in 1969. https://en.wikipedia.org/wiki/Marvin_Minsky
|
||||||
|
"minsky",
|
||||||
|
|
||||||
|
// Maryam Mirzakhani - an Iranian mathematician and the first woman to win the Fields Medal. https://en.wikipedia.org/wiki/Maryam_Mirzakhani
|
||||||
|
"mirzakhani",
|
||||||
|
|
||||||
|
// Samuel Morse - contributed to the invention of a single-wire telegraph system based on European telegraphs and was a co-developer of the Morse code - https://en.wikipedia.org/wiki/Samuel_Morse
|
||||||
|
"morse",
|
||||||
|
|
||||||
|
// Ian Murdock - founder of the Debian project - https://en.wikipedia.org/wiki/Ian_Murdock
|
||||||
|
"murdock",
|
||||||
|
|
||||||
|
// Isaac Newton invented classic mechanics and modern optics. https://en.wikipedia.org/wiki/Isaac_Newton
|
||||||
|
"newton",
|
||||||
|
|
||||||
|
// Florence Nightingale, more prominently known as a nurse, was also the first female member of the Royal Statistical Society and a pioneer in statistical graphics https://en.wikipedia.org/wiki/Florence_Nightingale#Statistics_and_sanitary_reform
|
||||||
|
"nightingale",
|
||||||
|
|
||||||
|
// Alfred Nobel - a Swedish chemist, engineer, innovator, and armaments manufacturer (inventor of dynamite) - https://en.wikipedia.org/wiki/Alfred_Nobel
|
||||||
|
"nobel",
|
||||||
|
|
||||||
|
// Emmy Noether, German mathematician. Noether's Theorem is named after her. https://en.wikipedia.org/wiki/Emmy_Noether
|
||||||
|
"noether",
|
||||||
|
|
||||||
|
// Poppy Northcutt. Poppy Northcutt was the first woman to work as part of NASA’s Mission Control. http://www.businessinsider.com/poppy-northcutt-helped-apollo-astronauts-2014-12?op=1
|
||||||
|
"northcutt",
|
||||||
|
|
||||||
|
// Robert Noyce invented silicone integrated circuits and gave Silicon Valley its name. - https://en.wikipedia.org/wiki/Robert_Noyce
|
||||||
|
"noyce",
|
||||||
|
|
||||||
|
// Panini - Ancient Indian linguist and grammarian from 4th century CE who worked on the world's first formal system - https://en.wikipedia.org/wiki/P%C4%81%E1%B9%87ini#Comparison_with_modern_formal_systems
|
||||||
|
"panini",
|
||||||
|
|
||||||
|
// Ambroise Pare invented modern surgery. https://en.wikipedia.org/wiki/Ambroise_Par%C3%A9
|
||||||
|
"pare",
|
||||||
|
|
||||||
|
// Louis Pasteur discovered vaccination, fermentation and pasteurization. https://en.wikipedia.org/wiki/Louis_Pasteur.
|
||||||
|
"pasteur",
|
||||||
|
|
||||||
|
// Cecilia Payne-Gaposchkin was an astronomer and astrophysicist who, in 1925, proposed in her Ph.D. thesis an explanation for the composition of stars in terms of the relative abundances of hydrogen and helium. https://en.wikipedia.org/wiki/Cecilia_Payne-Gaposchkin
|
||||||
|
"payne",
|
||||||
|
|
||||||
|
// Radia Perlman is a software designer and network engineer and most famous for her invention of the spanning-tree protocol (STP). https://en.wikipedia.org/wiki/Radia_Perlman
|
||||||
|
"perlman",
|
||||||
|
|
||||||
|
// Rob Pike was a key contributor to Unix, Plan 9, the X graphic system, utf-8, and the Go programming language. https://en.wikipedia.org/wiki/Rob_Pike
|
||||||
|
"pike",
|
||||||
|
|
||||||
|
// Henri Poincaré made fundamental contributions in several fields of mathematics. https://en.wikipedia.org/wiki/Henri_Poincar%C3%A9
|
||||||
|
"poincare",
|
||||||
|
|
||||||
|
// Laura Poitras is a director and producer whose work, made possible by open source crypto tools, advances the causes of truth and freedom of information by reporting disclosures by whistleblowers such as Edward Snowden. https://en.wikipedia.org/wiki/Laura_Poitras
|
||||||
|
"poitras",
|
||||||
|
|
||||||
|
// Claudius Ptolemy - a Greco-Egyptian writer of Alexandria, known as a mathematician, astronomer, geographer, astrologer, and poet of a single epigram in the Greek Anthology - https://en.wikipedia.org/wiki/Ptolemy
|
||||||
|
"ptolemy",
|
||||||
|
|
||||||
|
// C. V. Raman - Indian physicist who won the Nobel Prize in 1930 for proposing the Raman effect. - https://en.wikipedia.org/wiki/C._V._Raman
|
||||||
|
"raman",
|
||||||
|
|
||||||
|
// Srinivasa Ramanujan - Indian mathematician and autodidact who made extraordinary contributions to mathematical analysis, number theory, infinite series, and continued fractions. - https://en.wikipedia.org/wiki/Srinivasa_Ramanujan
|
||||||
|
"ramanujan",
|
||||||
|
|
||||||
|
// Sally Kristen Ride was an American physicist and astronaut. She was the first American woman in space, and the youngest American astronaut. https://en.wikipedia.org/wiki/Sally_Ride
|
||||||
|
"ride",
|
||||||
|
|
||||||
|
// Rita Levi-Montalcini - Won Nobel Prize in Physiology or Medicine jointly with colleague Stanley Cohen for the discovery of nerve growth factor (https://en.wikipedia.org/wiki/Rita_Levi-Montalcini)
|
||||||
|
"montalcini",
|
||||||
|
|
||||||
|
// Dennis Ritchie - co-creator of UNIX and the C programming language. - https://en.wikipedia.org/wiki/Dennis_Ritchie
|
||||||
|
"ritchie",
|
||||||
|
|
||||||
|
// Wilhelm Conrad Röntgen - German physicist who was awarded the first Nobel Prize in Physics in 1901 for the discovery of X-rays (Röntgen rays). https://en.wikipedia.org/wiki/Wilhelm_R%C3%B6ntgen
|
||||||
|
"roentgen",
|
||||||
|
|
||||||
|
// Rosalind Franklin - British biophysicist and X-ray crystallographer whose research was critical to the understanding of DNA - https://en.wikipedia.org/wiki/Rosalind_Franklin
|
||||||
|
"rosalind",
|
||||||
|
|
||||||
|
// Meghnad Saha - Indian astrophysicist best known for his development of the Saha equation, used to describe chemical and physical conditions in stars - https://en.wikipedia.org/wiki/Meghnad_Saha
|
||||||
|
"saha",
|
||||||
|
|
||||||
|
// Jean E. Sammet developed FORMAC, the first widely used computer language for symbolic manipulation of mathematical formulas. https://en.wikipedia.org/wiki/Jean_E._Sammet
|
||||||
|
"sammet",
|
||||||
|
|
||||||
|
// Carol Shaw - Originally an Atari employee, Carol Shaw is said to be the first female video game designer. https://en.wikipedia.org/wiki/Carol_Shaw_(video_game_designer)
|
||||||
|
"shaw",
|
||||||
|
|
||||||
|
// Dame Stephanie "Steve" Shirley - Founded a software company in 1962 employing women working from home. https://en.wikipedia.org/wiki/Steve_Shirley
|
||||||
|
"shirley",
|
||||||
|
|
||||||
|
// William Shockley co-invented the transistor - https://en.wikipedia.org/wiki/William_Shockley
|
||||||
|
"shockley",
|
||||||
|
|
||||||
|
// Françoise Barré-Sinoussi - French virologist and Nobel Prize Laureate in Physiology or Medicine; her work was fundamental in identifying HIV as the cause of AIDS. https://en.wikipedia.org/wiki/Fran%C3%A7oise_Barr%C3%A9-Sinoussi
|
||||||
|
"sinoussi",
|
||||||
|
|
||||||
|
// Betty Snyder - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Betty_Holberton
|
||||||
|
"snyder",
|
||||||
|
|
||||||
|
// Frances Spence - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Frances_Spence
|
||||||
|
"spence",
|
||||||
|
|
||||||
|
// Richard Matthew Stallman - the founder of the Free Software movement, the GNU project, the Free Software Foundation, and the League for Programming Freedom. He also invented the concept of copyleft to protect the ideals of this movement, and enshrined this concept in the widely-used GPL (General Public License) for software. https://en.wikiquote.org/wiki/Richard_Stallman
|
||||||
|
"stallman",
|
||||||
|
|
||||||
|
// Michael Stonebraker is a database research pioneer and architect of Ingres, Postgres, VoltDB and SciDB. Winner of 2014 ACM Turing Award. https://en.wikipedia.org/wiki/Michael_Stonebraker
|
||||||
|
"stonebraker",
|
||||||
|
|
||||||
|
// Janese Swanson (with others) developed the first of the Carmen Sandiego games. She went on to found Girl Tech. https://en.wikipedia.org/wiki/Janese_Swanson
|
||||||
|
"swanson",
|
||||||
|
|
||||||
|
// Aaron Swartz was influential in creating RSS, Markdown, Creative Commons, Reddit, and much of the internet as we know it today. He was devoted to freedom of information on the web. https://en.wikiquote.org/wiki/Aaron_Swartz
|
||||||
|
"swartz",
|
||||||
|
|
||||||
|
// Bertha Swirles was a theoretical physicist who made a number of contributions to early quantum theory. https://en.wikipedia.org/wiki/Bertha_Swirles
|
||||||
|
"swirles",
|
||||||
|
|
||||||
|
// Nikola Tesla invented the AC electric system and every gadget ever used by a James Bond villain. https://en.wikipedia.org/wiki/Nikola_Tesla
|
||||||
|
"tesla",
|
||||||
|
|
||||||
|
// Ken Thompson - co-creator of UNIX and the C programming language - https://en.wikipedia.org/wiki/Ken_Thompson
|
||||||
|
"thompson",
|
||||||
|
|
||||||
|
// Linus Torvalds invented Linux and Git. https://en.wikipedia.org/wiki/Linus_Torvalds
|
||||||
|
"torvalds",
|
||||||
|
|
||||||
|
// Alan Turing was a founding father of computer science. https://en.wikipedia.org/wiki/Alan_Turing.
|
||||||
|
"turing",
|
||||||
|
|
||||||
|
// Varahamihira - Ancient Indian mathematician who discovered trigonometric formulae during 505-587 CE - https://en.wikipedia.org/wiki/Var%C4%81hamihira#Contributions
|
||||||
|
"varahamihira",
|
||||||
|
|
||||||
|
// Sir Mokshagundam Visvesvaraya - is a notable Indian engineer. He is a recipient of the Indian Republic's highest honour, the Bharat Ratna, in 1955. On his birthday, 15 September is celebrated as Engineer's Day in India in his memory - https://en.wikipedia.org/wiki/Visvesvaraya
|
||||||
|
"visvesvaraya",
|
||||||
|
|
||||||
|
// Christiane Nüsslein-Volhard - German biologist, won Nobel Prize in Physiology or Medicine in 1995 for research on the genetic control of embryonic development. https://en.wikipedia.org/wiki/Christiane_N%C3%BCsslein-Volhard
|
||||||
|
"volhard",
|
||||||
|
|
||||||
|
// Marlyn Wescoff - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Marlyn_Meltzer
|
||||||
|
"wescoff",
|
||||||
|
|
||||||
|
// Andrew Wiles - Notable British mathematician who proved the enigmatic Fermat's Last Theorem - https://en.wikipedia.org/wiki/Andrew_Wiles
|
||||||
|
"wiles",
|
||||||
|
|
||||||
|
// Roberta Williams, did pioneering work in graphical adventure games for personal computers, particularly the King's Quest series. https://en.wikipedia.org/wiki/Roberta_Williams
|
||||||
|
"williams",
|
||||||
|
|
||||||
|
// Sophie Wilson designed the first Acorn Micro-Computer and the instruction set for ARM processors. https://en.wikipedia.org/wiki/Sophie_Wilson
|
||||||
|
"wilson",
|
||||||
|
|
||||||
|
// Jeannette Wing - co-developed the Liskov substitution principle. - https://en.wikipedia.org/wiki/Jeannette_Wing
|
||||||
|
"wing",
|
||||||
|
|
||||||
|
// Steve Wozniak invented the Apple I and Apple II. https://en.wikipedia.org/wiki/Steve_Wozniak
|
||||||
|
"wozniak",
|
||||||
|
|
||||||
|
// The Wright brothers, Orville and Wilbur - credited with inventing and building the world's first successful airplane and making the first controlled, powered and sustained heavier-than-air human flight - https://en.wikipedia.org/wiki/Wright_brothers
|
||||||
|
"wright",
|
||||||
|
|
||||||
|
// Rosalyn Sussman Yalow - Rosalyn Sussman Yalow was an American medical physicist, and a co-winner of the 1977 Nobel Prize in Physiology or Medicine for development of the radioimmunoassay technique. https://en.wikipedia.org/wiki/Rosalyn_Sussman_Yalow
|
||||||
|
"yalow",
|
||||||
|
|
||||||
|
// Ada Yonath - an Israeli crystallographer, the first woman from the Middle East to win a Nobel prize in the sciences. https://en.wikipedia.org/wiki/Ada_Yonath
|
||||||
|
"yonath",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetRandomName generates a random name from the list of adjectives and surnames in this package
|
||||||
|
// formatted as "adjective_surname". For example 'focused_turing'. If retry is non-zero, a random
|
||||||
|
// integer between 0 and 10 will be added to the end of the name, e.g `focused_turing3`
|
||||||
|
func GetRandomName(retry int) string {
|
||||||
|
rnd := random.Rand
|
||||||
|
begin:
|
||||||
|
name := fmt.Sprintf("%s_%s", left[rnd.Intn(len(left))], right[rnd.Intn(len(right))])
|
||||||
|
if name == "boring_wozniak" /* Steve Wozniak is not boring */ {
|
||||||
|
goto begin
|
||||||
|
}
|
||||||
|
|
||||||
|
if retry > 0 {
|
||||||
|
name = fmt.Sprintf("%s%d", name, rnd.Intn(10))
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package random
|
||||||
|
|
||||||
|
import (
|
||||||
|
cryptorand "crypto/rand"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rand is a global *rand.Rand instance, which initialized with NewSource() source.
|
||||||
|
var Rand = rand.New(NewSource())
|
||||||
|
|
||||||
|
// Reader is a global, shared instance of a pseudorandom bytes generator.
|
||||||
|
// It doesn't consume entropy.
|
||||||
|
var Reader io.Reader = &reader{rnd: Rand}
|
||||||
|
|
||||||
|
// copypaste from standard math/rand
|
||||||
|
type lockedSource struct {
|
||||||
|
lk sync.Mutex
|
||||||
|
src rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lockedSource) Int63() (n int64) {
|
||||||
|
r.lk.Lock()
|
||||||
|
n = r.src.Int63()
|
||||||
|
r.lk.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lockedSource) Seed(seed int64) {
|
||||||
|
r.lk.Lock()
|
||||||
|
r.src.Seed(seed)
|
||||||
|
r.lk.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSource returns math/rand.Source safe for concurrent use and initialized
|
||||||
|
// with current unix-nano timestamp
|
||||||
|
func NewSource() rand.Source {
|
||||||
|
var seed int64
|
||||||
|
if cryptoseed, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)); err != nil {
|
||||||
|
// This should not happen, but worst-case fallback to time-based seed.
|
||||||
|
seed = time.Now().UnixNano()
|
||||||
|
} else {
|
||||||
|
seed = cryptoseed.Int64()
|
||||||
|
}
|
||||||
|
return &lockedSource{
|
||||||
|
src: rand.NewSource(seed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type reader struct {
|
||||||
|
rnd *rand.Rand
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reader) Read(b []byte) (int, error) {
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
val := r.rnd.Int63()
|
||||||
|
for val > 0 {
|
||||||
|
b[i] = byte(val)
|
||||||
|
i++
|
||||||
|
if i == len(b) {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
val >>= 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ASCII list the possible supported ASCII key sequence
|
||||||
|
var ASCII = []string{
|
||||||
|
"ctrl-@",
|
||||||
|
"ctrl-a",
|
||||||
|
"ctrl-b",
|
||||||
|
"ctrl-c",
|
||||||
|
"ctrl-d",
|
||||||
|
"ctrl-e",
|
||||||
|
"ctrl-f",
|
||||||
|
"ctrl-g",
|
||||||
|
"ctrl-h",
|
||||||
|
"ctrl-i",
|
||||||
|
"ctrl-j",
|
||||||
|
"ctrl-k",
|
||||||
|
"ctrl-l",
|
||||||
|
"ctrl-m",
|
||||||
|
"ctrl-n",
|
||||||
|
"ctrl-o",
|
||||||
|
"ctrl-p",
|
||||||
|
"ctrl-q",
|
||||||
|
"ctrl-r",
|
||||||
|
"ctrl-s",
|
||||||
|
"ctrl-t",
|
||||||
|
"ctrl-u",
|
||||||
|
"ctrl-v",
|
||||||
|
"ctrl-w",
|
||||||
|
"ctrl-x",
|
||||||
|
"ctrl-y",
|
||||||
|
"ctrl-z",
|
||||||
|
"ctrl-[",
|
||||||
|
"ctrl-\\",
|
||||||
|
"ctrl-]",
|
||||||
|
"ctrl-^",
|
||||||
|
"ctrl-_",
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code.
|
||||||
|
func ToBytes(keys string) ([]byte, error) {
|
||||||
|
codes := []byte{}
|
||||||
|
next:
|
||||||
|
for _, key := range strings.Split(keys, ",") {
|
||||||
|
if len(key) != 1 {
|
||||||
|
for code, ctrl := range ASCII {
|
||||||
|
if ctrl == key {
|
||||||
|
codes = append(codes, byte(code))
|
||||||
|
continue next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if key == "DEL" {
|
||||||
|
codes = append(codes, 127)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Unknown character: '%s'", key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
codes = append(codes, byte(key[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return codes, nil
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// +build linux,cgo
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #include <termios.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// Termios is the Unix API for terminal I/O.
|
||||||
|
// It is passthrough for syscall.Termios in order to make it portable with
|
||||||
|
// other platforms where it is not available or handled differently.
|
||||||
|
type Termios syscall.Termios
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if err := tcget(fd, &oldState.termios); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
|
||||||
|
C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&newState)))
|
||||||
|
if err := tcset(fd, &newState); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcget(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
ret, err := C.tcgetattr(C.int(fd), (*C.struct_termios)(unsafe.Pointer(p)))
|
||||||
|
if ret != 0 {
|
||||||
|
return err.(syscall.Errno)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcset(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(p)))
|
||||||
|
if ret != 0 {
|
||||||
|
return err.(syscall.Errno)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// +build !windows
|
||||||
|
// +build !linux !cgo
|
||||||
|
// +build !solaris !cgo
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tcget(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(p)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcset(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(p)))
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
// +build solaris,cgo
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #include <termios.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// Termios is the Unix API for terminal I/O.
|
||||||
|
// It is passthrough for syscall.Termios in order to make it portable with
|
||||||
|
// other platforms where it is not available or handled differently.
|
||||||
|
type Termios syscall.Termios
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if err := tcget(fd, &oldState.termios); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
|
||||||
|
newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON | syscall.IXANY)
|
||||||
|
newState.Oflag &^= syscall.OPOST
|
||||||
|
newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN)
|
||||||
|
newState.Cflag &^= (syscall.CSIZE | syscall.PARENB)
|
||||||
|
newState.Cflag |= syscall.CS8
|
||||||
|
|
||||||
|
/*
|
||||||
|
VMIN is the minimum number of characters that needs to be read in non-canonical mode for it to be returned
|
||||||
|
Since VMIN is overloaded with another element in canonical mode when we switch modes it defaults to 4. It
|
||||||
|
needs to be explicitly set to 1.
|
||||||
|
*/
|
||||||
|
newState.Cc[C.VMIN] = 1
|
||||||
|
newState.Cc[C.VTIME] = 0
|
||||||
|
|
||||||
|
if err := tcset(fd, &newState); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcget(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
ret, err := C.tcgetattr(C.int(fd), (*C.struct_termios)(unsafe.Pointer(p)))
|
||||||
|
if ret != 0 {
|
||||||
|
return err.(syscall.Errno)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcset(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(p)))
|
||||||
|
if ret != 0 {
|
||||||
|
return err.(syscall.Errno)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
// Package term provides structures and helper functions to work with
|
||||||
|
// terminal (state, sizes).
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidState is returned if the state of the terminal is invalid.
|
||||||
|
ErrInvalidState = errors.New("Invalid terminal state")
|
||||||
|
)
|
||||||
|
|
||||||
|
// State represents the state of the terminal.
|
||||||
|
type State struct {
|
||||||
|
termios Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
// Winsize represents the size of the terminal window.
|
||||||
|
type Winsize struct {
|
||||||
|
Height uint16
|
||||||
|
Width uint16
|
||||||
|
x uint16
|
||||||
|
y uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// StdStreams returns the standard streams (stdin, stdout, stderr).
|
||||||
|
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||||
|
return os.Stdin, os.Stdout, os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
|
||||||
|
func GetFdInfo(in interface{}) (uintptr, bool) {
|
||||||
|
var inFd uintptr
|
||||||
|
var isTerminalIn bool
|
||||||
|
if file, ok := in.(*os.File); ok {
|
||||||
|
inFd = file.Fd()
|
||||||
|
isTerminalIn = IsTerminal(inFd)
|
||||||
|
}
|
||||||
|
return inFd, isTerminalIn
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
var termios Termios
|
||||||
|
return tcget(fd, &termios) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreTerminal restores the terminal connected to the given file descriptor
|
||||||
|
// to a previous state.
|
||||||
|
func RestoreTerminal(fd uintptr, state *State) error {
|
||||||
|
if state == nil {
|
||||||
|
return ErrInvalidState
|
||||||
|
}
|
||||||
|
if err := tcset(fd, &state.termios); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveState saves the state of the terminal connected to the given file descriptor.
|
||||||
|
func SaveState(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if err := tcget(fd, &oldState.termios); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableEcho applies the specified state to the terminal connected to the file
|
||||||
|
// descriptor, with echo disabled.
|
||||||
|
func DisableEcho(fd uintptr, state *State) error {
|
||||||
|
newState := state.termios
|
||||||
|
newState.Lflag &^= syscall.ECHO
|
||||||
|
|
||||||
|
if err := tcset(fd, &newState); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
handleInterrupt(fd, state)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawTerminal puts the terminal connected to the given file descriptor into
|
||||||
|
// raw mode and returns the previous state. On UNIX, this puts both the input
|
||||||
|
// and output into raw mode. On Windows, it only puts the input into raw mode.
|
||||||
|
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||||
|
oldState, err := MakeRaw(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
handleInterrupt(fd, oldState)
|
||||||
|
return oldState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawTerminalOutput puts the output of terminal connected to the given file
|
||||||
|
// descriptor into raw mode. On UNIX, this does nothing and returns nil for the
|
||||||
|
// state. On Windows, it disables LF -> CRLF translation.
|
||||||
|
func SetRawTerminalOutput(fd uintptr) (*State, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleInterrupt(fd uintptr, state *State) {
|
||||||
|
sigchan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigchan, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
for range sigchan {
|
||||||
|
// quit cleanly and the new terminal item is on a new line
|
||||||
|
fmt.Println()
|
||||||
|
signal.Stop(sigchan)
|
||||||
|
close(sigchan)
|
||||||
|
RestoreTerminal(fd, state)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
// +build solaris
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stropts.h>
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
|
// Small wrapper to get rid of variadic args of ioctl()
|
||||||
|
int my_ioctl(int fd, int cmd, struct winsize *ws) {
|
||||||
|
return ioctl(fd, cmd, ws);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// GetWinsize returns the window size based on the specified file descriptor.
|
||||||
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||||
|
ws := &Winsize{}
|
||||||
|
ret, err := C.my_ioctl(C.int(fd), C.int(syscall.TIOCGWINSZ), (*C.struct_winsize)(unsafe.Pointer(ws)))
|
||||||
|
// Skip retval = 0
|
||||||
|
if ret == 0 {
|
||||||
|
return ws, nil
|
||||||
|
}
|
||||||
|
return ws, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWinsize tries to set the specified window size for the specified file descriptor.
|
||||||
|
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||||
|
ret, err := C.my_ioctl(C.int(fd), C.int(syscall.TIOCSWINSZ), (*C.struct_winsize)(unsafe.Pointer(ws)))
|
||||||
|
// Skip retval = 0
|
||||||
|
if ret == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// +build !solaris,!windows
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetWinsize returns the window size based on the specified file descriptor.
|
||||||
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||||
|
ws := &Winsize{}
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||||
|
// Skipp errno = 0
|
||||||
|
if err == 0 {
|
||||||
|
return ws, nil
|
||||||
|
}
|
||||||
|
return ws, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWinsize tries to set the specified window size for the specified file descriptor.
|
||||||
|
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||||
|
// Skipp errno = 0
|
||||||
|
if err == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,233 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Azure/go-ansiterm/winterm"
|
||||||
|
"github.com/docker/docker/pkg/term/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State holds the console mode for the terminal.
|
||||||
|
type State struct {
|
||||||
|
mode uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Winsize is used for window size.
|
||||||
|
type Winsize struct {
|
||||||
|
Height uint16
|
||||||
|
Width uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
|
||||||
|
enableVirtualTerminalInput = 0x0200
|
||||||
|
enableVirtualTerminalProcessing = 0x0004
|
||||||
|
disableNewlineAutoReturn = 0x0008
|
||||||
|
)
|
||||||
|
|
||||||
|
// vtInputSupported is true if enableVirtualTerminalInput is supported by the console
|
||||||
|
var vtInputSupported bool
|
||||||
|
|
||||||
|
// StdStreams returns the standard streams (stdin, stdout, stderr).
|
||||||
|
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||||
|
// Turn on VT handling on all std handles, if possible. This might
|
||||||
|
// fail, in which case we will fall back to terminal emulation.
|
||||||
|
var emulateStdin, emulateStdout, emulateStderr bool
|
||||||
|
fd := os.Stdin.Fd()
|
||||||
|
if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
||||||
|
// Validate that enableVirtualTerminalInput is supported, but do not set it.
|
||||||
|
if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalInput); err != nil {
|
||||||
|
emulateStdin = true
|
||||||
|
} else {
|
||||||
|
vtInputSupported = true
|
||||||
|
}
|
||||||
|
// Unconditionally set the console mode back even on failure because SetConsoleMode
|
||||||
|
// remembers invalid bits on input handles.
|
||||||
|
winterm.SetConsoleMode(fd, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = os.Stdout.Fd()
|
||||||
|
if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
||||||
|
// Validate disableNewlineAutoReturn is supported, but do not set it.
|
||||||
|
if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil {
|
||||||
|
emulateStdout = true
|
||||||
|
} else {
|
||||||
|
winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = os.Stderr.Fd()
|
||||||
|
if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
||||||
|
// Validate disableNewlineAutoReturn is supported, but do not set it.
|
||||||
|
if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil {
|
||||||
|
emulateStderr = true
|
||||||
|
} else {
|
||||||
|
winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("ConEmuANSI") == "ON" || os.Getenv("ConsoleZVersion") != "" {
|
||||||
|
// The ConEmu and ConsoleZ terminals emulate ANSI on output streams well.
|
||||||
|
emulateStdin = true
|
||||||
|
emulateStdout = false
|
||||||
|
emulateStderr = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if emulateStdin {
|
||||||
|
stdIn = windows.NewAnsiReader(syscall.STD_INPUT_HANDLE)
|
||||||
|
} else {
|
||||||
|
stdIn = os.Stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
if emulateStdout {
|
||||||
|
stdOut = windows.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE)
|
||||||
|
} else {
|
||||||
|
stdOut = os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
if emulateStderr {
|
||||||
|
stdErr = windows.NewAnsiWriter(syscall.STD_ERROR_HANDLE)
|
||||||
|
} else {
|
||||||
|
stdErr = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
|
||||||
|
func GetFdInfo(in interface{}) (uintptr, bool) {
|
||||||
|
return windows.GetHandleInfo(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWinsize returns the window size based on the specified file descriptor.
|
||||||
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||||
|
info, err := winterm.GetConsoleScreenBufferInfo(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
winsize := &Winsize{
|
||||||
|
Width: uint16(info.Window.Right - info.Window.Left + 1),
|
||||||
|
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
return winsize, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
return windows.IsConsole(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreTerminal restores the terminal connected to the given file descriptor
|
||||||
|
// to a previous state.
|
||||||
|
func RestoreTerminal(fd uintptr, state *State) error {
|
||||||
|
return winterm.SetConsoleMode(fd, state.mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveState saves the state of the terminal connected to the given file descriptor.
|
||||||
|
func SaveState(fd uintptr) (*State, error) {
|
||||||
|
mode, e := winterm.GetConsoleMode(fd)
|
||||||
|
if e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return &State{mode: mode}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableEcho disables echo for the terminal connected to the given file descriptor.
|
||||||
|
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||||
|
func DisableEcho(fd uintptr, state *State) error {
|
||||||
|
mode := state.mode
|
||||||
|
mode &^= winterm.ENABLE_ECHO_INPUT
|
||||||
|
mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
|
||||||
|
err := winterm.SetConsoleMode(fd, mode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register an interrupt handler to catch and restore prior state
|
||||||
|
restoreAtInterrupt(fd, state)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawTerminal puts the terminal connected to the given file descriptor into
|
||||||
|
// raw mode and returns the previous state. On UNIX, this puts both the input
|
||||||
|
// and output into raw mode. On Windows, it only puts the input into raw mode.
|
||||||
|
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||||
|
state, err := MakeRaw(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register an interrupt handler to catch and restore prior state
|
||||||
|
restoreAtInterrupt(fd, state)
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawTerminalOutput puts the output of terminal connected to the given file
|
||||||
|
// descriptor into raw mode. On UNIX, this does nothing and returns nil for the
|
||||||
|
// state. On Windows, it disables LF -> CRLF translation.
|
||||||
|
func SetRawTerminalOutput(fd uintptr) (*State, error) {
|
||||||
|
state, err := SaveState(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore failures, since disableNewlineAutoReturn might not be supported on this
|
||||||
|
// version of Windows.
|
||||||
|
winterm.SetConsoleMode(fd, state.mode|disableNewlineAutoReturn)
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
state, err := SaveState(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := state.mode
|
||||||
|
|
||||||
|
// See
|
||||||
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||||
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||||
|
|
||||||
|
// Disable these modes
|
||||||
|
mode &^= winterm.ENABLE_ECHO_INPUT
|
||||||
|
mode &^= winterm.ENABLE_LINE_INPUT
|
||||||
|
mode &^= winterm.ENABLE_MOUSE_INPUT
|
||||||
|
mode &^= winterm.ENABLE_WINDOW_INPUT
|
||||||
|
mode &^= winterm.ENABLE_PROCESSED_INPUT
|
||||||
|
|
||||||
|
// Enable these modes
|
||||||
|
mode |= winterm.ENABLE_EXTENDED_FLAGS
|
||||||
|
mode |= winterm.ENABLE_INSERT_MODE
|
||||||
|
mode |= winterm.ENABLE_QUICK_EDIT_MODE
|
||||||
|
if vtInputSupported {
|
||||||
|
mode |= enableVirtualTerminalInput
|
||||||
|
}
|
||||||
|
|
||||||
|
err = winterm.SetConsoleMode(fd, mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreAtInterrupt(fd uintptr, state *State) {
|
||||||
|
sigchan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigchan, os.Interrupt)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = <-sigchan
|
||||||
|
RestoreTerminal(fd, state)
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTermios = syscall.TIOCGETA
|
||||||
|
setTermios = syscall.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
// Termios magic numbers, passthrough to the ones defined in syscall.
|
||||||
|
const (
|
||||||
|
IGNBRK = syscall.IGNBRK
|
||||||
|
PARMRK = syscall.PARMRK
|
||||||
|
INLCR = syscall.INLCR
|
||||||
|
IGNCR = syscall.IGNCR
|
||||||
|
ECHONL = syscall.ECHONL
|
||||||
|
CSIZE = syscall.CSIZE
|
||||||
|
ICRNL = syscall.ICRNL
|
||||||
|
ISTRIP = syscall.ISTRIP
|
||||||
|
PARENB = syscall.PARENB
|
||||||
|
ECHO = syscall.ECHO
|
||||||
|
ICANON = syscall.ICANON
|
||||||
|
ISIG = syscall.ISIG
|
||||||
|
IXON = syscall.IXON
|
||||||
|
BRKINT = syscall.BRKINT
|
||||||
|
INPCK = syscall.INPCK
|
||||||
|
OPOST = syscall.OPOST
|
||||||
|
CS8 = syscall.CS8
|
||||||
|
IEXTEN = syscall.IEXTEN
|
||||||
|
)
|
||||||
|
|
||||||
|
// Termios is the Unix API for terminal I/O.
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint64
|
||||||
|
Oflag uint64
|
||||||
|
Cflag uint64
|
||||||
|
Lflag uint64
|
||||||
|
Cc [20]byte
|
||||||
|
Ispeed uint64
|
||||||
|
Ospeed uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
|
||||||
|
newState.Oflag &^= OPOST
|
||||||
|
newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN)
|
||||||
|
newState.Cflag &^= (CSIZE | PARENB)
|
||||||
|
newState.Cflag |= CS8
|
||||||
|
newState.Cc[syscall.VMIN] = 1
|
||||||
|
newState.Cc[syscall.VTIME] = 0
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTermios = syscall.TIOCGETA
|
||||||
|
setTermios = syscall.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
// Termios magic numbers, passthrough to the ones defined in syscall.
|
||||||
|
const (
|
||||||
|
IGNBRK = syscall.IGNBRK
|
||||||
|
PARMRK = syscall.PARMRK
|
||||||
|
INLCR = syscall.INLCR
|
||||||
|
IGNCR = syscall.IGNCR
|
||||||
|
ECHONL = syscall.ECHONL
|
||||||
|
CSIZE = syscall.CSIZE
|
||||||
|
ICRNL = syscall.ICRNL
|
||||||
|
ISTRIP = syscall.ISTRIP
|
||||||
|
PARENB = syscall.PARENB
|
||||||
|
ECHO = syscall.ECHO
|
||||||
|
ICANON = syscall.ICANON
|
||||||
|
ISIG = syscall.ISIG
|
||||||
|
IXON = syscall.IXON
|
||||||
|
BRKINT = syscall.BRKINT
|
||||||
|
INPCK = syscall.INPCK
|
||||||
|
OPOST = syscall.OPOST
|
||||||
|
CS8 = syscall.CS8
|
||||||
|
IEXTEN = syscall.IEXTEN
|
||||||
|
)
|
||||||
|
|
||||||
|
// Termios is the Unix API for terminal I/O.
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint32
|
||||||
|
Oflag uint32
|
||||||
|
Cflag uint32
|
||||||
|
Lflag uint32
|
||||||
|
Cc [20]byte
|
||||||
|
Ispeed uint32
|
||||||
|
Ospeed uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
|
||||||
|
newState.Oflag &^= OPOST
|
||||||
|
newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN)
|
||||||
|
newState.Cflag &^= (CSIZE | PARENB)
|
||||||
|
newState.Cflag |= CS8
|
||||||
|
newState.Cc[syscall.VMIN] = 1
|
||||||
|
newState.Cc[syscall.VTIME] = 0
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// +build !cgo
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTermios = syscall.TCGETS
|
||||||
|
setTermios = syscall.TCSETS
|
||||||
|
)
|
||||||
|
|
||||||
|
// Termios is the Unix API for terminal I/O.
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint32
|
||||||
|
Oflag uint32
|
||||||
|
Cflag uint32
|
||||||
|
Lflag uint32
|
||||||
|
Cc [20]byte
|
||||||
|
Ispeed uint32
|
||||||
|
Ospeed uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
|
||||||
|
newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON)
|
||||||
|
newState.Oflag &^= syscall.OPOST
|
||||||
|
newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN)
|
||||||
|
newState.Cflag &^= (syscall.CSIZE | syscall.PARENB)
|
||||||
|
newState.Cflag |= syscall.CS8
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTermios = syscall.TIOCGETA
|
||||||
|
setTermios = syscall.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
// Termios magic numbers, passthrough to the ones defined in syscall.
|
||||||
|
const (
|
||||||
|
IGNBRK = syscall.IGNBRK
|
||||||
|
PARMRK = syscall.PARMRK
|
||||||
|
INLCR = syscall.INLCR
|
||||||
|
IGNCR = syscall.IGNCR
|
||||||
|
ECHONL = syscall.ECHONL
|
||||||
|
CSIZE = syscall.CSIZE
|
||||||
|
ICRNL = syscall.ICRNL
|
||||||
|
ISTRIP = syscall.ISTRIP
|
||||||
|
PARENB = syscall.PARENB
|
||||||
|
ECHO = syscall.ECHO
|
||||||
|
ICANON = syscall.ICANON
|
||||||
|
ISIG = syscall.ISIG
|
||||||
|
IXON = syscall.IXON
|
||||||
|
BRKINT = syscall.BRKINT
|
||||||
|
INPCK = syscall.INPCK
|
||||||
|
OPOST = syscall.OPOST
|
||||||
|
CS8 = syscall.CS8
|
||||||
|
IEXTEN = syscall.IEXTEN
|
||||||
|
)
|
||||||
|
|
||||||
|
// Termios is the Unix API for terminal I/O.
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint32
|
||||||
|
Oflag uint32
|
||||||
|
Cflag uint32
|
||||||
|
Lflag uint32
|
||||||
|
Cc [20]byte
|
||||||
|
Ispeed uint32
|
||||||
|
Ospeed uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
|
||||||
|
newState.Oflag &^= OPOST
|
||||||
|
newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN)
|
||||||
|
newState.Cflag &^= (CSIZE | PARENB)
|
||||||
|
newState.Cflag |= CS8
|
||||||
|
newState.Cc[syscall.VMIN] = 1
|
||||||
|
newState.Cc[syscall.VTIME] = 0
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
|
@ -0,0 +1,263 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
ansiterm "github.com/Azure/go-ansiterm"
|
||||||
|
"github.com/Azure/go-ansiterm/winterm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
escapeSequence = ansiterm.KEY_ESC_CSI
|
||||||
|
)
|
||||||
|
|
||||||
|
// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation.
|
||||||
|
type ansiReader struct {
|
||||||
|
file *os.File
|
||||||
|
fd uintptr
|
||||||
|
buffer []byte
|
||||||
|
cbBuffer int
|
||||||
|
command []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a
|
||||||
|
// Windows console input handle.
|
||||||
|
func NewAnsiReader(nFile int) io.ReadCloser {
|
||||||
|
initLogger()
|
||||||
|
file, fd := winterm.GetStdFile(nFile)
|
||||||
|
return &ansiReader{
|
||||||
|
file: file,
|
||||||
|
fd: fd,
|
||||||
|
command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH),
|
||||||
|
buffer: make([]byte, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the wrapped file.
|
||||||
|
func (ar *ansiReader) Close() (err error) {
|
||||||
|
return ar.file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fd returns the file descriptor of the wrapped file.
|
||||||
|
func (ar *ansiReader) Fd() uintptr {
|
||||||
|
return ar.fd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads up to len(p) bytes of translated input events into p.
|
||||||
|
func (ar *ansiReader) Read(p []byte) (int, error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previously read bytes exist, read as much as we can and return
|
||||||
|
if len(ar.buffer) > 0 {
|
||||||
|
logger.Debugf("Reading previously cached bytes")
|
||||||
|
|
||||||
|
originalLength := len(ar.buffer)
|
||||||
|
copiedLength := copy(p, ar.buffer)
|
||||||
|
|
||||||
|
if copiedLength == originalLength {
|
||||||
|
ar.buffer = make([]byte, 0, len(p))
|
||||||
|
} else {
|
||||||
|
ar.buffer = ar.buffer[copiedLength:]
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debugf("Read from cache p[%d]: % x", copiedLength, p)
|
||||||
|
return copiedLength, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and translate key events
|
||||||
|
events, err := readInputEvents(ar.fd, len(p))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if len(events) == 0 {
|
||||||
|
logger.Debug("No input events detected")
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keyBytes := translateKeyEvents(events, []byte(escapeSequence))
|
||||||
|
|
||||||
|
// Save excess bytes and right-size keyBytes
|
||||||
|
if len(keyBytes) > len(p) {
|
||||||
|
logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p))
|
||||||
|
ar.buffer = keyBytes[len(p):]
|
||||||
|
keyBytes = keyBytes[:len(p)]
|
||||||
|
} else if len(keyBytes) == 0 {
|
||||||
|
logger.Debug("No key bytes returned from the translator")
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copiedLength := copy(p, keyBytes)
|
||||||
|
if copiedLength != len(keyBytes) {
|
||||||
|
return 0, errors.New("unexpected copy length encountered")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debugf("Read p[%d]: % x", copiedLength, p)
|
||||||
|
logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes)
|
||||||
|
return copiedLength, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInputEvents polls until at least one event is available.
|
||||||
|
func readInputEvents(fd uintptr, maxBytes int) ([]winterm.INPUT_RECORD, error) {
|
||||||
|
// Determine the maximum number of records to retrieve
|
||||||
|
// -- Cast around the type system to obtain the size of a single INPUT_RECORD.
|
||||||
|
// unsafe.Sizeof requires an expression vs. a type-reference; the casting
|
||||||
|
// tricks the type system into believing it has such an expression.
|
||||||
|
recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes)))))
|
||||||
|
countRecords := maxBytes / recordSize
|
||||||
|
if countRecords > ansiterm.MAX_INPUT_EVENTS {
|
||||||
|
countRecords = ansiterm.MAX_INPUT_EVENTS
|
||||||
|
} else if countRecords == 0 {
|
||||||
|
countRecords = 1
|
||||||
|
}
|
||||||
|
logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize)
|
||||||
|
|
||||||
|
// Wait for and read input events
|
||||||
|
events := make([]winterm.INPUT_RECORD, countRecords)
|
||||||
|
nEvents := uint32(0)
|
||||||
|
eventsExist, err := winterm.WaitForSingleObject(fd, winterm.WAIT_INFINITE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if eventsExist {
|
||||||
|
err = winterm.ReadConsoleInput(fd, events, &nEvents)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a slice restricted to the number of returned records
|
||||||
|
logger.Debugf("[windows] readInputEvents: Read %v events", nEvents)
|
||||||
|
return events[:nEvents], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyEvent Translation Helpers
|
||||||
|
|
||||||
|
var arrowKeyMapPrefix = map[uint16]string{
|
||||||
|
winterm.VK_UP: "%s%sA",
|
||||||
|
winterm.VK_DOWN: "%s%sB",
|
||||||
|
winterm.VK_RIGHT: "%s%sC",
|
||||||
|
winterm.VK_LEFT: "%s%sD",
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyMapPrefix = map[uint16]string{
|
||||||
|
winterm.VK_UP: "\x1B[%sA",
|
||||||
|
winterm.VK_DOWN: "\x1B[%sB",
|
||||||
|
winterm.VK_RIGHT: "\x1B[%sC",
|
||||||
|
winterm.VK_LEFT: "\x1B[%sD",
|
||||||
|
winterm.VK_HOME: "\x1B[1%s~", // showkey shows ^[[1
|
||||||
|
winterm.VK_END: "\x1B[4%s~", // showkey shows ^[[4
|
||||||
|
winterm.VK_INSERT: "\x1B[2%s~",
|
||||||
|
winterm.VK_DELETE: "\x1B[3%s~",
|
||||||
|
winterm.VK_PRIOR: "\x1B[5%s~",
|
||||||
|
winterm.VK_NEXT: "\x1B[6%s~",
|
||||||
|
winterm.VK_F1: "",
|
||||||
|
winterm.VK_F2: "",
|
||||||
|
winterm.VK_F3: "\x1B[13%s~",
|
||||||
|
winterm.VK_F4: "\x1B[14%s~",
|
||||||
|
winterm.VK_F5: "\x1B[15%s~",
|
||||||
|
winterm.VK_F6: "\x1B[17%s~",
|
||||||
|
winterm.VK_F7: "\x1B[18%s~",
|
||||||
|
winterm.VK_F8: "\x1B[19%s~",
|
||||||
|
winterm.VK_F9: "\x1B[20%s~",
|
||||||
|
winterm.VK_F10: "\x1B[21%s~",
|
||||||
|
winterm.VK_F11: "\x1B[23%s~",
|
||||||
|
winterm.VK_F12: "\x1B[24%s~",
|
||||||
|
}
|
||||||
|
|
||||||
|
// translateKeyEvents converts the input events into the appropriate ANSI string.
|
||||||
|
func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
for _, event := range events {
|
||||||
|
if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 {
|
||||||
|
buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyToString maps the given input event record to the corresponding string.
|
||||||
|
func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) string {
|
||||||
|
if keyEvent.UnicodeChar == 0 {
|
||||||
|
return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, alt, control := getControlKeys(keyEvent.ControlKeyState)
|
||||||
|
if control {
|
||||||
|
// TODO(azlinux): Implement following control sequences
|
||||||
|
// <Ctrl>-D Signals the end of input from the keyboard; also exits current shell.
|
||||||
|
// <Ctrl>-H Deletes the first character to the left of the cursor. Also called the ERASE key.
|
||||||
|
// <Ctrl>-Q Restarts printing after it has been stopped with <Ctrl>-s.
|
||||||
|
// <Ctrl>-S Suspends printing on the screen (does not stop the program).
|
||||||
|
// <Ctrl>-U Deletes all characters on the current line. Also called the KILL key.
|
||||||
|
// <Ctrl>-E Quits current command and creates a core
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// <Alt>+Key generates ESC N Key
|
||||||
|
if !control && alt {
|
||||||
|
return ansiterm.KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(keyEvent.UnicodeChar)
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string.
|
||||||
|
func formatVirtualKey(key uint16, controlState uint32, escapeSequence []byte) string {
|
||||||
|
shift, alt, control := getControlKeys(controlState)
|
||||||
|
modifier := getControlKeysModifier(shift, alt, control)
|
||||||
|
|
||||||
|
if format, ok := arrowKeyMapPrefix[key]; ok {
|
||||||
|
return fmt.Sprintf(format, escapeSequence, modifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
if format, ok := keyMapPrefix[key]; ok {
|
||||||
|
return fmt.Sprintf(format, modifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getControlKeys extracts the shift, alt, and ctrl key states.
|
||||||
|
func getControlKeys(controlState uint32) (shift, alt, control bool) {
|
||||||
|
shift = 0 != (controlState & winterm.SHIFT_PRESSED)
|
||||||
|
alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED))
|
||||||
|
control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED))
|
||||||
|
return shift, alt, control
|
||||||
|
}
|
||||||
|
|
||||||
|
// getControlKeysModifier returns the ANSI modifier for the given combination of control keys.
|
||||||
|
func getControlKeysModifier(shift, alt, control bool) string {
|
||||||
|
if shift && alt && control {
|
||||||
|
return ansiterm.KEY_CONTROL_PARAM_8
|
||||||
|
}
|
||||||
|
if alt && control {
|
||||||
|
return ansiterm.KEY_CONTROL_PARAM_7
|
||||||
|
}
|
||||||
|
if shift && control {
|
||||||
|
return ansiterm.KEY_CONTROL_PARAM_6
|
||||||
|
}
|
||||||
|
if control {
|
||||||
|
return ansiterm.KEY_CONTROL_PARAM_5
|
||||||
|
}
|
||||||
|
if shift && alt {
|
||||||
|
return ansiterm.KEY_CONTROL_PARAM_4
|
||||||
|
}
|
||||||
|
if alt {
|
||||||
|
return ansiterm.KEY_CONTROL_PARAM_3
|
||||||
|
}
|
||||||
|
if shift {
|
||||||
|
return ansiterm.KEY_CONTROL_PARAM_2
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
ansiterm "github.com/Azure/go-ansiterm"
|
||||||
|
"github.com/Azure/go-ansiterm/winterm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ansiWriter wraps a standard output file (e.g., os.Stdout) providing ANSI sequence translation.
|
||||||
|
type ansiWriter struct {
|
||||||
|
file *os.File
|
||||||
|
fd uintptr
|
||||||
|
infoReset *winterm.CONSOLE_SCREEN_BUFFER_INFO
|
||||||
|
command []byte
|
||||||
|
escapeSequence []byte
|
||||||
|
inAnsiSequence bool
|
||||||
|
parser *ansiterm.AnsiParser
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnsiWriter returns an io.Writer that provides VT100 terminal emulation on top of a
|
||||||
|
// Windows console output handle.
|
||||||
|
func NewAnsiWriter(nFile int) io.Writer {
|
||||||
|
initLogger()
|
||||||
|
file, fd := winterm.GetStdFile(nFile)
|
||||||
|
info, err := winterm.GetConsoleScreenBufferInfo(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := ansiterm.CreateParser("Ground", winterm.CreateWinEventHandler(fd, file))
|
||||||
|
logger.Infof("newAnsiWriter: parser %p", parser)
|
||||||
|
|
||||||
|
aw := &ansiWriter{
|
||||||
|
file: file,
|
||||||
|
fd: fd,
|
||||||
|
infoReset: info,
|
||||||
|
command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH),
|
||||||
|
escapeSequence: []byte(ansiterm.KEY_ESC_CSI),
|
||||||
|
parser: parser,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("newAnsiWriter: aw.parser %p", aw.parser)
|
||||||
|
logger.Infof("newAnsiWriter: %v", aw)
|
||||||
|
return aw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aw *ansiWriter) Fd() uintptr {
|
||||||
|
return aw.fd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes len(p) bytes from p to the underlying data stream.
|
||||||
|
func (aw *ansiWriter) Write(p []byte) (total int, err error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("Write: % x", p)
|
||||||
|
logger.Infof("Write: %s", string(p))
|
||||||
|
return aw.parser.Parse(p)
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Azure/go-ansiterm/winterm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetHandleInfo returns file descriptor and bool indicating whether the file is a console.
|
||||||
|
func GetHandleInfo(in interface{}) (uintptr, bool) {
|
||||||
|
switch t := in.(type) {
|
||||||
|
case *ansiReader:
|
||||||
|
return t.Fd(), true
|
||||||
|
case *ansiWriter:
|
||||||
|
return t.Fd(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
var inFd uintptr
|
||||||
|
var isTerminal bool
|
||||||
|
|
||||||
|
if file, ok := in.(*os.File); ok {
|
||||||
|
inFd = file.Fd()
|
||||||
|
isTerminal = IsConsole(inFd)
|
||||||
|
}
|
||||||
|
return inFd, isTerminal
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsConsole returns true if the given file descriptor is a Windows Console.
|
||||||
|
// The code assumes that GetConsoleMode will return an error for file descriptors that are not a console.
|
||||||
|
func IsConsole(fd uintptr) bool {
|
||||||
|
_, e := winterm.GetConsoleMode(fd)
|
||||||
|
return e == nil
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
// These files implement ANSI-aware input and output streams for use by the Docker Windows client.
|
||||||
|
// When asked for the set of standard streams (e.g., stdin, stdout, stderr), the code will create
|
||||||
|
// and return pseudo-streams that convert ANSI sequences to / from Windows Console API calls.
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
ansiterm "github.com/Azure/go-ansiterm"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger *logrus.Logger
|
||||||
|
var initOnce sync.Once
|
||||||
|
|
||||||
|
func initLogger() {
|
||||||
|
initOnce.Do(func() {
|
||||||
|
logFile := ioutil.Discard
|
||||||
|
|
||||||
|
if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
|
||||||
|
logFile, _ = os.Create("ansiReaderWriter.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = &logrus.Logger{
|
||||||
|
Out: logFile,
|
||||||
|
Formatter: new(logrus.TextFormatter),
|
||||||
|
Level: logrus.DebugLevel,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
Want to contribute? Great! First, read this page (including the small print at the end).
|
||||||
|
|
||||||
|
### Before you contribute
|
||||||
|
Before we can use your code, you must sign the
|
||||||
|
[Google Individual Contributor License Agreement]
|
||||||
|
(https://cla.developers.google.com/about/google-individual)
|
||||||
|
(CLA), which you can do online. The CLA is necessary mainly because you own the
|
||||||
|
copyright to your changes, even after your contribution becomes part of our
|
||||||
|
codebase, so we need your permission to use and distribute your code. We also
|
||||||
|
need to be sure of various other things—for instance that you'll tell us if you
|
||||||
|
know that your code infringes on other people's patents. You don't have to sign
|
||||||
|
the CLA until after you've submitted your code for review and a member has
|
||||||
|
approved it, but you must do it before we can put your code into our codebase.
|
||||||
|
Before you start working on a larger contribution, you should get in touch with
|
||||||
|
us first through the issue tracker with your idea so that we can help out and
|
||||||
|
possibly guide you. Coordinating up front makes it much easier to avoid
|
||||||
|
frustration later on.
|
||||||
|
|
||||||
|
### Code reviews
|
||||||
|
All submissions, including submissions by project members, require review. We
|
||||||
|
use Github pull requests for this purpose.
|
||||||
|
|
||||||
|
### The small print
|
||||||
|
Contributions made by corporations are covered by a different agreement than
|
||||||
|
the one above, the
|
||||||
|
[Software Grant and Corporate Contributor License Agreement]
|
||||||
|
(https://cla.developers.google.com/about/google-corporate).
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright 2016, 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.
|
|
@ -0,0 +1,24 @@
|
||||||
|
Google API Extensions for Go
|
||||||
|
============================
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/googleapis/gax-go.svg?branch=master)](https://travis-ci.org/googleapis/gax-go)
|
||||||
|
[![Code Coverage](https://img.shields.io/codecov/c/github/googleapis/gax-go.svg)](https://codecov.io/github/googleapis/gax-go)
|
||||||
|
|
||||||
|
Google API Extensions for Go (gax-go) is a set of modules which aids the
|
||||||
|
development of APIs for clients and servers based on `gRPC` and Google API
|
||||||
|
conventions.
|
||||||
|
|
||||||
|
Application code will rarely need to use this library directly,
|
||||||
|
but the code generated automatically from API definition files can use it
|
||||||
|
to simplify code generation and to provide more convenient and idiomatic API surface.
|
||||||
|
|
||||||
|
**This project is currently experimental and not supported.**
|
||||||
|
|
||||||
|
Go Versions
|
||||||
|
===========
|
||||||
|
This library requires Go 1.6 or above.
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
BSD - please see [LICENSE](https://github.com/googleapis/gax-go/blob/master/LICENSE)
|
||||||
|
for more information.
|
|
@ -0,0 +1,136 @@
|
||||||
|
// Copyright 2016, 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.
|
||||||
|
|
||||||
|
package gax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CallOption is an option used by Invoke to control behaviors of RPC calls.
|
||||||
|
// CallOption works by modifying relevant fields of CallSettings.
|
||||||
|
type CallOption interface {
|
||||||
|
// Resolve applies the option by modifying cs.
|
||||||
|
Resolve(cs *CallSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retryer is used by Invoke to determine retry behavior.
|
||||||
|
type Retryer interface {
|
||||||
|
// Retry reports whether a request should be retriedand how long to pause before retrying
|
||||||
|
// if the previous attempt returned with err. Invoke never calls Retry with nil error.
|
||||||
|
Retry(err error) (pause time.Duration, shouldRetry bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type retryerOption func() Retryer
|
||||||
|
|
||||||
|
func (o retryerOption) Resolve(s *CallSettings) {
|
||||||
|
s.Retry = o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRetry sets CallSettings.Retry to fn.
|
||||||
|
func WithRetry(fn func() Retryer) CallOption {
|
||||||
|
return retryerOption(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnCodes returns a Retryer that retries if and only if
|
||||||
|
// the previous attempt returns a GRPC error whose error code is stored in cc.
|
||||||
|
// Pause times between retries are specified by bo.
|
||||||
|
//
|
||||||
|
// bo is only used for its parameters; each Retryer has its own copy.
|
||||||
|
func OnCodes(cc []codes.Code, bo Backoff) Retryer {
|
||||||
|
return &boRetryer{
|
||||||
|
backoff: bo,
|
||||||
|
codes: append([]codes.Code(nil), cc...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type boRetryer struct {
|
||||||
|
backoff Backoff
|
||||||
|
codes []codes.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *boRetryer) Retry(err error) (time.Duration, bool) {
|
||||||
|
c := grpc.Code(err)
|
||||||
|
for _, rc := range r.codes {
|
||||||
|
if c == rc {
|
||||||
|
return r.backoff.Pause(), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backoff implements exponential backoff.
|
||||||
|
// The wait time between retries is a random value between 0 and the "retry envelope".
|
||||||
|
// The envelope starts at Initial and increases by the factor of Multiplier every retry,
|
||||||
|
// but is capped at Max.
|
||||||
|
type Backoff struct {
|
||||||
|
// Initial is the initial value of the retry envelope, defaults to 1 second.
|
||||||
|
Initial time.Duration
|
||||||
|
|
||||||
|
// Max is the maximum value of the retry envelope, defaults to 30 seconds.
|
||||||
|
Max time.Duration
|
||||||
|
|
||||||
|
// Multiplier is the factor by which the retry envelope increases.
|
||||||
|
// It should be greater than 1 and defaults to 2.
|
||||||
|
Multiplier float64
|
||||||
|
|
||||||
|
// cur is the current retry envelope
|
||||||
|
cur time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bo *Backoff) Pause() time.Duration {
|
||||||
|
if bo.Initial == 0 {
|
||||||
|
bo.Initial = time.Second
|
||||||
|
}
|
||||||
|
if bo.cur == 0 {
|
||||||
|
bo.cur = bo.Initial
|
||||||
|
}
|
||||||
|
if bo.Max == 0 {
|
||||||
|
bo.Max = 30 * time.Second
|
||||||
|
}
|
||||||
|
if bo.Multiplier < 1 {
|
||||||
|
bo.Multiplier = 2
|
||||||
|
}
|
||||||
|
d := time.Duration(rand.Int63n(int64(bo.cur)))
|
||||||
|
bo.cur = time.Duration(float64(bo.cur) * bo.Multiplier)
|
||||||
|
if bo.cur > bo.Max {
|
||||||
|
bo.cur = bo.Max
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallSettings struct {
|
||||||
|
// Retry returns a Retryer to be used to control retry logic of a method call.
|
||||||
|
// If Retry is nil or the returned Retryer is nil, the call will not be retried.
|
||||||
|
Retry func() Retryer
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2016, 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.
|
||||||
|
|
||||||
|
// Package gax contains a set of modules which aid the development of APIs
|
||||||
|
// for clients and servers based on gRPC and Google API conventions.
|
||||||
|
//
|
||||||
|
// Application code will rarely need to use this library directly.
|
||||||
|
// However, code generated automatically from API definition files can use it
|
||||||
|
// to simplify code generation and to provide more convenient and idiomatic API surfaces.
|
||||||
|
//
|
||||||
|
// This project is currently experimental and not supported.
|
||||||
|
package gax
|
||||||
|
|
||||||
|
const Version = "0.1.0"
|
|
@ -0,0 +1,90 @@
|
||||||
|
// Copyright 2016, 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.
|
||||||
|
|
||||||
|
package gax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A user defined call stub.
|
||||||
|
type APICall func(context.Context) error
|
||||||
|
|
||||||
|
// Invoke calls the given APICall,
|
||||||
|
// performing retries as specified by opts, if any.
|
||||||
|
func Invoke(ctx context.Context, call APICall, opts ...CallOption) error {
|
||||||
|
var settings CallSettings
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.Resolve(&settings)
|
||||||
|
}
|
||||||
|
return invoke(ctx, call, settings, Sleep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing.
|
||||||
|
// If interrupted, Sleep returns ctx.Err().
|
||||||
|
func Sleep(ctx context.Context, d time.Duration) error {
|
||||||
|
t := time.NewTimer(d)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Stop()
|
||||||
|
return ctx.Err()
|
||||||
|
case <-t.C:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sleeper func(ctx context.Context, d time.Duration) error
|
||||||
|
|
||||||
|
// invoke implements Invoke, taking an additional sleeper argument for testing.
|
||||||
|
func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error {
|
||||||
|
var retryer Retryer
|
||||||
|
for {
|
||||||
|
err := call(ctx)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if settings.Retry == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if retryer == nil {
|
||||||
|
if r := settings.Retry(); r != nil {
|
||||||
|
retryer = r
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d, ok := retryer.Retry(err); !ok {
|
||||||
|
return err
|
||||||
|
} else if err = sp(ctx, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
// Copyright 2016, 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.
|
||||||
|
|
||||||
|
package gax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type matcher interface {
|
||||||
|
match([]string) (int, error)
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type segment struct {
|
||||||
|
matcher
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type labelMatcher string
|
||||||
|
|
||||||
|
func (ls labelMatcher) match(segments []string) (int, error) {
|
||||||
|
if len(segments) == 0 {
|
||||||
|
return 0, fmt.Errorf("expected %s but no more segments found", ls)
|
||||||
|
}
|
||||||
|
if segments[0] != string(ls) {
|
||||||
|
return 0, fmt.Errorf("expected %s but got %s", ls, segments[0])
|
||||||
|
}
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls labelMatcher) String() string {
|
||||||
|
return string(ls)
|
||||||
|
}
|
||||||
|
|
||||||
|
type wildcardMatcher int
|
||||||
|
|
||||||
|
func (wm wildcardMatcher) match(segments []string) (int, error) {
|
||||||
|
if len(segments) == 0 {
|
||||||
|
return 0, errors.New("no more segments found")
|
||||||
|
}
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm wildcardMatcher) String() string {
|
||||||
|
return "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathWildcardMatcher int
|
||||||
|
|
||||||
|
func (pwm pathWildcardMatcher) match(segments []string) (int, error) {
|
||||||
|
length := len(segments) - int(pwm)
|
||||||
|
if length <= 0 {
|
||||||
|
return 0, errors.New("not sufficient segments are supplied for path wildcard")
|
||||||
|
}
|
||||||
|
return length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pwm pathWildcardMatcher) String() string {
|
||||||
|
return "**"
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParseError struct {
|
||||||
|
Pos int
|
||||||
|
Template string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe ParseError) Error() string {
|
||||||
|
return fmt.Sprintf("at %d of template '%s', %s", pe.Pos, pe.Template, pe.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathTemplate manages the template to build and match with paths used
|
||||||
|
// by API services. It holds a template and variable names in it, and
|
||||||
|
// it can extract matched patterns from a path string or build a path
|
||||||
|
// string from a binding.
|
||||||
|
//
|
||||||
|
// See http.proto in github.com/googleapis/googleapis/ for the details of
|
||||||
|
// the template syntax.
|
||||||
|
type PathTemplate struct {
|
||||||
|
segments []segment
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPathTemplate parses a path template, and returns a PathTemplate
|
||||||
|
// instance if successful.
|
||||||
|
func NewPathTemplate(template string) (*PathTemplate, error) {
|
||||||
|
return parsePathTemplate(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustCompilePathTemplate is like NewPathTemplate but panics if the
|
||||||
|
// expression cannot be parsed. It simplifies safe initialization of
|
||||||
|
// global variables holding compiled regular expressions.
|
||||||
|
func MustCompilePathTemplate(template string) *PathTemplate {
|
||||||
|
pt, err := NewPathTemplate(template)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return pt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match attempts to match the given path with the template, and returns
|
||||||
|
// the mapping of the variable name to the matched pattern string.
|
||||||
|
func (pt *PathTemplate) Match(path string) (map[string]string, error) {
|
||||||
|
paths := strings.Split(path, "/")
|
||||||
|
values := map[string]string{}
|
||||||
|
for _, segment := range pt.segments {
|
||||||
|
length, err := segment.match(paths)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if segment.name != "" {
|
||||||
|
value := strings.Join(paths[:length], "/")
|
||||||
|
if oldValue, ok := values[segment.name]; ok {
|
||||||
|
values[segment.name] = oldValue + "/" + value
|
||||||
|
} else {
|
||||||
|
values[segment.name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paths = paths[length:]
|
||||||
|
}
|
||||||
|
if len(paths) != 0 {
|
||||||
|
return nil, fmt.Errorf("Trailing path %s remains after the matching", strings.Join(paths, "/"))
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render creates a path string from its template and the binding from
|
||||||
|
// the variable name to the value.
|
||||||
|
func (pt *PathTemplate) Render(binding map[string]string) (string, error) {
|
||||||
|
result := make([]string, 0, len(pt.segments))
|
||||||
|
var lastVariableName string
|
||||||
|
for _, segment := range pt.segments {
|
||||||
|
name := segment.name
|
||||||
|
if lastVariableName != "" && name == lastVariableName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lastVariableName = name
|
||||||
|
if name == "" {
|
||||||
|
result = append(result, segment.String())
|
||||||
|
} else if value, ok := binding[name]; ok {
|
||||||
|
result = append(result, value)
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("%s is not found", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
built := strings.Join(result, "/")
|
||||||
|
return built, nil
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
// Copyright 2016, 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.
|
||||||
|
|
||||||
|
package gax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This parser follows the syntax of path templates, from
|
||||||
|
// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto.
|
||||||
|
// The differences are that there is no custom verb, we allow the initial slash
|
||||||
|
// to be absent, and that we are not strict as
|
||||||
|
// https://tools.ietf.org/html/rfc6570 about the characters in identifiers and
|
||||||
|
// literals.
|
||||||
|
|
||||||
|
type pathTemplateParser struct {
|
||||||
|
r *strings.Reader
|
||||||
|
runeCount int // the number of the current rune in the original string
|
||||||
|
nextVar int // the number to use for the next unnamed variable
|
||||||
|
seenName map[string]bool // names we've seen already
|
||||||
|
seenPathWildcard bool // have we seen "**" already?
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePathTemplate(template string) (pt *PathTemplate, err error) {
|
||||||
|
p := &pathTemplateParser{
|
||||||
|
r: strings.NewReader(template),
|
||||||
|
seenName: map[string]bool{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle panics with strings like errors.
|
||||||
|
// See pathTemplateParser.error, below.
|
||||||
|
defer func() {
|
||||||
|
if x := recover(); x != nil {
|
||||||
|
errmsg, ok := x.(errString)
|
||||||
|
if !ok {
|
||||||
|
panic(x)
|
||||||
|
}
|
||||||
|
pt = nil
|
||||||
|
err = ParseError{p.runeCount, template, string(errmsg)}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
segs := p.template()
|
||||||
|
// If there is a path wildcard, set its length. We can't do this
|
||||||
|
// until we know how many segments we've got all together.
|
||||||
|
for i, seg := range segs {
|
||||||
|
if _, ok := seg.matcher.(pathWildcardMatcher); ok {
|
||||||
|
segs[i].matcher = pathWildcardMatcher(len(segs) - i - 1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &PathTemplate{segments: segs}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to indicate errors "thrown" by this parser. We don't use string because
|
||||||
|
// many parts of the standard library panic with strings.
|
||||||
|
type errString string
|
||||||
|
|
||||||
|
// Terminates parsing immediately with an error.
|
||||||
|
func (p *pathTemplateParser) error(msg string) {
|
||||||
|
panic(errString(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template = [ "/" ] Segments
|
||||||
|
func (p *pathTemplateParser) template() []segment {
|
||||||
|
var segs []segment
|
||||||
|
if p.consume('/') {
|
||||||
|
// Initial '/' needs an initial empty matcher.
|
||||||
|
segs = append(segs, segment{matcher: labelMatcher("")})
|
||||||
|
}
|
||||||
|
return append(segs, p.segments("")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Segments = Segment { "/" Segment }
|
||||||
|
func (p *pathTemplateParser) segments(name string) []segment {
|
||||||
|
var segs []segment
|
||||||
|
for {
|
||||||
|
subsegs := p.segment(name)
|
||||||
|
segs = append(segs, subsegs...)
|
||||||
|
if !p.consume('/') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return segs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Segment = "*" | "**" | LITERAL | Variable
|
||||||
|
func (p *pathTemplateParser) segment(name string) []segment {
|
||||||
|
if p.consume('*') {
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("$%d", p.nextVar)
|
||||||
|
p.nextVar++
|
||||||
|
}
|
||||||
|
if p.consume('*') {
|
||||||
|
if p.seenPathWildcard {
|
||||||
|
p.error("multiple '**' disallowed")
|
||||||
|
}
|
||||||
|
p.seenPathWildcard = true
|
||||||
|
// We'll change 0 to the right number at the end.
|
||||||
|
return []segment{{name: name, matcher: pathWildcardMatcher(0)}}
|
||||||
|
}
|
||||||
|
return []segment{{name: name, matcher: wildcardMatcher(0)}}
|
||||||
|
}
|
||||||
|
if p.consume('{') {
|
||||||
|
if name != "" {
|
||||||
|
p.error("recursive named bindings are not allowed")
|
||||||
|
}
|
||||||
|
return p.variable()
|
||||||
|
}
|
||||||
|
return []segment{{name: name, matcher: labelMatcher(p.literal())}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable = "{" FieldPath [ "=" Segments ] "}"
|
||||||
|
// "{" is already consumed.
|
||||||
|
func (p *pathTemplateParser) variable() []segment {
|
||||||
|
// Simplification: treat FieldPath as LITERAL, instead of IDENT { '.' IDENT }
|
||||||
|
name := p.literal()
|
||||||
|
if p.seenName[name] {
|
||||||
|
p.error(name + " appears multiple times")
|
||||||
|
}
|
||||||
|
p.seenName[name] = true
|
||||||
|
var segs []segment
|
||||||
|
if p.consume('=') {
|
||||||
|
segs = p.segments(name)
|
||||||
|
} else {
|
||||||
|
// "{var}" is equivalent to "{var=*}"
|
||||||
|
segs = []segment{{name: name, matcher: wildcardMatcher(0)}}
|
||||||
|
}
|
||||||
|
if !p.consume('}') {
|
||||||
|
p.error("expected '}'")
|
||||||
|
}
|
||||||
|
return segs
|
||||||
|
}
|
||||||
|
|
||||||
|
// A literal is any sequence of characters other than a few special ones.
|
||||||
|
// The list of stop characters is not quite the same as in the template RFC.
|
||||||
|
func (p *pathTemplateParser) literal() string {
|
||||||
|
lit := p.consumeUntil("/*}{=")
|
||||||
|
if lit == "" {
|
||||||
|
p.error("empty literal")
|
||||||
|
}
|
||||||
|
return lit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read runes until EOF or one of the runes in stopRunes is encountered.
|
||||||
|
// If the latter, unread the stop rune. Return the accumulated runes as a string.
|
||||||
|
func (p *pathTemplateParser) consumeUntil(stopRunes string) string {
|
||||||
|
var runes []rune
|
||||||
|
for {
|
||||||
|
r, ok := p.readRune()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if strings.IndexRune(stopRunes, r) >= 0 {
|
||||||
|
p.unreadRune()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
runes = append(runes, r)
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the next rune is r, consume it and return true.
|
||||||
|
// Otherwise, leave the input unchanged and return false.
|
||||||
|
func (p *pathTemplateParser) consume(r rune) bool {
|
||||||
|
rr, ok := p.readRune()
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r == rr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.unreadRune()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the next rune from the input. Return it.
|
||||||
|
// The second return value is false at EOF.
|
||||||
|
func (p *pathTemplateParser) readRune() (rune, bool) {
|
||||||
|
r, _, err := p.r.ReadRune()
|
||||||
|
if err == io.EOF {
|
||||||
|
return r, false
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
p.error(err.Error())
|
||||||
|
}
|
||||||
|
p.runeCount++
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the last rune that was read back on the input.
|
||||||
|
func (p *pathTemplateParser) unreadRune() {
|
||||||
|
if err := p.r.UnreadRune(); err != nil {
|
||||||
|
p.error(err.Error())
|
||||||
|
}
|
||||||
|
p.runeCount--
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Manfred Touron
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Project-specific variables
|
||||||
|
BINARIES ?= gotty-client
|
||||||
|
GOTTY_URL := http://localhost:8081/
|
||||||
|
VERSION := $(shell cat .goxc.json | jq -c .PackageVersion | sed 's/"//g')
|
||||||
|
|
||||||
|
CONVEY_PORT ?= 9042
|
||||||
|
|
||||||
|
|
||||||
|
# Common variables
|
||||||
|
SOURCES := $(shell find . -type f -name "*.go")
|
||||||
|
COMMANDS := $(shell go list ./... | grep -v /vendor/ | grep /cmd/)
|
||||||
|
PACKAGES := $(shell go list ./... | grep -v /vendor/ | grep -v /cmd/)
|
||||||
|
GOENV ?= GO15VENDOREXPERIMENT=1
|
||||||
|
GO ?= $(GOENV) go
|
||||||
|
GODEP ?= $(GOENV) godep
|
||||||
|
USER ?= $(shell whoami)
|
||||||
|
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: $(BINARIES)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install:
|
||||||
|
$(GO) install ./cmd/gotty-client
|
||||||
|
|
||||||
|
|
||||||
|
$(BINARIES): $(SOURCES)
|
||||||
|
$(GO) build -o $@ ./cmd/$@
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
$(GO) get -t .
|
||||||
|
$(GO) test -v .
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: godep-save
|
||||||
|
godep-save:
|
||||||
|
$(GODEP) save $(PACKAGES) $(COMMANDS)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f $(BINARIES)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: re
|
||||||
|
re: clean all
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: convey
|
||||||
|
convey:
|
||||||
|
$(GO) get github.com/smartystreets/goconvey
|
||||||
|
goconvey -cover -port=$(CONVEY_PORT) -workDir="$(realpath .)" -depth=1
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: cover
|
||||||
|
cover: profile.out
|
||||||
|
|
||||||
|
|
||||||
|
profile.out: $(SOURCES)
|
||||||
|
rm -f $@
|
||||||
|
$(GO) test -covermode=count -coverpkg=. -coverprofile=$@ .
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: docker-build
|
||||||
|
docker-build:
|
||||||
|
go get github.com/laher/goxc
|
||||||
|
rm -rf contrib/docker/linux_386
|
||||||
|
for binary in $(BINARIES); do \
|
||||||
|
goxc -bc="linux,386" -d . -pv contrib/docker -n $$binary xc; \
|
||||||
|
mv contrib/docker/linux_386/$$binary contrib/docker/entrypoint; \
|
||||||
|
docker build -t $(USER)/$$binary contrib/docker; \
|
||||||
|
docker run -it --rm $(USER)/$$binary || true; \
|
||||||
|
docker inspect --type=image --format="{{ .Id }}" moul/$$binary || true; \
|
||||||
|
echo "Now you can run 'docker push $(USER)/$$binary'"; \
|
||||||
|
done
|
|
@ -0,0 +1,201 @@
|
||||||
|
# gotty-client
|
||||||
|
:wrench: Terminal client for [GoTTY](https://github.com/yudai/gotty).
|
||||||
|
|
||||||
|
![](https://raw.githubusercontent.com/moul/gotty-client/master/resources/gotty-client.png)
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/moul/gotty-client.svg?branch=master)](https://travis-ci.org/moul/gotty-client)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/moul/gotty-client?status.svg)](https://godoc.org/github.com/moul/gotty-client)
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
┌─────────────────┐
|
||||||
|
┌──────▶│ /bin/bash │
|
||||||
|
│ └─────────────────┘
|
||||||
|
┌──────────────┐ ┌──────────┐
|
||||||
|
│ │ │ Gotty │
|
||||||
|
┌───────┐ ┌──▶│ Browser │───────┐ │ │
|
||||||
|
│ │ │ │ │ │ │ │
|
||||||
|
│ │ │ └──────────────┘ │ │ │ ┌─────────────────┐
|
||||||
|
│ Bob │───┤ websockets─▶│ │─▶│ emacs /var/www │
|
||||||
|
│ │ │ ╔═ ══ ══ ══ ══ ╗ │ │ │ └─────────────────┘
|
||||||
|
│ │ │ ║ ║ │ │ │
|
||||||
|
└───────┘ └──▶║ gotty-client ───────┘ │ │
|
||||||
|
║ │ │
|
||||||
|
╚═ ══ ══ ══ ══ ╝ └──────────┘
|
||||||
|
│ ┌─────────────────┐
|
||||||
|
└──────▶│ tmux attach │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Server side ([GoTTY](https://github.com/yudai/gotty))
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ gotty -p 9191 sh -c 'while true; do date; sleep 1; done'
|
||||||
|
2015/08/24 18:54:31 Server is starting with command: sh -c while true; do date; sleep 1; done
|
||||||
|
2015/08/24 18:54:31 URL: http://[::1]:9191/
|
||||||
|
2015/08/24 18:54:34 GET /ws
|
||||||
|
2015/08/24 18:54:34 New client connected: 127.0.0.1:61811
|
||||||
|
2015/08/24 18:54:34 Command is running for client 127.0.0.1:61811 with PID 64834
|
||||||
|
2015/08/24 18:54:39 Command exited for: 127.0.0.1:61811
|
||||||
|
2015/08/24 18:54:39 Connection closed: 127.0.0.1:61811
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Client side**
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ gotty-client http://localhost:9191/
|
||||||
|
INFO[0000] New title: GoTTY - sh -c while true; do date; sleep 1; done (jean-michel-van-damme.local)
|
||||||
|
WARN[0000] Unhandled protocol message: json pref: 2{}
|
||||||
|
Mon Aug 24 18:54:34 CEST 2015
|
||||||
|
Mon Aug 24 18:54:35 CEST 2015
|
||||||
|
Mon Aug 24 18:54:36 CEST 2015
|
||||||
|
Mon Aug 24 18:54:37 CEST 2015
|
||||||
|
Mon Aug 24 18:54:38 CEST 2015
|
||||||
|
^C
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ gotty-client -h
|
||||||
|
NAME:
|
||||||
|
gotty-client - GoTTY client for your terminal
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
gotty-client [global options] command [command options] GOTTY_URL
|
||||||
|
|
||||||
|
VERSION:
|
||||||
|
1.3.0+
|
||||||
|
|
||||||
|
AUTHOR(S):
|
||||||
|
Manfred Touron <https://github.com/moul/gotty-client>
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
help, h Shows a list of commands or help for one command
|
||||||
|
|
||||||
|
GLOBAL OPTIONS:
|
||||||
|
--debug, -D Enable debug mode [$GOTTY_CLIENT_DEBUG]
|
||||||
|
--help, -h show help
|
||||||
|
--version, -v print the version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Install latest version using Golang (recommended)
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ go get github.com/moul/gotty-client/cmd/gotty-client
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Install latest version using Homebrew (Mac OS X)
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ brew install https://raw.githubusercontent.com/moul/gotty-client/master/contrib/homebrew/gotty-client.rb --HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
or the latest released version
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ brew install https://raw.githubusercontent.com/moul/gotty-client/master/contrib/homebrew/gotty-client.rb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### master (unreleased)
|
||||||
|
|
||||||
|
* No entry
|
||||||
|
|
||||||
|
[full commits list](https://github.com/moul/gotty-client/compare/v1.6.1...master)
|
||||||
|
|
||||||
|
### [v1.6.1](https://github.com/moul/gotty-client/releases/tag/v1.6.1) (2017-01-19)
|
||||||
|
|
||||||
|
* Do not exit on EOF ([#45](https://github.com/moul/gotty-client/pull/45)) ([@gurjeet](https://github.com/gurjeet))
|
||||||
|
|
||||||
|
[full commits list](https://github.com/moul/gotty-client/compare/v1.6.0...v1.6.1)
|
||||||
|
|
||||||
|
### [v1.6.0](https://github.com/moul/gotty-client/releases/tag/v1.6.0) (2016-05-23)
|
||||||
|
|
||||||
|
* Support of `--use-proxy-from-env` (Add Proxy support) ([#36](https://github.com/moul/gotty-client/pull/36)) ([@byung2](https://github.com/byung2))
|
||||||
|
* Add debug mode ([#18](https://github.com/moul/gotty-client/issues/18))
|
||||||
|
* Fix argument passing ([#16](https://github.com/moul/gotty-client/issues/16))
|
||||||
|
* Remove warnings + golang fixes and refactoring ([@QuentinPerez](https://github.com/QuentinPerez))
|
||||||
|
|
||||||
|
[full commits list](https://github.com/moul/gotty-client/compare/v1.5.0...v1.6.0)
|
||||||
|
|
||||||
|
### [v1.5.0](https://github.com/moul/gotty-client/releases/tag/v1.5.0) (2016-02-18)
|
||||||
|
|
||||||
|
* Add autocomplete support ([#19](https://github.com/moul/gotty-client/issues/19))
|
||||||
|
* Switch from `Party` to `Godep`
|
||||||
|
* Fix terminal data being interpreted as format string ([#34](https://github.com/moul/gotty-client/pull/34)) ([@mickael9](https://github.com/mickael9))
|
||||||
|
|
||||||
|
[full commits list](https://github.com/moul/gotty-client/compare/v1.4.0...v1.5.0)
|
||||||
|
|
||||||
|
### [v1.4.0](https://github.com/moul/gotty-client/releases/tag/v1.4.0) (2015-12-09)
|
||||||
|
|
||||||
|
* Remove solaris,plan9,nacl for `.goxc.json`
|
||||||
|
* Add an error if the go version is lower than 1.5
|
||||||
|
* Flexible parsing of the input URL
|
||||||
|
* Add tests
|
||||||
|
* Support of `--skip-tls-verify`
|
||||||
|
|
||||||
|
[full commits list](https://github.com/moul/gotty-client/compare/v1.3.0...v1.4.0)
|
||||||
|
|
||||||
|
### [v1.3.0](https://github.com/moul/gotty-client/releases/tag/v1.3.0) (2015-10-27)
|
||||||
|
|
||||||
|
* Fix `connected` state when using `Connect()` + `Loop()` methods
|
||||||
|
* Add `ExitLoop` which allow to exit from `Loop` function
|
||||||
|
|
||||||
|
[full commits list](https://github.com/moul/gotty-client/compare/v1.2.0...v1.3.0)
|
||||||
|
|
||||||
|
### [v1.2.0](https://github.com/moul/gotty-client/releases/tag/v1.2.0) (2015-10-23)
|
||||||
|
|
||||||
|
* Removed an annoying warning when exiting connection ([#22](https://github.com/moul/gotty-client/issues/22)) ([@QuentinPerez](https://github.com/QuentinPerez))
|
||||||
|
* Add the ability to configure alternative stdout ([#21](https://github.com/moul/gotty-client/issues/21)) ([@QuentinPerez](https://github.com/QuentinPerez))
|
||||||
|
* Refactored the goroutine system with select, improve speed and stability ([@QuentinPerez](https://github.com/QuentinPerez))
|
||||||
|
* Add debug mode (`--debug`/`-D`) ([#18](https://github.com/moul/gotty-client/issues/18))
|
||||||
|
* Improve error message when connecting by checking HTTP status code
|
||||||
|
* Fix arguments passing ([#16](https://github.com/moul/gotty-client/issues/16))
|
||||||
|
* Dropped support for golang<1.5
|
||||||
|
* Small fixes
|
||||||
|
|
||||||
|
[full commits list](https://github.com/moul/gotty-client/compare/v1.1.0...v1.2.0)
|
||||||
|
|
||||||
|
### [v1.1.0](https://github.com/moul/gotty-client/releases/tag/v1.1.0) (2015-10-10)
|
||||||
|
|
||||||
|
* Handling arguments + using mutexes (thanks to [@QuentinPerez](https://github.com/QuentinPerez))
|
||||||
|
* Add logo ([#9](https://github.com/moul/gotty-client/issues/9))
|
||||||
|
* Using codegansta/cli for CLI parsing ([#3](https://github.com/moul/gotty-client/issues/3))
|
||||||
|
* Fix panic when running on older GoTTY server ([#13](https://github.com/moul/gotty-client/issues/13))
|
||||||
|
* Add 'homebrew support' ([#1](https://github.com/moul/gotty-client/issues/1))
|
||||||
|
* Add Changelog ([#5](https://github.com/moul/gotty-client/issues/5))
|
||||||
|
* Add GOXC configuration to build binaries for multiple architectures ([#2](https://github.com/moul/gotty-client/issues/2))
|
||||||
|
|
||||||
|
[full commits list](https://github.com/moul/gotty-client/compare/v1.0.1...v1.1.0)
|
||||||
|
|
||||||
|
### [v1.0.1](https://github.com/moul/gotty-client/releases/tag/v1.0.1) (2015-09-27)
|
||||||
|
|
||||||
|
* Using party to manage dependencies
|
||||||
|
|
||||||
|
[full commits list](https://github.com/moul/gotty-client/compare/v1.0.0...v1.0.1)
|
||||||
|
|
||||||
|
### [v1.0.0](https://github.com/moul/gotty-client/releases/tag/v1.0.0) (2015-09-27)
|
||||||
|
|
||||||
|
Compatible with [GoTTY](https://github.com/yudai/gotty) version: [v0.0.10](https://github.com/yudai/gotty/releases/tag/v0.0.10)
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
|
||||||
|
* Support **basic-auth**
|
||||||
|
* Support **terminal-(re)size**
|
||||||
|
* Support **write**
|
||||||
|
* Support **title**
|
||||||
|
* Support **custom URI**
|
||||||
|
|
||||||
|
[full commits list](https://github.com/moul/gotty-client/compare/cf0c1146c7ce20fe0bd65764c13253bc575cd43a...v1.0.0)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
|
@ -0,0 +1,34 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package gottyclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func notifySignalSIGWINCH(c chan<- os.Signal) {
|
||||||
|
signal.Notify(c, syscall.SIGWINCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetSignalSIGWINCH() {
|
||||||
|
signal.Reset(syscall.SIGWINCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func syscallTIOCGWINSZ() ([]byte, error) {
|
||||||
|
ws := winsize{}
|
||||||
|
|
||||||
|
syscall.Syscall(syscall.SYS_IOCTL,
|
||||||
|
uintptr(0), uintptr(syscall.TIOCGWINSZ),
|
||||||
|
uintptr(unsafe.Pointer(&ws)))
|
||||||
|
|
||||||
|
b, err := json.Marshal(ws)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("json.Marshal error: %v", err)
|
||||||
|
}
|
||||||
|
return b, err
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package gottyclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func notifySignalSIGWINCH(c chan<- os.Signal) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetSignalSIGWINCH() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func syscallTIOCGWINSZ() ([]byte, error) {
|
||||||
|
return nil, errors.New("SIGWINCH isn't supported on this ARCH")
|
||||||
|
}
|
|
@ -0,0 +1,469 @@
|
||||||
|
package gottyclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/creack/goselect"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetAuthTokenURL transforms a GoTTY http URL to its AuthToken file URL
|
||||||
|
func GetAuthTokenURL(httpURL string) (*url.URL, *http.Header, error) {
|
||||||
|
header := http.Header{}
|
||||||
|
target, err := url.Parse(httpURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
target.Path = strings.TrimLeft(target.Path+"auth_token.js", "/")
|
||||||
|
|
||||||
|
if target.User != nil {
|
||||||
|
header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
|
||||||
|
target.User = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return target, &header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetURLQuery returns url.query
|
||||||
|
func GetURLQuery(rawurl string) (url.Values, error) {
|
||||||
|
target, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return target.Query(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
|
||||||
|
func GetWebsocketURL(httpURL string) (*url.URL, *http.Header, error) {
|
||||||
|
header := http.Header{}
|
||||||
|
target, err := url.Parse(httpURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Scheme == "https" {
|
||||||
|
target.Scheme = "wss"
|
||||||
|
} else {
|
||||||
|
target.Scheme = "ws"
|
||||||
|
}
|
||||||
|
|
||||||
|
target.Path = strings.TrimLeft(target.Path+"ws", "/")
|
||||||
|
|
||||||
|
if target.User != nil {
|
||||||
|
header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
|
||||||
|
target.User = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return target, &header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Dialer *websocket.Dialer
|
||||||
|
Conn *websocket.Conn
|
||||||
|
URL string
|
||||||
|
WriteMutex *sync.Mutex
|
||||||
|
Output io.Writer
|
||||||
|
poison chan bool
|
||||||
|
SkipTLSVerify bool
|
||||||
|
UseProxyFromEnv bool
|
||||||
|
Connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type querySingleType struct {
|
||||||
|
AuthToken string `json:"AuthToken"`
|
||||||
|
Arguments string `json:"Arguments"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) write(data []byte) error {
|
||||||
|
c.WriteMutex.Lock()
|
||||||
|
defer c.WriteMutex.Unlock()
|
||||||
|
return c.Conn.WriteMessage(websocket.TextMessage, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthToken retrieves an Auth Token from dynamic auth_token.js file
|
||||||
|
func (c *Client) GetAuthToken() (string, error) {
|
||||||
|
target, header, err := GetAuthTokenURL(c.URL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("Fetching auth token auth-token: %q", target.String())
|
||||||
|
req, err := http.NewRequest("GET", target.String(), nil)
|
||||||
|
req.Header = *header
|
||||||
|
tr := &http.Transport{}
|
||||||
|
if c.SkipTLSVerify {
|
||||||
|
conf := &tls.Config{InsecureSkipVerify: true}
|
||||||
|
tr.TLSClientConfig = conf
|
||||||
|
}
|
||||||
|
if c.UseProxyFromEnv {
|
||||||
|
tr.Proxy = http.ProxyFromEnvironment
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case 200:
|
||||||
|
// Everything is OK
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unknown status code: %d (%s)", resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
re := regexp.MustCompile("var gotty_auth_token = '(.*)'")
|
||||||
|
output := re.FindStringSubmatch(string(body))
|
||||||
|
if len(output) == 0 {
|
||||||
|
return "", fmt.Errorf("Cannot fetch GoTTY auth-token, please upgrade your GoTTY server.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return output[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect tries to dial a websocket server
|
||||||
|
func (c *Client) Connect() error {
|
||||||
|
// Retrieve AuthToken
|
||||||
|
authToken, err := c.GetAuthToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Debugf("Auth-token: %q", authToken)
|
||||||
|
|
||||||
|
// Open WebSocket connection
|
||||||
|
target, header, err := GetWebsocketURL(c.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Debugf("Connecting to websocket: %q", target.String())
|
||||||
|
if c.SkipTLSVerify {
|
||||||
|
c.Dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
}
|
||||||
|
if c.UseProxyFromEnv {
|
||||||
|
c.Dialer.Proxy = http.ProxyFromEnvironment
|
||||||
|
}
|
||||||
|
conn, _, err := c.Dialer.Dial(target.String(), *header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Conn = conn
|
||||||
|
c.Connected = true
|
||||||
|
|
||||||
|
// Pass arguments and auth-token
|
||||||
|
query, err := GetURLQuery(c.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
querySingle := querySingleType{
|
||||||
|
Arguments: "?" + query.Encode(),
|
||||||
|
AuthToken: authToken,
|
||||||
|
}
|
||||||
|
json, err := json.Marshal(querySingle)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Failed to parse init message %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Send Json
|
||||||
|
logrus.Debugf("Sending arguments and auth-token")
|
||||||
|
err = c.write(json)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go c.pingLoop()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) pingLoop() {
|
||||||
|
for {
|
||||||
|
logrus.Debugf("Sending ping")
|
||||||
|
c.write([]byte("1"))
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close will nicely close the dialer
|
||||||
|
func (c *Client) Close() {
|
||||||
|
c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitLoop will kill all goroutines launched by c.Loop()
|
||||||
|
// ExitLoop() -> wait Loop() -> Close()
|
||||||
|
func (c *Client) ExitLoop() {
|
||||||
|
fname := "ExitLoop"
|
||||||
|
openPoison(fname, c.poison)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop will look indefinitely for new messages
|
||||||
|
func (c *Client) Loop() error {
|
||||||
|
if !c.Connected {
|
||||||
|
err := c.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go c.termsizeLoop(wg)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go c.readLoop(wg)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go c.writeLoop(wg)
|
||||||
|
|
||||||
|
/* Wait for all of the above goroutines to finish */
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
logrus.Debug("Client.Loop() exiting")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type winsize struct {
|
||||||
|
Rows uint16 `json:"rows"`
|
||||||
|
Columns uint16 `json:"columns"`
|
||||||
|
// unused
|
||||||
|
x uint16
|
||||||
|
y uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type posionReason int
|
||||||
|
|
||||||
|
const (
|
||||||
|
committedSuicide = iota
|
||||||
|
killed
|
||||||
|
)
|
||||||
|
|
||||||
|
func openPoison(fname string, poison chan bool) posionReason {
|
||||||
|
logrus.Debug(fname + " suicide")
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The close() may raise panic if multiple goroutines commit suicide at the
|
||||||
|
* same time. Prevent that panic from bubbling up.
|
||||||
|
*/
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logrus.Debug("Prevented panic() of simultaneous suicides", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
/* Signal others to die */
|
||||||
|
close(poison)
|
||||||
|
return committedSuicide
|
||||||
|
}
|
||||||
|
|
||||||
|
func die(fname string, poison chan bool) posionReason {
|
||||||
|
logrus.Debug(fname + " died")
|
||||||
|
|
||||||
|
wasOpen := <-poison
|
||||||
|
if wasOpen {
|
||||||
|
logrus.Error("ERROR: The channel was open when it wasn't suppoed to be")
|
||||||
|
}
|
||||||
|
|
||||||
|
return killed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) termsizeLoop(wg *sync.WaitGroup) posionReason {
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
fname := "termsizeLoop"
|
||||||
|
|
||||||
|
ch := make(chan os.Signal, 1)
|
||||||
|
notifySignalSIGWINCH(ch)
|
||||||
|
defer resetSignalSIGWINCH()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if b, err := syscallTIOCGWINSZ(); err != nil {
|
||||||
|
logrus.Warn(err)
|
||||||
|
} else {
|
||||||
|
if err = c.write(append([]byte("2"), b...)); err != nil {
|
||||||
|
logrus.Warnf("ws.WriteMessage failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-c.poison:
|
||||||
|
/* Somebody poisoned the well; die */
|
||||||
|
return die(fname, c.poison)
|
||||||
|
case <-ch:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type exposeFd interface {
|
||||||
|
Fd() uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) writeLoop(wg *sync.WaitGroup) posionReason {
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
fname := "writeLoop"
|
||||||
|
|
||||||
|
buff := make([]byte, 128)
|
||||||
|
oldState, err := terminal.MakeRaw(0)
|
||||||
|
if err == nil {
|
||||||
|
defer terminal.Restore(0, oldState)
|
||||||
|
}
|
||||||
|
|
||||||
|
rdfs := &goselect.FDSet{}
|
||||||
|
reader := io.Reader(os.Stdin)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.poison:
|
||||||
|
/* Somebody poisoned the well; die */
|
||||||
|
return die(fname, c.poison)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
rdfs.Zero()
|
||||||
|
rdfs.Set(reader.(exposeFd).Fd())
|
||||||
|
err := goselect.Select(1, rdfs, nil, nil, 50*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return openPoison(fname, c.poison)
|
||||||
|
}
|
||||||
|
if rdfs.IsSet(reader.(exposeFd).Fd()) {
|
||||||
|
size, err := reader.Read(buff)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
// Send EOF to GoTTY
|
||||||
|
|
||||||
|
// Send 'Input' marker, as defined in GoTTY::client_context.go,
|
||||||
|
// followed by EOT (a translation of Ctrl-D for terminals)
|
||||||
|
err = c.write(append([]byte("0"), byte(4)))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return openPoison(fname, c.poison)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return openPoison(fname, c.poison)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
data := buff[:size]
|
||||||
|
err = c.write(append([]byte("0"), data...))
|
||||||
|
if err != nil {
|
||||||
|
return openPoison(fname, c.poison)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) readLoop(wg *sync.WaitGroup) posionReason {
|
||||||
|
|
||||||
|
defer wg.Done()
|
||||||
|
fname := "readLoop"
|
||||||
|
|
||||||
|
type MessageNonBlocking struct {
|
||||||
|
Data []byte
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
msgChan := make(chan MessageNonBlocking)
|
||||||
|
|
||||||
|
for {
|
||||||
|
go func() {
|
||||||
|
_, data, err := c.Conn.ReadMessage()
|
||||||
|
msgChan <- MessageNonBlocking{Data: data, Err: err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-c.poison:
|
||||||
|
/* Somebody poisoned the well; die */
|
||||||
|
return die(fname, c.poison)
|
||||||
|
case msg := <-msgChan:
|
||||||
|
if msg.Err != nil {
|
||||||
|
|
||||||
|
if _, ok := msg.Err.(*websocket.CloseError); !ok {
|
||||||
|
logrus.Warnf("c.Conn.ReadMessage: %v", msg.Err)
|
||||||
|
}
|
||||||
|
return openPoison(fname, c.poison)
|
||||||
|
}
|
||||||
|
if len(msg.Data) == 0 {
|
||||||
|
|
||||||
|
logrus.Warnf("An error has occured")
|
||||||
|
return openPoison(fname, c.poison)
|
||||||
|
}
|
||||||
|
switch msg.Data[0] {
|
||||||
|
case '0': // data
|
||||||
|
buf, err := base64.StdEncoding.DecodeString(string(msg.Data[1:]))
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("Invalid base64 content: %q", msg.Data[1:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.Output.Write(buf)
|
||||||
|
case '1': // pong
|
||||||
|
case '2': // new title
|
||||||
|
newTitle := string(msg.Data[1:])
|
||||||
|
fmt.Fprintf(c.Output, "\033]0;%s\007", newTitle)
|
||||||
|
case '3': // json prefs
|
||||||
|
logrus.Debugf("Unhandled protocol message: json pref: %s", string(msg.Data[1:]))
|
||||||
|
case '4': // autoreconnect
|
||||||
|
logrus.Debugf("Unhandled protocol message: autoreconnect: %s", string(msg.Data))
|
||||||
|
default:
|
||||||
|
logrus.Warnf("Unhandled protocol message: %s", string(msg.Data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput changes the output stream
|
||||||
|
func (c *Client) SetOutput(w io.Writer) {
|
||||||
|
c.Output = w
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseURL parses an URL which may be incomplete and tries to standardize it
|
||||||
|
func ParseURL(input string) (string, error) {
|
||||||
|
parsed, err := url.Parse(input)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
switch parsed.Scheme {
|
||||||
|
case "http", "https":
|
||||||
|
// everything is ok
|
||||||
|
default:
|
||||||
|
return ParseURL(fmt.Sprintf("http://%s", input))
|
||||||
|
}
|
||||||
|
return parsed.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a GoTTY client object
|
||||||
|
func NewClient(inputURL string) (*Client, error) {
|
||||||
|
url, err := ParseURL(inputURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
|
Dialer: &websocket.Dialer{},
|
||||||
|
URL: url,
|
||||||
|
WriteMutex: &sync.Mutex{},
|
||||||
|
Output: os.Stdout,
|
||||||
|
poison: make(chan bool),
|
||||||
|
}, nil
|
||||||
|
}
|
124
vendor/github.com/scaleway/scaleway-cli/pkg/sshcommand/sshcommand.go
generated
vendored
Normal file
124
vendor/github.com/scaleway/scaleway-cli/pkg/sshcommand/sshcommand.go
generated
vendored
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package sshcommand
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command contains settings to build a ssh command
|
||||||
|
type Command struct {
|
||||||
|
Host string
|
||||||
|
User string
|
||||||
|
Port int
|
||||||
|
SSHOptions []string
|
||||||
|
Gateway *Command
|
||||||
|
Command []string
|
||||||
|
Debug bool
|
||||||
|
NoEscapeCommand bool
|
||||||
|
SkipHostKeyChecking bool
|
||||||
|
Quiet bool
|
||||||
|
AllocateTTY bool
|
||||||
|
EnableSSHKeyForwarding bool
|
||||||
|
|
||||||
|
isGateway bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a minimal Command
|
||||||
|
func New(host string) *Command {
|
||||||
|
return &Command{
|
||||||
|
Host: host,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) applyDefaults() {
|
||||||
|
if strings.Contains(c.Host, "@") {
|
||||||
|
parts := strings.Split(c.Host, "@")
|
||||||
|
c.User = parts[0]
|
||||||
|
c.Host = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Port == 0 {
|
||||||
|
c.Port = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.isGateway {
|
||||||
|
c.SSHOptions = []string{"-W", "%h:%p"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice returns an execve compatible slice of arguments
|
||||||
|
func (c *Command) Slice() []string {
|
||||||
|
c.applyDefaults()
|
||||||
|
|
||||||
|
slice := []string{}
|
||||||
|
|
||||||
|
slice = append(slice, "ssh")
|
||||||
|
|
||||||
|
if c.EnableSSHKeyForwarding {
|
||||||
|
slice = append(slice, "-A")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Quiet {
|
||||||
|
slice = append(slice, "-q")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SkipHostKeyChecking {
|
||||||
|
slice = append(slice, "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.SSHOptions) > 0 {
|
||||||
|
slice = append(slice, c.SSHOptions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Gateway != nil {
|
||||||
|
c.Gateway.isGateway = true
|
||||||
|
slice = append(slice, "-o", "ProxyCommand="+c.Gateway.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.User != "" {
|
||||||
|
slice = append(slice, "-l", c.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
slice = append(slice, c.Host)
|
||||||
|
|
||||||
|
if c.AllocateTTY {
|
||||||
|
slice = append(slice, "-t", "-t")
|
||||||
|
}
|
||||||
|
|
||||||
|
slice = append(slice, "-p", fmt.Sprintf("%d", c.Port))
|
||||||
|
if len(c.Command) > 0 {
|
||||||
|
slice = append(slice, "--", "/bin/sh", "-e")
|
||||||
|
if c.Debug {
|
||||||
|
slice = append(slice, "-x")
|
||||||
|
}
|
||||||
|
slice = append(slice, "-c")
|
||||||
|
|
||||||
|
var escapedCommand []string
|
||||||
|
if c.NoEscapeCommand {
|
||||||
|
escapedCommand = c.Command
|
||||||
|
} else {
|
||||||
|
escapedCommand = []string{}
|
||||||
|
for _, part := range c.Command {
|
||||||
|
escapedCommand = append(escapedCommand, fmt.Sprintf("%q", part))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slice = append(slice, fmt.Sprintf("%q", strings.Join(escapedCommand, " ")))
|
||||||
|
}
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
slice[len(slice)-1] = slice[len(slice)-1] + " " // Why ?
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a copy-pasteable command, useful for debugging
|
||||||
|
func (c *Command) String() string {
|
||||||
|
slice := c.Slice()
|
||||||
|
for i := range slice {
|
||||||
|
quoted := fmt.Sprintf("%q", slice[i])
|
||||||
|
if strings.Contains(slice[i], " ") || len(quoted) != len(slice[i])+2 {
|
||||||
|
slice[i] = quoted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(slice, " ")
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (C) 2015 Scaleway. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// Package utils contains logquiet
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogQuietStruct is a struct to store information about quiet state
|
||||||
|
type LogQuietStruct struct {
|
||||||
|
quiet bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var instanceQuiet LogQuietStruct
|
||||||
|
|
||||||
|
// Quiet enable or disable quiet
|
||||||
|
func Quiet(option bool) {
|
||||||
|
instanceQuiet.quiet = option
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogQuiet Displays info if quiet is activated
|
||||||
|
func LogQuiet(str string) {
|
||||||
|
if !instanceQuiet.quiet {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s", str)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,253 @@
|
||||||
|
// Copyright (C) 2015 Scaleway. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// scw helpers
|
||||||
|
|
||||||
|
// Package utils contains helpers
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
"github.com/moul/gotty-client"
|
||||||
|
"github.com/scaleway/scaleway-cli/pkg/sshcommand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SpawnRedirection is used to redirects the fluxes
|
||||||
|
type SpawnRedirection struct {
|
||||||
|
Stdin io.Reader
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSHExec executes a command over SSH and redirects file-descriptors
|
||||||
|
func SSHExec(publicIPAddress, privateIPAddress, user string, port int, command []string, checkConnection bool, gateway string, enableSSHKeyForwarding bool) error {
|
||||||
|
gatewayUser := "root"
|
||||||
|
gatewayIPAddress := gateway
|
||||||
|
if strings.Contains(gateway, "@") {
|
||||||
|
parts := strings.Split(gatewayIPAddress, "@")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("gateway: must be like root@IP")
|
||||||
|
}
|
||||||
|
gatewayUser = parts[0]
|
||||||
|
gatewayIPAddress = parts[1]
|
||||||
|
gateway = gatewayUser + "@" + gatewayIPAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
if publicIPAddress == "" && gatewayIPAddress == "" {
|
||||||
|
return errors.New("server does not have public IP")
|
||||||
|
}
|
||||||
|
if privateIPAddress == "" && gatewayIPAddress != "" {
|
||||||
|
return errors.New("server does not have private IP")
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkConnection {
|
||||||
|
useGateway := gatewayIPAddress != ""
|
||||||
|
if useGateway && !IsTCPPortOpen(fmt.Sprintf("%s:22", gatewayIPAddress)) {
|
||||||
|
return errors.New("gateway is not available, try again later")
|
||||||
|
}
|
||||||
|
if !useGateway && !IsTCPPortOpen(fmt.Sprintf("%s:%d", publicIPAddress, port)) {
|
||||||
|
return errors.New("server is not ready, try again later")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sshCommand := NewSSHExecCmd(publicIPAddress, privateIPAddress, user, port, isatty.IsTerminal(os.Stdin.Fd()), command, gateway, enableSSHKeyForwarding)
|
||||||
|
|
||||||
|
log.Debugf("Executing: %s", sshCommand)
|
||||||
|
|
||||||
|
spawn := exec.Command("ssh", sshCommand.Slice()[1:]...)
|
||||||
|
spawn.Stdout = os.Stdout
|
||||||
|
spawn.Stdin = os.Stdin
|
||||||
|
spawn.Stderr = os.Stderr
|
||||||
|
return spawn.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSSHExecCmd computes execve compatible arguments to run a command via ssh
|
||||||
|
func NewSSHExecCmd(publicIPAddress, privateIPAddress, user string, port int, allocateTTY bool, command []string, gatewayIPAddress string, enableSSHKeyForwarding bool) *sshcommand.Command {
|
||||||
|
quiet := os.Getenv("DEBUG") != "1"
|
||||||
|
secureExec := os.Getenv("SCW_SECURE_EXEC") == "1"
|
||||||
|
sshCommand := &sshcommand.Command{
|
||||||
|
AllocateTTY: allocateTTY,
|
||||||
|
Command: command,
|
||||||
|
Host: publicIPAddress,
|
||||||
|
Quiet: quiet,
|
||||||
|
SkipHostKeyChecking: !secureExec,
|
||||||
|
User: user,
|
||||||
|
NoEscapeCommand: true,
|
||||||
|
Port: port,
|
||||||
|
EnableSSHKeyForwarding: enableSSHKeyForwarding,
|
||||||
|
}
|
||||||
|
if gatewayIPAddress != "" {
|
||||||
|
sshCommand.Host = privateIPAddress
|
||||||
|
sshCommand.Gateway = &sshcommand.Command{
|
||||||
|
Host: gatewayIPAddress,
|
||||||
|
SkipHostKeyChecking: !secureExec,
|
||||||
|
AllocateTTY: allocateTTY,
|
||||||
|
Quiet: quiet,
|
||||||
|
User: user,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sshCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratingAnSSHKey generates an SSH key
|
||||||
|
func GeneratingAnSSHKey(cfg SpawnRedirection, path string, name string) (string, error) {
|
||||||
|
args := []string{
|
||||||
|
"-t",
|
||||||
|
"rsa",
|
||||||
|
"-b",
|
||||||
|
"4096",
|
||||||
|
"-f",
|
||||||
|
filepath.Join(path, name),
|
||||||
|
"-N",
|
||||||
|
"",
|
||||||
|
"-C",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
log.Infof("Executing commands %v", args)
|
||||||
|
spawn := exec.Command("ssh-keygen", args...)
|
||||||
|
spawn.Stdout = cfg.Stdout
|
||||||
|
spawn.Stdin = cfg.Stdin
|
||||||
|
spawn.Stderr = cfg.Stderr
|
||||||
|
return args[5], spawn.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForTCPPortOpen calls IsTCPPortOpen in a loop
|
||||||
|
func WaitForTCPPortOpen(dest string) error {
|
||||||
|
for {
|
||||||
|
if IsTCPPortOpen(dest) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTCPPortOpen returns true if a TCP communication with "host:port" can be initialized
|
||||||
|
func IsTCPPortOpen(dest string) bool {
|
||||||
|
conn, err := net.DialTimeout("tcp", dest, time.Duration(2000)*time.Millisecond)
|
||||||
|
if err == nil {
|
||||||
|
defer conn.Close()
|
||||||
|
}
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TruncIf ensures the input string does not exceed max size if cond is met
|
||||||
|
func TruncIf(str string, max int, cond bool) string {
|
||||||
|
if cond && len(str) > max {
|
||||||
|
return str[:max]
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wordify convert complex name to a single word without special shell characters
|
||||||
|
func Wordify(str string) string {
|
||||||
|
str = regexp.MustCompile(`[^a-zA-Z0-9-]`).ReplaceAllString(str, "_")
|
||||||
|
str = regexp.MustCompile(`__+`).ReplaceAllString(str, "_")
|
||||||
|
str = strings.Trim(str, "_")
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathToTARPathparts returns the two parts of a unix path
|
||||||
|
func PathToTARPathparts(fullPath string) (string, string) {
|
||||||
|
fullPath = strings.TrimRight(fullPath, "/")
|
||||||
|
return path.Dir(fullPath), path.Base(fullPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDuplicates transforms an array into a unique array
|
||||||
|
func RemoveDuplicates(elements []string) []string {
|
||||||
|
encountered := map[string]bool{}
|
||||||
|
|
||||||
|
// Create a map of all unique elements.
|
||||||
|
for v := range elements {
|
||||||
|
encountered[elements[v]] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place all keys from the map into a slice.
|
||||||
|
result := []string{}
|
||||||
|
for key := range encountered {
|
||||||
|
result = append(result, key)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachToSerial tries to connect to server serial using 'gotty-client' and fallback with a help message
|
||||||
|
func AttachToSerial(serverID, apiToken, url string) (*gottyclient.Client, chan bool, error) {
|
||||||
|
gottyURL := os.Getenv("SCW_GOTTY_URL")
|
||||||
|
if gottyURL == "" {
|
||||||
|
gottyURL = url
|
||||||
|
}
|
||||||
|
URL := fmt.Sprintf("%s?arg=%s&arg=%s", gottyURL, apiToken, serverID)
|
||||||
|
|
||||||
|
logrus.Debug("Connection to ", URL)
|
||||||
|
gottycli, err := gottyclient.NewClient(URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("SCW_TLSVERIFY") == "0" {
|
||||||
|
gottycli.SkipTLSVerify = true
|
||||||
|
}
|
||||||
|
|
||||||
|
gottycli.UseProxyFromEnv = true
|
||||||
|
|
||||||
|
if err = gottycli.Connect(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
done := make(chan bool)
|
||||||
|
|
||||||
|
fmt.Println("You are connected, type 'Ctrl+q' to quit.")
|
||||||
|
go func() {
|
||||||
|
gottycli.Loop()
|
||||||
|
gottycli.Close()
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
return gottycli, done, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rfc4716hex(data []byte) string {
|
||||||
|
fingerprint := ""
|
||||||
|
|
||||||
|
for i := 0; i < len(data); i++ {
|
||||||
|
fingerprint = fmt.Sprintf("%s%0.2x", fingerprint, data[i])
|
||||||
|
if i != len(data)-1 {
|
||||||
|
fingerprint = fingerprint + ":"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fingerprint
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSHGetFingerprint returns the fingerprint of an SSH key
|
||||||
|
func SSHGetFingerprint(key []byte) (string, error) {
|
||||||
|
publicKey, comment, _, _, err := ssh.ParseAuthorizedKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
switch reflect.TypeOf(publicKey).String() {
|
||||||
|
case "*ssh.rsaPublicKey", "*ssh.dsaPublicKey", "*ssh.ecdsaPublicKey":
|
||||||
|
md5sum := md5.Sum(publicKey.Marshal())
|
||||||
|
return publicKey.Type() + " " + rfc4716hex(md5sum[:]) + " " + comment, nil
|
||||||
|
default:
|
||||||
|
return "", errors.New("Can't handle this key")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
// Copyright 2016 The Go 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 ed25519 implements the Ed25519 signature algorithm. See
|
||||||
|
// http://ed25519.cr.yp.to/.
|
||||||
|
//
|
||||||
|
// These functions are also compatible with the “Ed25519” function defined in
|
||||||
|
// https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05.
|
||||||
|
package ed25519
|
||||||
|
|
||||||
|
// This code is a port of the public domain, “ref10” implementation of ed25519
|
||||||
|
// from SUPERCOP.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
cryptorand "crypto/rand"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/subtle"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519/internal/edwards25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||||
|
PublicKeySize = 32
|
||||||
|
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||||
|
PrivateKeySize = 64
|
||||||
|
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||||
|
SignatureSize = 64
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicKey is the type of Ed25519 public keys.
|
||||||
|
type PublicKey []byte
|
||||||
|
|
||||||
|
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
|
||||||
|
type PrivateKey []byte
|
||||||
|
|
||||||
|
// Public returns the PublicKey corresponding to priv.
|
||||||
|
func (priv PrivateKey) Public() crypto.PublicKey {
|
||||||
|
publicKey := make([]byte, PublicKeySize)
|
||||||
|
copy(publicKey, priv[32:])
|
||||||
|
return PublicKey(publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the given message with priv.
|
||||||
|
// Ed25519 performs two passes over messages to be signed and therefore cannot
|
||||||
|
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
|
||||||
|
// indicate the message hasn't been hashed. This can be achieved by passing
|
||||||
|
// crypto.Hash(0) as the value for opts.
|
||||||
|
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
if opts.HashFunc() != crypto.Hash(0) {
|
||||||
|
return nil, errors.New("ed25519: cannot sign hashed message")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sign(priv, message), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateKey generates a public/private key pair using entropy from rand.
|
||||||
|
// If rand is nil, crypto/rand.Reader will be used.
|
||||||
|
func GenerateKey(rand io.Reader) (publicKey PublicKey, privateKey PrivateKey, err error) {
|
||||||
|
if rand == nil {
|
||||||
|
rand = cryptorand.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey = make([]byte, PrivateKeySize)
|
||||||
|
publicKey = make([]byte, PublicKeySize)
|
||||||
|
_, err = io.ReadFull(rand, privateKey[:32])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := sha512.Sum512(privateKey[:32])
|
||||||
|
digest[0] &= 248
|
||||||
|
digest[31] &= 127
|
||||||
|
digest[31] |= 64
|
||||||
|
|
||||||
|
var A edwards25519.ExtendedGroupElement
|
||||||
|
var hBytes [32]byte
|
||||||
|
copy(hBytes[:], digest[:])
|
||||||
|
edwards25519.GeScalarMultBase(&A, &hBytes)
|
||||||
|
var publicKeyBytes [32]byte
|
||||||
|
A.ToBytes(&publicKeyBytes)
|
||||||
|
|
||||||
|
copy(privateKey[32:], publicKeyBytes[:])
|
||||||
|
copy(publicKey, publicKeyBytes[:])
|
||||||
|
|
||||||
|
return publicKey, privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the message with privateKey and returns a signature. It will
|
||||||
|
// panic if len(privateKey) is not PrivateKeySize.
|
||||||
|
func Sign(privateKey PrivateKey, message []byte) []byte {
|
||||||
|
if l := len(privateKey); l != PrivateKeySize {
|
||||||
|
panic("ed25519: bad private key length: " + strconv.Itoa(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write(privateKey[:32])
|
||||||
|
|
||||||
|
var digest1, messageDigest, hramDigest [64]byte
|
||||||
|
var expandedSecretKey [32]byte
|
||||||
|
h.Sum(digest1[:0])
|
||||||
|
copy(expandedSecretKey[:], digest1[:])
|
||||||
|
expandedSecretKey[0] &= 248
|
||||||
|
expandedSecretKey[31] &= 63
|
||||||
|
expandedSecretKey[31] |= 64
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(digest1[32:])
|
||||||
|
h.Write(message)
|
||||||
|
h.Sum(messageDigest[:0])
|
||||||
|
|
||||||
|
var messageDigestReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
|
||||||
|
var R edwards25519.ExtendedGroupElement
|
||||||
|
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
|
||||||
|
|
||||||
|
var encodedR [32]byte
|
||||||
|
R.ToBytes(&encodedR)
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(encodedR[:])
|
||||||
|
h.Write(privateKey[32:])
|
||||||
|
h.Write(message)
|
||||||
|
h.Sum(hramDigest[:0])
|
||||||
|
var hramDigestReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
|
||||||
|
|
||||||
|
var s [32]byte
|
||||||
|
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
|
||||||
|
|
||||||
|
signature := make([]byte, SignatureSize)
|
||||||
|
copy(signature[:], encodedR[:])
|
||||||
|
copy(signature[32:], s[:])
|
||||||
|
|
||||||
|
return signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify reports whether sig is a valid signature of message by publicKey. It
|
||||||
|
// will panic if len(publicKey) is not PublicKeySize.
|
||||||
|
func Verify(publicKey PublicKey, message, sig []byte) bool {
|
||||||
|
if l := len(publicKey); l != PublicKeySize {
|
||||||
|
panic("ed25519: bad public key length: " + strconv.Itoa(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sig) != SignatureSize || sig[63]&224 != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var A edwards25519.ExtendedGroupElement
|
||||||
|
var publicKeyBytes [32]byte
|
||||||
|
copy(publicKeyBytes[:], publicKey)
|
||||||
|
if !A.FromBytes(&publicKeyBytes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
edwards25519.FeNeg(&A.X, &A.X)
|
||||||
|
edwards25519.FeNeg(&A.T, &A.T)
|
||||||
|
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write(sig[:32])
|
||||||
|
h.Write(publicKey[:])
|
||||||
|
h.Write(message)
|
||||||
|
var digest [64]byte
|
||||||
|
h.Sum(digest[:0])
|
||||||
|
|
||||||
|
var hReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&hReduced, &digest)
|
||||||
|
|
||||||
|
var R edwards25519.ProjectiveGroupElement
|
||||||
|
var b [32]byte
|
||||||
|
copy(b[:], sig[32:])
|
||||||
|
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
|
||||||
|
|
||||||
|
var checkR [32]byte
|
||||||
|
R.ToBytes(&checkR)
|
||||||
|
return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1
|
||||||
|
}
|
1422
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go
generated
vendored
Normal file
1422
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1771
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go
generated
vendored
Normal file
1771
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,951 @@
|
||||||
|
// Copyright 2011 The Go 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 terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EscapeCodes contains escape sequences that can be written to the terminal in
|
||||||
|
// order to achieve different styles of text.
|
||||||
|
type EscapeCodes struct {
|
||||||
|
// Foreground colors
|
||||||
|
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
|
||||||
|
|
||||||
|
// Reset all attributes
|
||||||
|
Reset []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var vt100EscapeCodes = EscapeCodes{
|
||||||
|
Black: []byte{keyEscape, '[', '3', '0', 'm'},
|
||||||
|
Red: []byte{keyEscape, '[', '3', '1', 'm'},
|
||||||
|
Green: []byte{keyEscape, '[', '3', '2', 'm'},
|
||||||
|
Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
|
||||||
|
Blue: []byte{keyEscape, '[', '3', '4', 'm'},
|
||||||
|
Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
|
||||||
|
Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
|
||||||
|
White: []byte{keyEscape, '[', '3', '7', 'm'},
|
||||||
|
|
||||||
|
Reset: []byte{keyEscape, '[', '0', 'm'},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminal contains the state for running a VT100 terminal that is capable of
|
||||||
|
// reading lines of input.
|
||||||
|
type Terminal struct {
|
||||||
|
// AutoCompleteCallback, if non-null, is called for each keypress with
|
||||||
|
// the full input line and the current position of the cursor (in
|
||||||
|
// bytes, as an index into |line|). If it returns ok=false, the key
|
||||||
|
// press is processed normally. Otherwise it returns a replacement line
|
||||||
|
// and the new cursor position.
|
||||||
|
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
|
||||||
|
|
||||||
|
// Escape contains a pointer to the escape codes for this terminal.
|
||||||
|
// It's always a valid pointer, although the escape codes themselves
|
||||||
|
// may be empty if the terminal doesn't support them.
|
||||||
|
Escape *EscapeCodes
|
||||||
|
|
||||||
|
// lock protects the terminal and the state in this object from
|
||||||
|
// concurrent processing of a key press and a Write() call.
|
||||||
|
lock sync.Mutex
|
||||||
|
|
||||||
|
c io.ReadWriter
|
||||||
|
prompt []rune
|
||||||
|
|
||||||
|
// line is the current line being entered.
|
||||||
|
line []rune
|
||||||
|
// pos is the logical position of the cursor in line
|
||||||
|
pos int
|
||||||
|
// echo is true if local echo is enabled
|
||||||
|
echo bool
|
||||||
|
// pasteActive is true iff there is a bracketed paste operation in
|
||||||
|
// progress.
|
||||||
|
pasteActive bool
|
||||||
|
|
||||||
|
// cursorX contains the current X value of the cursor where the left
|
||||||
|
// edge is 0. cursorY contains the row number where the first row of
|
||||||
|
// the current line is 0.
|
||||||
|
cursorX, cursorY int
|
||||||
|
// maxLine is the greatest value of cursorY so far.
|
||||||
|
maxLine int
|
||||||
|
|
||||||
|
termWidth, termHeight int
|
||||||
|
|
||||||
|
// outBuf contains the terminal data to be sent.
|
||||||
|
outBuf []byte
|
||||||
|
// remainder contains the remainder of any partial key sequences after
|
||||||
|
// a read. It aliases into inBuf.
|
||||||
|
remainder []byte
|
||||||
|
inBuf [256]byte
|
||||||
|
|
||||||
|
// history contains previously entered commands so that they can be
|
||||||
|
// accessed with the up and down keys.
|
||||||
|
history stRingBuffer
|
||||||
|
// historyIndex stores the currently accessed history entry, where zero
|
||||||
|
// means the immediately previous entry.
|
||||||
|
historyIndex int
|
||||||
|
// When navigating up and down the history it's possible to return to
|
||||||
|
// the incomplete, initial line. That value is stored in
|
||||||
|
// historyPending.
|
||||||
|
historyPending string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
|
||||||
|
// a local terminal, that terminal must first have been put into raw mode.
|
||||||
|
// prompt is a string that is written at the start of each input line (i.e.
|
||||||
|
// "> ").
|
||||||
|
func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
|
||||||
|
return &Terminal{
|
||||||
|
Escape: &vt100EscapeCodes,
|
||||||
|
c: c,
|
||||||
|
prompt: []rune(prompt),
|
||||||
|
termWidth: 80,
|
||||||
|
termHeight: 24,
|
||||||
|
echo: true,
|
||||||
|
historyIndex: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyCtrlD = 4
|
||||||
|
keyCtrlU = 21
|
||||||
|
keyEnter = '\r'
|
||||||
|
keyEscape = 27
|
||||||
|
keyBackspace = 127
|
||||||
|
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
|
||||||
|
keyUp
|
||||||
|
keyDown
|
||||||
|
keyLeft
|
||||||
|
keyRight
|
||||||
|
keyAltLeft
|
||||||
|
keyAltRight
|
||||||
|
keyHome
|
||||||
|
keyEnd
|
||||||
|
keyDeleteWord
|
||||||
|
keyDeleteLine
|
||||||
|
keyClearScreen
|
||||||
|
keyPasteStart
|
||||||
|
keyPasteEnd
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
crlf = []byte{'\r', '\n'}
|
||||||
|
pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
|
||||||
|
pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
|
||||||
|
)
|
||||||
|
|
||||||
|
// bytesToKey tries to parse a key sequence from b. If successful, it returns
|
||||||
|
// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
|
||||||
|
func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return utf8.RuneError, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive {
|
||||||
|
switch b[0] {
|
||||||
|
case 1: // ^A
|
||||||
|
return keyHome, b[1:]
|
||||||
|
case 5: // ^E
|
||||||
|
return keyEnd, b[1:]
|
||||||
|
case 8: // ^H
|
||||||
|
return keyBackspace, b[1:]
|
||||||
|
case 11: // ^K
|
||||||
|
return keyDeleteLine, b[1:]
|
||||||
|
case 12: // ^L
|
||||||
|
return keyClearScreen, b[1:]
|
||||||
|
case 23: // ^W
|
||||||
|
return keyDeleteWord, b[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[0] != keyEscape {
|
||||||
|
if !utf8.FullRune(b) {
|
||||||
|
return utf8.RuneError, b
|
||||||
|
}
|
||||||
|
r, l := utf8.DecodeRune(b)
|
||||||
|
return r, b[l:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
|
||||||
|
switch b[2] {
|
||||||
|
case 'A':
|
||||||
|
return keyUp, b[3:]
|
||||||
|
case 'B':
|
||||||
|
return keyDown, b[3:]
|
||||||
|
case 'C':
|
||||||
|
return keyRight, b[3:]
|
||||||
|
case 'D':
|
||||||
|
return keyLeft, b[3:]
|
||||||
|
case 'H':
|
||||||
|
return keyHome, b[3:]
|
||||||
|
case 'F':
|
||||||
|
return keyEnd, b[3:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
||||||
|
switch b[5] {
|
||||||
|
case 'C':
|
||||||
|
return keyAltRight, b[6:]
|
||||||
|
case 'D':
|
||||||
|
return keyAltLeft, b[6:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
|
||||||
|
return keyPasteStart, b[6:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
|
||||||
|
return keyPasteEnd, b[6:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here then we have a key that we don't recognise, or a
|
||||||
|
// partial sequence. It's not clear how one should find the end of a
|
||||||
|
// sequence without knowing them all, but it seems that [a-zA-Z~] only
|
||||||
|
// appears at the end of a sequence.
|
||||||
|
for i, c := range b[0:] {
|
||||||
|
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
|
||||||
|
return keyUnknown, b[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return utf8.RuneError, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue appends data to the end of t.outBuf
|
||||||
|
func (t *Terminal) queue(data []rune) {
|
||||||
|
t.outBuf = append(t.outBuf, []byte(string(data))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
|
||||||
|
var space = []rune{' '}
|
||||||
|
|
||||||
|
func isPrintable(key rune) bool {
|
||||||
|
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
|
||||||
|
return key >= 32 && !isInSurrogateArea
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveCursorToPos appends data to t.outBuf which will move the cursor to the
|
||||||
|
// given, logical position in the text.
|
||||||
|
func (t *Terminal) moveCursorToPos(pos int) {
|
||||||
|
if !t.echo {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
x := visualLength(t.prompt) + pos
|
||||||
|
y := x / t.termWidth
|
||||||
|
x = x % t.termWidth
|
||||||
|
|
||||||
|
up := 0
|
||||||
|
if y < t.cursorY {
|
||||||
|
up = t.cursorY - y
|
||||||
|
}
|
||||||
|
|
||||||
|
down := 0
|
||||||
|
if y > t.cursorY {
|
||||||
|
down = y - t.cursorY
|
||||||
|
}
|
||||||
|
|
||||||
|
left := 0
|
||||||
|
if x < t.cursorX {
|
||||||
|
left = t.cursorX - x
|
||||||
|
}
|
||||||
|
|
||||||
|
right := 0
|
||||||
|
if x > t.cursorX {
|
||||||
|
right = x - t.cursorX
|
||||||
|
}
|
||||||
|
|
||||||
|
t.cursorX = x
|
||||||
|
t.cursorY = y
|
||||||
|
t.move(up, down, left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) move(up, down, left, right int) {
|
||||||
|
movement := make([]rune, 3*(up+down+left+right))
|
||||||
|
m := movement
|
||||||
|
for i := 0; i < up; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'A'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < down; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'B'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < left; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'D'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < right; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'C'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
|
||||||
|
t.queue(movement)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) clearLineToRight() {
|
||||||
|
op := []rune{keyEscape, '[', 'K'}
|
||||||
|
t.queue(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxLineLength = 4096
|
||||||
|
|
||||||
|
func (t *Terminal) setLine(newLine []rune, newPos int) {
|
||||||
|
if t.echo {
|
||||||
|
t.moveCursorToPos(0)
|
||||||
|
t.writeLine(newLine)
|
||||||
|
for i := len(newLine); i < len(t.line); i++ {
|
||||||
|
t.writeLine(space)
|
||||||
|
}
|
||||||
|
t.moveCursorToPos(newPos)
|
||||||
|
}
|
||||||
|
t.line = newLine
|
||||||
|
t.pos = newPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) advanceCursor(places int) {
|
||||||
|
t.cursorX += places
|
||||||
|
t.cursorY += t.cursorX / t.termWidth
|
||||||
|
if t.cursorY > t.maxLine {
|
||||||
|
t.maxLine = t.cursorY
|
||||||
|
}
|
||||||
|
t.cursorX = t.cursorX % t.termWidth
|
||||||
|
|
||||||
|
if places > 0 && t.cursorX == 0 {
|
||||||
|
// Normally terminals will advance the current position
|
||||||
|
// when writing a character. But that doesn't happen
|
||||||
|
// for the last character in a line. However, when
|
||||||
|
// writing a character (except a new line) that causes
|
||||||
|
// a line wrap, the position will be advanced two
|
||||||
|
// places.
|
||||||
|
//
|
||||||
|
// So, if we are stopping at the end of a line, we
|
||||||
|
// need to write a newline so that our cursor can be
|
||||||
|
// advanced to the next line.
|
||||||
|
t.outBuf = append(t.outBuf, '\r', '\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) eraseNPreviousChars(n int) {
|
||||||
|
if n == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.pos < n {
|
||||||
|
n = t.pos
|
||||||
|
}
|
||||||
|
t.pos -= n
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
|
||||||
|
copy(t.line[t.pos:], t.line[n+t.pos:])
|
||||||
|
t.line = t.line[:len(t.line)-n]
|
||||||
|
if t.echo {
|
||||||
|
t.writeLine(t.line[t.pos:])
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
t.queue(space)
|
||||||
|
}
|
||||||
|
t.advanceCursor(n)
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// countToLeftWord returns then number of characters from the cursor to the
|
||||||
|
// start of the previous word.
|
||||||
|
func (t *Terminal) countToLeftWord() int {
|
||||||
|
if t.pos == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := t.pos - 1
|
||||||
|
for pos > 0 {
|
||||||
|
if t.line[pos] != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos--
|
||||||
|
}
|
||||||
|
for pos > 0 {
|
||||||
|
if t.line[pos] == ' ' {
|
||||||
|
pos++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos--
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.pos - pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// countToRightWord returns then number of characters from the cursor to the
|
||||||
|
// start of the next word.
|
||||||
|
func (t *Terminal) countToRightWord() int {
|
||||||
|
pos := t.pos
|
||||||
|
for pos < len(t.line) {
|
||||||
|
if t.line[pos] == ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
for pos < len(t.line) {
|
||||||
|
if t.line[pos] != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
return pos - t.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// visualLength returns the number of visible glyphs in s.
|
||||||
|
func visualLength(runes []rune) int {
|
||||||
|
inEscapeSeq := false
|
||||||
|
length := 0
|
||||||
|
|
||||||
|
for _, r := range runes {
|
||||||
|
switch {
|
||||||
|
case inEscapeSeq:
|
||||||
|
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
|
||||||
|
inEscapeSeq = false
|
||||||
|
}
|
||||||
|
case r == '\x1b':
|
||||||
|
inEscapeSeq = true
|
||||||
|
default:
|
||||||
|
length++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return length
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleKey processes the given key and, optionally, returns a line of text
|
||||||
|
// that the user has entered.
|
||||||
|
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||||
|
if t.pasteActive && key != keyEnter {
|
||||||
|
t.addKeyToLine(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case keyBackspace:
|
||||||
|
if t.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.eraseNPreviousChars(1)
|
||||||
|
case keyAltLeft:
|
||||||
|
// move left by a word.
|
||||||
|
t.pos -= t.countToLeftWord()
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyAltRight:
|
||||||
|
// move right by a word.
|
||||||
|
t.pos += t.countToRightWord()
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyLeft:
|
||||||
|
if t.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos--
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyRight:
|
||||||
|
if t.pos == len(t.line) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos++
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyHome:
|
||||||
|
if t.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos = 0
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyEnd:
|
||||||
|
if t.pos == len(t.line) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos = len(t.line)
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyUp:
|
||||||
|
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if t.historyIndex == -1 {
|
||||||
|
t.historyPending = string(t.line)
|
||||||
|
}
|
||||||
|
t.historyIndex++
|
||||||
|
runes := []rune(entry)
|
||||||
|
t.setLine(runes, len(runes))
|
||||||
|
case keyDown:
|
||||||
|
switch t.historyIndex {
|
||||||
|
case -1:
|
||||||
|
return
|
||||||
|
case 0:
|
||||||
|
runes := []rune(t.historyPending)
|
||||||
|
t.setLine(runes, len(runes))
|
||||||
|
t.historyIndex--
|
||||||
|
default:
|
||||||
|
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
|
||||||
|
if ok {
|
||||||
|
t.historyIndex--
|
||||||
|
runes := []rune(entry)
|
||||||
|
t.setLine(runes, len(runes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case keyEnter:
|
||||||
|
t.moveCursorToPos(len(t.line))
|
||||||
|
t.queue([]rune("\r\n"))
|
||||||
|
line = string(t.line)
|
||||||
|
ok = true
|
||||||
|
t.line = t.line[:0]
|
||||||
|
t.pos = 0
|
||||||
|
t.cursorX = 0
|
||||||
|
t.cursorY = 0
|
||||||
|
t.maxLine = 0
|
||||||
|
case keyDeleteWord:
|
||||||
|
// Delete zero or more spaces and then one or more characters.
|
||||||
|
t.eraseNPreviousChars(t.countToLeftWord())
|
||||||
|
case keyDeleteLine:
|
||||||
|
// Delete everything from the current cursor position to the
|
||||||
|
// end of line.
|
||||||
|
for i := t.pos; i < len(t.line); i++ {
|
||||||
|
t.queue(space)
|
||||||
|
t.advanceCursor(1)
|
||||||
|
}
|
||||||
|
t.line = t.line[:t.pos]
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyCtrlD:
|
||||||
|
// Erase the character under the current position.
|
||||||
|
// The EOF case when the line is empty is handled in
|
||||||
|
// readLine().
|
||||||
|
if t.pos < len(t.line) {
|
||||||
|
t.pos++
|
||||||
|
t.eraseNPreviousChars(1)
|
||||||
|
}
|
||||||
|
case keyCtrlU:
|
||||||
|
t.eraseNPreviousChars(t.pos)
|
||||||
|
case keyClearScreen:
|
||||||
|
// Erases the screen and moves the cursor to the home position.
|
||||||
|
t.queue([]rune("\x1b[2J\x1b[H"))
|
||||||
|
t.queue(t.prompt)
|
||||||
|
t.cursorX, t.cursorY = 0, 0
|
||||||
|
t.advanceCursor(visualLength(t.prompt))
|
||||||
|
t.setLine(t.line, t.pos)
|
||||||
|
default:
|
||||||
|
if t.AutoCompleteCallback != nil {
|
||||||
|
prefix := string(t.line[:t.pos])
|
||||||
|
suffix := string(t.line[t.pos:])
|
||||||
|
|
||||||
|
t.lock.Unlock()
|
||||||
|
newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
|
||||||
|
t.lock.Lock()
|
||||||
|
|
||||||
|
if completeOk {
|
||||||
|
t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isPrintable(key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(t.line) == maxLineLength {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.addKeyToLine(key)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKeyToLine inserts the given key at the current position in the current
|
||||||
|
// line.
|
||||||
|
func (t *Terminal) addKeyToLine(key rune) {
|
||||||
|
if len(t.line) == cap(t.line) {
|
||||||
|
newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
|
||||||
|
copy(newLine, t.line)
|
||||||
|
t.line = newLine
|
||||||
|
}
|
||||||
|
t.line = t.line[:len(t.line)+1]
|
||||||
|
copy(t.line[t.pos+1:], t.line[t.pos:])
|
||||||
|
t.line[t.pos] = key
|
||||||
|
if t.echo {
|
||||||
|
t.writeLine(t.line[t.pos:])
|
||||||
|
}
|
||||||
|
t.pos++
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) writeLine(line []rune) {
|
||||||
|
for len(line) != 0 {
|
||||||
|
remainingOnLine := t.termWidth - t.cursorX
|
||||||
|
todo := len(line)
|
||||||
|
if todo > remainingOnLine {
|
||||||
|
todo = remainingOnLine
|
||||||
|
}
|
||||||
|
t.queue(line[:todo])
|
||||||
|
t.advanceCursor(visualLength(line[:todo]))
|
||||||
|
line = line[todo:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
|
||||||
|
func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
|
||||||
|
for len(buf) > 0 {
|
||||||
|
i := bytes.IndexByte(buf, '\n')
|
||||||
|
todo := len(buf)
|
||||||
|
if i >= 0 {
|
||||||
|
todo = i
|
||||||
|
}
|
||||||
|
|
||||||
|
var nn int
|
||||||
|
nn, err = w.Write(buf[:todo])
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
buf = buf[todo:]
|
||||||
|
|
||||||
|
if i >= 0 {
|
||||||
|
if _, err = w.Write(crlf); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += 1
|
||||||
|
buf = buf[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) Write(buf []byte) (n int, err error) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
if t.cursorX == 0 && t.cursorY == 0 {
|
||||||
|
// This is the easy case: there's nothing on the screen that we
|
||||||
|
// have to move out of the way.
|
||||||
|
return writeWithCRLF(t.c, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a prompt and possibly user input on the screen. We
|
||||||
|
// have to clear it first.
|
||||||
|
t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
|
||||||
|
t.cursorX = 0
|
||||||
|
t.clearLineToRight()
|
||||||
|
|
||||||
|
for t.cursorY > 0 {
|
||||||
|
t.move(1 /* up */, 0, 0, 0)
|
||||||
|
t.cursorY--
|
||||||
|
t.clearLineToRight()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = t.c.Write(t.outBuf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
|
||||||
|
if n, err = writeWithCRLF(t.c, buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.writeLine(t.prompt)
|
||||||
|
if t.echo {
|
||||||
|
t.writeLine(t.line)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
|
||||||
|
if _, err = t.c.Write(t.outBuf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword temporarily changes the prompt and reads a password, without
|
||||||
|
// echo, from the terminal.
|
||||||
|
func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
oldPrompt := t.prompt
|
||||||
|
t.prompt = []rune(prompt)
|
||||||
|
t.echo = false
|
||||||
|
|
||||||
|
line, err = t.readLine()
|
||||||
|
|
||||||
|
t.prompt = oldPrompt
|
||||||
|
t.echo = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLine returns a line of input from the terminal.
|
||||||
|
func (t *Terminal) ReadLine() (line string, err error) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
return t.readLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) readLine() (line string, err error) {
|
||||||
|
// t.lock must be held at this point
|
||||||
|
|
||||||
|
if t.cursorX == 0 && t.cursorY == 0 {
|
||||||
|
t.writeLine(t.prompt)
|
||||||
|
t.c.Write(t.outBuf)
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
lineIsPasted := t.pasteActive
|
||||||
|
|
||||||
|
for {
|
||||||
|
rest := t.remainder
|
||||||
|
lineOk := false
|
||||||
|
for !lineOk {
|
||||||
|
var key rune
|
||||||
|
key, rest = bytesToKey(rest, t.pasteActive)
|
||||||
|
if key == utf8.RuneError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !t.pasteActive {
|
||||||
|
if key == keyCtrlD {
|
||||||
|
if len(t.line) == 0 {
|
||||||
|
return "", io.EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if key == keyPasteStart {
|
||||||
|
t.pasteActive = true
|
||||||
|
if len(t.line) == 0 {
|
||||||
|
lineIsPasted = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if key == keyPasteEnd {
|
||||||
|
t.pasteActive = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !t.pasteActive {
|
||||||
|
lineIsPasted = false
|
||||||
|
}
|
||||||
|
line, lineOk = t.handleKey(key)
|
||||||
|
}
|
||||||
|
if len(rest) > 0 {
|
||||||
|
n := copy(t.inBuf[:], rest)
|
||||||
|
t.remainder = t.inBuf[:n]
|
||||||
|
} else {
|
||||||
|
t.remainder = nil
|
||||||
|
}
|
||||||
|
t.c.Write(t.outBuf)
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
if lineOk {
|
||||||
|
if t.echo {
|
||||||
|
t.historyIndex = -1
|
||||||
|
t.history.Add(line)
|
||||||
|
}
|
||||||
|
if lineIsPasted {
|
||||||
|
err = ErrPasteIndicator
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.remainder is a slice at the beginning of t.inBuf
|
||||||
|
// containing a partial key sequence
|
||||||
|
readBuf := t.inBuf[len(t.remainder):]
|
||||||
|
var n int
|
||||||
|
|
||||||
|
t.lock.Unlock()
|
||||||
|
n, err = t.c.Read(readBuf)
|
||||||
|
t.lock.Lock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.remainder = t.inBuf[:n+len(t.remainder)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrompt sets the prompt to be used when reading subsequent lines.
|
||||||
|
func (t *Terminal) SetPrompt(prompt string) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
t.prompt = []rune(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
|
||||||
|
// Move cursor to column zero at the start of the line.
|
||||||
|
t.move(t.cursorY, 0, t.cursorX, 0)
|
||||||
|
t.cursorX, t.cursorY = 0, 0
|
||||||
|
t.clearLineToRight()
|
||||||
|
for t.cursorY < numPrevLines {
|
||||||
|
// Move down a line
|
||||||
|
t.move(0, 1, 0, 0)
|
||||||
|
t.cursorY++
|
||||||
|
t.clearLineToRight()
|
||||||
|
}
|
||||||
|
// Move back to beginning.
|
||||||
|
t.move(t.cursorY, 0, 0, 0)
|
||||||
|
t.cursorX, t.cursorY = 0, 0
|
||||||
|
|
||||||
|
t.queue(t.prompt)
|
||||||
|
t.advanceCursor(visualLength(t.prompt))
|
||||||
|
t.writeLine(t.line)
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) SetSize(width, height int) error {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
if width == 0 {
|
||||||
|
width = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
oldWidth := t.termWidth
|
||||||
|
t.termWidth, t.termHeight = width, height
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case width == oldWidth:
|
||||||
|
// If the width didn't change then nothing else needs to be
|
||||||
|
// done.
|
||||||
|
return nil
|
||||||
|
case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
|
||||||
|
// If there is nothing on current line and no prompt printed,
|
||||||
|
// just do nothing
|
||||||
|
return nil
|
||||||
|
case width < oldWidth:
|
||||||
|
// Some terminals (e.g. xterm) will truncate lines that were
|
||||||
|
// too long when shinking. Others, (e.g. gnome-terminal) will
|
||||||
|
// attempt to wrap them. For the former, repainting t.maxLine
|
||||||
|
// works great, but that behaviour goes badly wrong in the case
|
||||||
|
// of the latter because they have doubled every full line.
|
||||||
|
|
||||||
|
// We assume that we are working on a terminal that wraps lines
|
||||||
|
// and adjust the cursor position based on every previous line
|
||||||
|
// wrapping and turning into two. This causes the prompt on
|
||||||
|
// xterms to move upwards, which isn't great, but it avoids a
|
||||||
|
// huge mess with gnome-terminal.
|
||||||
|
if t.cursorX >= t.termWidth {
|
||||||
|
t.cursorX = t.termWidth - 1
|
||||||
|
}
|
||||||
|
t.cursorY *= 2
|
||||||
|
t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
|
||||||
|
case width > oldWidth:
|
||||||
|
// If the terminal expands then our position calculations will
|
||||||
|
// be wrong in the future because we think the cursor is
|
||||||
|
// |t.pos| chars into the string, but there will be a gap at
|
||||||
|
// the end of any wrapped line.
|
||||||
|
//
|
||||||
|
// But the position will actually be correct until we move, so
|
||||||
|
// we can move back to the beginning and repaint everything.
|
||||||
|
t.clearAndRepaintLinePlusNPrevious(t.maxLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := t.c.Write(t.outBuf)
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type pasteIndicatorError struct{}
|
||||||
|
|
||||||
|
func (pasteIndicatorError) Error() string {
|
||||||
|
return "terminal: ErrPasteIndicator not correctly handled"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrPasteIndicator may be returned from ReadLine as the error, in addition
|
||||||
|
// to valid line data. It indicates that bracketed paste mode is enabled and
|
||||||
|
// that the returned line consists only of pasted data. Programs may wish to
|
||||||
|
// interpret pasted data more literally than typed data.
|
||||||
|
var ErrPasteIndicator = pasteIndicatorError{}
|
||||||
|
|
||||||
|
// SetBracketedPasteMode requests that the terminal bracket paste operations
|
||||||
|
// with markers. Not all terminals support this but, if it is supported, then
|
||||||
|
// enabling this mode will stop any autocomplete callback from running due to
|
||||||
|
// pastes. Additionally, any lines that are completely pasted will be returned
|
||||||
|
// from ReadLine with the error set to ErrPasteIndicator.
|
||||||
|
func (t *Terminal) SetBracketedPasteMode(on bool) {
|
||||||
|
if on {
|
||||||
|
io.WriteString(t.c, "\x1b[?2004h")
|
||||||
|
} else {
|
||||||
|
io.WriteString(t.c, "\x1b[?2004l")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stRingBuffer is a ring buffer of strings.
|
||||||
|
type stRingBuffer struct {
|
||||||
|
// entries contains max elements.
|
||||||
|
entries []string
|
||||||
|
max int
|
||||||
|
// head contains the index of the element most recently added to the ring.
|
||||||
|
head int
|
||||||
|
// size contains the number of elements in the ring.
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stRingBuffer) Add(a string) {
|
||||||
|
if s.entries == nil {
|
||||||
|
const defaultNumEntries = 100
|
||||||
|
s.entries = make([]string, defaultNumEntries)
|
||||||
|
s.max = defaultNumEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
s.head = (s.head + 1) % s.max
|
||||||
|
s.entries[s.head] = a
|
||||||
|
if s.size < s.max {
|
||||||
|
s.size++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NthPreviousEntry returns the value passed to the nth previous call to Add.
|
||||||
|
// If n is zero then the immediately prior value is returned, if one, then the
|
||||||
|
// next most recent, and so on. If such an element doesn't exist then ok is
|
||||||
|
// false.
|
||||||
|
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
|
||||||
|
if n >= s.size {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
index := s.head - n
|
||||||
|
if index < 0 {
|
||||||
|
index += s.max
|
||||||
|
}
|
||||||
|
return s.entries[index], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
||||||
|
// The slice returned does not include the \n.
|
||||||
|
// readPasswordLine also ignores any \r it finds.
|
||||||
|
func readPasswordLine(reader io.Reader) ([]byte, error) {
|
||||||
|
var buf [1]byte
|
||||||
|
var ret []byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := reader.Read(buf[:])
|
||||||
|
if n > 0 {
|
||||||
|
switch buf[0] {
|
||||||
|
case '\n':
|
||||||
|
return ret, nil
|
||||||
|
case '\r':
|
||||||
|
// remove \r from passwords on Windows
|
||||||
|
default:
|
||||||
|
ret = append(ret, buf[0])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF && len(ret) > 0 {
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
|
||||||
|
|
||||||
|
// Package terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := terminal.MakeRaw(0)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// defer terminal.Restore(0, oldState)
|
||||||
|
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State contains the state of a terminal.
|
||||||
|
type State struct {
|
||||||
|
termios syscall.Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
var termios syscall.Termios
|
||||||
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return err == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
// This attempts to replicate the behaviour documented for cfmakeraw in
|
||||||
|
// the termios(3) manpage.
|
||||||
|
newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
|
||||||
|
newState.Oflag &^= syscall.OPOST
|
||||||
|
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
|
||||||
|
newState.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||||
|
newState.Cflag |= syscall.CS8
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, state *State) error {
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
var dimensions [4]uint16
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
return int(dimensions[1]), int(dimensions[0]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
||||||
|
type passwordReader int
|
||||||
|
|
||||||
|
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||||
|
return syscall.Read(int(r), buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
var oldState syscall.Termios
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState
|
||||||
|
newState.Lflag &^= syscall.ECHO
|
||||||
|
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||||
|
newState.Iflag |= syscall.ICRNL
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return readPasswordLine(passwordReader(fd))
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
const ioctlWriteTermios = syscall.TIOCSETA
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright 2013 The Go 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 terminal
|
||||||
|
|
||||||
|
// These constants are declared here, rather than importing
|
||||||
|
// them from the syscall package as some syscall packages, even
|
||||||
|
// on linux, for example gccgo, do not declare them.
|
||||||
|
const ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||||
|
const ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2016 The Go 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 terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := terminal.MakeRaw(0)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// defer terminal.Restore(0, oldState)
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct{}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, state *State) error {
|
||||||
|
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build solaris
|
||||||
|
|
||||||
|
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"io"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State contains the state of a terminal.
|
||||||
|
type State struct {
|
||||||
|
termios syscall.Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
|
||||||
|
var termio unix.Termio
|
||||||
|
err := unix.IoctlSetTermio(fd, unix.TCGETA, &termio)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
// see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
||||||
|
val, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oldState := *val
|
||||||
|
|
||||||
|
newState := oldState
|
||||||
|
newState.Lflag &^= syscall.ECHO
|
||||||
|
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||||
|
newState.Iflag |= syscall.ICRNL
|
||||||
|
err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState)
|
||||||
|
|
||||||
|
var buf [16]byte
|
||||||
|
var ret []byte
|
||||||
|
for {
|
||||||
|
n, err := syscall.Read(fd, buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
if len(ret) == 0 {
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if buf[n-1] == '\n' {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
ret = append(ret, buf[:n]...)
|
||||||
|
if n < len(buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
// Package terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := terminal.MakeRaw(0)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// defer terminal.Restore(0, oldState)
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
enableLineInput = 2
|
||||||
|
enableEchoInput = 4
|
||||||
|
enableProcessedInput = 1
|
||||||
|
enableWindowInput = 8
|
||||||
|
enableMouseInput = 16
|
||||||
|
enableInsertMode = 32
|
||||||
|
enableQuickEditMode = 64
|
||||||
|
enableExtendedFlags = 128
|
||||||
|
enableAutoPosition = 256
|
||||||
|
enableProcessedOutput = 1
|
||||||
|
enableWrapAtEolOutput = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
var (
|
||||||
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||||
|
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||||
|
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
short int16
|
||||||
|
word uint16
|
||||||
|
|
||||||
|
coord struct {
|
||||||
|
x short
|
||||||
|
y short
|
||||||
|
}
|
||||||
|
smallRect struct {
|
||||||
|
left short
|
||||||
|
top short
|
||||||
|
right short
|
||||||
|
bottom short
|
||||||
|
}
|
||||||
|
consoleScreenBufferInfo struct {
|
||||||
|
size coord
|
||||||
|
cursorPosition coord
|
||||||
|
attributes word
|
||||||
|
window smallRect
|
||||||
|
maximumWindowSize coord
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
mode uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
var st uint32
|
||||||
|
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
return r != 0 && e == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
var st uint32
|
||||||
|
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, error(e)
|
||||||
|
}
|
||||||
|
raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||||
|
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, error(e)
|
||||||
|
}
|
||||||
|
return &State{st}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
var st uint32
|
||||||
|
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, error(e)
|
||||||
|
}
|
||||||
|
return &State{st}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, state *State) error {
|
||||||
|
_, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
var info consoleScreenBufferInfo
|
||||||
|
_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return 0, 0, error(e)
|
||||||
|
}
|
||||||
|
return int(info.size.x), int(info.size.y), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// passwordReader is an io.Reader that reads from a specific Windows HANDLE.
|
||||||
|
type passwordReader int
|
||||||
|
|
||||||
|
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||||
|
return syscall.Read(syscall.Handle(r), buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
var st uint32
|
||||||
|
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, error(e)
|
||||||
|
}
|
||||||
|
old := st
|
||||||
|
|
||||||
|
st &^= (enableEchoInput)
|
||||||
|
st |= (enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||||
|
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return nil, error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return readPasswordLine(passwordReader(fd))
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
#
|
||||||
|
# This Dockerfile builds a recent curl with HTTP/2 client support, using
|
||||||
|
# a recent nghttp2 build.
|
||||||
|
#
|
||||||
|
# See the Makefile for how to tag it. If Docker and that image is found, the
|
||||||
|
# Go tests use this curl binary for integration tests.
|
||||||
|
#
|
||||||
|
|
||||||
|
FROM ubuntu:trusty
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get upgrade -y && \
|
||||||
|
apt-get install -y git-core build-essential wget
|
||||||
|
|
||||||
|
RUN apt-get install -y --no-install-recommends \
|
||||||
|
autotools-dev libtool pkg-config zlib1g-dev \
|
||||||
|
libcunit1-dev libssl-dev libxml2-dev libevent-dev \
|
||||||
|
automake autoconf
|
||||||
|
|
||||||
|
# The list of packages nghttp2 recommends for h2load:
|
||||||
|
RUN apt-get install -y --no-install-recommends make binutils \
|
||||||
|
autoconf automake autotools-dev \
|
||||||
|
libtool pkg-config zlib1g-dev libcunit1-dev libssl-dev libxml2-dev \
|
||||||
|
libev-dev libevent-dev libjansson-dev libjemalloc-dev \
|
||||||
|
cython python3.4-dev python-setuptools
|
||||||
|
|
||||||
|
# Note: setting NGHTTP2_VER before the git clone, so an old git clone isn't cached:
|
||||||
|
ENV NGHTTP2_VER 895da9a
|
||||||
|
RUN cd /root && git clone https://github.com/tatsuhiro-t/nghttp2.git
|
||||||
|
|
||||||
|
WORKDIR /root/nghttp2
|
||||||
|
RUN git reset --hard $NGHTTP2_VER
|
||||||
|
RUN autoreconf -i
|
||||||
|
RUN automake
|
||||||
|
RUN autoconf
|
||||||
|
RUN ./configure
|
||||||
|
RUN make
|
||||||
|
RUN make install
|
||||||
|
|
||||||
|
WORKDIR /root
|
||||||
|
RUN wget http://curl.haxx.se/download/curl-7.45.0.tar.gz
|
||||||
|
RUN tar -zxvf curl-7.45.0.tar.gz
|
||||||
|
WORKDIR /root/curl-7.45.0
|
||||||
|
RUN ./configure --with-ssl --with-nghttp2=/usr/local
|
||||||
|
RUN make
|
||||||
|
RUN make install
|
||||||
|
RUN ldconfig
|
||||||
|
|
||||||
|
CMD ["-h"]
|
||||||
|
ENTRYPOINT ["/usr/local/bin/curl"]
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
curlimage:
|
||||||
|
docker build -t gohttp2/curl .
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
This is a work-in-progress HTTP/2 implementation for Go.
|
||||||
|
|
||||||
|
It will eventually live in the Go standard library and won't require
|
||||||
|
any changes to your code to use. It will just be automatic.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
* The server support is pretty good. A few things are missing
|
||||||
|
but are being worked on.
|
||||||
|
* The client work has just started but shares a lot of code
|
||||||
|
is coming along much quicker.
|
||||||
|
|
||||||
|
Docs are at https://godoc.org/golang.org/x/net/http2
|
||||||
|
|
||||||
|
Demo test server at https://http2.golang.org/
|
||||||
|
|
||||||
|
Help & bug reports welcome!
|
||||||
|
|
||||||
|
Contributing: https://golang.org/doc/contribute.html
|
||||||
|
Bugs: https://golang.org/issue/new?title=x/net/http2:+
|
|
@ -0,0 +1,256 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Transport code's client connection pooling.
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientConnPool manages a pool of HTTP/2 client connections.
|
||||||
|
type ClientConnPool interface {
|
||||||
|
GetClientConn(req *http.Request, addr string) (*ClientConn, error)
|
||||||
|
MarkDead(*ClientConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clientConnPoolIdleCloser is the interface implemented by ClientConnPool
|
||||||
|
// implementations which can close their idle connections.
|
||||||
|
type clientConnPoolIdleCloser interface {
|
||||||
|
ClientConnPool
|
||||||
|
closeIdleConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ clientConnPoolIdleCloser = (*clientConnPool)(nil)
|
||||||
|
_ clientConnPoolIdleCloser = noDialClientConnPool{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: use singleflight for dialing and addConnCalls?
|
||||||
|
type clientConnPool struct {
|
||||||
|
t *Transport
|
||||||
|
|
||||||
|
mu sync.Mutex // TODO: maybe switch to RWMutex
|
||||||
|
// TODO: add support for sharing conns based on cert names
|
||||||
|
// (e.g. share conn for googleapis.com and appspot.com)
|
||||||
|
conns map[string][]*ClientConn // key is host:port
|
||||||
|
dialing map[string]*dialCall // currently in-flight dials
|
||||||
|
keys map[*ClientConn][]string
|
||||||
|
addConnCalls map[string]*addConnCall // in-flight addConnIfNeede calls
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
|
||||||
|
return p.getClientConn(req, addr, dialOnMiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
dialOnMiss = true
|
||||||
|
noDialOnMiss = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) {
|
||||||
|
if isConnectionCloseRequest(req) && dialOnMiss {
|
||||||
|
// It gets its own connection.
|
||||||
|
const singleUse = true
|
||||||
|
cc, err := p.t.dialClientConn(addr, singleUse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cc, nil
|
||||||
|
}
|
||||||
|
p.mu.Lock()
|
||||||
|
for _, cc := range p.conns[addr] {
|
||||||
|
if cc.CanTakeNewRequest() {
|
||||||
|
p.mu.Unlock()
|
||||||
|
return cc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !dialOnMiss {
|
||||||
|
p.mu.Unlock()
|
||||||
|
return nil, ErrNoCachedConn
|
||||||
|
}
|
||||||
|
call := p.getStartDialLocked(addr)
|
||||||
|
p.mu.Unlock()
|
||||||
|
<-call.done
|
||||||
|
return call.res, call.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialCall is an in-flight Transport dial call to a host.
|
||||||
|
type dialCall struct {
|
||||||
|
p *clientConnPool
|
||||||
|
done chan struct{} // closed when done
|
||||||
|
res *ClientConn // valid after done is closed
|
||||||
|
err error // valid after done is closed
|
||||||
|
}
|
||||||
|
|
||||||
|
// requires p.mu is held.
|
||||||
|
func (p *clientConnPool) getStartDialLocked(addr string) *dialCall {
|
||||||
|
if call, ok := p.dialing[addr]; ok {
|
||||||
|
// A dial is already in-flight. Don't start another.
|
||||||
|
return call
|
||||||
|
}
|
||||||
|
call := &dialCall{p: p, done: make(chan struct{})}
|
||||||
|
if p.dialing == nil {
|
||||||
|
p.dialing = make(map[string]*dialCall)
|
||||||
|
}
|
||||||
|
p.dialing[addr] = call
|
||||||
|
go call.dial(addr)
|
||||||
|
return call
|
||||||
|
}
|
||||||
|
|
||||||
|
// run in its own goroutine.
|
||||||
|
func (c *dialCall) dial(addr string) {
|
||||||
|
const singleUse = false // shared conn
|
||||||
|
c.res, c.err = c.p.t.dialClientConn(addr, singleUse)
|
||||||
|
close(c.done)
|
||||||
|
|
||||||
|
c.p.mu.Lock()
|
||||||
|
delete(c.p.dialing, addr)
|
||||||
|
if c.err == nil {
|
||||||
|
c.p.addConnLocked(addr, c.res)
|
||||||
|
}
|
||||||
|
c.p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't
|
||||||
|
// already exist. It coalesces concurrent calls with the same key.
|
||||||
|
// This is used by the http1 Transport code when it creates a new connection. Because
|
||||||
|
// the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know
|
||||||
|
// the protocol), it can get into a situation where it has multiple TLS connections.
|
||||||
|
// This code decides which ones live or die.
|
||||||
|
// The return value used is whether c was used.
|
||||||
|
// c is never closed.
|
||||||
|
func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
|
||||||
|
p.mu.Lock()
|
||||||
|
for _, cc := range p.conns[key] {
|
||||||
|
if cc.CanTakeNewRequest() {
|
||||||
|
p.mu.Unlock()
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
call, dup := p.addConnCalls[key]
|
||||||
|
if !dup {
|
||||||
|
if p.addConnCalls == nil {
|
||||||
|
p.addConnCalls = make(map[string]*addConnCall)
|
||||||
|
}
|
||||||
|
call = &addConnCall{
|
||||||
|
p: p,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
p.addConnCalls[key] = call
|
||||||
|
go call.run(t, key, c)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
<-call.done
|
||||||
|
if call.err != nil {
|
||||||
|
return false, call.err
|
||||||
|
}
|
||||||
|
return !dup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type addConnCall struct {
|
||||||
|
p *clientConnPool
|
||||||
|
done chan struct{} // closed when done
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
|
||||||
|
cc, err := t.NewClientConn(tc)
|
||||||
|
|
||||||
|
p := c.p
|
||||||
|
p.mu.Lock()
|
||||||
|
if err != nil {
|
||||||
|
c.err = err
|
||||||
|
} else {
|
||||||
|
p.addConnLocked(key, cc)
|
||||||
|
}
|
||||||
|
delete(p.addConnCalls, key)
|
||||||
|
p.mu.Unlock()
|
||||||
|
close(c.done)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *clientConnPool) addConn(key string, cc *ClientConn) {
|
||||||
|
p.mu.Lock()
|
||||||
|
p.addConnLocked(key, cc)
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// p.mu must be held
|
||||||
|
func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
|
||||||
|
for _, v := range p.conns[key] {
|
||||||
|
if v == cc {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.conns == nil {
|
||||||
|
p.conns = make(map[string][]*ClientConn)
|
||||||
|
}
|
||||||
|
if p.keys == nil {
|
||||||
|
p.keys = make(map[*ClientConn][]string)
|
||||||
|
}
|
||||||
|
p.conns[key] = append(p.conns[key], cc)
|
||||||
|
p.keys[cc] = append(p.keys[cc], key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *clientConnPool) MarkDead(cc *ClientConn) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
for _, key := range p.keys[cc] {
|
||||||
|
vv, ok := p.conns[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newList := filterOutClientConn(vv, cc)
|
||||||
|
if len(newList) > 0 {
|
||||||
|
p.conns[key] = newList
|
||||||
|
} else {
|
||||||
|
delete(p.conns, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(p.keys, cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *clientConnPool) closeIdleConnections() {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
// TODO: don't close a cc if it was just added to the pool
|
||||||
|
// milliseconds ago and has never been used. There's currently
|
||||||
|
// a small race window with the HTTP/1 Transport's integration
|
||||||
|
// where it can add an idle conn just before using it, and
|
||||||
|
// somebody else can concurrently call CloseIdleConns and
|
||||||
|
// break some caller's RoundTrip.
|
||||||
|
for _, vv := range p.conns {
|
||||||
|
for _, cc := range vv {
|
||||||
|
cc.closeIfIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn {
|
||||||
|
out := in[:0]
|
||||||
|
for _, v := range in {
|
||||||
|
if v != exclude {
|
||||||
|
out = append(out, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we filtered it out, zero out the last item to prevent
|
||||||
|
// the GC from seeing it.
|
||||||
|
if len(in) != len(out) {
|
||||||
|
in[len(in)-1] = nil
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// noDialClientConnPool is an implementation of http2.ClientConnPool
|
||||||
|
// which never dials. We let the HTTP/1.1 client dial and use its TLS
|
||||||
|
// connection instead.
|
||||||
|
type noDialClientConnPool struct{ *clientConnPool }
|
||||||
|
|
||||||
|
func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
|
||||||
|
return p.getClientConn(req, addr, noDialOnMiss)
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.6
|
||||||
|
|
||||||
|
package http2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func configureTransport(t1 *http.Transport) (*Transport, error) {
|
||||||
|
connPool := new(clientConnPool)
|
||||||
|
t2 := &Transport{
|
||||||
|
ConnPool: noDialClientConnPool{connPool},
|
||||||
|
t1: t1,
|
||||||
|
}
|
||||||
|
connPool.t = t2
|
||||||
|
if err := registerHTTPSProtocol(t1, noDialH2RoundTripper{t2}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t1.TLSClientConfig == nil {
|
||||||
|
t1.TLSClientConfig = new(tls.Config)
|
||||||
|
}
|
||||||
|
if !strSliceContains(t1.TLSClientConfig.NextProtos, "h2") {
|
||||||
|
t1.TLSClientConfig.NextProtos = append([]string{"h2"}, t1.TLSClientConfig.NextProtos...)
|
||||||
|
}
|
||||||
|
if !strSliceContains(t1.TLSClientConfig.NextProtos, "http/1.1") {
|
||||||
|
t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1")
|
||||||
|
}
|
||||||
|
upgradeFn := func(authority string, c *tls.Conn) http.RoundTripper {
|
||||||
|
addr := authorityAddr("https", authority)
|
||||||
|
if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil {
|
||||||
|
go c.Close()
|
||||||
|
return erringRoundTripper{err}
|
||||||
|
} else if !used {
|
||||||
|
// Turns out we don't need this c.
|
||||||
|
// For example, two goroutines made requests to the same host
|
||||||
|
// at the same time, both kicking off TCP dials. (since protocol
|
||||||
|
// was unknown)
|
||||||
|
go c.Close()
|
||||||
|
}
|
||||||
|
return t2
|
||||||
|
}
|
||||||
|
if m := t1.TLSNextProto; len(m) == 0 {
|
||||||
|
t1.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{
|
||||||
|
"h2": upgradeFn,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m["h2"] = upgradeFn
|
||||||
|
}
|
||||||
|
return t2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerHTTPSProtocol calls Transport.RegisterProtocol but
|
||||||
|
// convering panics into errors.
|
||||||
|
func registerHTTPSProtocol(t *http.Transport, rt http.RoundTripper) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
err = fmt.Errorf("%v", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
t.RegisterProtocol("https", rt)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// noDialH2RoundTripper is a RoundTripper which only tries to complete the request
|
||||||
|
// if there's already has a cached connection to the host.
|
||||||
|
type noDialH2RoundTripper struct{ t *Transport }
|
||||||
|
|
||||||
|
func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
res, err := rt.t.RoundTrip(req)
|
||||||
|
if err == ErrNoCachedConn {
|
||||||
|
return nil, http.ErrSkipAltProtocol
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue