infra
/
goutils
Archived
1
0
Fork 0

유틸클레스 잔뜩 추가

This commit is contained in:
Sangbum Kim 2018-06-06 15:30:23 +09:00
parent e0bebbbb06
commit 251ba74dbe
33 changed files with 2153 additions and 0 deletions

88
buf/bytebuf_pool.go Normal file
View File

@ -0,0 +1,88 @@
package buf
import (
"sync"
"unicode/utf8"
)
const (
defaultByteBufferSize = 4096
)
// ByteBuffer provides byte buffer, which can be used with fasthttp API
// in order to minimize memory allocations.
//
// ByteBuffer may be used with functions appending data to the given []byte
// slice. See example code for details.
//
// Use AcquireByteBuffer for obtaining an empty byte buffer.
type ByteBuffer struct {
// B is a byte buffer to use in append-like workloads.
// See example code for details.
B []byte
}
func (b *ByteBuffer) WriteByte(c byte) error {
b.B = append(b.B, c)
return nil
}
// Write implements io.Writer - it appends p to ByteBuffer.B
func (b *ByteBuffer) Write(p []byte) (int, error) {
b.B = append(b.B, p...)
return len(p), nil
}
// WriteString appends s to ByteBuffer.B
func (b *ByteBuffer) WriteString(s string) (int, error) {
b.B = append(b.B, s...)
return len(s), nil
}
//(r rune) (n int, err error) {
// WriteString appends s to ByteBuffer.B
func (b *ByteBuffer) WriteRune(r rune) (n int, err error) {
if r < utf8.RuneSelf {
b.B = append(b.B, byte(r))
return 1, nil
}
curSize := len(b.B)
runCharBuf := b.B[curSize:curSize+utf8.UTFMax]
n = utf8.EncodeRune(runCharBuf, r)
b.B = b.B[:curSize+n]
return n, nil
}
// Set sets ByteBuffer.B to p
func (b *ByteBuffer) Set(p []byte) {
b.B = append(b.B[:0], p...)
}
// SetString sets ByteBuffer.B to s
func (b *ByteBuffer) SetString(s string) {
b.B = append(b.B[:0], s...)
}
// Reset makes ByteBuffer.B empty.
func (b *ByteBuffer) Reset() {
b.B = b.B[:0]
}
type ByteBufferPool struct {
pool sync.Pool
}
func (p *ByteBufferPool) Acquire() *ByteBuffer {
v := p.pool.Get()
if v == nil {
return &ByteBuffer{
B: make([]byte, 0, defaultByteBufferSize),
}
}
return v.(*ByteBuffer)
}
func (p *ByteBufferPool) Release(b *ByteBuffer) {
b.Reset()
p.pool.Put(b)
}

41
buf/bytes_queue.go Normal file
View File

@ -0,0 +1,41 @@
package buf
import (
"container/list"
)
func NewBytesQueue() (chan<- []byte, <-chan []byte) {
send := make(chan []byte, 1)
receive := make(chan []byte, 1)
go manageBytesQueue(send, receive)
return send, receive
}
func manageBytesQueue(send <-chan []byte, receive chan<- []byte) {
queue := list.New()
for {
if front := queue.Front(); front == nil {
if send == nil {
close(receive)
return
}
value, ok := <-send
if !ok {
close(receive)
return
}
queue.PushBack(value)
} else {
select {
case receive <- front.Value.([]byte):
queue.Remove(front)
case value, ok := <-send:
if ok {
queue.PushBack(value)
} else {
send = nil
}
}
}
}
}

42
buf/string_queue.go Normal file
View File

@ -0,0 +1,42 @@
package buf
import (
"container/list"
)
// 블럭되지 않는 큐체널
func NewStringQueue() (chan<- string, <-chan string) {
send := make(chan string, 1)
receive := make(chan string, 1)
go manageStringQueue(send, receive)
return send, receive
}
func manageStringQueue(send <-chan string, receive chan<- string) {
queue := list.New()
for {
if front := queue.Front(); front == nil {
if send == nil {
close(receive)
return
}
value, ok := <-send
if !ok {
close(receive)
return
}
queue.PushBack(value)
} else {
select {
case receive <- front.Value.(string):
queue.Remove(front)
case value, ok := <-send:
if ok {
queue.PushBack(value)
} else {
send = nil
}
}
}
}
}

48
handler/handler.go Normal file
View File

@ -0,0 +1,48 @@
package util
import (
"sync"
"context"
"log"
)
// 자식들을 기다리는 context waiter
type Handler struct {
errorChan chan error
ctx context.Context
canceler context.CancelFunc
waiter *sync.WaitGroup
}
func NewHandler(ctx context.Context) *Handler {
ctx, canceler := context.WithCancel(ctx)
return &Handler{
ctx: ctx,
canceler: canceler,
waiter: &sync.WaitGroup{},
errorChan: make(chan error, 5),
}
}
func (h *Handler) NotifyError(err error) { h.errorChan <- err }
func (h *Handler) Error() <-chan error { return h.errorChan }
func (h *Handler) Done() <-chan struct{} { return h.ctx.Done() }
func (h *Handler) GracefulWait() {
if h.ctx.Err() == nil {
h.canceler()
}
h.waiter.Wait()
close(h.errorChan)
for remainError := range h.errorChan {
log.Println("remain errors ", remainError)
}
}
func (h *Handler) IncreaseWait() {
h.waiter.Add(1)
}
func (h *Handler) DecreaseWait() {
h.waiter.Done()
}

103
http/client/client.go Normal file
View File

@ -0,0 +1,103 @@
package client
import (
"net/http"
"net"
"time"
"errors"
"net/url"
"io"
)
var (
httpCannotRedirectError = errors.New("this client cannot redirect")
disableRedirect = func(_ *http.Request, _ []*http.Request) error {
return httpCannotRedirectError
}
limitedRedirect = func(_ *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
}
)
type GracefulClient interface {
http.RoundTripper
client() *http.Client
roundTripper() http.RoundTripper
Do(req *http.Request) (*http.Response, error)
Get(url string) (resp *http.Response, err error)
Head(url string) (resp *http.Response, err error)
Post(url string, contentType string, body io.Reader) (resp *http.Response, err error)
PostForm(url string, data url.Values) (resp *http.Response, err error)
}
type wrappedClient struct {
http.Client
}
func (cli *wrappedClient) client() (*http.Client) {
return &cli.Client
}
func (cli *wrappedClient) roundTripper() http.RoundTripper {
return cli.Transport
}
func (cli *wrappedClient) RoundTrip(req *http.Request) (*http.Response, error) {
return cli.Client.Transport.RoundTrip(req)
}
func NewClient(
keepaliveDuration time.Duration,
connectTimeout time.Duration,
responseHeaderTimeout time.Duration,
idleConnectionTimeout time.Duration,
maxIdleConnections int,
redirectSupport bool,
serverName string,
) GracefulClient {
srvName := []string{serverName}
var redirectChecker func(*http.Request, []*http.Request) error
if redirectSupport {
redirectChecker = limitedRedirect
} else {
redirectChecker = disableRedirect
}
keepaliveDisabled := keepaliveDuration == 0
dialer := &net.Dialer{
Timeout: connectTimeout,
KeepAlive: keepaliveDuration,
DualStack: false,
}
transport := &predefinedHeaderTransport{
useragentName: srvName,
Transport: http.Transport{
Proxy: nil,
DialTLS: nil,
TLSClientConfig: nil,
DisableKeepAlives: keepaliveDisabled,
DisableCompression: true,
MaxIdleConnsPerHost: maxIdleConnections,
DialContext: dialer.DialContext,
MaxIdleConns: maxIdleConnections,
IdleConnTimeout: idleConnectionTimeout,
ResponseHeaderTimeout: responseHeaderTimeout,
TLSNextProto: nil,
ExpectContinueTimeout: 0,
},
}
return &wrappedClient{
Client: http.Client{
Transport: transport,
CheckRedirect: redirectChecker,
Jar: nil,
},
}
}

View File

@ -0,0 +1,35 @@
package client
import (
"net/http"
)
const (
connectionHeaderKey = "Connection"
connectionUserAgentHeaderKey = "User-Agent"
connectionCloseHeader = "close"
connectionKeepAliveHeader = "keep-alive"
)
var (
connectionCloseHeaderValue = []string{connectionCloseHeader}
connectionKeepAliveHeaderValue = []string{connectionKeepAliveHeader}
)
type predefinedHeaderTransport struct {
useragentName []string
http.Transport
}
func (pht *predefinedHeaderTransport) RoundTrip(req *http.Request) (res *http.Response, err error) {
var connectionValue []string
if pht.DisableKeepAlives {
connectionValue = connectionCloseHeaderValue
} else {
connectionValue = connectionKeepAliveHeaderValue
}
req.Header[connectionHeaderKey] = connectionValue
req.Header[connectionUserAgentHeaderKey] = pht.useragentName
res, err = pht.Transport.RoundTrip(req)
return
}

26
http/cookie.go Normal file
View File

@ -0,0 +1,26 @@
package http
import (
"time"
"net/http"
)
func SetCookieValue(w http.ResponseWriter, host, key, newCookieValue string) {
http.SetCookie(w,
&http.Cookie{
Name: key,
Value: newCookieValue,
Path: "/",
Domain: host,
Expires: time.Now().UTC().AddDate(3, 0, 0),
Secure: true,
HttpOnly: true,
},
)
}
func GetCookieValue(req *http.Request, name string) (cookieValue string) {
if cookie, _ := req.Cookie(name); cookie != nil {
cookieValue = cookie.Value
}
return
}

View File

@ -0,0 +1,136 @@
package middleware
import (
"net"
"net/http"
"time"
"sync"
myhttp "amuz.es/src/infra/goutils/http"
"amuz.es/src/infra/goutils/buf"
"amuz.es/src/infra/goutils/misc"
"io"
)
const (
dateFormat = "02/Jan/2006:15:04:05 -0700"
forwardedForIPHeader = "X-Forwarded-For"
)
type bufferedWriter interface {
WriteTo(w io.Writer) (n int64, err error)
Write(p []byte) (n int, err error)
}
type accessLoggerMiddleware struct {
serverNameValue []string
writer io.Writer
waiter *sync.WaitGroup
dumpQ bufferedWriter
writerFlushChan chan bool
}
func (m *accessLoggerMiddleware) Handle(next http.Handler) http.Handler {
accessLoggerBufPool := buf.ByteBufferPool{}
fn := func(w http.ResponseWriter, r *http.Request) {
m.waiter.Add(1)
respWrapper := myhttp.NewWrapResponseWriter(w, r.ProtoMajor)
defer func(now time.Time) {
dur := time.Since(now)
buf := accessLoggerBufPool.Acquire()
defer accessLoggerBufPool.Release(buf)
buf.WriteString(m.remoteHost(r))
buf.WriteString(` - - [`)
buf.WriteString(now.Format(dateFormat))
buf.WriteString(`] "`)
buf.WriteString(r.Method)
buf.WriteByte(' ')
buf.WriteString(r.RequestURI)
buf.WriteByte(' ')
buf.WriteString(r.Proto)
buf.WriteString(`" `)
buf.WriteString(misc.FormatInt(respWrapper.Status()))
buf.WriteByte(' ')
buf.WriteString(misc.FormatInt(respWrapper.BytesWritten()))
buf.WriteString(` "`)
buf.WriteString(r.Referer())
buf.WriteString(`" "`)
buf.WriteString(r.UserAgent())
buf.WriteString(`" `)
buf.WriteString(misc.FormatInt64(dur.Nanoseconds() / time.Millisecond.Nanoseconds()))
buf.WriteByte(' ')
buf.WriteString(r.Host)
buf.WriteByte('\n')
m.dumpQ.Write(buf.B)
m.waiter.Done()
}(time.Now().Local())
respWrapper.Header()["Server"] = m.serverNameValue
next.ServeHTTP(respWrapper, r)
}
return http.HandlerFunc(fn)
}
func (m *accessLoggerMiddleware) Close() {
m.waiter.Wait()
m.writerFlushChan <- true
<-m.writerFlushChan
}
func (m *accessLoggerMiddleware) lineByLineWriter() {
ticker := time.NewTicker(250 * time.Millisecond)
defer func() {
ticker.Stop()
m.dumpQ.WriteTo(m.writer)
close(m.writerFlushChan)
}()
for {
<-ticker.C
m.dumpQ.WriteTo(m.writer)
select {
case <-m.writerFlushChan:
return
default:
}
}
}
// strip port from addresses with hostname, ipv4 or ipv6
func (m *accessLoggerMiddleware) stripPort(address string) string {
if h, _, err := net.SplitHostPort(address); err == nil {
return h
}
return address
}
// The remote address of the client. When the 'X-Forwarded-For'
// header is set, then it is used instead.
func (m *accessLoggerMiddleware) remoteAddr(r *http.Request) string {
ff := r.Header.Get(forwardedForIPHeader)
if ff != "" {
return ff
}
return r.RemoteAddr
}
func (m *accessLoggerMiddleware) remoteHost(r *http.Request) string {
a := m.remoteAddr(r)
h := m.stripPort(a)
if h != "" {
return h
}
return "-"
}
func AccessLog(serverName string, bufferingWriter bufferedWriter, accessLogWriter io.Writer) (func(next http.Handler) http.Handler, func()) {
impl := &accessLoggerMiddleware{}
impl.serverNameValue = append(impl.serverNameValue, serverName)
impl.writer = accessLogWriter
impl.waiter = &sync.WaitGroup{}
impl.writerFlushChan = make(chan bool, 1)
impl.dumpQ = bufferingWriter
go impl.lineByLineWriter()
return impl.Handle, impl.Close
}

55
http/resp_message.go Normal file
View File

@ -0,0 +1,55 @@
package http
const (
// 200
JsonOK ResponseMessage = "{\"message\":\"OK\"}"
JsonPartialContent = "{\"message\":\"Partial Content\"}"
// 300
JsonMovedPermanently ResponseMessage = "{\"message\":\"Moved Permanently\"}"
JsonFound = "{\"message\":\"Found\"}"
JsonSeeOther = "{\"message\":\"See Other\"}"
JsonTemporaryRedirect = "{\"message\":\"Temporary Redirect\"}"
JsonPermanentRedirect = "{\"message\":\"Permanent Redirect\"}"
JsonUnauthorized = "{\"message\":\"Unauthorized\"}"
JsonPaymentRequired = "{\"message\":\"Payment Required\"}"
// 400
JsonBadRequest ResponseMessage = "{\"message\":\"Bad Request\"}"
JsonForbidden = "{\"message\":\"Forbidden\"}"
JsonNotFound = "{\"message\":\"Not Found\"}"
JsonMethodNotAllowed = "{\"message\":\"Method Not Allowed\"}"
JsonNotAcceptable = "{\"message\":\"Not Acceptable\"}"
JsonRequestTimeout = "{\"message\":\"Request Timeout\"}"
JsonConflict = "{\"message\":\"Conflict\"}"
JsonGone = "{\"message\":\"Gone\"}"
JsonPreconditionFailed = "{\"message\":\"Precondition Failed\"}"
JsonRequestEntityTooLarge = "{\"message\":\"Request Entity Too Large\"}"
JsonRequestURITooLong = "{\"message\":\"Request URI Too Long\"}"
JsonUnsupportedMediaType = "{\"message\":\"Unsupported Media Type\"}"
JsonUpgradeRequired = "{\"message\":\"Upgrade Required\"}"
JsonPreconditionRequired = "{\"message\":\"Precondition Required\"}"
JsonTooManyRequests = "{\"message\":\"Too Many Requests\"}"
JsonRequestHeaderFieldsTooLarge = "{\"message\":\"Request Header Fields Too Large\"}"
JsonUnavailableForLegalReasons = "{\"message\":\"Unavailable For Legal Reasons\"}"
// 500
JsonInternalServerError ResponseMessage = "{\"message\":\"Internal Server Error\"}"
JsonNotImplemented = "{\"message\":\"Not Implemented\"}"
JsonBadGateway = "{\"message\":\"Bad Gateway\"}"
JsonServiceUnavailable = "{\"message\":\"Service Unavailable\"}"
JsonGatewayTimeout = "{\"message\":\"Gateway Timeout\"}"
JsonHTTPVersionNotSupported = "{\"message\":\"HTTP Version Not Supported\"}"
JsonInsufficientStorage = "{\"message\":\"Insufficient Storage\"}"
JsonLoopDetected = "{\"message\":\"Loop Detected\"}"
)
var (
JsonContentType = []string{"application/json; charset=utf-8"}
)
type ResponseMessage string
func (rm ResponseMessage) MarshalJSON() ([]byte, error) {
return []byte(rm), nil
}

182
http/response.go Normal file
View File

@ -0,0 +1,182 @@
package http
// The original work was derived from Goji's middleware, source:
// https://github.com/zenazn/goji/tree/master/web/middleware
import (
"bufio"
"io"
"net"
"net/http"
)
// WrapResponseWriter is a proxy around an http.ResponseWriter that allows you to hook
// into various parts of the response process.
type WrapResponseWriter interface {
http.ResponseWriter
// Status returns the HTTP status of the request, or 0 if one has not
// yet been sent.
Status() int
// BytesWritten returns the total number of bytes sent to the client.
BytesWritten() int
// Tee causes the response body to be written to the given io.Writer in
// addition to proxying the writes through. Only one io.Writer can be
// tee'd to at once: setting a second one will overwrite the first.
// Writes will be sent to the proxy before being written to this
// io.Writer. It is illegal for the tee'd writer to be modified
// concurrently with writes.
Tee(io.Writer)
// Unwrap returns the original proxied target.
Unwrap() http.ResponseWriter
}
// basicWriter wraps a http.ResponseWriter that implements the minimal
// http.ResponseWriter interface.
type basicWriter struct {
http.ResponseWriter
wroteHeader bool
code int
bytes int
tee io.Writer
}
func (b *basicWriter) WriteHeader(code int) {
if !b.wroteHeader {
b.code = code
b.wroteHeader = true
b.ResponseWriter.WriteHeader(code)
}
}
func (b *basicWriter) Write(buf []byte) (int, error) {
b.WriteHeader(http.StatusOK)
n, err := b.ResponseWriter.Write(buf)
if b.tee != nil {
_, err2 := b.tee.Write(buf[:n])
// Prefer errors generated by the proxied writer.
if err == nil {
err = err2
}
}
b.bytes += n
return n, err
}
func (b *basicWriter) maybeWriteHeader() {
if !b.wroteHeader {
b.WriteHeader(http.StatusOK)
}
}
func (b *basicWriter) Status() int {
return b.code
}
func (b *basicWriter) BytesWritten() int {
return b.bytes
}
func (b *basicWriter) Tee(w io.Writer) {
b.tee = w
}
func (b *basicWriter) Unwrap() http.ResponseWriter {
return b.ResponseWriter
}
type flushWriter struct {
basicWriter
}
func (f *flushWriter) Flush() {
fl := f.basicWriter.ResponseWriter.(http.Flusher)
fl.Flush()
}
var _ http.Flusher = &flushWriter{}
// httpFancyWriter is a HTTP writer that additionally satisfies http.CloseNotifier,
// http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case
// of wrapping the http.ResponseWriter that package http gives you, in order to
// make the proxied object support the full method set of the proxied object.
type httpFancyWriter struct {
basicWriter
}
func (f *httpFancyWriter) CloseNotify() <-chan bool {
cn := f.basicWriter.ResponseWriter.(http.CloseNotifier)
return cn.CloseNotify()
}
func (f *httpFancyWriter) Flush() {
fl := f.basicWriter.ResponseWriter.(http.Flusher)
fl.Flush()
}
func (f *httpFancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hj := f.basicWriter.ResponseWriter.(http.Hijacker)
return hj.Hijack()
}
func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) {
if f.basicWriter.tee != nil {
n, err := io.Copy(&f.basicWriter, r)
f.basicWriter.bytes += int(n)
return n, err
}
rf := f.basicWriter.ResponseWriter.(io.ReaderFrom)
f.basicWriter.maybeWriteHeader()
n, err := rf.ReadFrom(r)
f.basicWriter.bytes += int(n)
return n, err
}
var _ http.CloseNotifier = &httpFancyWriter{}
var _ http.Flusher = &httpFancyWriter{}
var _ http.Hijacker = &httpFancyWriter{}
var _ io.ReaderFrom = &httpFancyWriter{}
// http2FancyWriter is a HTTP2 writer that additionally satisfies http.CloseNotifier,
// http.Flusher, and io.ReaderFrom. It exists for the common case
// of wrapping the http.ResponseWriter that package http gives you, in order to
// make the proxied object support the full method set of the proxied object.
type http2FancyWriter struct {
basicWriter
}
func (f *http2FancyWriter) CloseNotify() <-chan bool {
cn := f.basicWriter.ResponseWriter.(http.CloseNotifier)
return cn.CloseNotify()
}
func (f *http2FancyWriter) Flush() {
fl := f.basicWriter.ResponseWriter.(http.Flusher)
fl.Flush()
}
func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error {
return f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts)
}
var _ http.CloseNotifier = &http2FancyWriter{}
var _ http.Flusher = &http2FancyWriter{}
var _ http.Pusher = &http2FancyWriter{}
// NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to
// hook into various parts of the response process.
func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter {
_, cn := w.(http.CloseNotifier)
_, fl := w.(http.Flusher)
bw := basicWriter{ResponseWriter: w}
if protoMajor == 2 {
_, ps := w.(http.Pusher)
if cn && fl && ps {
return &http2FancyWriter{bw}
}
} else {
_, hj := w.(http.Hijacker)
_, rf := w.(io.ReaderFrom)
if cn && fl && hj && rf {
return &httpFancyWriter{bw}
}
}
if fl {
return &flushWriter{bw}
}
return &bw
}

33
http/tcp_listener.go Normal file
View File

@ -0,0 +1,33 @@
package http
import (
"time"
"net"
)
func NewListener(addr *net.TCPAddr) (net.Listener, error) {
listener, err := net.ListenTCP("tcp", addr)
if err != nil {
return nil, err
}
return &keepAliveListener{Listener: listener}, nil
}
type keepAliveListener struct {
// the number of open connections
net.Listener
}
func (ln *keepAliveListener) Accept() (c net.Conn, err error) {
c, err = ln.Listener.Accept()
if err != nil {
return
}
if tc, ok := c.(*net.TCPConn); ok {
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
}
return
}

17
io/reader.go Normal file
View File

@ -0,0 +1,17 @@
package io
import "io"
type nopReader bool
func (nopReader) Read(p []byte) (int, error) {
return 0, io.EOF
}
func (nopReader) Close() (error) {
return nil
}
const (
NopReader nopReader = false
)

25
io/reader_writer.go Normal file
View File

@ -0,0 +1,25 @@
package io
import (
"io"
"context"
)
type wrappedIO struct {
stdin io.Reader
stdout io.Writer
closer context.CancelFunc
}
func (wio *wrappedIO) Read(p []byte) (n int, err error) {
return wio.stdin.Read(p)
}
func (wio *wrappedIO) Write(p []byte) (n int, err error) {
return wio.stdout.Write(p)
}
func (wio *wrappedIO) Close() (err error) {
wio.closer()
return io.EOF
}

80
io/remover.go Normal file
View File

@ -0,0 +1,80 @@
package io
import (
"golang.org/x/text/transform"
)
const (
linefeed = '\n'
LineFeedRemover linefeedRemover = iota
)
type linefeedRemover int
func (linefeedRemover) Reset() {}
func (linefeedRemover) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
cur, start := 0, 0
for ; cur < len(src); {
if src[cur] == linefeed {
if nDst+1 > len(dst) {
err = transform.ErrShortDst
return
}
nSrc += cur - start
nDst += copy(dst[nDst:], src[start:cur])
cur++
start = cur
} else {
cur++
}
}
if remain := cur - start; remain == 0 {
} else if nDst+1 > len(dst) {
err = transform.ErrShortDst
} else {
nSrc += remain
nDst += copy(dst[nDst:], src[start:cur])
}
return
}
type Remover struct {
Removes []byte
}
func (r *Remover) Reset() {}
func (r *Remover) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
cur, start := 0, 0
for ; cur < len(src); {
var (
curData = src[cur]
matched = false
)
for i := 0; i < len(r.Removes); i++ {
if matched = r.Removes[i] == curData; matched {
break
}
}
if matched {
if nDst+1 > len(dst) {
err = transform.ErrShortDst
return
}
nSrc += cur - start
nDst += copy(dst[nDst:], src[start:cur])
cur++
start = cur
} else {
cur++
}
}
if remain := cur - start; remain == 0 {
} else if nDst+1 > len(dst) {
err = transform.ErrShortDst
} else {
nSrc += remain
nDst += copy(dst[nDst:], src[start:cur])
}
return
}

17
io/writer.go Normal file
View File

@ -0,0 +1,17 @@
package io
import (
"io"
)
type nopWriter struct {
io.Writer
}
func (nopWriter) Close() error { return nil }
// NopCloser returns a ReadCloser with a no-op Close method wrapping
// the provided Reader r.
func NopCloser(w io.Writer) io.WriteCloser {
return nopWriter{w}
}

14
logger/common.go Normal file
View File

@ -0,0 +1,14 @@
package logger
import "io"
type WriteSyncer interface {
io.Writer
Sync() error
}
type RotateSyncer interface {
WriteSyncer
SetOnClose(func())
Rotate() error
}

52
logger/rotater/global.go Normal file
View File

@ -0,0 +1,52 @@
package rotater
import (
"os"
"path"
"log"
"amuz.es/src/infra/goutils/logger"
)
var loggers logger.RotateSyncerSet
func NewLogWriter(FileName string, MaxSizeMb, MaxBackup, MaxDay int, logDir string) logger.RotateSyncer {
switch FileName {
case "Stdout":
return newLocked(os.Stdout)
case "Stderr":
return newLocked(os.Stderr)
default:
logpath := FileName
if logDir != "" {
logpath = path.Join(logDir, FileName)
}
log.Println(" Attention!! log writes to ", logpath)
logWriter := newRotater(
logpath,
MaxSizeMb, // megabytes
MaxBackup,
MaxDay, //days
)
loggers.Store(logWriter)
logWriter.SetOnClose(func() { loggers.Delete(logWriter) })
return logWriter
}
}
func Rotate() {
loggers.Range(func(rotater logger.RotateSyncer) {
rotater.Sync()
rotater.Rotate()
})
log.Println("rotated")
}
func Close() {
loggers.Range(func(rotater logger.RotateSyncer) {
rotater.Sync()
rotater.Close()
})
log.Println("end of log")
}

View File

@ -0,0 +1,47 @@
package rotater
import (
"sync"
"sync/atomic"
"amuz.es/src/infra/goutils/logger"
)
/**
logger set
*/
type RotateSyncerSet struct {
storage sync.Map
}
func (s *RotateSyncerSet) Delete(key logger.RotateSyncer) {
s.storage.Delete(key)
}
func (s *RotateSyncerSet) Exist(key logger.RotateSyncer) (ok bool) {
_, ok = s.storage.Load(key)
return
}
func (s *RotateSyncerSet) SetNx(key logger.RotateSyncer) (bool) {
_, exist := s.storage.LoadOrStore(key, 0)
return !exist
}
func (s *RotateSyncerSet) Range(f func(key logger.RotateSyncer)) {
s.storage.Range(s.rangeWrap(f))
}
func (s *RotateSyncerSet) Store(key logger.RotateSyncer) {
s.storage.Store(key, 0)
}
func (s *RotateSyncerSet) rangeWrap(f func(key logger.RotateSyncer)) func(key, value interface{}) bool {
ok := true
return func(key, value interface{}) bool {
f(key.(logger.RotateSyncer))
return ok
}
}
func (s *RotateSyncerSet) Len() int {
var count uint64
s.Range(func(conn logger.RotateSyncer) {
atomic.AddUint64(&count, 1)
})
return int(count)
}

39
logger/rotater/wrapped.go Normal file
View File

@ -0,0 +1,39 @@
package rotater
import (
"sync"
"amuz.es/src/infra/goutils/logger"
)
type LockedWriteSyncer struct {
sync.Mutex
ws logger.WriteSyncer
}
// Lock wraps a WriteSyncer in a mutex to make it safe for concurrent use. In
// particular, *os.Files must be locked before use.
func newLocked(ws logger.WriteSyncer) logger.RotateSyncer {
if lws, ok := ws.(*LockedWriteSyncer); ok {
// no need to layer on another lock
return lws
}
return &LockedWriteSyncer{ws: ws}
}
func (s *LockedWriteSyncer) Write(bs []byte) (int, error) {
s.Lock()
n, err := s.ws.Write(bs)
s.Unlock()
return n, err
}
func (s *LockedWriteSyncer) Sync() error {
s.Lock()
err := s.ws.Sync()
s.Unlock()
return err
}
func (r *LockedWriteSyncer) SetOnClose(closeFunc logger.CloseFunc) {}
func (r *LockedWriteSyncer) Rotate() (err error) { return }
func (r *LockedWriteSyncer) Close() (err error) { return }

View File

@ -0,0 +1,51 @@
package rotater
import (
"sync"
"amuz.es/src/infra/goutils/logger"
)
type rotateSyncer struct {
setOnceOnclose *sync.Once
onClose func()
lumberjack.Logger
}
func newRotater(filename string, maxSize, maxBackup, maxDay int) logger.RotateSyncer {
return &rotateSyncer{
setOnceOnclose: &sync.Once{},
Logger: lumberjack.Logger{
Filename: filename,
MaxSize: maxSize, // megabytes
MaxBackups: maxBackup,
MaxAge: maxDay, //days
LocalTime: false,
Compress: false,
},
}
}
func (r *rotateSyncer) SetOnClose(closeFunc func()) {
r.setOnceOnclose.Do(func() {
r.onClose = closeFunc
})
}
func (r *rotateSyncer) Rotate() error {
return r.Logger.Rotate()
}
func (r *rotateSyncer) Close() error {
defer func() {
if r.onClose != nil {
r.onClose()
}
}()
return r.Logger.Close()
}
func (r *rotateSyncer) Sync() error {
return nil
}
func (s *rotateSyncer) Write(bs []byte) (int, error) {
return s.Logger.Write(bs)
}

17
logger/zap/log_format.go Normal file
View File

@ -0,0 +1,17 @@
package zap
import "go.uber.org/zap/zapcore"
var LogCommonFormat = zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}

105
logger/zap/logger.go Normal file
View File

@ -0,0 +1,105 @@
package zap
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"amuz.es/src/infra/goutils/logger"
)
var (
defaultWriter logger.RotateSyncer
defaultErrorOutputOptions []zap.Option
nopCloser = func() (err error) { return }
)
func init() {
zap.RedirectStdLog(zap.L())
}
func replaceGlobalLogger(newOne *zap.Logger) {
zap.ReplaceGlobals(newOne)
zap.RedirectStdLog(newOne)
}
func Init(
verbose bool,
formatter zapcore.Encoder,
mainLogName, logFilename, logDir string,
maxSizeMb, maxBackup, maxDay int,
logLevel zapcore.Level,
additionalOptions ...zap.Option,
) *zap.SugaredLogger {
level := zap.NewAtomicLevelAt(logLevel)
defaultWriter = rotater.NewLogWriter(logFilename, maxSizeMb, maxBackup, maxDay, logDir)
defaultErrorOutputOptions = []zap.Option{zap.ErrorOutput(defaultWriter)}
options := defaultErrorOutputOptions
if verbose {
options = append(options, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.PanicLevel)))
}
// reset log option slice
options = append(options, additionalOptions...)
log := initLogger(defaultWriter, mainLogName, formatter, level, options...)
replaceGlobalLogger(log)
return log.Sugar()
}
func New(parent *zap.SugaredLogger, moduleName string, options ...zap.Option) *zap.SugaredLogger {
var subLogger *zap.Logger
if parent == nil {
subLogger = zap.L().Named(moduleName)
} else {
subLogger = parent.Desugar().Named(moduleName)
}
subLogger.WithOptions(options...)
return subLogger.Sugar()
}
func NewOtherLogger(
formatter zapcore.Encoder,
moduleName, logFilename, logDir string,
maxSizeMb, maxBackup, maxDay int,
logLevel zapcore.Level,
fields ...zapcore.Field,
) (logger *zap.SugaredLogger, closer func() error) {
loglevel := zap.NewAtomicLevelAt(logLevel)
logWriter := rotater.NewLogWriter(logFilename, maxSizeMb, maxBackup, maxDay, logDir)
core := zapcore.NewCore(formatter, logWriter, loglevel)
closer = logWriter.Close
logger = zap.New(core, defaultErrorOutputOptions...).
Named(moduleName).With(fields...).Sugar()
return
}
func NewOtherLoggerWithOption(
formatter zapcore.Encoder,
moduleName, logFilename, logDir string,
maxSizeMb, maxBackup, maxDay int,
logLevel zapcore.Level,
options []zap.Option,
fields ...zapcore.Field,
) (logger *zap.SugaredLogger, closer func() error) {
loglevel := zap.NewAtomicLevelAt(logLevel)
logWriter := rotater.NewLogWriter(logFilename, maxSizeMb, maxBackup, maxDay, logDir)
core := zapcore.NewCore(formatter, logWriter, loglevel)
closer = logWriter.Close
options = append(defaultErrorOutputOptions, options...)
logger = zap.New(core, options...).
Named(moduleName).With(fields...).Sugar()
return
}
func initLogger(
writer zapcore.WriteSyncer,
moduleName string,
formatter zapcore.Encoder,
level zap.AtomicLevel,
options ...zap.Option,
) *zap.Logger {
core := zapcore.NewCore(formatter, writer, level)
return zap.New(core, options...).Named(moduleName)
}

14
logger/zap/wrapped.go Normal file
View File

@ -0,0 +1,14 @@
package zap
import (
"go.uber.org/zap/zapcore"
)
type zapWrappedSyncer struct {
zapcore.WriteSyncer
}
func (r *zapWrappedSyncer) SetOnClose(closeFunc func()) {}
func (r *zapWrappedSyncer) Rotate() (err error) { return }
func (r *zapWrappedSyncer) Close() (err error) { return }
func (r *zapWrappedSyncer) Sync() error { return r.WriteSyncer.Sync() }

126
misc/converse.go Normal file
View File

@ -0,0 +1,126 @@
package misc
import (
"strconv"
)
func mustParseInt(value string, bits int) int64 {
if parsed, err := strconv.ParseInt(value, 10, bits); err != nil {
panic(err)
} else {
return parsed
}
}
func mustParseUint(value string, bits int) uint64 {
if parsed, err := strconv.ParseUint(value, 10, bits); err != nil {
panic(err)
} else {
return parsed
}
}
func ParseUint8(value string) (ret *uint8) {
if parsed, err := strconv.ParseUint(value, 10, 8); err == nil {
ret = new(uint8)
*ret = uint8(parsed)
}
return
}
func ParseUint16(value string) (ret *uint16) {
if parsed, err := strconv.ParseUint(value, 10, 16); err == nil {
ret = new(uint16)
*ret = uint16(parsed)
}
return
}
func ParseUint32(value string) (ret *uint32) {
if parsed, err := strconv.ParseUint(value, 10, 32); err == nil {
ret = new(uint32)
*ret = uint32(parsed)
}
return
}
func ParseUint64(value string) (ret *uint64) {
if parsed, err := strconv.ParseUint(value, 10, 64); err == nil {
ret = new(uint64)
*ret = uint64(parsed)
}
return
}
func ParseUint(value string) (ret *uint) {
if parsed, err := strconv.ParseUint(value, 10, 0); err == nil {
ret = new(uint)
*ret = uint(parsed)
}
return
}
func ParseInt8(value string) (ret *int8) {
if parsed, err := strconv.ParseInt(value, 10, 8); err == nil {
ret = new(int8)
*ret = int8(parsed)
}
return
}
func ParseInt16(value string) (ret *int16) {
if parsed, err := strconv.ParseInt(value, 10, 16); err == nil {
ret = new(int16)
*ret = int16(parsed)
}
return
}
func ParseInt32(value string) (ret *int32) {
if parsed, err := strconv.ParseInt(value, 10, 32); err == nil {
ret = new(int32)
*ret = int32(parsed)
}
return
}
func ParseInt64(value string) (ret *int64) {
if parsed, err := strconv.ParseInt(value, 10, 64); err == nil {
ret = new(int64)
*ret = int64(parsed)
}
return
}
func ParseInt(value string) (ret *int) {
if parsed, err := strconv.ParseInt(value, 10, 0); err == nil {
ret = new(int)
*ret = int(parsed)
}
return
}
func ParseUint8Must(value string) uint8 { return uint8(mustParseUint(value, 8)) }
func ParseUint16Must(value string) uint16 { return uint16(mustParseUint(value, 16)) }
func ParseUint32Must(value string) uint32 { return uint32(mustParseUint(value, 32)) }
func ParseUint64Must(value string) uint64 { return mustParseUint(value, 64) }
func ParseUintMust(value string) uint { return uint(mustParseUint(value, 0)) }
func ParseInt8Must(value string) int8 { return int8(mustParseInt(value, 8)) }
func ParseInt16Must(value string) int16 { return int16(mustParseInt(value, 16)) }
func ParseInt32Must(value string) int32 { return int32(mustParseInt(value, 32)) }
func ParseInt64Must(value string) int64 { return mustParseInt(value, 64) }
func ParseIntMust(value string) int { return int(mustParseInt(value, 0)) }
func FormatUint8(value uint8) string { return strconv.FormatUint(uint64(value), 10) }
func FormatUint16(value uint16) string { return strconv.FormatUint(uint64(value), 10) }
func FormatUint32(value uint32) string { return strconv.FormatUint(uint64(value), 10) }
func FormatUint64(value uint64) string { return strconv.FormatUint(value, 10) }
func FormatUint(value uint) string { return strconv.FormatUint(uint64(value), 10) }
func FormatInt8(value int8) string { return strconv.FormatInt(int64(value), 10) }
func FormatInt16(value int16) string { return strconv.FormatInt(int64(value), 10) }
func FormatInt32(value int32) string { return strconv.FormatInt(int64(value), 10) }
func FormatInt64(value int64) string { return strconv.FormatInt(value, 10) }
func FormatInt(value int) string { return strconv.FormatInt(int64(value), 10) }

112
misc/dumper.go Normal file
View File

@ -0,0 +1,112 @@
package misc
import "bytes"
import (
"fmt"
"unicode/utf8"
"golang.org/x/text/width"
)
func HexDump(by []byte) string {
n := len(by)
rowcount := 0
stop := (n / 16) * 16
k := 0
buf := &bytes.Buffer{}
for i := 0; i <= stop; i += 16 {
k++
if i+16 < n {
rowcount = 16
} else {
rowcount = min(k*16, n) % 16
}
fmt.Fprintf(buf, "%08x ", i)
for j := 0; j < rowcount; j++ {
if j%8 == 0 {
fmt.Fprintf(buf, " %02x ", by[i+j])
} else {
fmt.Fprintf(buf, "%02x ", by[i+j])
}
}
for j := rowcount; j < 16; j++ {
if j%8 == 0 {
fmt.Fprintf(buf, " ")
} else {
fmt.Fprintf(buf, " ")
}
}
buf.WriteRune('|')
viewString(by[i:(i + rowcount)], buf)
buf.WriteRune('|')
buf.WriteRune('\n')
buf.WriteRune('\r')
}
return buf.String()
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func GuessUnicodeWidth(char rune) (realSize int) {
prop := width.LookupRune(char)
switch prop.Kind() {
case width.EastAsianFullwidth:
fallthrough
case width.EastAsianWide:
realSize = 2
case width.EastAsianHalfwidth:
fallthrough
case width.EastAsianNarrow:
realSize = 2
case width.EastAsianAmbiguous:
fallthrough
case width.Neutral:
fallthrough
default:
realSize = 1
}
return
}
func FillUnicodeWidth(byteLength int, char rune) int {
fillWidth := GuessUnicodeWidth(char)
return max(0, byteLength-fillWidth)
}
func viewString(b []byte, buf *bytes.Buffer) {
for {
if r, size := utf8.DecodeRune(b); size == 0 {
return
} else if r == utf8.RuneError {
for i := 0; i < size; i++ {
buf.WriteRune('_')
}
b = b[size:]
} else if r < 32 {
for i := 0; i < size; i++ {
buf.WriteRune('.')
}
b = b[size:]
} else {
buf.WriteRune(r)
pad := FillUnicodeWidth(size, r)
for i := 0; i < pad; i++ {
buf.WriteRune('.')
}
b = b[size:]
}
}
}

118
misc/uuid.go Normal file
View File

@ -0,0 +1,118 @@
package misc
import (
"bytes"
"database/sql/driver"
"encoding/hex"
"errors"
"github.com/NebulousLabs/fastrand"
"fmt"
)
type UUID [16]byte
func (u UUID) Marshal() ([]byte, error) {
return u[:], nil
}
func (u UUID) MarshalTo(buf []byte) (n int, err error) {
if len(u) == 0 {
return 0, nil
}
copy(buf, u[:])
return len(u), nil
}
func (u *UUID) Unmarshal(buf []byte) error {
if len(buf) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(buf))
}
copy(u[:], buf)
return nil
}
func (u UUID) Compare(other UUID) int {
return bytes.Compare(u[:], other[:])
}
func (u UUID) Equal(other UUID) bool {
return u.Compare(other) == 0
}
func (u *UUID) UnmarshalJSON(from []byte) error {
quote := []byte("\"")
quoteSize := len(quote)
if len(from) < quoteSize*2 {
return errors.New("invalid quote notation")
}
if !bytes.HasPrefix(from, quote) || !bytes.HasSuffix(from, quote) {
return errors.New("invalid quote notation")
} else if err := u.Unmarshal(from[quoteSize:len(from)-quoteSize]); err != nil {
return err
}
return nil
}
func (u UUID) MarshalJSON() ([]byte, error) {
var buffer bytes.Buffer
buffer.WriteRune('"')
buffer.WriteString(hex.EncodeToString(u[:]))
buffer.WriteRune('"')
return buffer.Bytes(), nil
}
func (u *UUID) Size() int {
if u == nil {
return 0
}
if len(*u) == 0 {
return 0
}
return 16
}
func NewUUID() (u UUID) {
newObj := UUID{}
newObj.Random()
return newObj
}
func (u *UUID) UUIDFromHexString(buf []byte) (error) {
hexBuf := make([]byte, hex.DecodedLen(len(buf)))
if _, err := hex.Decode(hexBuf, buf); err != nil {
return err
} else if err := u.Unmarshal(hexBuf); err != nil {
return err
}
return nil
}
func (u UUID) ToHexString() (string) {
return hex.EncodeToString(u[:])
}
// Scan implements the Scanner interface.
func (u *UUID) Scan(src interface{}) error {
if src == nil {
return nil
}
b, ok := src.([]byte)
if !ok {
return errors.New("Scan source was not []bytes")
}
u.UUIDFromHexString(b)
return nil
}
// Value implements the driver Valuer interface.
func (u UUID) Value() (driver.Value, error) {
return u.ToHexString(), nil
}
func (u UUID) Random() {
fastrand.Read(u[:])
u[6] = (u[6] & 0x0f) | 0x40 // Version 4
u[8] = (u[8] & 0x3f) | 0x80 // Variant is 10
}

41
net/ipv4_resolver.go Normal file
View File

@ -0,0 +1,41 @@
package net
import (
"context"
"github.com/benburkert/dns"
"net"
)
func ReplaceDefaultDnsResolver(){
cli := &dns.Client{Resolver: &PreferIPV4Resolver{}}
net.DefaultResolver.PreferGo = true
net.DefaultResolver.Dial = cli.Dial
}
type PreferIPV4Resolver struct{}
func (p *PreferIPV4Resolver) ServeDNS(ctx context.Context, w dns.MessageWriter, r *dns.Query) {
if r.Questions[0].Type == dns.TypeAAAA {
w.Status(dns.ServFail)
} else if msg, err := w.Recur(ctx); err != nil {
w.Status(dns.ServFail)
} else {
p.writeMessage(w, msg)
}
}
func (p *PreferIPV4Resolver) writeMessage(w dns.MessageWriter, msg *dns.Message) {
w.Status(msg.RCode)
w.Authoritative(msg.Authoritative)
w.Recursion(msg.RecursionAvailable)
for _, res := range msg.Answers {
w.Answer(res.Name, res.TTL, res.Record)
}
for _, res := range msg.Authorities {
w.Authority(res.Name, res.TTL, res.Record)
}
for _, res := range msg.Additionals {
w.Additional(res.Name, res.TTL, res.Record)
}
}

37
net/ipv4_resolver_test.go Normal file
View File

@ -0,0 +1,37 @@
package net
import (
"testing"
"net"
"github.com/benburkert/dns"
)
func TestPreferIPV4Resolver(t *testing.T) {
var (
cli = &dns.Client{Resolver: &PreferIPV4Resolver{}}
addrs []net.IP
err error
)
addrs, err = net.LookupIP("www.v6.facebook.com")
if err == nil && len(addrs) != 0 && len(addrs[0]) == net.IPv6len {
err = nil
t.Log("checked AAAA dns reponse")
} else {
t.Fatal("unknown error : ", err)
}
net.DefaultResolver.PreferGo = true
net.DefaultResolver.Dial = cli.Dial
addrs, err = net.LookupIP("www.v6.facebook.com")
if len(addrs) != 0 {
t.Fatal("ipv6 address isn't empty")
} else if err == nil {
t.Fatal("no error in AAAA record")
} else if dnserr, ok := err.(*net.DNSError); !ok || dnserr.Err != "no such host" {
t.Fatal("unknown error : ", err)
} else {
err = nil
t.Log("AAAA dns request filtered")
}
}

36
net/local.go Normal file
View File

@ -0,0 +1,36 @@
package net
import "net"
// GetLocalIP returns the non loopback local IP of the host
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
for _, address := range addrs {
// check the address type and if it is not a loopback the display it
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return ""
}
func ResolveIp(name string) (net.IP, error) {
if addrs, err := net.ResolveIPAddr("ip4", name); err != nil {
return nil, err
} else {
return addrs.IP, nil
}
}
func ExtractIp(remoteAddr net.Addr) string {
if addr, ok := remoteAddr.(*net.TCPAddr); ok {
return addr.IP.String()
} else {
return remoteAddr.String()
}
}

212
tty/code/termcodes.go Normal file
View File

@ -0,0 +1,212 @@
// +build !windows
package code
import (
"os"
"syscall"
)
// POSIX terminal mode flags as listed in RFC 4254 Section 8.
const (
tty_OP_END = 0
VINTR = 1
VQUIT = 2
VERASE = 3
VKILL = 4
VEOF = 5
VEOL = 6
VEOL2 = 7
VSTART = 8
VSTOP = 9
VSUSP = 10
VDSUSP = 11
VREPRINT = 12
VWERASE = 13
VLNEXT = 14
VFLUSH = 15
VSWTCH = 16
VSTATUS = 17
VDISCARD = 18
IGNPAR = 30
PARMRK = 31
INPCK = 32
ISTRIP = 33
INLCR = 34
IGNCR = 35
ICRNL = 36
IUCLC = 37
IXON = 38
IXANY = 39
IXOFF = 40
IMAXBEL = 41
IUTF8 = 42
ISIG = 50
ICANON = 51
XCASE = 52
ECHO = 53
ECHOE = 54
ECHOK = 55
ECHONL = 56
NOFLSH = 57
TOSTOP = 58
IEXTEN = 59
ECHOCTL = 60
ECHOKE = 61
PENDIN = 62
OPOST = 70
OLCUC = 71
ONLCR = 72
OCRNL = 73
ONOCR = 74
ONLRET = 75
CS7 = 90
CS8 = 91
PARENB = 92
PARODD = 93
TTY_OP_ISPEED = 128
TTY_OP_OSPEED = 129
)
// struct termios {
// tcflag_t c_iflag; /* input modes */
// tcflag_t c_oflag; /* output modes */
// tcflag_t c_cflag; /* control modes */
// tcflag_t c_lflag; /* local modes */
// cc_t c_cc[NCCS]; /* special characters */
// speed_t c_ispeed;
// speed_t c_ospeed;
// };
type Setter interface {
Set(pty *os.File, termios *syscall.Termios, value uint32) error
}
var TermAttrSetters = map[uint8]Setter{
VINTR: &ccSetter{Character: syscall.VINTR},
VQUIT: &ccSetter{Character: syscall.VQUIT},
VERASE: &ccSetter{Character: syscall.VERASE},
VKILL: &ccSetter{Character: syscall.VKILL},
VEOF: &ccSetter{Character: syscall.VEOF},
VEOL: &ccSetter{Character: syscall.VEOL},
VEOL2: &ccSetter{Character: syscall.VEOL2},
VSTART: &ccSetter{Character: syscall.VSTART},
VSTOP: &ccSetter{Character: syscall.VSTOP},
VSUSP: &ccSetter{Character: syscall.VSUSP},
VDSUSP: &nopSetter{},
VREPRINT: &ccSetter{Character: syscall.VREPRINT},
VWERASE: &ccSetter{Character: syscall.VWERASE},
VLNEXT: &ccSetter{Character: syscall.VLNEXT},
VFLUSH: &nopSetter{},
VSWTCH: &nopSetter{},
VSTATUS: &nopSetter{},
VDISCARD: &ccSetter{Character: syscall.VDISCARD},
// Input modes
IGNPAR: &iflagSetter{Flag: syscall.IGNPAR},
PARMRK: &iflagSetter{Flag: syscall.PARMRK},
INPCK: &iflagSetter{Flag: syscall.INPCK},
ISTRIP: &iflagSetter{Flag: syscall.ISTRIP},
INLCR: &iflagSetter{Flag: syscall.INLCR},
IGNCR: &iflagSetter{Flag: syscall.IGNCR},
ICRNL: &iflagSetter{Flag: syscall.ICRNL},
IUCLC: &nopSetter{},
IXON: &iflagSetter{Flag: syscall.IXON},
IXANY: &iflagSetter{Flag: syscall.IXANY},
IXOFF: &iflagSetter{Flag: syscall.IXOFF},
IMAXBEL: &iflagSetter{Flag: syscall.IMAXBEL},
IUTF8: &iflagSetter{Flag: syscall.IUTF8}, //IUTF8
// Local modes
ISIG: &lflagSetter{Flag: syscall.ISIG},
ICANON: &lflagSetter{Flag: syscall.ICANON},
XCASE: &nopSetter{},
ECHO: &lflagSetter{Flag: syscall.ECHO},
ECHOE: &lflagSetter{Flag: syscall.ECHOE},
ECHOK: &lflagSetter{Flag: syscall.ECHOK},
ECHONL: &lflagSetter{Flag: syscall.ECHONL},
NOFLSH: &lflagSetter{Flag: syscall.NOFLSH},
TOSTOP: &lflagSetter{Flag: syscall.TOSTOP},
IEXTEN: &lflagSetter{Flag: syscall.IEXTEN},
ECHOCTL: &lflagSetter{Flag: syscall.ECHOCTL},
ECHOKE: &lflagSetter{Flag: syscall.ECHOKE},
PENDIN: &lflagSetter{Flag: syscall.PENDIN},
// Output modes
OPOST: &oflagSetter{Flag: syscall.OPOST},
OLCUC: &nopSetter{},
ONLCR: &oflagSetter{Flag: syscall.ONLCR},
OCRNL: &oflagSetter{Flag: syscall.OCRNL},
ONOCR: &oflagSetter{Flag: syscall.ONOCR},
ONLRET: &oflagSetter{Flag: syscall.ONLRET},
// Control modes
CS7: &cflagSetter{Flag: syscall.CS7},
CS8: &cflagSetter{Flag: syscall.CS8},
PARENB: &cflagSetter{Flag: syscall.PARENB},
PARODD: &cflagSetter{Flag: syscall.PARODD},
// Baud rates (ignore)
TTY_OP_ISPEED: &nopSetter{},
TTY_OP_OSPEED: &nopSetter{},
}
type nopSetter struct{}
type ccSetter struct {
Character uint8
}
func (cc *ccSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error {
termios.Cc[cc.Character] = byte(value)
return SetAttr(pty, termios)
}
func (i *iflagSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error {
if value == 0 {
termios.Iflag &^= i.Flag
} else {
termios.Iflag |= i.Flag
}
return SetAttr(pty, termios)
}
func (l *lflagSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error {
if value == 0 {
termios.Lflag &^= l.Flag
} else {
termios.Lflag |= l.Flag
}
return SetAttr(pty, termios)
}
func (o *oflagSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error {
if value == 0 {
termios.Oflag &^= o.Flag
} else {
termios.Oflag |= o.Flag
}
return SetAttr(pty, termios)
}
func (c *cflagSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error {
switch c.Flag {
// CSIZE is a field
case syscall.CS7, syscall.CS8:
termios.Cflag &^= syscall.CSIZE
termios.Cflag |= c.Flag
default:
if value == 0 {
termios.Cflag &^= c.Flag
} else {
termios.Cflag |= c.Flag
}
}
return SetAttr(pty, termios)
}
func (n *nopSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error {
return nil
}

View File

@ -0,0 +1,45 @@
// +build darwin
package code
import (
"os"
"syscall"
"unsafe"
)
type iflagSetter struct {
Flag uint64
}
type lflagSetter struct {
Flag uint64
}
type oflagSetter struct {
Flag uint64
}
type cflagSetter struct {
Flag uint64
}
func SetAttr(tty *os.File, termios *syscall.Termios) error {
r, _, e := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios)))
if r != 0 {
return os.NewSyscallError("SYS_IOCTL", e)
}
return nil
}
func GetAttr(tty *os.File) (*syscall.Termios, error) {
termios := &syscall.Termios{}
r, _, e := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios)))
if r != 0 {
return nil, os.NewSyscallError("SYS_IOCTL", e)
}
return termios, nil
}

View File

@ -0,0 +1,45 @@
// +build linux
package code
import (
"os"
"syscall"
"unsafe"
)
type iflagSetter struct {
Flag uint32
}
type lflagSetter struct {
Flag uint32
}
type oflagSetter struct {
Flag uint32
}
type cflagSetter struct {
Flag uint32
}
func SetAttr(tty *os.File, termios *syscall.Termios) error {
r, _, e := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TCSETS, uintptr(unsafe.Pointer(termios)))
if r != 0 {
return os.NewSyscallError("SYS_IOCTL", e)
}
return nil
}
func GetAttr(tty *os.File) (*syscall.Termios, error) {
termios := &syscall.Termios{}
r, _, e := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TCGETS, uintptr(unsafe.Pointer(termios)))
if r != 0 {
return nil, os.NewSyscallError("SYS_IOCTL", e)
}
return termios, nil
}

114
tty/term.go Normal file
View File

@ -0,0 +1,114 @@
package tty
import (
"bytes"
)
// Window represents the size of a PTY window.
type Window struct {
Columns uint32
Rows uint32
Width uint32
Height uint32
}
// Pty represents a PTY request and configuration.
type Pty struct {
Term string
Columns uint32
Rows uint32
Width uint32
Height uint32
Modelist string
}
type Environ struct {
Key string
Value string
}
type TermInfo struct {
Term string
Cols uint32
Rows uint32
Modelist string
}
type CommandLine struct {
Command string
}
type ExitStatus struct {
Status uint32
}
func (i *TermInfo) ToWindow() (*Window) {
if i == nil {
return nil
}
return &Window{
Columns: i.Cols,
Rows: i.Rows,
Width: 8 * i.Cols,
Height: 8 * i.Rows,
}
}
func (i *TermInfo) ToPty() (*Pty) {
if i == nil {
return nil
}
return &Pty{
Term: i.Term,
Columns: i.Cols,
Rows: i.Rows,
Width: 8 * i.Cols,
Height: 8 * i.Rows,
Modelist: i.Modelist,
}
}
func (i *TermInfo) TermMap() (mode map[uint8]uint32) {
mode = make(map[uint8]uint32)
if i == nil {
return
}
reader := bytes.NewReader([]byte(i.Modelist))
buf := make([]byte, 4)
for {
if k, err := reader.ReadByte(); err != nil {
break
} else if read, err := reader.Read(buf); err != nil {
break
} else if read != 4 {
break
} else {
mode[uint8(k)] = uint32(buf[0])<<24 | uint32(buf[1])<<16 | uint32(buf[2])<<9 | uint32(buf[3])<<0
}
}
return mode
}
func (p *Pty) ToTermInfo() (*TermInfo) {
if p == nil {
return nil
}
return &TermInfo{
Term: p.Term,
Cols: p.Columns,
Rows: p.Rows,
Modelist: p.Modelist,
}
}
func (p *Pty) ToWindow() (*Window) {
if p == nil {
return nil
}
return &Window{
Columns: p.Columns,
Rows: p.Rows,
Width: 8 * p.Columns,
Height: 8 * p.Rows,
}
}