mirror of https://github.com/k3d-io/k3d
parent
f77fb62934
commit
5474da5422
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@ |
|||||||
|
* @microsoft/containerplat |
@ -0,0 +1,307 @@ |
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"net" |
||||||
|
"os" |
||||||
|
"syscall" |
||||||
|
"time" |
||||||
|
"unsafe" |
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/pkg/guid" |
||||||
|
) |
||||||
|
|
||||||
|
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
|
||||||
|
|
||||||
|
const ( |
||||||
|
afHvSock = 34 // AF_HYPERV
|
||||||
|
|
||||||
|
socketError = ^uintptr(0) |
||||||
|
) |
||||||
|
|
||||||
|
// An HvsockAddr is an address for a AF_HYPERV socket.
|
||||||
|
type HvsockAddr struct { |
||||||
|
VMID guid.GUID |
||||||
|
ServiceID guid.GUID |
||||||
|
} |
||||||
|
|
||||||
|
type rawHvsockAddr struct { |
||||||
|
Family uint16 |
||||||
|
_ uint16 |
||||||
|
VMID guid.GUID |
||||||
|
ServiceID guid.GUID |
||||||
|
} |
||||||
|
|
||||||
|
// Network returns the address's network name, "hvsock".
|
||||||
|
func (addr *HvsockAddr) Network() string { |
||||||
|
return "hvsock" |
||||||
|
} |
||||||
|
|
||||||
|
func (addr *HvsockAddr) String() string { |
||||||
|
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID) |
||||||
|
} |
||||||
|
|
||||||
|
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
|
||||||
|
func VsockServiceID(port uint32) guid.GUID { |
||||||
|
g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3") |
||||||
|
g.Data1 = port |
||||||
|
return g |
||||||
|
} |
||||||
|
|
||||||
|
func (addr *HvsockAddr) raw() rawHvsockAddr { |
||||||
|
return rawHvsockAddr{ |
||||||
|
Family: afHvSock, |
||||||
|
VMID: addr.VMID, |
||||||
|
ServiceID: addr.ServiceID, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) { |
||||||
|
addr.VMID = raw.VMID |
||||||
|
addr.ServiceID = raw.ServiceID |
||||||
|
} |
||||||
|
|
||||||
|
// HvsockListener is a socket listener for the AF_HYPERV address family.
|
||||||
|
type HvsockListener struct { |
||||||
|
sock *win32File |
||||||
|
addr HvsockAddr |
||||||
|
} |
||||||
|
|
||||||
|
// HvsockConn is a connected socket of the AF_HYPERV address family.
|
||||||
|
type HvsockConn struct { |
||||||
|
sock *win32File |
||||||
|
local, remote HvsockAddr |
||||||
|
} |
||||||
|
|
||||||
|
func newHvSocket() (*win32File, error) { |
||||||
|
fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1) |
||||||
|
if err != nil { |
||||||
|
return nil, os.NewSyscallError("socket", err) |
||||||
|
} |
||||||
|
f, err := makeWin32File(fd) |
||||||
|
if err != nil { |
||||||
|
syscall.Close(fd) |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
f.socket = true |
||||||
|
return f, nil |
||||||
|
} |
||||||
|
|
||||||
|
// ListenHvsock listens for connections on the specified hvsock address.
|
||||||
|
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) { |
||||||
|
l := &HvsockListener{addr: *addr} |
||||||
|
sock, err := newHvSocket() |
||||||
|
if err != nil { |
||||||
|
return nil, l.opErr("listen", err) |
||||||
|
} |
||||||
|
sa := addr.raw() |
||||||
|
err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa))) |
||||||
|
if err != nil { |
||||||
|
return nil, l.opErr("listen", os.NewSyscallError("socket", err)) |
||||||
|
} |
||||||
|
err = syscall.Listen(sock.handle, 16) |
||||||
|
if err != nil { |
||||||
|
return nil, l.opErr("listen", os.NewSyscallError("listen", err)) |
||||||
|
} |
||||||
|
return &HvsockListener{sock: sock, addr: *addr}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *HvsockListener) opErr(op string, err error) error { |
||||||
|
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err} |
||||||
|
} |
||||||
|
|
||||||
|
// Addr returns the listener's network address.
|
||||||
|
func (l *HvsockListener) Addr() net.Addr { |
||||||
|
return &l.addr |
||||||
|
} |
||||||
|
|
||||||
|
// Accept waits for the next connection and returns it.
|
||||||
|
func (l *HvsockListener) Accept() (_ net.Conn, err error) { |
||||||
|
sock, err := newHvSocket() |
||||||
|
if err != nil { |
||||||
|
return nil, l.opErr("accept", err) |
||||||
|
} |
||||||
|
defer func() { |
||||||
|
if sock != nil { |
||||||
|
sock.Close() |
||||||
|
} |
||||||
|
}() |
||||||
|
c, err := l.sock.prepareIo() |
||||||
|
if err != nil { |
||||||
|
return nil, l.opErr("accept", err) |
||||||
|
} |
||||||
|
defer l.sock.wg.Done() |
||||||
|
|
||||||
|
// AcceptEx, per documentation, requires an extra 16 bytes per address.
|
||||||
|
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{})) |
||||||
|
var addrbuf [addrlen * 2]byte |
||||||
|
|
||||||
|
var bytes uint32 |
||||||
|
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o) |
||||||
|
_, err = l.sock.asyncIo(c, nil, bytes, err) |
||||||
|
if err != nil { |
||||||
|
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err)) |
||||||
|
} |
||||||
|
conn := &HvsockConn{ |
||||||
|
sock: sock, |
||||||
|
} |
||||||
|
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0]))) |
||||||
|
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen]))) |
||||||
|
sock = nil |
||||||
|
return conn, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Close closes the listener, causing any pending Accept calls to fail.
|
||||||
|
func (l *HvsockListener) Close() error { |
||||||
|
return l.sock.Close() |
||||||
|
} |
||||||
|
|
||||||
|
/* Need to finish ConnectEx handling |
||||||
|
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) { |
||||||
|
sock, err := newHvSocket() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
defer func() { |
||||||
|
if sock != nil { |
||||||
|
sock.Close() |
||||||
|
} |
||||||
|
}() |
||||||
|
c, err := sock.prepareIo() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
defer sock.wg.Done() |
||||||
|
var bytes uint32 |
||||||
|
err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o) |
||||||
|
_, err = sock.asyncIo(ctx, c, nil, bytes, err) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
conn := &HvsockConn{ |
||||||
|
sock: sock, |
||||||
|
remote: *addr, |
||||||
|
} |
||||||
|
sock = nil |
||||||
|
return conn, nil |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
func (conn *HvsockConn) opErr(op string, err error) error { |
||||||
|
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err} |
||||||
|
} |
||||||
|
|
||||||
|
func (conn *HvsockConn) Read(b []byte) (int, error) { |
||||||
|
c, err := conn.sock.prepareIo() |
||||||
|
if err != nil { |
||||||
|
return 0, conn.opErr("read", err) |
||||||
|
} |
||||||
|
defer conn.sock.wg.Done() |
||||||
|
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} |
||||||
|
var flags, bytes uint32 |
||||||
|
err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil) |
||||||
|
n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err) |
||||||
|
if err != nil { |
||||||
|
if _, ok := err.(syscall.Errno); ok { |
||||||
|
err = os.NewSyscallError("wsarecv", err) |
||||||
|
} |
||||||
|
return 0, conn.opErr("read", err) |
||||||
|
} else if n == 0 { |
||||||
|
err = io.EOF |
||||||
|
} |
||||||
|
return n, err |
||||||
|
} |
||||||
|
|
||||||
|
func (conn *HvsockConn) Write(b []byte) (int, error) { |
||||||
|
t := 0 |
||||||
|
for len(b) != 0 { |
||||||
|
n, err := conn.write(b) |
||||||
|
if err != nil { |
||||||
|
return t + n, err |
||||||
|
} |
||||||
|
t += n |
||||||
|
b = b[n:] |
||||||
|
} |
||||||
|
return t, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (conn *HvsockConn) write(b []byte) (int, error) { |
||||||
|
c, err := conn.sock.prepareIo() |
||||||
|
if err != nil { |
||||||
|
return 0, conn.opErr("write", err) |
||||||
|
} |
||||||
|
defer conn.sock.wg.Done() |
||||||
|
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} |
||||||
|
var bytes uint32 |
||||||
|
err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil) |
||||||
|
n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err) |
||||||
|
if err != nil { |
||||||
|
if _, ok := err.(syscall.Errno); ok { |
||||||
|
err = os.NewSyscallError("wsasend", err) |
||||||
|
} |
||||||
|
return 0, conn.opErr("write", err) |
||||||
|
} |
||||||
|
return n, err |
||||||
|
} |
||||||
|
|
||||||
|
// Close closes the socket connection, failing any pending read or write calls.
|
||||||
|
func (conn *HvsockConn) Close() error { |
||||||
|
return conn.sock.Close() |
||||||
|
} |
||||||
|
|
||||||
|
func (conn *HvsockConn) shutdown(how int) error { |
||||||
|
err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD) |
||||||
|
if err != nil { |
||||||
|
return os.NewSyscallError("shutdown", err) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// CloseRead shuts down the read end of the socket.
|
||||||
|
func (conn *HvsockConn) CloseRead() error { |
||||||
|
err := conn.shutdown(syscall.SHUT_RD) |
||||||
|
if err != nil { |
||||||
|
return conn.opErr("close", err) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// CloseWrite shuts down the write end of the socket, notifying the other endpoint that
|
||||||
|
// no more data will be written.
|
||||||
|
func (conn *HvsockConn) CloseWrite() error { |
||||||
|
err := conn.shutdown(syscall.SHUT_WR) |
||||||
|
if err != nil { |
||||||
|
return conn.opErr("close", err) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// LocalAddr returns the local address of the connection.
|
||||||
|
func (conn *HvsockConn) LocalAddr() net.Addr { |
||||||
|
return &conn.local |
||||||
|
} |
||||||
|
|
||||||
|
// RemoteAddr returns the remote address of the connection.
|
||||||
|
func (conn *HvsockConn) RemoteAddr() net.Addr { |
||||||
|
return &conn.remote |
||||||
|
} |
||||||
|
|
||||||
|
// SetDeadline implements the net.Conn SetDeadline method.
|
||||||
|
func (conn *HvsockConn) SetDeadline(t time.Time) error { |
||||||
|
conn.SetReadDeadline(t) |
||||||
|
conn.SetWriteDeadline(t) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// SetReadDeadline implements the net.Conn SetReadDeadline method.
|
||||||
|
func (conn *HvsockConn) SetReadDeadline(t time.Time) error { |
||||||
|
return conn.sock.SetReadDeadline(t) |
||||||
|
} |
||||||
|
|
||||||
|
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
|
||||||
|
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error { |
||||||
|
return conn.sock.SetWriteDeadline(t) |
||||||
|
} |
@ -0,0 +1,237 @@ |
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
// Package guid provides a GUID type. The backing structure for a GUID is
|
||||||
|
// identical to that used by the golang.org/x/sys/windows GUID type.
|
||||||
|
// There are two main binary encodings used for a GUID, the big-endian encoding,
|
||||||
|
// and the Windows (mixed-endian) encoding. See here for details:
|
||||||
|
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
|
||||||
|
package guid |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/rand" |
||||||
|
"crypto/sha1" |
||||||
|
"encoding" |
||||||
|
"encoding/binary" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
|
||||||
|
"golang.org/x/sys/windows" |
||||||
|
) |
||||||
|
|
||||||
|
// Variant specifies which GUID variant (or "type") of the GUID. It determines
|
||||||
|
// how the entirety of the rest of the GUID is interpreted.
|
||||||
|
type Variant uint8 |
||||||
|
|
||||||
|
// The variants specified by RFC 4122.
|
||||||
|
const ( |
||||||
|
// VariantUnknown specifies a GUID variant which does not conform to one of
|
||||||
|
// the variant encodings specified in RFC 4122.
|
||||||
|
VariantUnknown Variant = iota |
||||||
|
VariantNCS |
||||||
|
VariantRFC4122 |
||||||
|
VariantMicrosoft |
||||||
|
VariantFuture |
||||||
|
) |
||||||
|
|
||||||
|
// Version specifies how the bits in the GUID were generated. For instance, a
|
||||||
|
// version 4 GUID is randomly generated, and a version 5 is generated from the
|
||||||
|
// hash of an input string.
|
||||||
|
type Version uint8 |
||||||
|
|
||||||
|
var _ = (encoding.TextMarshaler)(GUID{}) |
||||||
|
var _ = (encoding.TextUnmarshaler)(&GUID{}) |
||||||
|
|
||||||
|
// GUID represents a GUID/UUID. It has the same structure as
|
||||||
|
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
|
||||||
|
// that type. It is defined as its own type so that stringification and
|
||||||
|
// marshaling can be supported. The representation matches that used by native
|
||||||
|
// Windows code.
|
||||||
|
type GUID windows.GUID |
||||||
|
|
||||||
|
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
|
||||||
|
func NewV4() (GUID, error) { |
||||||
|
var b [16]byte |
||||||
|
if _, err := rand.Read(b[:]); err != nil { |
||||||
|
return GUID{}, err |
||||||
|
} |
||||||
|
|
||||||
|
g := FromArray(b) |
||||||
|
g.setVersion(4) // Version 4 means randomly generated.
|
||||||
|
g.setVariant(VariantRFC4122) |
||||||
|
|
||||||
|
return g, nil |
||||||
|
} |
||||||
|
|
||||||
|
// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
|
||||||
|
// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
|
||||||
|
// and the sample code treats it as a series of bytes, so we do the same here.
|
||||||
|
//
|
||||||
|
// Some implementations, such as those found on Windows, treat the name as a
|
||||||
|
// big-endian UTF16 stream of bytes. If that is desired, the string can be
|
||||||
|
// encoded as such before being passed to this function.
|
||||||
|
func NewV5(namespace GUID, name []byte) (GUID, error) { |
||||||
|
b := sha1.New() |
||||||
|
namespaceBytes := namespace.ToArray() |
||||||
|
b.Write(namespaceBytes[:]) |
||||||
|
b.Write(name) |
||||||
|
|
||||||
|
a := [16]byte{} |
||||||
|
copy(a[:], b.Sum(nil)) |
||||||
|
|
||||||
|
g := FromArray(a) |
||||||
|
g.setVersion(5) // Version 5 means generated from a string.
|
||||||
|
g.setVariant(VariantRFC4122) |
||||||
|
|
||||||
|
return g, nil |
||||||
|
} |
||||||
|
|
||||||
|
func fromArray(b [16]byte, order binary.ByteOrder) GUID { |
||||||
|
var g GUID |
||||||
|
g.Data1 = order.Uint32(b[0:4]) |
||||||
|
g.Data2 = order.Uint16(b[4:6]) |
||||||
|
g.Data3 = order.Uint16(b[6:8]) |
||||||
|
copy(g.Data4[:], b[8:16]) |
||||||
|
return g |
||||||
|
} |
||||||
|
|
||||||
|
func (g GUID) toArray(order binary.ByteOrder) [16]byte { |
||||||
|
b := [16]byte{} |
||||||
|
order.PutUint32(b[0:4], g.Data1) |
||||||
|
order.PutUint16(b[4:6], g.Data2) |
||||||
|
order.PutUint16(b[6:8], g.Data3) |
||||||
|
copy(b[8:16], g.Data4[:]) |
||||||
|
return b |
||||||
|
} |
||||||
|
|
||||||
|
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
|
||||||
|
func FromArray(b [16]byte) GUID { |
||||||
|
return fromArray(b, binary.BigEndian) |
||||||
|
} |
||||||
|
|
||||||
|
// ToArray returns an array of 16 bytes representing the GUID in big-endian
|
||||||
|
// encoding.
|
||||||
|
func (g GUID) ToArray() [16]byte { |
||||||
|
return g.toArray(binary.BigEndian) |
||||||
|
} |
||||||
|
|
||||||
|
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
|
||||||
|
func FromWindowsArray(b [16]byte) GUID { |
||||||
|
return fromArray(b, binary.LittleEndian) |
||||||
|
} |
||||||
|
|
||||||
|
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
|
||||||
|
// encoding.
|
||||||
|
func (g GUID) ToWindowsArray() [16]byte { |
||||||
|
return g.toArray(binary.LittleEndian) |
||||||
|
} |
||||||
|
|
||||||
|
func (g GUID) String() string { |
||||||
|
return fmt.Sprintf( |
||||||
|
"%08x-%04x-%04x-%04x-%012x", |
||||||
|
g.Data1, |
||||||
|
g.Data2, |
||||||
|
g.Data3, |
||||||
|
g.Data4[:2], |
||||||
|
g.Data4[2:]) |
||||||
|
} |
||||||
|
|
||||||
|
// FromString parses a string containing a GUID and returns the GUID. The only
|
||||||
|
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||||
|
// format.
|
||||||
|
func FromString(s string) (GUID, error) { |
||||||
|
if len(s) != 36 { |
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s) |
||||||
|
} |
||||||
|
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { |
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s) |
||||||
|
} |
||||||
|
|
||||||
|
var g GUID |
||||||
|
|
||||||
|
data1, err := strconv.ParseUint(s[0:8], 16, 32) |
||||||
|
if err != nil { |
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s) |
||||||
|
} |
||||||
|
g.Data1 = uint32(data1) |
||||||
|
|
||||||
|
data2, err := strconv.ParseUint(s[9:13], 16, 16) |
||||||
|
if err != nil { |
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s) |
||||||
|
} |
||||||
|
g.Data2 = uint16(data2) |
||||||
|
|
||||||
|
data3, err := strconv.ParseUint(s[14:18], 16, 16) |
||||||
|
if err != nil { |
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s) |
||||||
|
} |
||||||
|
g.Data3 = uint16(data3) |
||||||
|
|
||||||
|
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} { |
||||||
|
v, err := strconv.ParseUint(s[x:x+2], 16, 8) |
||||||
|
if err != nil { |
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s) |
||||||
|
} |
||||||
|
g.Data4[i] = uint8(v) |
||||||
|
} |
||||||
|
|
||||||
|
return g, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (g *GUID) setVariant(v Variant) { |
||||||
|
d := g.Data4[0] |
||||||
|
switch v { |
||||||
|
case VariantNCS: |
||||||
|
d = (d & 0x7f) |
||||||
|
case VariantRFC4122: |
||||||
|
d = (d & 0x3f) | 0x80 |
||||||
|
case VariantMicrosoft: |
||||||
|
d = (d & 0x1f) | 0xc0 |
||||||
|
case VariantFuture: |
||||||
|
d = (d & 0x0f) | 0xe0 |
||||||
|
case VariantUnknown: |
||||||
|
fallthrough |
||||||
|
default: |
||||||
|
panic(fmt.Sprintf("invalid variant: %d", v)) |
||||||
|
} |
||||||
|
g.Data4[0] = d |
||||||
|
} |
||||||
|
|
||||||
|
// Variant returns the GUID variant, as defined in RFC 4122.
|
||||||
|
func (g GUID) Variant() Variant { |
||||||
|
b := g.Data4[0] |
||||||
|
if b&0x80 == 0 { |
||||||
|
return VariantNCS |
||||||
|
} else if b&0xc0 == 0x80 { |
||||||
|
return VariantRFC4122 |
||||||
|
} else if b&0xe0 == 0xc0 { |
||||||
|
return VariantMicrosoft |
||||||
|
} else if b&0xe0 == 0xe0 { |
||||||
|
return VariantFuture |
||||||
|
} |
||||||
|
return VariantUnknown |
||||||
|
} |
||||||
|
|
||||||
|
func (g *GUID) setVersion(v Version) { |
||||||
|
g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12) |
||||||
|
} |
||||||
|
|
||||||
|
// Version returns the GUID version, as defined in RFC 4122.
|
||||||
|
func (g GUID) Version() Version { |
||||||
|
return Version((g.Data3 & 0xF000) >> 12) |
||||||
|
} |
||||||
|
|
||||||
|
// MarshalText returns the textual representation of the GUID.
|
||||||
|
func (g GUID) MarshalText() ([]byte, error) { |
||||||
|
return []byte(g.String()), nil |
||||||
|
} |
||||||
|
|
||||||
|
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
|
||||||
|
// into this GUID.
|
||||||
|
func (g *GUID) UnmarshalText(text []byte) error { |
||||||
|
g2, err := FromString(string(text)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
*g = g2 |
||||||
|
return nil |
||||||
|
} |
@ -1,3 +1,3 @@ |
|||||||
package winio |
package winio |
||||||
|
|
||||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go
|
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
|
||||||
|
@ -0,0 +1,21 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2014 Brian Goff |
||||||
|
|
||||||
|
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,14 @@ |
|||||||
|
package md2man |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/russross/blackfriday/v2" |
||||||
|
) |
||||||
|
|
||||||
|
// Render converts a markdown document into a roff formatted document.
|
||||||
|
func Render(doc []byte) []byte { |
||||||
|
renderer := NewRoffRenderer() |
||||||
|
|
||||||
|
return blackfriday.Run(doc, |
||||||
|
[]blackfriday.Option{blackfriday.WithRenderer(renderer), |
||||||
|
blackfriday.WithExtensions(renderer.GetExtensions())}...) |
||||||
|
} |
@ -0,0 +1,345 @@ |
|||||||
|
package md2man |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"os" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/russross/blackfriday/v2" |
||||||
|
) |
||||||
|
|
||||||
|
// roffRenderer implements the blackfriday.Renderer interface for creating
|
||||||
|
// roff format (manpages) from markdown text
|
||||||
|
type roffRenderer struct { |
||||||
|
extensions blackfriday.Extensions |
||||||
|
listCounters []int |
||||||
|
firstHeader bool |
||||||
|
defineTerm bool |
||||||
|
listDepth int |
||||||
|
} |
||||||
|
|
||||||
|
const ( |
||||||
|
titleHeader = ".TH " |
||||||
|
topLevelHeader = "\n\n.SH " |
||||||
|
secondLevelHdr = "\n.SH " |
||||||
|
otherHeader = "\n.SS " |
||||||
|
crTag = "\n" |
||||||
|
emphTag = "\\fI" |
||||||
|
emphCloseTag = "\\fP" |
||||||
|
strongTag = "\\fB" |
||||||
|
strongCloseTag = "\\fP" |
||||||
|
breakTag = "\n.br\n" |
||||||
|
paraTag = "\n.PP\n" |
||||||
|
hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n" |
||||||
|
linkTag = "\n\\[la]" |
||||||
|
linkCloseTag = "\\[ra]" |
||||||
|
codespanTag = "\\fB\\fC" |
||||||
|
codespanCloseTag = "\\fR" |
||||||
|
codeTag = "\n.PP\n.RS\n\n.nf\n" |
||||||
|
codeCloseTag = "\n.fi\n.RE\n" |
||||||
|
quoteTag = "\n.PP\n.RS\n" |
||||||
|
quoteCloseTag = "\n.RE\n" |
||||||
|
listTag = "\n.RS\n" |
||||||
|
listCloseTag = "\n.RE\n" |
||||||
|
arglistTag = "\n.TP\n" |
||||||
|
tableStart = "\n.TS\nallbox;\n" |
||||||
|
tableEnd = ".TE\n" |
||||||
|
tableCellStart = "T{\n" |
||||||
|
tableCellEnd = "\nT}\n" |
||||||
|
) |
||||||
|
|
||||||
|
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
|
||||||
|
// from markdown
|
||||||
|
func NewRoffRenderer() *roffRenderer { // nolint: golint
|
||||||
|
var extensions blackfriday.Extensions |
||||||
|
|
||||||
|
extensions |= blackfriday.NoIntraEmphasis |
||||||
|
extensions |= blackfriday.Tables |
||||||
|
extensions |= blackfriday.FencedCode |
||||||
|
extensions |= blackfriday.SpaceHeadings |
||||||
|
extensions |= blackfriday.Footnotes |
||||||
|
extensions |= blackfriday.Titleblock |
||||||
|
extensions |= blackfriday.DefinitionLists |
||||||
|
return &roffRenderer{ |
||||||
|
extensions: extensions, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// GetExtensions returns the list of extensions used by this renderer implementation
|
||||||
|
func (r *roffRenderer) GetExtensions() blackfriday.Extensions { |
||||||
|
return r.extensions |
||||||
|
} |
||||||
|
|
||||||
|
// RenderHeader handles outputting the header at document start
|
||||||
|
func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) { |
||||||
|
// disable hyphenation
|
||||||
|
out(w, ".nh\n") |
||||||
|
} |
||||||
|
|
||||||
|
// RenderFooter handles outputting the footer at the document end; the roff
|
||||||
|
// renderer has no footer information
|
||||||
|
func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) { |
||||||
|
} |
||||||
|
|
||||||
|
// RenderNode is called for each node in a markdown document; based on the node
|
||||||
|
// type the equivalent roff output is sent to the writer
|
||||||
|
func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus { |
||||||
|
|
||||||
|
var walkAction = blackfriday.GoToNext |
||||||
|
|
||||||
|
switch node.Type { |
||||||
|
case blackfriday.Text: |
||||||
|
r.handleText(w, node, entering) |
||||||
|
case blackfriday.Softbreak: |
||||||
|
out(w, crTag) |
||||||
|
case blackfriday.Hardbreak: |
||||||
|
out(w, breakTag) |
||||||
|
case blackfriday.Emph: |
||||||
|
if entering { |
||||||
|
out(w, emphTag) |
||||||
|
} else { |
||||||
|
out(w, emphCloseTag) |
||||||
|
} |
||||||
|
case blackfriday.Strong: |
||||||
|
if entering { |
||||||
|
out(w, strongTag) |
||||||
|
} else { |
||||||
|
out(w, strongCloseTag) |
||||||
|
} |
||||||
|
case blackfriday.Link: |
||||||
|
if !entering { |
||||||
|
out(w, linkTag+string(node.LinkData.Destination)+linkCloseTag) |
||||||
|
} |
||||||
|
case blackfriday.Image: |
||||||
|
// ignore images
|
||||||
|
walkAction = blackfriday.SkipChildren |
||||||
|
case blackfriday.Code: |
||||||
|
out(w, codespanTag) |
||||||
|
escapeSpecialChars(w, node.Literal) |
||||||
|
out(w, codespanCloseTag) |
||||||
|
case blackfriday.Document: |
||||||
|
break |
||||||
|
case blackfriday.Paragraph: |
||||||
|
// roff .PP markers break lists
|
||||||
|
if r.listDepth > 0 { |
||||||
|
return blackfriday.GoToNext |
||||||
|
} |
||||||
|
if entering { |
||||||
|
out(w, paraTag) |
||||||
|
} else { |
||||||
|
out(w, crTag) |
||||||
|
} |
||||||
|
case blackfriday.BlockQuote: |
||||||
|
if entering { |
||||||
|
out(w, quoteTag) |
||||||
|
} else { |
||||||
|
out(w, quoteCloseTag) |
||||||
|
} |
||||||
|
case blackfriday.Heading: |
||||||
|
r.handleHeading(w, node, entering) |
||||||
|
case blackfriday.HorizontalRule: |
||||||
|
out(w, hruleTag) |
||||||
|
case blackfriday.List: |
||||||
|
r.handleList(w, node, entering) |
||||||
|
case blackfriday.Item: |
||||||
|
r.handleItem(w, node, entering) |
||||||
|
case blackfriday.CodeBlock: |
||||||
|
out(w, codeTag) |
||||||
|
escapeSpecialChars(w, node.Literal) |
||||||
|
out(w, codeCloseTag) |
||||||
|
case blackfriday.Table: |
||||||
|
r.handleTable(w, node, entering) |
||||||
|
case blackfriday.TableCell: |
||||||
|
r.handleTableCell(w, node, entering) |
||||||
|
case blackfriday.TableHead: |
||||||
|
case blackfriday.TableBody: |
||||||
|
case blackfriday.TableRow: |
||||||
|
// no action as cell entries do all the nroff formatting
|
||||||
|
return blackfriday.GoToNext |
||||||
|
default: |
||||||
|
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String()) |
||||||
|
} |
||||||
|
return walkAction |
||||||
|
} |
||||||
|
|
||||||
|
func (r *roffRenderer) handleText(w io.Writer, node *blackfriday.Node, entering bool) { |
||||||
|
var ( |
||||||
|
start, end string |
||||||
|
) |
||||||
|
// handle special roff table cell text encapsulation
|
||||||
|
if node.Parent.Type == blackfriday.TableCell { |
||||||
|
if len(node.Literal) > 30 { |
||||||
|
start = tableCellStart |
||||||
|
end = tableCellEnd |
||||||
|
} else { |
||||||
|
// end rows that aren't terminated by "tableCellEnd" with a cr if end of row
|
||||||
|
if node.Parent.Next == nil && !node.Parent.IsHeader { |
||||||
|
end = crTag |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
out(w, start) |
||||||
|
escapeSpecialChars(w, node.Literal) |
||||||
|
out(w, end) |
||||||
|
} |
||||||
|
|
||||||
|
func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) { |
||||||
|
if entering { |
||||||
|
switch node.Level { |
||||||
|
case 1: |
||||||
|
if !r.firstHeader { |
||||||
|
out(w, titleHeader) |
||||||
|
r.firstHeader = true |
||||||
|
break |
||||||
|
} |
||||||
|
out(w, topLevelHeader) |
||||||
|
case 2: |
||||||
|
out(w, secondLevelHdr) |
||||||
|
default: |
||||||
|
out(w, otherHeader) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) { |
||||||
|
openTag := listTag |
||||||
|
closeTag := listCloseTag |
||||||
|
if node.ListFlags&blackfriday.ListTypeDefinition != 0 { |
||||||
|
// tags for definition lists handled within Item node
|
||||||
|
openTag = "" |
||||||
|
closeTag = "" |
||||||
|
} |
||||||
|
if entering { |
||||||
|
r.listDepth++ |
||||||
|
if node.ListFlags&blackfriday.ListTypeOrdered != 0 { |
||||||
|
r.listCounters = append(r.listCounters, 1) |
||||||
|
} |
||||||
|
out(w, openTag) |
||||||
|
} else { |
||||||
|
if node.ListFlags&blackfriday.ListTypeOrdered != 0 { |
||||||
|
r.listCounters = r.listCounters[:len(r.listCounters)-1] |
||||||
|
} |
||||||
|
out(w, closeTag) |
||||||
|
r.listDepth-- |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) { |
||||||
|
if entering { |
||||||
|
if node.ListFlags&blackfriday.ListTypeOrdered != 0 { |
||||||
|
out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1])) |
||||||
|
r.listCounters[len(r.listCounters)-1]++ |
||||||
|
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 { |
||||||
|
// state machine for handling terms and following definitions
|
||||||
|
// since blackfriday does not distinguish them properly, nor
|
||||||
|
// does it seperate them into separate lists as it should
|
||||||
|
if !r.defineTerm { |
||||||
|
out(w, arglistTag) |
||||||
|
r.defineTerm = true |
||||||
|
} else { |
||||||
|
r.defineTerm = false |
||||||
|
} |
||||||
|
} else { |
||||||
|
out(w, ".IP \\(bu 2\n") |
||||||
|
} |
||||||
|
} else { |
||||||
|
out(w, "\n") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) { |
||||||
|
if entering { |
||||||
|
out(w, tableStart) |
||||||
|
//call walker to count cells (and rows?) so format section can be produced
|
||||||
|
columns := countColumns(node) |
||||||
|
out(w, strings.Repeat("l ", columns)+"\n") |
||||||
|
out(w, strings.Repeat("l ", columns)+".\n") |
||||||
|
} else { |
||||||
|
out(w, tableEnd) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) { |
||||||
|
var ( |
||||||
|
start, end string |
||||||
|
) |
||||||
|
if node.IsHeader { |
||||||
|
start = codespanTag |
||||||
|
end = codespanCloseTag |
||||||
|
} |
||||||
|
if entering { |
||||||
|
if node.Prev != nil && node.Prev.Type == blackfriday.TableCell { |
||||||
|
out(w, "\t"+start) |
||||||
|
} else { |
||||||
|
out(w, start) |
||||||
|
} |
||||||
|
} else { |
||||||
|
// need to carriage return if we are at the end of the header row
|
||||||
|
if node.IsHeader && node.Next == nil { |
||||||
|
end = end + crTag |
||||||
|
} |
||||||
|
out(w, end) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// because roff format requires knowing the column count before outputting any table
|
||||||
|
// data we need to walk a table tree and count the columns
|
||||||
|
func countColumns(node *blackfriday.Node) int { |
||||||
|
var columns int |
||||||
|
|
||||||
|
node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus { |
||||||
|
switch node.Type { |
||||||
|
case blackfriday.TableRow: |
||||||
|
if !entering { |
||||||
|
return blackfriday.Terminate |
||||||
|
} |
||||||
|
case blackfriday.TableCell: |
||||||
|
if entering { |
||||||
|
columns++ |
||||||
|
} |
||||||
|
default: |
||||||
|
} |
||||||
|
return blackfriday.GoToNext |
||||||
|
}) |
||||||
|
return columns |
||||||
|
} |
||||||
|
|
||||||
|
func out(w io.Writer, output string) { |
||||||
|
io.WriteString(w, output) // nolint: errcheck
|
||||||
|
} |
||||||
|
|
||||||
|
func needsBackslash(c byte) bool { |
||||||
|
for _, r := range []byte("-_&\\~") { |
||||||
|
if c == r { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func escapeSpecialChars(w io.Writer, text []byte) { |
||||||
|
for i := 0; i < len(text); i++ { |
||||||
|
// escape initial apostrophe or period
|
||||||
|
if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') { |
||||||
|
out(w, "\\&") |
||||||
|
} |
||||||
|
|
||||||
|
// directly copy normal characters
|
||||||
|
org := i |
||||||
|
|
||||||
|
for i < len(text) && !needsBackslash(text[i]) { |
||||||
|
i++ |
||||||
|
} |
||||||
|
if i > org { |
||||||
|
w.Write(text[org:i]) // nolint: errcheck
|
||||||
|
} |
||||||
|
|
||||||
|
// escape a character
|
||||||
|
if i >= len(text) { |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
w.Write([]byte{'\\', text[i]}) // nolint: errcheck
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
*.out |
||||||
|
*.swp |
||||||
|
*.8 |
||||||
|
*.6 |
||||||
|
_obj |
||||||
|
_test* |
||||||
|
markdown |
||||||
|
tags |
@ -0,0 +1,17 @@ |
|||||||
|
sudo: false |
||||||
|
language: go |
||||||
|
go: |
||||||
|
- "1.10.x" |
||||||
|
- "1.11.x" |
||||||
|
- tip |
||||||
|
matrix: |
||||||
|
fast_finish: true |
||||||
|
allow_failures: |
||||||
|
- go: tip |
||||||
|
install: |
||||||
|
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). |
||||||
|
script: |
||||||
|
- go get -t -v ./... |
||||||
|
- diff -u <(echo -n) <(gofmt -d -s .) |
||||||
|
- go tool vet . |
||||||
|
- go test -v ./... |
@ -0,0 +1,29 @@ |
|||||||
|
Blackfriday is distributed under the Simplified BSD License: |
||||||
|
|
||||||
|
> Copyright © 2011 Russ Ross |
||||||
|
> All rights reserved. |
||||||
|
> |
||||||
|
> Redistribution and use in source and binary forms, with or without |
||||||
|
> modification, are permitted provided that the following conditions |
||||||
|
> are met: |
||||||
|
> |
||||||
|
> 1. Redistributions of source code must retain the above copyright |
||||||
|
> notice, this list of conditions and the following disclaimer. |
||||||
|
> |
||||||
|
> 2. Redistributions in binary form must reproduce the above |
||||||
|
> copyright notice, this list of conditions and the following |
||||||
|
> disclaimer in the documentation and/or other materials provided with |
||||||
|
> the distribution. |
||||||
|
> |
||||||
|
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||||||
|
> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||||||
|
> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||||
|
> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||||
|
> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||||
|
> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||||
|
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||||
|
> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||||
|
> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||||
|
> POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,291 @@ |
|||||||
|
Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday) |
||||||
|
=========== |
||||||
|
|
||||||
|
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It |
||||||
|
is paranoid about its input (so you can safely feed it user-supplied |
||||||
|
data), it is fast, it supports common extensions (tables, smart |
||||||
|
punctuation substitutions, etc.), and it is safe for all utf-8 |
||||||
|
(unicode) input. |
||||||
|
|
||||||
|
HTML output is currently supported, along with Smartypants |
||||||
|
extensions. |
||||||
|
|
||||||
|
It started as a translation from C of [Sundown][3]. |
||||||
|
|
||||||
|
|
||||||
|
Installation |
||||||
|
------------ |
||||||
|
|
||||||
|
Blackfriday is compatible with any modern Go release. With Go 1.7 and git |
||||||
|
installed: |
||||||
|
|
||||||
|
go get gopkg.in/russross/blackfriday.v2 |
||||||
|
|
||||||
|
will download, compile, and install the package into your `$GOPATH` |
||||||
|
directory hierarchy. Alternatively, you can achieve the same if you |
||||||
|
import it into a project: |
||||||
|
|
||||||
|
import "gopkg.in/russross/blackfriday.v2" |
||||||
|
|
||||||
|
and `go get` without parameters. |
||||||
|
|
||||||
|
|
||||||
|
Versions |
||||||
|
-------- |
||||||
|
|
||||||
|
Currently maintained and recommended version of Blackfriday is `v2`. It's being |
||||||
|
developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the |
||||||
|
documentation is available at |
||||||
|
https://godoc.org/gopkg.in/russross/blackfriday.v2. |
||||||
|
|
||||||
|
It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`, |
||||||
|
but we highly recommend using package management tool like [dep][7] or |
||||||
|
[Glide][8] and make use of semantic versioning. With package management you |
||||||
|
should import `github.com/russross/blackfriday` and specify that you're using |
||||||
|
version 2.0.0. |
||||||
|
|
||||||
|
Version 2 offers a number of improvements over v1: |
||||||
|
|
||||||
|
* Cleaned up API |
||||||
|
* A separate call to [`Parse`][4], which produces an abstract syntax tree for |
||||||
|
the document |
||||||
|
* Latest bug fixes |
||||||
|
* Flexibility to easily add your own rendering extensions |
||||||
|
|
||||||
|
Potential drawbacks: |
||||||
|
|
||||||
|
* Our benchmarks show v2 to be slightly slower than v1. Currently in the |
||||||
|
ballpark of around 15%. |
||||||
|
* API breakage. If you can't afford modifying your code to adhere to the new API |
||||||
|
and don't care too much about the new features, v2 is probably not for you. |
||||||
|
* Several bug fixes are trailing behind and still need to be forward-ported to |
||||||
|
v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for |
||||||
|
tracking. |
||||||
|
|
||||||
|
Usage |
||||||
|
----- |
||||||
|
|
||||||
|
For the most sensible markdown processing, it is as simple as getting your input |
||||||
|
into a byte slice and calling: |
||||||
|
|
||||||
|
```go |
||||||
|
output := blackfriday.Run(input) |
||||||
|
``` |
||||||
|
|
||||||
|
Your input will be parsed and the output rendered with a set of most popular |
||||||
|
extensions enabled. If you want the most basic feature set, corresponding with |
||||||
|
the bare Markdown specification, use: |
||||||
|
|
||||||
|
```go |
||||||
|
output := blackfriday.Run(input, blackfriday.WithNoExtensions()) |
||||||
|
``` |
||||||
|
|
||||||
|
### Sanitize untrusted content |
||||||
|
|
||||||
|
Blackfriday itself does nothing to protect against malicious content. If you are |
||||||
|
dealing with user-supplied markdown, we recommend running Blackfriday's output |
||||||
|
through HTML sanitizer such as [Bluemonday][5]. |
||||||
|
|
||||||
|
Here's an example of simple usage of Blackfriday together with Bluemonday: |
||||||
|
|
||||||
|
```go |
||||||
|
import ( |
||||||
|
"github.com/microcosm-cc/bluemonday" |
||||||
|
"github.com/russross/blackfriday" |
||||||
|
) |
||||||
|
|
||||||
|
// ... |
||||||
|
unsafe := blackfriday.Run(input) |
||||||
|
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) |
||||||
|
``` |
||||||
|
|
||||||
|
### Custom options |
||||||
|
|
||||||
|
If you want to customize the set of options, use `blackfriday.WithExtensions`, |
||||||
|
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`. |
||||||
|
|
||||||
|
You can also check out `blackfriday-tool` for a more complete example |
||||||
|
of how to use it. Download and install it using: |
||||||
|
|
||||||
|
go get github.com/russross/blackfriday-tool |
||||||
|
|
||||||
|
This is a simple command-line tool that allows you to process a |
||||||
|
markdown file using a standalone program. You can also browse the |
||||||
|
source directly on github if you are just looking for some example |
||||||
|
code: |
||||||
|
|
||||||
|
* <http://github.com/russross/blackfriday-tool> |
||||||
|
|
||||||
|
Note that if you have not already done so, installing |
||||||
|
`blackfriday-tool` will be sufficient to download and install |
||||||
|
blackfriday in addition to the tool itself. The tool binary will be |
||||||
|
installed in `$GOPATH/bin`. This is a statically-linked binary that |
||||||
|
can be copied to wherever you need it without worrying about |
||||||
|
dependencies and library versions. |
||||||
|
|
||||||
|
|
||||||
|
Features |
||||||
|
-------- |
||||||
|
|
||||||
|
All features of Sundown are supported, including: |
||||||
|
|
||||||
|
* **Compatibility**. The Markdown v1.0.3 test suite passes with |
||||||
|
the `--tidy` option. Without `--tidy`, the differences are |
||||||
|
mostly in whitespace and entity escaping, where blackfriday is |
||||||
|
more consistent and cleaner. |
||||||
|
|
||||||
|
* **Common extensions**, including table support, fenced code |
||||||
|
blocks, autolinks, strikethroughs, non-strict emphasis, etc. |
||||||
|
|
||||||
|
* **Safety**. Blackfriday is paranoid when parsing, making it safe |
||||||
|
to feed untrusted user input without fear of bad things |
||||||
|
happening. The test suite stress tests this and there are no |
||||||
|
known inputs that make it crash. If you find one, please let me |
||||||
|
know and send me the input that does it. |
||||||
|
|
||||||
|
NOTE: "safety" in this context means *runtime safety only*. In order to |
||||||
|
protect yourself against JavaScript injection in untrusted content, see |
||||||
|
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content). |
||||||
|
|
||||||
|
* **Fast processing**. It is fast enough to render on-demand in |
||||||
|
most web applications without having to cache the output. |
||||||
|
|
||||||
|
* **Thread safety**. You can run multiple parsers in different |
||||||
|
goroutines without ill effect. There is no dependence on global |
||||||
|
shared state. |
||||||
|
|
||||||
|
* **Minimal dependencies**. Blackfriday only depends on standard |
||||||
|
library packages in Go. The source code is pretty |
||||||
|
self-contained, so it is easy to add to any project, including |
||||||
|
Google App Engine projects. |
||||||
|
|
||||||
|
* **Standards compliant**. Output successfully validates using the |
||||||
|
W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional. |
||||||
|
|
||||||
|
|
||||||
|
Extensions |
||||||
|
---------- |
||||||
|
|
||||||
|
In addition to the standard markdown syntax, this package |
||||||
|
implements the following extensions: |
||||||
|
|
||||||
|
* **Intra-word emphasis supression**. The `_` character is |
||||||
|
commonly used inside words when discussing code, so having |
||||||
|
markdown interpret it as an emphasis command is usually the |
||||||
|
wrong thing. Blackfriday lets you treat all emphasis markers as |
||||||
|
normal characters when they occur inside a word. |
||||||
|
|
||||||
|
* **Tables**. Tables can be created by drawing them in the input |
||||||
|
using a simple syntax: |
||||||
|
|
||||||
|
``` |
||||||
|
Name | Age |
||||||
|
--------|------ |
||||||
|
Bob | 27 |
||||||
|
Alice | 23 |
||||||
|
``` |
||||||
|
|
||||||
|
* **Fenced code blocks**. In addition to the normal 4-space |
||||||
|
indentation to mark code blocks, you can explicitly mark them |
||||||
|
and supply a language (to make syntax highlighting simple). Just |
||||||
|
mark it like this: |
||||||
|
|
||||||
|
```go |
||||||
|
func getTrue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
You can use 3 or more backticks to mark the beginning of the |
||||||
|
block, and the same number to mark the end of the block. |
||||||
|
|
||||||
|
* **Definition lists**. A simple definition list is made of a single-line |
||||||
|
term followed by a colon and the definition for that term. |
||||||
|
|
||||||
|
Cat |
||||||
|
: Fluffy animal everyone likes |
||||||
|
|
||||||
|
Internet |
||||||
|
: Vector of transmission for pictures of cats |
||||||
|
|
||||||
|
Terms must be separated from the previous definition by a blank line. |
||||||
|
|
||||||
|
* **Footnotes**. A marker in the text that will become a superscript number; |
||||||
|
a footnote definition that will be placed in a list of footnotes at the |
||||||
|
end of the document. A footnote looks like this: |
||||||
|
|
||||||
|
This is a footnote.[^1] |
||||||
|
|
||||||
|
[^1]: the footnote text. |
||||||
|
|
||||||
|
* **Autolinking**. Blackfriday can find URLs that have not been |
||||||
|
explicitly marked as links and turn them into links. |
||||||
|
|
||||||
|
* **Strikethrough**. Use two tildes (`~~`) to mark text that |
||||||
|
should be crossed out. |
||||||
|
|
||||||
|
* **Hard line breaks**. With this extension enabled newlines in the input |
||||||
|
translate into line breaks in the output. This extension is off by default. |
||||||
|
|
||||||
|
* **Smart quotes**. Smartypants-style punctuation substitution is |
||||||
|
supported, turning normal double- and single-quote marks into |
||||||
|
curly quotes, etc. |
||||||
|
|
||||||
|
* **LaTeX-style dash parsing** is an additional option, where `--` |
||||||
|
is translated into `–`, and `---` is translated into |
||||||
|
`—`. This differs from most smartypants processors, which |
||||||
|
turn a single hyphen into an ndash and a double hyphen into an |
||||||
|
mdash. |
||||||
|
|
||||||
|
* **Smart fractions**, where anything that looks like a fraction |
||||||
|
is translated into suitable HTML (instead of just a few special |
||||||
|
cases like most smartypant processors). For example, `4/5` |
||||||
|
becomes `<sup>4</sup>⁄<sub>5</sub>`, which renders as |
||||||
|
<sup>4</sup>⁄<sub>5</sub>. |
||||||
|
|
||||||
|
|
||||||
|
Other renderers |
||||||
|
--------------- |
||||||
|
|
||||||
|
Blackfriday is structured to allow alternative rendering engines. Here |
||||||
|
are a few of note: |
||||||
|
|
||||||
|
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown): |
||||||
|
provides a GitHub Flavored Markdown renderer with fenced code block |
||||||
|
highlighting, clickable heading anchor links. |
||||||
|
|
||||||
|
It's not customizable, and its goal is to produce HTML output |
||||||
|
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode), |
||||||
|
except the rendering is performed locally. |
||||||
|
|
||||||
|
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt, |
||||||
|
but for markdown. |
||||||
|
|
||||||
|
* [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX): |
||||||
|
renders output as LaTeX. |
||||||
|
|
||||||
|
* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer. |
||||||
|
|
||||||
|
|
||||||
|
Todo |
||||||
|
---- |
||||||
|
|
||||||
|
* More unit testing |
||||||
|
* Improve unicode support. It does not understand all unicode |
||||||
|
rules (about what constitutes a letter, a punctuation symbol, |
||||||
|
etc.), so it may fail to detect word boundaries correctly in |
||||||
|
some instances. It is safe on all utf-8 input. |
||||||
|
|
||||||
|
|
||||||
|
License |
||||||
|
------- |
||||||
|
|
||||||
|
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt) |
||||||
|
|
||||||
|
|
||||||
|
[1]: https://daringfireball.net/projects/markdown/ "Markdown" |
||||||
|
[2]: https://golang.org/ "Go Language" |
||||||
|
[3]: https://github.com/vmg/sundown "Sundown" |
||||||
|
[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func" |
||||||
|
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday" |
||||||
|
[6]: https://labix.org/gopkg.in "gopkg.in" |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,18 @@ |
|||||||
|
// Package blackfriday is a markdown processor.
|
||||||
|
//
|
||||||
|
// It translates plain text with simple formatting rules into an AST, which can
|
||||||
|
// then be further processed to HTML (provided by Blackfriday itself) or other
|
||||||
|
// formats (provided by the community).
|
||||||
|
//
|
||||||
|
// The simplest way to invoke Blackfriday is to call the Run function. It will
|
||||||
|
// take a text input and produce a text output in HTML (or other format).
|
||||||
|
//
|
||||||
|
// A slightly more sophisticated way to use Blackfriday is to create a Markdown
|
||||||
|
// processor and to call Parse, which returns a syntax tree for the input
|
||||||
|
// document. You can leverage Blackfriday's parsing for content extraction from
|
||||||
|
// markdown documents. You can assign a custom renderer and set various options
|
||||||
|
// to the Markdown processor.
|
||||||
|
//
|
||||||
|
// If you're interested in calling Blackfriday from command line, see
|
||||||
|
// https://github.com/russross/blackfriday-tool.
|
||||||
|
package blackfriday |
@ -0,0 +1,34 @@ |
|||||||
|
package blackfriday |
||||||
|
|
||||||
|
import ( |
||||||
|
"html" |
||||||
|
"io" |
||||||
|
) |
||||||
|
|
||||||
|
var htmlEscaper = [256][]byte{ |
||||||
|
'&': []byte("&"), |
||||||
|
'<': []byte("<"), |
||||||
|
'>': []byte(">"), |
||||||
|
'"': []byte("""), |
||||||
|
} |
||||||
|
|
||||||
|
func escapeHTML(w io.Writer, s []byte) { |
||||||
|
var start, end int |
||||||
|
for end < len(s) { |
||||||
|
escSeq := htmlEscaper[s[end]] |
||||||
|
if escSeq != nil { |
||||||
|
w.Write(s[start:end]) |
||||||
|
w.Write(escSeq) |
||||||
|
start = end + 1 |
||||||
|
} |
||||||
|
end++ |
||||||
|
} |
||||||
|
if start < len(s) && end <= len(s) { |
||||||
|
w.Write(s[start:end]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func escLink(w io.Writer, text []byte) { |
||||||
|
unesc := html.UnescapeString(string(text)) |
||||||
|
escapeHTML(w, []byte(unesc)) |
||||||
|
} |
@ -0,0 +1,949 @@ |
|||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// HTML rendering backend
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
// HTMLFlags control optional behavior of HTML renderer.
|
||||||
|
type HTMLFlags int |
||||||
|
|
||||||
|
// HTML renderer configuration options.
|
||||||
|
const ( |
||||||
|
HTMLFlagsNone HTMLFlags = 0 |
||||||
|
SkipHTML HTMLFlags = 1 << iota // Skip preformatted HTML blocks
|
||||||
|
SkipImages // Skip embedded images
|
||||||
|
SkipLinks // Skip all links
|
||||||
|
Safelink // Only link to trusted protocols
|
||||||
|
NofollowLinks // Only link with rel="nofollow"
|
||||||
|
NoreferrerLinks // Only link with rel="noreferrer"
|
||||||
|
NoopenerLinks // Only link with rel="noopener"
|
||||||
|
HrefTargetBlank // Add a blank target
|
||||||
|
CompletePage // Generate a complete HTML page
|
||||||
|
UseXHTML // Generate XHTML output instead of HTML
|
||||||
|
FootnoteReturnLinks // Generate a link at the end of a footnote to return to the source
|
||||||
|
Smartypants // Enable smart punctuation substitutions
|
||||||
|
SmartypantsFractions // Enable smart fractions (with Smartypants)
|
||||||
|
SmartypantsDashes // Enable smart dashes (with Smartypants)
|
||||||
|
SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants)
|
||||||
|
SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering
|
||||||
|
SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants)
|
||||||
|
TOC // Generate a table of contents
|
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag) |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" + |
||||||
|
processingInstruction + "|" + declaration + "|" + cdata + ")" |
||||||
|
closeTag = "</" + tagName + "\\s*[>]" |
||||||
|
openTag = "<" + tagName + attribute + "*" + "\\s*/?>" |
||||||
|
attribute = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)" |
||||||
|
attributeValue = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")" |
||||||
|
attributeValueSpec = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")" |
||||||
|
attributeName = "[a-zA-Z_:][a-zA-Z0-9:._-]*" |
||||||
|
cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>" |
||||||
|
declaration = "<![A-Z]+" + "\\s+[^>]*>" |
||||||
|
doubleQuotedValue = "\"[^\"]*\"" |
||||||
|
htmlComment = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->" |
||||||
|
processingInstruction = "[<][?].*?[?][>]" |
||||||
|
singleQuotedValue = "'[^']*'" |
||||||
|
tagName = "[A-Za-z][A-Za-z0-9-]*" |
||||||
|
unquotedValue = "[^\"'=<>`\\x00-\\x20]+" |
||||||
|
) |
||||||
|
|
||||||
|
// HTMLRendererParameters is a collection of supplementary parameters tweaking
|
||||||
|
// the behavior of various parts of HTML renderer.
|
||||||
|
type HTMLRendererParameters struct { |
||||||
|
// Prepend this text to each relative URL.
|
||||||
|
AbsolutePrefix string |
||||||
|
// Add this text to each footnote anchor, to ensure uniqueness.
|
||||||
|
FootnoteAnchorPrefix string |
||||||
|
// Show this text inside the <a> tag for a footnote return link, if the
|
||||||
|
// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
|
||||||
|
// <sup>[return]</sup> is used.
|
||||||
|
FootnoteReturnLinkContents string |
||||||
|
// If set, add this text to the front of each Heading ID, to ensure
|
||||||
|
// uniqueness.
|
||||||
|
HeadingIDPrefix string |
||||||
|
// If set, add this text to the back of each Heading ID, to ensure uniqueness.
|
||||||
|
HeadingIDSuffix string |
||||||
|
// Increase heading levels: if the offset is 1, <h1> becomes <h2> etc.
|
||||||
|
// Negative offset is also valid.
|
||||||
|
// Resulting levels are clipped between 1 and 6.
|
||||||
|
HeadingLevelOffset int |
||||||
|
|
||||||
|
Title string // Document title (used if CompletePage is set)
|
||||||
|
CSS string // Optional CSS file URL (used if CompletePage is set)
|
||||||
|
Icon string // Optional icon file URL (used if CompletePage is set)
|
||||||
|
|
||||||
|
Flags HTMLFlags // Flags allow customizing this renderer's behavior
|
||||||
|
} |
||||||
|
|
||||||
|
// HTMLRenderer is a type that implements the Renderer interface for HTML output.
|
||||||
|
//
|
||||||
|
// Do not create this directly, instead use the NewHTMLRenderer function.
|
||||||
|
type HTMLRenderer struct { |
||||||
|
HTMLRendererParameters |
||||||
|
|
||||||
|
closeTag string // how to end singleton tags: either " />" or ">"
|
||||||
|
|
||||||
|
// Track heading IDs to prevent ID collision in a single generation.
|
||||||
|
headingIDs map[string]int |
||||||
|
|
||||||
|
lastOutputLen int |
||||||
|
disableTags int |
||||||
|
|
||||||
|
sr *SPRenderer |
||||||
|
} |
||||||
|
|
||||||
|
const ( |
||||||
|
xhtmlClose = " />" |
||||||
|
htmlClose = ">" |
||||||
|
) |
||||||
|
|
||||||
|
// NewHTMLRenderer creates and configures an HTMLRenderer object, which
|
||||||
|
// satisfies the Renderer interface.
|
||||||
|
func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer { |
||||||
|
// configure the rendering engine
|
||||||
|
closeTag := htmlClose |
||||||
|
if params.Flags&UseXHTML != 0 { |
||||||
|
closeTag = xhtmlClose |
||||||
|
} |
||||||
|
|
||||||
|
if params.FootnoteReturnLinkContents == "" { |
||||||
|
params.FootnoteReturnLinkContents = `<sup>[return]</sup>` |
||||||
|
} |
||||||
|
|
||||||
|
return &HTMLRenderer{ |
||||||
|
HTMLRendererParameters: params, |
||||||
|
|
||||||
|
closeTag: closeTag, |
||||||
|
headingIDs: make(map[string]int), |
||||||
|
|
||||||
|
sr: NewSmartypantsRenderer(params.Flags), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func isHTMLTag(tag []byte, tagname string) bool { |
||||||
|
found, _ := findHTMLTagPos(tag, tagname) |
||||||
|
return found |
||||||
|
} |
||||||
|
|
||||||
|
// Look for a character, but ignore it when it's in any kind of quotes, it
|
||||||
|
// might be JavaScript
|
||||||
|
func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int { |
||||||
|
inSingleQuote := false |
||||||
|
inDoubleQuote := false |
||||||
|
inGraveQuote := false |
||||||
|
i := start |
||||||
|
for i < len(html) { |
||||||
|
switch { |
||||||
|
case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote: |
||||||
|
return i |
||||||
|
case html[i] == '\'': |
||||||
|
inSingleQuote = !inSingleQuote |
||||||
|
case html[i] == '"': |
||||||
|
inDoubleQuote = !inDoubleQuote |
||||||
|
case html[i] == '`': |
||||||
|
inGraveQuote = !inGraveQuote |
||||||
|
} |
||||||
|
i++ |
||||||
|
} |
||||||
|
return start |
||||||
|
} |
||||||
|
|
||||||
|
func findHTMLTagPos(tag []byte, tagname string) (bool, int) { |
||||||
|
i := 0 |
||||||
|
if i < len(tag) && tag[0] != '<' { |
||||||
|
return false, -1 |
||||||
|
} |
||||||
|
i++ |
||||||
|
i = skipSpace(tag, i) |
||||||
|
|
||||||
|
if i < len(tag) && tag[i] == '/' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
|
||||||
|
i = skipSpace(tag, i) |
||||||
|
j := 0 |
||||||
|
for ; i < len(tag); i, j = i+1, j+1 { |
||||||
|
if j >= len(tagname) { |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
if strings.ToLower(string(tag[i]))[0] != tagname[j] { |
||||||
|
return false, -1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if i == len(tag) { |
||||||
|
return false, -1 |
||||||
|
} |
||||||
|
|
||||||
|
rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>') |
||||||
|
if rightAngle >= i { |
||||||
|
return true, rightAngle |
||||||
|
} |
||||||
|
|
||||||
|
return false, -1 |
||||||
|
} |
||||||
|
|
||||||
|
func skipSpace(tag []byte, i int) int { |
||||||
|
for i < len(tag) && isspace(tag[i]) { |
||||||
|
i++ |
||||||
|
} |
||||||
|
return i |
||||||
|
} |
||||||
|
|
||||||
|
func isRelativeLink(link []byte) (yes bool) { |
||||||
|
// a tag begin with '#'
|
||||||
|
if link[0] == '#' { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// link begin with '/' but not '//', the second maybe a protocol relative link
|
||||||
|
if len(link) >= 2 && link[0] == '/' && link[1] != '/' { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// only the root '/'
|
||||||
|
if len(link) == 1 && link[0] == '/' { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// current directory : begin with "./"
|
||||||
|
if bytes.HasPrefix(link, []byte("./")) { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// parent directory : begin with "../"
|
||||||
|
if bytes.HasPrefix(link, []byte("../")) { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string { |
||||||
|
for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] { |
||||||
|
tmp := fmt.Sprintf("%s-%d", id, count+1) |
||||||
|
|
||||||
|
if _, tmpFound := r.headingIDs[tmp]; !tmpFound { |
||||||
|
r.headingIDs[id] = count + 1 |
||||||
|
id = tmp |
||||||
|
} else { |
||||||
|
id = id + "-1" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if _, found := r.headingIDs[id]; !found { |
||||||
|
r.headingIDs[id] = 0 |
||||||
|
} |
||||||
|
|
||||||
|
return id |
||||||
|
} |
||||||
|
|
||||||
|
func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte { |
||||||
|
if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' { |
||||||
|
newDest := r.AbsolutePrefix |
||||||
|
if link[0] != '/' { |
||||||
|
newDest += "/" |
||||||
|
} |
||||||
|
newDest += string(link) |
||||||
|
return []byte(newDest) |
||||||
|
} |
||||||
|
return link |
||||||
|
} |
||||||
|
|
||||||
|
func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string { |
||||||
|
if isRelativeLink(link) { |
||||||
|
return attrs |
||||||
|
} |
||||||
|
val := []string{} |
||||||
|
if flags&NofollowLinks != 0 { |
||||||
|
val = append(val, "nofollow") |
||||||
|
} |
||||||
|
if flags&NoreferrerLinks != 0 { |
||||||
|
val = append(val, "noreferrer") |
||||||
|
} |
||||||
|
if flags&NoopenerLinks != 0 { |
||||||
|
val = append(val, "noopener") |
||||||
|
} |
||||||
|
if flags&HrefTargetBlank != 0 { |
||||||
|
attrs = append(attrs, "target=\"_blank\"") |
||||||
|
} |
||||||
|
if len(val) == 0 { |
||||||
|
return attrs |
||||||
|
} |
||||||
|
attr := fmt.Sprintf("rel=%q", strings.Join(val, " ")) |
||||||
|
return append(attrs, attr) |
||||||
|
} |
||||||
|
|
||||||
|
func isMailto(link []byte) bool { |
||||||
|
return bytes.HasPrefix(link, []byte("mailto:")) |
||||||
|
} |
||||||
|
|
||||||
|
func needSkipLink(flags HTMLFlags, dest []byte) bool { |
||||||
|
if flags&SkipLinks != 0 { |
||||||
|
return true |
||||||
|
} |
||||||
|
return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest) |
||||||
|
} |
||||||
|
|
||||||
|
func isSmartypantable(node *Node) bool { |
||||||
|
pt := node.Parent.Type |
||||||
|
return pt != Link && pt != CodeBlock && pt != Code |
||||||
|
} |
||||||
|
|
||||||
|
func appendLanguageAttr(attrs []string, info []byte) []string { |
||||||
|
if len(info) == 0 { |
||||||
|
return attrs |
||||||
|
} |
||||||
|
endOfLang := bytes.IndexAny(info, "\t ") |
||||||
|
if endOfLang < 0 { |
||||||
|
endOfLang = len(info) |
||||||
|
} |
||||||
|
return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang])) |
||||||
|
} |
||||||
|
|
||||||
|
func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) { |
||||||
|
w.Write(name) |
||||||
|
if len(attrs) > 0 { |
||||||
|
w.Write(spaceBytes) |
||||||
|
w.Write([]byte(strings.Join(attrs, " "))) |
||||||
|
} |
||||||
|
w.Write(gtBytes) |
||||||
|
r.lastOutputLen = 1 |
||||||
|
} |
||||||
|
|
||||||
|
func footnoteRef(prefix string, node *Node) []byte { |
||||||
|
urlFrag := prefix + string(slugify(node.Destination)) |
||||||
|
anchor := fmt.Sprintf(`<a href="#fn:%s">%d</a>`, urlFrag, node.NoteID) |
||||||
|
return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor)) |
||||||
|
} |
||||||
|
|
||||||
|
func footnoteItem(prefix string, slug []byte) []byte { |
||||||
|
return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug)) |
||||||
|
} |
||||||
|
|
||||||
|
func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte { |
||||||
|
const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>` |
||||||
|
return []byte(fmt.Sprintf(format, prefix, slug, returnLink)) |
||||||
|
} |
||||||
|
|
||||||
|
func itemOpenCR(node *Node) bool { |
||||||
|
if node.Prev == nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
ld := node.Parent.ListData |
||||||
|
return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0 |
||||||
|
} |
||||||
|
|
||||||
|
func skipParagraphTags(node *Node) bool { |
||||||
|
grandparent := node.Parent.Parent |
||||||
|
if grandparent == nil || grandparent.Type != List { |
||||||
|
return false |
||||||
|
} |
||||||
|
tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0 |
||||||
|
return grandparent.Type == List && tightOrTerm |
||||||
|
} |
||||||
|
|
||||||
|
func cellAlignment(align CellAlignFlags) string { |
||||||
|
switch align { |
||||||
|
case TableAlignmentLeft: |
||||||
|
return "left" |
||||||
|
case TableAlignmentRight: |
||||||
|
return "right" |
||||||
|
case TableAlignmentCenter: |
||||||
|
return "center" |
||||||
|
default: |
||||||
|
return "" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (r *HTMLRenderer) out(w io.Writer, text []byte) { |
||||||
|
if r.disableTags > 0 { |
||||||
|
w.Write(htmlTagRe.ReplaceAll(text, []byte{})) |
||||||
|
} else { |
||||||
|
w.Write(text) |
||||||
|
} |
||||||
|
r.lastOutputLen = len(text) |
||||||
|
} |
||||||
|
|
||||||
|
func (r *HTMLRenderer) cr(w io.Writer) { |
||||||
|
if r.lastOutputLen > 0 { |
||||||
|
r.out(w, nlBytes) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
nlBytes = []byte{'\n'} |
||||||
|
gtBytes = []byte{'>'} |
||||||
|
spaceBytes = []byte{' '} |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
brTag = []byte("<br>") |
||||||
|
brXHTMLTag = []byte("<br />") |
||||||
|
emTag = []byte("<em>") |
||||||
|
emCloseTag = []byte("</em>") |
||||||
|
strongTag = []byte("<strong>") |
||||||
|
strongCloseTag = []byte("</strong>") |
||||||
|
delTag = []byte("<del>") |
||||||
|
delCloseTag = []byte("</del>") |
||||||
|
ttTag = []byte("<tt>") |
||||||
|
ttCloseTag = []byte("</tt>") |
||||||
|
aTag = []byte("<a") |
||||||
|
aCloseTag = []byte("</a>") |
||||||
|
preTag = []byte("<pre>") |
||||||
|
preCloseTag = []byte("</pre>") |
||||||
|
codeTag = []byte("<code>") |
||||||
|
codeCloseTag = []byte("</code>") |
||||||
|
pTag = []byte("<p>") |
||||||
|
pCloseTag = []byte("</p>") |
||||||
|
blockquoteTag = []byte("<blockquote>") |
||||||
|
blockquoteCloseTag = []byte("</blockquote>") |
||||||
|
hrTag = []byte("<hr>") |
||||||
|
hrXHTMLTag = []byte("<hr />") |
||||||
|
ulTag = []byte("<ul>") |
||||||
|
ulCloseTag = []byte("</ul>") |
||||||
|
olTag = []byte("<ol>") |
||||||
|
olCloseTag = []byte("</ol>") |
||||||
|
dlTag = []byte("<dl>") |
||||||
|
dlCloseTag = []byte("</dl>") |
||||||
|
liTag = []byte("<li>") |
||||||
|
liCloseTag = []byte("</li>") |
||||||
|
ddTag = []byte("<dd>") |
||||||
|
ddCloseTag = []byte("</dd>") |
||||||
|
dtTag = []byte("<dt>") |
||||||
|
dtCloseTag = []byte("</dt>") |
||||||
|
tableTag = []byte("<table>") |
||||||
|
tableCloseTag = []byte("</table>") |
||||||
|
tdTag = []byte("<td") |
||||||
|
tdCloseTag = []byte("</td>") |
||||||
|
thTag = []byte("<th") |
||||||
|
thCloseTag = []byte("</th>") |
||||||
|
theadTag = []byte("<thead>") |
||||||
|
theadCloseTag = []byte("</thead>") |
||||||
|
tbodyTag = []byte("<tbody>") |
||||||
|
tbodyCloseTag = []byte("</tbody>") |
||||||
|
trTag = []byte("<tr>") |
||||||
|
trCloseTag = []byte("</tr>") |
||||||
|
h1Tag = []byte("<h1") |
||||||
|
h1CloseTag = []byte("</h1>") |
||||||
|
h2Tag = []byte("<h2") |
||||||
|
h2CloseTag = []byte("</h2>") |
||||||
|
h3Tag = []byte("<h3") |
||||||
|
h3CloseTag = []byte("</h3>") |
||||||
|
h4Tag = []byte("<h4") |
||||||
|
h4CloseTag = []byte("</h4>") |
||||||
|
h5Tag = []byte("<h5") |
||||||
|
h5CloseTag = []byte("</h5>") |
||||||
|
h6Tag = []byte("<h6") |
||||||
|
h6CloseTag = []byte("</h6>") |
||||||
|
|
||||||
|
footnotesDivBytes = []byte("\n<div class=\"footnotes\">\n\n") |
||||||
|
footnotesCloseDivBytes = []byte("\n</div>\n") |
||||||
|
) |
||||||
|
|
||||||
|
func headingTagsFromLevel(level int) ([]byte, []byte) { |
||||||
|
if level <= 1 { |
||||||
|
return h1Tag, h1CloseTag |
||||||
|
} |
||||||
|
switch level { |
||||||
|
case 2: |
||||||
|
return h2Tag, h2CloseTag |
||||||
|
case 3: |
||||||
|
return h3Tag, h3CloseTag |
||||||
|
case 4: |
||||||
|
return h4Tag, h4CloseTag |
||||||
|
case 5: |
||||||
|
return h5Tag, h5CloseTag |
||||||
|
} |
||||||
|
return h6Tag, h6CloseTag |
||||||
|
} |
||||||
|
|
||||||
|
func (r *HTMLRenderer) outHRTag(w io.Writer) { |
||||||
|
if r.Flags&UseXHTML == 0 { |
||||||
|
r.out(w, hrTag) |
||||||
|
} else { |
||||||
|
r.out(w, hrXHTMLTag) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// RenderNode is a default renderer of a single node of a syntax tree. For
|
||||||
|
// block nodes it will be called twice: first time with entering=true, second
|
||||||
|
// time with entering=false, so that it could know when it's working on an open
|
||||||
|
// tag and when on close. It writes the result to w.
|
||||||
|
//
|
||||||
|
// The return value is a way to tell the calling walker to adjust its walk
|
||||||
|
// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
|
||||||
|
// can ask the walker to skip a subtree of this node by returning SkipChildren.
|
||||||
|
// The typical behavior is to return GoToNext, which asks for the usual
|
||||||
|
// traversal to the next node.
|
||||||
|
func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus { |
||||||
|
attrs := []string{} |
||||||
|
switch node.Type { |
||||||
|
case Text: |
||||||
|
if r.Flags&Smartypants != 0 { |
||||||
|
var tmp bytes.Buffer |
||||||
|
escapeHTML(&tmp, node.Literal) |
||||||
|
r.sr.Process(w, tmp.Bytes()) |
||||||
|
} else { |
||||||
|
if node.Parent.Type == Link { |
||||||
|
escLink(w, node.Literal) |
||||||
|
} else { |
||||||
|
escapeHTML(w, node.Literal) |
||||||
|
} |
||||||
|
} |
||||||
|
case Softbreak: |
||||||
|
r.cr(w) |
||||||
|
// TODO: make it configurable via out(renderer.softbreak)
|
||||||
|
case Hardbreak: |
||||||
|
if r.Flags&UseXHTML == 0 { |
||||||
|
r.out(w, brTag) |
||||||
|
} else { |
||||||
|
r.out(w, brXHTMLTag) |
||||||
|
} |
||||||
|
r.cr(w) |
||||||
|
case Emph: |
||||||
|
if entering { |
||||||
|
r.out(w, emTag) |
||||||
|
} else { |
||||||
|
r.out(w, emCloseTag) |
||||||
|
} |
||||||
|
case Strong: |
||||||
|
if entering { |
||||||
|
r.out(w, strongTag) |
||||||
|
} else { |
||||||
|
r.out(w, strongCloseTag) |
||||||
|
} |
||||||
|
case Del: |
||||||
|
if entering { |
||||||
|
r.out(w, delTag) |
||||||
|
} else { |
||||||
|
r.out(w, delCloseTag) |
||||||
|
} |
||||||
|
case HTMLSpan: |
||||||
|
if r.Flags&SkipHTML != 0 { |
||||||
|
break |
||||||
|
} |
||||||
|
r.out(w, node.Literal) |
||||||
|
case Link: |
||||||
|
// mark it but don't link it if it is not a safe link: no smartypants
|
||||||
|
dest := node.LinkData.Destination |
||||||
|
if needSkipLink(r.Flags, dest) { |
||||||
|
if entering { |
||||||
|
r.out(w, ttTag) |
||||||
|
} else { |
||||||
|
r.out(w, ttCloseTag) |
||||||
|
} |
||||||
|
} else { |
||||||
|
if entering { |
||||||
|
dest = r.addAbsPrefix(dest) |
||||||
|
var hrefBuf bytes.Buffer |
||||||
|
hrefBuf.WriteString("href=\"") |
||||||
|
escLink(&hrefBuf, dest) |
||||||
|
hrefBuf.WriteByte('"') |
||||||
|
attrs = append(attrs, hrefBuf.String()) |
||||||
|
if node.NoteID != 0 { |
||||||
|
r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node)) |
||||||
|
break |
||||||
|
} |
||||||
|
attrs = appendLinkAttrs(attrs, r.Flags, dest) |
||||||
|
if len(node.LinkData.Title) > 0 { |
||||||
|
var titleBuff bytes.Buffer |
||||||
|
titleBuff.WriteString("title=\"") |
||||||
|
escapeHTML(&titleBuff, node.LinkData.Title) |
||||||
|
titleBuff.WriteByte('"') |
||||||
|
attrs = append(attrs, titleBuff.String()) |
||||||
|
} |
||||||
|
r.tag(w, aTag, attrs) |
||||||
|
} else { |
||||||
|
if node.NoteID != 0 { |
||||||
|
break |
||||||
|
} |
||||||
|
r.out(w, aCloseTag) |
||||||
|
} |
||||||
|
} |
||||||
|
case Image: |
||||||
|
if r.Flags&SkipImages != 0 { |
||||||
|
return SkipChildren |
||||||
|
} |
||||||
|
if entering { |
||||||
|
dest := node.LinkData.Destination |
||||||
|
dest = r.addAbsPrefix(dest) |
||||||
|
if r.disableTags == 0 { |
||||||
|
//if options.safe && potentiallyUnsafe(dest) {
|
||||||
|
//out(w, `<img src="" alt="`)
|
||||||
|
//} else {
|
||||||
|
r.out(w, []byte(`<img src="`)) |
||||||
|
escLink(w, dest) |
||||||
|
r.out(w, []byte(`" alt="`)) |
||||||
|
//}
|
||||||
|
} |
||||||
|
r.disableTags++ |
||||||
|
} else { |
||||||
|
r.disableTags-- |
||||||
|
if r.disableTags == 0 { |
||||||
|
if node.LinkData.Title != nil { |
||||||
|
r.out(w, []byte(`" title="`)) |
||||||
|
escapeHTML(w, node.LinkData.Title) |
||||||
|
} |
||||||
|
r.out(w, []byte(`" />`)) |
||||||
|
} |
||||||
|
} |
||||||
|
case Code: |
||||||
|
r.out(w, codeTag) |
||||||
|
escapeHTML(w, node.Literal) |
||||||
|
r.out(w, codeCloseTag) |
||||||
|
case Document: |
||||||
|
break |
||||||
|
case Paragraph: |
||||||
|
if skipParagraphTags(node) { |
||||||
|
break |
||||||
|
} |
||||||
|
if entering { |
||||||
|
// TODO: untangle this clusterfuck about when the newlines need
|
||||||
|
// to be added and when not.
|
||||||
|
if node.Prev != nil { |
||||||
|
switch node.Prev.Type { |
||||||
|
case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule: |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
} |
||||||
|
if node.Parent.Type == BlockQuote && node.Prev == nil { |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
r.out(w, pTag) |
||||||
|
} else { |
||||||
|
r.out(w, pCloseTag) |
||||||
|
if !(node.Parent.Type == Item && node.Next == nil) { |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
} |
||||||
|
case BlockQuote: |
||||||
|
if entering { |
||||||
|
r.cr(w) |
||||||
|
r.out(w, blockquoteTag) |
||||||
|
} else { |
||||||
|
r.out(w, blockquoteCloseTag) |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
case HTMLBlock: |
||||||
|
if r.Flags&SkipHTML != 0 { |
||||||
|
break |
||||||
|
} |
||||||
|
r.cr(w) |
||||||
|
r.out(w, node.Literal) |
||||||
|
r.cr(w) |
||||||
|
case Heading: |
||||||
|
headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level |
||||||
|
openTag, closeTag := headingTagsFromLevel(headingLevel) |
||||||
|
if entering { |
||||||
|
if node.IsTitleblock { |
||||||
|
attrs = append(attrs, `class="title"`) |
||||||
|
} |
||||||
|
if node.HeadingID != "" { |
||||||
|
id := r.ensureUniqueHeadingID(node.HeadingID) |
||||||
|
if r.HeadingIDPrefix != "" { |
||||||
|
id = r.HeadingIDPrefix + id |
||||||
|
} |
||||||
|
if r.HeadingIDSuffix != "" { |
||||||
|
id = id + r.HeadingIDSuffix |
||||||
|
} |
||||||
|
attrs = append(attrs, fmt.Sprintf(`id="%s"`, id)) |
||||||
|
} |
||||||
|
r.cr(w) |
||||||
|
r.tag(w, openTag, attrs) |
||||||
|
} else { |
||||||
|
r.out(w, closeTag) |
||||||
|
if !(node.Parent.Type == Item && node.Next == nil) { |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
} |
||||||
|
case HorizontalRule: |
||||||
|
r.cr(w) |
||||||
|
r.outHRTag(w) |
||||||
|
r.cr(w) |
||||||
|
case List: |
||||||
|
openTag := ulTag |
||||||
|
closeTag := ulCloseTag |
||||||
|
if node.ListFlags&ListTypeOrdered != 0 { |
||||||
|
openTag = olTag |
||||||
|
closeTag = olCloseTag |
||||||
|
} |
||||||
|
if node.ListFlags&ListTypeDefinition != 0 { |
||||||
|
openTag = dlTag |
||||||
|
closeTag = dlCloseTag |
||||||
|
} |
||||||
|
if entering { |
||||||
|
if node.IsFootnotesList { |
||||||
|
r.out(w, footnotesDivBytes) |
||||||
|
r.outHRTag(w) |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
r.cr(w) |
||||||
|
if node.Parent.Type == Item && node.Parent.Parent.Tight { |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
r.tag(w, openTag[:len(openTag)-1], attrs) |
||||||
|
r.cr(w) |
||||||
|
} else { |
||||||
|
r.out(w, closeTag) |
||||||
|
//cr(w)
|
||||||
|
//if node.parent.Type != Item {
|
||||||
|
// cr(w)
|
||||||
|
//}
|
||||||
|
if node.Parent.Type == Item && node.Next != nil { |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
if node.Parent.Type == Document || node.Parent.Type == BlockQuote { |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
if node.IsFootnotesList { |
||||||
|
r.out(w, footnotesCloseDivBytes) |
||||||
|
} |
||||||
|
} |
||||||
|
case Item: |
||||||
|
openTag := liTag |
||||||
|
closeTag := liCloseTag |
||||||
|
if node.ListFlags&ListTypeDefinition != 0 { |
||||||
|
openTag = ddTag |
||||||
|
closeTag = ddCloseTag |
||||||
|
} |
||||||
|
if node.ListFlags&ListTypeTerm != 0 { |
||||||
|
openTag = dtTag |
||||||
|
closeTag = dtCloseTag |
||||||
|
} |
||||||
|
if entering { |
||||||
|
if itemOpenCR(node) { |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
if node.ListData.RefLink != nil { |
||||||
|
slug := slugify(node.ListData.RefLink) |
||||||
|
r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug)) |
||||||
|
break |
||||||
|
} |
||||||
|
r.out(w, openTag) |
||||||
|
} else { |
||||||
|
if node.ListData.RefLink != nil { |
||||||
|
slug := slugify(node.ListData.RefLink) |
||||||
|
if r.Flags&FootnoteReturnLinks != 0 { |
||||||
|
r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug)) |
||||||
|
} |
||||||
|
} |
||||||
|
r.out(w, closeTag) |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
case CodeBlock: |
||||||
|
attrs = appendLanguageAttr(attrs, node.Info) |
||||||
|
r.cr(w) |
||||||
|
r.out(w, preTag) |
||||||
|
r.tag(w, codeTag[:len(codeTag)-1], attrs) |
||||||
|
escapeHTML(w, node.Literal) |
||||||
|
r.out(w, codeCloseTag) |
||||||
|
r.out(w, preCloseTag) |
||||||
|
if node.Parent.Type != Item { |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
case Table: |
||||||
|
if entering { |
||||||
|
r.cr(w) |
||||||
|
r.out(w, tableTag) |
||||||
|
} else { |
||||||
|
r.out(w, tableCloseTag) |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
case TableCell: |
||||||
|
openTag := tdTag |
||||||
|
closeTag := tdCloseTag |
||||||
|
if node.IsHeader { |
||||||
|
openTag = thTag |
||||||
|
closeTag = thCloseTag |
||||||
|
} |
||||||
|
if entering { |
||||||
|
align := cellAlignment(node.Align) |
||||||
|
if align != "" { |
||||||
|
attrs = append(attrs, fmt.Sprintf(`align="%s"`, align)) |
||||||
|
} |
||||||
|
if node.Prev == nil { |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
r.tag(w, openTag, attrs) |
||||||
|
} else { |
||||||
|
r.out(w, closeTag) |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
case TableHead: |
||||||
|
if entering { |
||||||
|
r.cr(w) |
||||||
|
r.out(w, theadTag) |
||||||
|
} else { |
||||||
|
r.out(w, theadCloseTag) |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
case TableBody: |
||||||
|
if entering { |
||||||
|
r.cr(w) |
||||||
|
r.out(w, tbodyTag) |
||||||
|
// XXX: this is to adhere to a rather silly test. Should fix test.
|
||||||
|
if node.FirstChild == nil { |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
} else { |
||||||
|
r.out(w, tbodyCloseTag) |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
case TableRow: |
||||||
|
if entering { |
||||||
|
r.cr(w) |
||||||
|
r.out(w, trTag) |
||||||
|
} else { |
||||||
|
r.out(w, trCloseTag) |
||||||
|
r.cr(w) |
||||||
|
} |
||||||
|
default: |
||||||
|
panic("Unknown node type " + node.Type.String()) |
||||||
|
} |
||||||
|
return GoToNext |
||||||
|
} |
||||||
|
|
||||||
|
// RenderHeader writes HTML document preamble and TOC if requested.
|
||||||
|
func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) { |
||||||
|
r.writeDocumentHeader(w) |
||||||
|
if r.Flags&TOC != 0 { |
||||||
|
r.writeTOC(w, ast) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// RenderFooter writes HTML document footer.
|
||||||
|
func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) { |
||||||
|
if r.Flags&CompletePage == 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
io.WriteString(w, "\n</body>\n</html>\n") |
||||||
|
} |
||||||
|
|
||||||
|
func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) { |
||||||
|
if r.Flags&CompletePage == 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
ending := "" |
||||||
|
if r.Flags&UseXHTML != 0 { |
||||||
|
io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ") |
||||||
|
io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n") |
||||||
|
io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n") |
||||||
|
ending = " /" |
||||||
|
} else { |
||||||
|
io.WriteString(w, "<!DOCTYPE html>\n") |
||||||
|
io.WriteString(w, "<html>\n") |
||||||
|
} |
||||||
|
io.WriteString(w, "<head>\n") |
||||||
|
io.WriteString(w, " <title>") |
||||||
|
if r.Flags&Smartypants != 0 { |
||||||
|
r.sr.Process(w, []byte(r.Title)) |
||||||
|
} else { |
||||||
|
escapeHTML(w, []byte(r.Title)) |
||||||
|
} |
||||||
|
io.WriteString(w, "</title>\n") |
||||||
|
io.WriteString(w, " <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v") |
||||||
|
io.WriteString(w, Version) |
||||||
|
io.WriteString(w, "\"") |
||||||
|
io.WriteString(w, ending) |
||||||
|
io.WriteString(w, ">\n") |
||||||
|
io.WriteString(w, " <meta charset=\"utf-8\"") |
||||||
|
io.WriteString(w, ending) |
||||||
|
io.WriteString(w, ">\n") |
||||||
|
if r.CSS != "" { |
||||||
|
io.WriteString(w, " <link rel=\"stylesheet\" type=\"text/css\" href=\"") |
||||||
|
escapeHTML(w, []byte(r.CSS)) |
||||||
|
io.WriteString(w, "\"") |
||||||
|
io.WriteString(w, ending) |
||||||
|
io.WriteString(w, ">\n") |
||||||
|
} |
||||||
|
if r.Icon != "" { |
||||||
|
io.WriteString(w, " <link rel=\"icon\" type=\"image/x-icon\" href=\"") |
||||||
|
escapeHTML(w, []byte(r.Icon)) |
||||||
|
io.WriteString(w, "\"") |
||||||
|
io.WriteString(w, ending) |
||||||
|
io.WriteString(w, ">\n") |
||||||
|
} |
||||||
|
io.WriteString(w, "</head>\n") |
||||||
|
io.WriteString(w, "<body>\n\n") |
||||||
|
} |
||||||
|
|
||||||
|
func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) { |
||||||
|
buf := bytes.Buffer{} |
||||||
|
|
||||||
|
inHeading := false |
||||||
|
tocLevel := 0 |
||||||
|
headingCount := 0 |
||||||
|
|
||||||
|
ast.Walk(func(node *Node, entering bool) WalkStatus { |
||||||
|
if node.Type == Heading && !node.HeadingData.IsTitleblock { |
||||||
|
inHeading = entering |
||||||
|
if entering { |
||||||
|
node.HeadingID = fmt.Sprintf("toc_%d", headingCount) |
||||||
|
if node.Level == tocLevel { |
||||||
|
buf.WriteString("</li>\n\n<li>") |
||||||
|
} else if node.Level < tocLevel { |
||||||
|
for node.Level < tocLevel { |
||||||
|
tocLevel-- |
||||||
|
buf.WriteString("</li>\n</ul>") |
||||||
|
} |
||||||
|
buf.WriteString("</li>\n\n<li>") |
||||||
|
} else { |
||||||
|
for node.Level > tocLevel { |
||||||
|
tocLevel++ |
||||||
|
buf.WriteString("\n<ul>\n<li>") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount) |
||||||
|
headingCount++ |
||||||
|
} else { |
||||||
|
buf.WriteString("</a>") |
||||||
|
} |
||||||
|
return GoToNext |
||||||
|
} |
||||||
|
|
||||||
|
if inHeading { |
||||||
|
return r.RenderNode(&buf, node, entering) |
||||||
|
} |
||||||
|
|
||||||
|
return GoToNext |
||||||
|
}) |
||||||
|
|
||||||
|
for ; tocLevel > 0; tocLevel-- { |
||||||
|
buf.WriteString("</li>\n</ul>") |
||||||
|
} |
||||||
|
|
||||||
|
if buf.Len() > 0 { |
||||||
|
io.WriteString(w, "<nav>\n") |
||||||
|
w.Write(buf.Bytes()) |
||||||
|
io.WriteString(w, "\n\n</nav>\n") |
||||||
|
} |
||||||
|
r.lastOutputLen = buf.Len() |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,950 @@ |
|||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
|
||||||
|
package blackfriday |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"strings" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
//
|
||||||
|
// Markdown parsing and processing
|
||||||
|
//
|
||||||
|
|
||||||
|
// Version string of the package. Appears in the rendered document when
|
||||||
|
// CompletePage flag is on.
|
||||||
|
const Version = "2.0" |
||||||
|
|
||||||
|
// Extensions is a bitwise or'ed collection of enabled Blackfriday's
|
||||||
|
// extensions.
|
||||||
|
type Extensions int |
||||||
|
|
||||||
|
// These are the supported markdown parsing extensions.
|
||||||
|
// OR these values together to select multiple extensions.
|
||||||
|
const ( |
||||||
|
NoExtensions Extensions = 0 |
||||||
|
NoIntraEmphasis Extensions = 1 << iota // Ignore emphasis markers inside words
|
||||||
|
Tables // Render tables
|
||||||
|
FencedCode // Render fenced code blocks
|
||||||
|
Autolink // Detect embedded URLs that are not explicitly marked
|
||||||
|
Strikethrough // Strikethrough text using ~~test~~
|
||||||
|
LaxHTMLBlocks // Loosen up HTML block parsing rules
|
||||||
|
SpaceHeadings // Be strict about prefix heading rules
|
||||||
|
HardLineBreak // Translate newlines into line breaks
|
||||||
|
TabSizeEight // Expand tabs to eight spaces instead of four
|
||||||
|
Footnotes // Pandoc-style footnotes
|
||||||
|
NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
|
||||||
|
HeadingIDs // specify heading IDs with {#id}
|
||||||
|
Titleblock // Titleblock ala pandoc
|
||||||
|
AutoHeadingIDs // Create the heading ID from the text
|
||||||
|
BackslashLineBreak // Translate trailing backslashes into line breaks
|
||||||
|
DefinitionLists // Render definition lists
|
||||||
|
|
||||||
|
CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants | |
||||||
|
SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes |
||||||
|
|
||||||
|
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode | |
||||||
|
Autolink | Strikethrough | SpaceHeadings | HeadingIDs | |
||||||
|
BackslashLineBreak | DefinitionLists |
||||||
|
) |
||||||
|
|
||||||
|
// ListType contains bitwise or'ed flags for list and list item objects.
|
||||||
|
type ListType int |
||||||
|
|
||||||
|
// These are the possible flag values for the ListItem renderer.
|
||||||
|
// Multiple flag values may be ORed together.
|
||||||
|
// These are mostly of interest if you are writing a new output format.
|
||||||
|
const ( |
||||||
|
ListTypeOrdered ListType = 1 << iota |
||||||
|
ListTypeDefinition |
||||||
|
ListTypeTerm |
||||||
|
|
||||||
|
ListItemContainsBlock |
||||||
|
ListItemBeginningOfList // TODO: figure out if this is of any use now
|
||||||
|
ListItemEndOfList |
||||||
|
) |
||||||
|
|
||||||
|
// CellAlignFlags holds a type of alignment in a table cell.
|
||||||
|
type CellAlignFlags int |
||||||
|
|
||||||
|
// These are the possible flag values for the table cell renderer.
|
||||||
|
// Only a single one of these values will be used; they are not ORed together.
|
||||||
|
// These are mostly of interest if you are writing a new output format.
|
||||||
|
const ( |
||||||
|
TableAlignmentLeft CellAlignFlags = 1 << iota |
||||||
|
TableAlignmentRight |
||||||
|
TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight) |
||||||
|
) |
||||||
|
|
||||||
|
// The size of a tab stop.
|
||||||
|
const ( |
||||||
|
TabSizeDefault = 4 |
||||||
|
TabSizeDouble = 8 |
||||||
|
) |
||||||
|
|
||||||
|
// blockTags is a set of tags that are recognized as HTML block tags.
|
||||||
|
// Any of these can be included in markdown text without special escaping.
|
||||||
|
var blockTags = map[string]struct{}{ |
||||||
|
"blockquote": {}, |
||||||
|
"del": {}, |
||||||
|
"div": {}, |
||||||
|
"dl": {}, |
||||||
|
"fieldset": {}, |
||||||
|
"form": {}, |
||||||
|
"h1": {}, |
||||||
|
"h2": {}, |
||||||
|
"h3": {}, |
||||||
|
"h4": {}, |
||||||
|
"h5": {}, |
||||||
|
"h6": {}, |
||||||
|
"iframe": {}, |
||||||
|
"ins": {}, |
||||||
|
"math": {}, |
||||||
|
"noscript": {}, |
||||||
|
"ol": {}, |
||||||
|
"pre": {}, |
||||||
|
"p": {}, |
||||||
|
"script": {}, |
||||||
|
"style": {}, |
||||||
|
"table": {}, |
||||||
|
"ul": {}, |
||||||
|
|
||||||
|
// HTML5
|
||||||
|
"address": {}, |
||||||
|
"article": {}, |
||||||
|
"aside": {}, |
||||||
|
"canvas": {}, |
||||||
|
"figcaption": {}, |
||||||
|
"figure": {}, |
||||||
|
"footer": {}, |
||||||
|
"header": {}, |
||||||
|
"hgroup": {}, |
||||||
|
"main": {}, |
||||||
|
"nav": {}, |
||||||
|
"output": {}, |
||||||
|
"progress": {}, |
||||||
|
"section": {}, |
||||||
|
"video": {}, |
||||||
|
} |
||||||
|
|
||||||
|
// Renderer is the rendering interface. This is mostly of interest if you are
|
||||||
|
// implementing a new rendering format.
|
||||||
|
//
|
||||||
|
// Only an HTML implementation is provided in this repository, see the README
|
||||||
|
// for external implementations.
|
||||||
|
type Renderer interface { |
||||||
|
// RenderNode is the main rendering method. It will be called once for
|
||||||
|
// every leaf node and twice for every non-leaf node (first with
|
||||||
|
// entering=true, then with entering=false). The method should write its
|
||||||
|
// rendition of the node to the supplied writer w.
|
||||||
|
RenderNode(w io.Writer, node *Node, entering bool) WalkStatus |
||||||
|
|
||||||
|
// RenderHeader is a method that allows the renderer to produce some
|
||||||
|
// content preceding the main body of the output document. The header is
|
||||||
|
// understood in the broad sense here. For example, the default HTML
|
||||||
|
// renderer will write not only the HTML document preamble, but also the
|
||||||
|
// table of contents if it was requested.
|
||||||
|
//
|
||||||
|
// The method will be passed an entire document tree, in case a particular
|
||||||
|
// implementation needs to inspect it to produce output.
|
||||||
|
//
|
||||||
|
// The output should be written to the supplied writer w. If your
|
||||||
|
// implementation has no header to write, supply an empty implementation.
|
||||||
|
RenderHeader(w io.Writer, ast *Node) |
||||||
|
|
||||||
|
// RenderFooter is a symmetric counterpart of RenderHeader.
|
||||||
|
RenderFooter(w io.Writer, ast *Node) |
||||||
|
} |
||||||
|
|
||||||
|
// Callback functions for inline parsing. One such function is defined
|
||||||
|
// for each character that triggers a response when parsing inline data.
|
||||||
|
type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node) |
||||||
|
|
||||||
|
// Markdown is a type that holds extensions and the runtime state used by
|
||||||
|
// Parse, and the renderer. You can not use it directly, construct it with New.
|
||||||
|
type Markdown struct { |
||||||
|
renderer Renderer |
||||||
|
referenceOverride ReferenceOverrideFunc |
||||||
|
refs map[string]*reference |
||||||
|
inlineCallback [256]inlineParser |
||||||
|
extensions Extensions |
||||||
|
nesting int |
||||||
|
maxNesting int |
||||||
|
insideLink bool |
||||||
|
|
||||||
|
// Footnotes need to be ordered as well as available to quickly check for
|
||||||
|
// presence. If a ref is also a footnote, it's stored both in refs and here
|
||||||
|
// in notes. Slice is nil if footnotes not enabled.
|
||||||
|
notes []*reference |
||||||
|
|
||||||
|
doc *Node |
||||||
|
tip *Node // = doc
|
||||||
|
oldTip *Node |
||||||
|
lastMatchedContainer *Node // = doc
|
||||||
|
allClosed bool |
||||||
|
} |
||||||
|
|
||||||
|
func (p *Markdown) getRef(refid string) (ref *reference, found bool) { |
||||||
|
if p.referenceOverride != nil { |
||||||
|
r, overridden := p.referenceOverride(refid) |
||||||
|
if overridden { |
||||||
|
if r == nil { |
||||||
|
return nil, false |
||||||
|
} |
||||||
|
return &reference{ |
||||||
|
link: []byte(r.Link), |
||||||
|
title: []byte(r.Title), |
||||||
|
noteID: 0, |
||||||
|
hasBlock: false, |
||||||
|
text: []byte(r.Text)}, true |
||||||
|
} |
||||||
|
} |
||||||
|
// refs are case insensitive
|
||||||
|
ref, found = p.refs[strings.ToLower(refid)] |
||||||
|
return ref, found |
||||||
|
} |
||||||
|
|
||||||
|
func (p *Markdown) finalize(block *Node) { |
||||||
|
above := block.Parent |
||||||
|
block.open = false |
||||||
|
p.tip = above |
||||||
|
} |
||||||
|
|
||||||
|
func (p *Markdown) addChild(node NodeType, offset uint32) *Node { |
||||||
|
return p.addExistingChild(NewNode(node), offset) |
||||||
|
} |
||||||
|
|
||||||
|
func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node { |
||||||
|
for !p.tip.canContain(node.Type) { |
||||||
|
p.finalize(p.tip) |
||||||
|
} |
||||||
|
p.tip.AppendChild(node) |
||||||
|
p.tip = node |
||||||
|
return node |
||||||
|
} |
||||||
|
|
||||||
|
func (p *Markdown) closeUnmatchedBlocks() { |
||||||
|
if !p.allClosed { |
||||||
|
for p.oldTip != p.lastMatchedContainer { |
||||||
|
parent := p.oldTip.Parent |
||||||
|
p.finalize(p.oldTip) |
||||||
|
p.oldTip = parent |
||||||
|
} |
||||||
|
p.allClosed = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Public interface
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
// Reference represents the details of a link.
|
||||||
|
// See the documentation in Options for more details on use-case.
|
||||||
|
type Reference struct { |
||||||
|
// Link is usually the URL the reference points to.
|
||||||
|
Link string |
||||||
|
// Title is the alternate text describing the link in more detail.
|
||||||
|
Title string |
||||||
|
// Text is the optional text to override the ref with if the syntax used was
|
||||||
|
// [refid][]
|
||||||
|
Text string |
||||||
|
} |
||||||
|
|
||||||
|
// ReferenceOverrideFunc is expected to be called with a reference string and
|
||||||
|
// return either a valid Reference type that the reference string maps to or
|
||||||
|
// nil. If overridden is false, the default reference logic will be executed.
|
||||||
|
// See the documentation in Options for more details on use-case.
|
||||||
|
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool) |
||||||
|
|
||||||
|
// New constructs a Markdown processor. You can use the same With* functions as
|
||||||
|
// for Run() to customize parser's behavior and the renderer.
|
||||||
|
func New(opts ...Option) *Markdown { |
||||||
|
var p Markdown |
||||||
|
for _, opt := range opts { |
||||||
|
opt(&p) |
||||||
|
} |
||||||
|
p.refs = make(map[string]*reference) |
||||||
|
p.maxNesting = 16 |
||||||
|
p.insideLink = false |
||||||
|
docNode := NewNode(Document) |
||||||
|
p.doc = docNode |
||||||
|
p.tip = docNode |
||||||
|
p.oldTip = docNode |
||||||
|
p.lastMatchedContainer = docNode |
||||||
|
p.allClosed = true |
||||||
|
// register inline parsers
|
||||||
|
p.inlineCallback[' '] = maybeLineBreak |
||||||
|
p.inlineCallback['*'] = emphasis |
||||||
|
p.inlineCallback['_'] = emphasis |
||||||
|
if p.extensions&Strikethrough != 0 { |
||||||
|
p.inlineCallback['~'] = emphasis |
||||||
|
} |
||||||
|
p.inlineCallback['`'] = codeSpan |
||||||
|
p.inlineCallback['\n'] = lineBreak |
||||||
|
p.inlineCallback['['] = link |
||||||
|
p.inlineCallback['<'] = leftAngle |
||||||
|
p.inlineCallback['\\'] = escape |
||||||
|
p.inlineCallback['&'] = entity |
||||||
|
p.inlineCallback['!'] = maybeImage |
||||||
|
p.inlineCallback['^'] = maybeInlineFootnote |
||||||
|
if p.extensions&Autolink != 0 { |
||||||
|
p.inlineCallback['h'] = maybeAutoLink |
||||||
|
p.inlineCallback['m'] = maybeAutoLink |
||||||
|
p.inlineCallback['f'] = maybeAutoLink |
||||||
|
p.inlineCallback['H'] = maybeAutoLink |
||||||
|
p.inlineCallback['M'] = maybeAutoLink |
||||||
|
p.inlineCallback['F'] = maybeAutoLink |
||||||
|
} |
||||||
|
if p.extensions&Footnotes != 0 { |
||||||
|
p.notes = make([]*reference, 0) |
||||||
|
} |
||||||
|
return &p |
||||||
|
} |
||||||
|
|
||||||
|
// Option customizes the Markdown processor's default behavior.
|
||||||
|
type Option func(*Markdown) |
||||||
|
|
||||||
|
// WithRenderer allows you to override the default renderer.
|
||||||
|
func WithRenderer(r Renderer) Option { |
||||||
|
return func(p *Markdown) { |
||||||
|
p.renderer = r |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithExtensions allows you to pick some of the many extensions provided by
|
||||||
|
// Blackfriday. You can bitwise OR them.
|
||||||
|
func WithExtensions(e Extensions) Option { |
||||||
|
return func(p *Markdown) { |
||||||
|
p.extensions = e |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithNoExtensions turns off all extensions and custom behavior.
|
||||||
|
func WithNoExtensions() Option { |
||||||
|
return func(p *Markdown) { |
||||||
|
p.extensions = NoExtensions |
||||||
|
p.renderer = NewHTMLRenderer(HTMLRendererParameters{ |
||||||
|
Flags: HTMLFlagsNone, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithRefOverride sets an optional function callback that is called every
|
||||||
|
// time a reference is resolved.
|
||||||
|
//
|
||||||
|
// In Markdown, the link reference syntax can be made to resolve a link to
|
||||||
|
// a reference instead of an inline URL, in one of the following ways:
|
||||||
|
//
|
||||||
|
// * [link text][refid]
|
||||||
|
// * [refid][]
|
||||||
|
//
|
||||||
|
// Usually, the refid is defined at the bottom of the Markdown document. If
|
||||||
|
// this override function is provided, the refid is passed to the override
|
||||||
|
// function first, before consulting the defined refids at the bottom. If
|
||||||
|
// the override function indicates an override did not occur, the refids at
|
||||||
|
// the bottom will be used to fill in the link details.
|
||||||
|
func WithRefOverride(o ReferenceOverrideFunc) Option { |
||||||
|
return func(p *Markdown) { |
||||||
|
p.referenceOverride = o |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Run is the main entry point to Blackfriday. It parses and renders a
|
||||||
|
// block of markdown-encoded text.
|
||||||
|
//
|
||||||
|
// The simplest invocation of Run takes one argument, input:
|
||||||
|
// output := Run(input)
|
||||||
|
// This will parse the input with CommonExtensions enabled and render it with
|
||||||
|
// the default HTMLRenderer (with CommonHTMLFlags).
|
||||||
|
//
|
||||||
|
// Variadic arguments opts can customize the default behavior. Since Markdown
|
||||||
|
// type does not contain exported fields, you can not use it directly. Instead,
|
||||||
|
// use the With* functions. For example, this will call the most basic
|
||||||
|
// functionality, with no extensions:
|
||||||
|
// output := Run(input, WithNoExtensions())
|
||||||
|
//
|
||||||
|
// You can use any number of With* arguments, even contradicting ones. They
|
||||||
|
// will be applied in order of appearance and the latter will override the
|
||||||
|
// former:
|
||||||
|
// output := Run(input, WithNoExtensions(), WithExtensions(exts),
|
||||||
|
// WithRenderer(yourRenderer))
|
||||||
|
func Run(input []byte, opts ...Option) []byte { |
||||||
|
r := NewHTMLRenderer(HTMLRendererParameters{ |
||||||
|
Flags: CommonHTMLFlags, |
||||||
|
}) |
||||||
|
optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)} |
||||||
|
optList = append(optList, opts...) |
||||||
|
parser := New(optList...) |
||||||
|
ast := parser.Parse(input) |
||||||
|
var buf bytes.Buffer |
||||||
|
parser.renderer.RenderHeader(&buf, ast) |
||||||
|
ast.Walk(func(node *Node, entering bool) WalkStatus { |
||||||
|
return parser.renderer.RenderNode(&buf, node, entering) |
||||||
|
}) |
||||||
|
parser.renderer.RenderFooter(&buf, ast) |
||||||
|
return buf.Bytes() |
||||||
|
} |
||||||
|
|
||||||
|
// Parse is an entry point to the parsing part of Blackfriday. It takes an
|
||||||
|
// input markdown document and produces a syntax tree for its contents. This
|
||||||
|
// tree can then be rendered with a default or custom renderer, or
|
||||||
|
// analyzed/transformed by the caller to whatever non-standard needs they have.
|
||||||
|
// The return value is the root node of the syntax tree.
|
||||||
|
func (p *Markdown) Parse(input []byte) *Node { |
||||||
|
p.block(input) |
||||||
|
// Walk the tree and finish up some of unfinished blocks
|
||||||
|
for p.tip != nil { |
||||||
|
p.finalize(p.tip) |
||||||
|
} |
||||||
|
// Walk the tree again and process inline markdown in each block
|
||||||
|
p.doc.Walk(func(node *Node, entering bool) WalkStatus { |
||||||
|
if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell { |
||||||
|
p.inline(node, node.content) |
||||||
|
node.content = nil |
||||||
|
} |
||||||
|
return GoToNext |
||||||
|
}) |
||||||
|
p.parseRefsToAST() |
||||||
|
return p.doc |
||||||
|
} |
||||||
|
|
||||||
|
func (p *Markdown) parseRefsToAST() { |
||||||
|
if p.extensions&Footnotes == 0 || len(p.notes) == 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
p.tip = p.doc |
||||||
|
block := p.addBlock(List, nil) |
||||||
|
block.IsFootnotesList = true |
||||||
|
block.ListFlags = ListTypeOrdered |
||||||
|
flags := ListItemBeginningOfList |
||||||
|
// Note: this loop is intentionally explicit, not range-form. This is
|
||||||
|
// because the body of the loop will append nested footnotes to p.notes and
|
||||||
|
// we need to process those late additions. Range form would only walk over
|
||||||
|
// the fixed initial set.
|
||||||
|
for i := 0; i < len(p.notes); i++ { |
||||||
|
ref := p.notes[i] |
||||||
|
p.addExistingChild(ref.footnote, 0) |
||||||
|
block := ref.footnote |
||||||
|
block.ListFlags = flags | ListTypeOrdered |
||||||
|
block.RefLink = ref.link |
||||||
|
if ref.hasBlock { |
||||||
|
flags |= ListItemContainsBlock |
||||||
|
p.block(ref.title) |
||||||
|
} else { |
||||||
|
p.inline(block, ref.title) |
||||||
|
} |
||||||
|
flags &^= ListItemBeginningOfList | ListItemContainsBlock |
||||||
|
} |
||||||
|
above := block.Parent |
||||||
|
finalizeList(block) |
||||||
|
p.tip = above |
||||||
|
block.Walk(func(node *Node, entering bool) WalkStatus { |
||||||
|
if node.Type == Paragraph || node.Type == Heading { |
||||||
|
p.inline(node, node.content) |
||||||
|
node.content = nil |
||||||
|
} |
||||||
|
return GoToNext |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
// Link references
|
||||||
|
//
|
||||||
|
// This section implements support for references that (usually) appear
|
||||||
|
// as footnotes in a document, and can be referenced anywhere in the document.
|
||||||
|
// The basic format is:
|
||||||
|
//
|
||||||
|
// [1]: http://www.google.com/ "Google"
|
||||||
|
// [2]: http://www.github.com/ "Github"
|
||||||
|
//
|
||||||
|
// Anywhere in the document, the reference can be linked by referring to its
|
||||||
|
// label, i.e., 1 and 2 in this example, as in:
|
||||||
|
//
|
||||||
|
// This library is hosted on [Github][2], a git hosting site.
|
||||||
|
//
|
||||||
|
// Actual footnotes as specified in Pandoc and supported by some other Markdown
|
||||||
|
// libraries such as php-markdown are also taken care of. They look like this:
|
||||||
|
//
|
||||||
|
// This sentence needs a bit of further explanation.[^note]
|
||||||
|
//
|
||||||
|
// [^note]: This is the explanation.
|
||||||
|
//
|
||||||
|
// Footnotes should be placed at the end of the document in an ordered list.
|
||||||
|
// Finally, there are inline footnotes such as:
|
||||||
|
//
|
||||||
|
// Inline footnotes^[Also supported.] provide a quick inline explanation,
|
||||||
|
// but are rendered at the bottom of the document.
|
||||||
|
//
|
||||||
|
|
||||||
|
// reference holds all information necessary for a reference-style links or
|
||||||
|
// footnotes.
|
||||||
|
//
|
||||||
|
// Consider this markdown with reference-style links:
|
||||||
|
//
|
||||||
|
// [link][ref]
|
||||||
|
//
|
||||||
|
// [ref]: /url/ "tooltip title"
|
||||||
|
//
|
||||||
|
// It will be ultimately converted to this HTML:
|
||||||
|
//
|
||||||
|
// <p><a href=\"/url/\" title=\"title\">link</a></p>
|
||||||
|
//
|
||||||
|
// And a reference structure will be populated as follows:
|
||||||
|
//
|
||||||
|
// p.refs["ref"] = &reference{
|
||||||
|
// link: "/url/",
|
||||||
|
// title: "tooltip title",
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Alternatively, reference can contain information about a footnote. Consider
|
||||||
|
// this markdown:
|
||||||
|
//
|
||||||
|
// Text needing a footnote.[^a]
|
||||||
|
//
|
||||||
|
// [^a]: This is the note
|
||||||
|
//
|
||||||
|
// A reference structure will be populated as follows:
|
||||||
|
//
|
||||||
|
// p.refs["a"] = &reference{
|
||||||
|
// link: "a",
|
||||||
|
// title: "This is the note",
|
||||||
|
// noteID: <some positive int>,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// TODO: As you can see, it begs for splitting into two dedicated structures
|
||||||
|
// for refs and for footnotes.
|
||||||
|
type reference struct { |
||||||
|
link []byte |
||||||
|
title []byte |
||||||
|
noteID int // 0 if not a footnote ref
|
||||||
|
hasBlock bool |
||||||
|
footnote *Node // a link to the Item node within a list of footnotes
|
||||||
|
|
||||||
|
text []byte // only gets populated by refOverride feature with Reference.Text
|
||||||
|
} |
||||||
|
|
||||||
|
func (r *reference) String() string { |
||||||
|
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}", |
||||||
|
r.link, r.title, r.text, r.noteID, r.hasBlock) |
||||||
|
} |
||||||
|
|
||||||
|
// Check whether or not data starts with a reference link.
|
||||||
|
// If so, it is parsed and stored in the list of references
|
||||||
|
// (in the render struct).
|
||||||
|
// Returns the number of bytes to skip to move past it,
|
||||||
|
// or zero if the first line is not a reference.
|
||||||
|
func isReference(p *Markdown, data []byte, tabSize int) int { |
||||||
|
// up to 3 optional leading spaces
|
||||||
|
if len(data) < 4 { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
i := 0 |
||||||
|
for i < 3 && data[i] == ' ' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
|
||||||
|
noteID := 0 |
||||||
|
|
||||||
|
// id part: anything but a newline between brackets
|
||||||
|
if data[i] != '[' { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
i++ |
||||||
|
if p.extensions&Footnotes != 0 { |
||||||
|
if i < len(data) && data[i] == '^' { |
||||||
|
// we can set it to anything here because the proper noteIds will
|
||||||
|
// be assigned later during the second pass. It just has to be != 0
|
||||||
|
noteID = 1 |
||||||
|
i++ |
||||||
|
} |
||||||
|
} |
||||||
|
idOffset := i |
||||||
|
for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
if i >= len(data) || data[i] != ']' { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
idEnd := i |
||||||
|
// footnotes can have empty ID, like this: [^], but a reference can not be
|
||||||
|
// empty like this: []. Break early if it's not a footnote and there's no ID
|
||||||
|
if noteID == 0 && idOffset == idEnd { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
// spacer: colon (space | tab)* newline? (space | tab)*
|
||||||
|
i++ |
||||||
|
if i >= len(data) || data[i] != ':' { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
i++ |
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') { |
||||||
|
i++ |
||||||
|
} |
||||||
|
if i < len(data) && (data[i] == '\n' || data[i] == '\r') { |
||||||
|
i++ |
||||||
|
if i < len(data) && data[i] == '\n' && data[i-1] == '\r' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
} |
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') { |
||||||
|
i++ |
||||||
|
} |
||||||
|
if i >= len(data) { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
linkOffset, linkEnd int |
||||||
|
titleOffset, titleEnd int |
||||||
|
lineEnd int |
||||||
|
raw []byte |
||||||
|
hasBlock bool |
||||||
|
) |
||||||
|
|
||||||
|
if p.extensions&Footnotes != 0 && noteID != 0 { |
||||||
|
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize) |
||||||
|
lineEnd = linkEnd |
||||||
|
} else { |
||||||
|
linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i) |
||||||
|
} |
||||||
|
if lineEnd == 0 { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
// a valid ref has been found
|
||||||
|
|
||||||
|
ref := &reference{ |
||||||
|
noteID: noteID, |
||||||
|
hasBlock: hasBlock, |
||||||
|
} |
||||||
|
|
||||||
|
if noteID > 0 { |
||||||
|
// reusing the link field for the id since footnotes don't have links
|
||||||
|
ref.link = data[idOffset:idEnd] |
||||||
|
// if footnote, it's not really a title, it's the contained text
|
||||||
|
ref.title = raw |
||||||
|
} else { |
||||||
|
ref.link = data[linkOffset:linkEnd] |
||||||
|
ref.title = data[titleOffset:titleEnd] |
||||||
|
} |
||||||
|
|
||||||
|
// id matches are case-insensitive
|
||||||
|
id := string(bytes.ToLower(data[idOffset:idEnd])) |
||||||
|
|
||||||
|
p.refs[id] = ref |
||||||
|
|
||||||
|
return lineEnd |
||||||
|
} |
||||||
|
|
||||||
|
func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) { |
||||||
|
// link: whitespace-free sequence, optionally between angle brackets
|
||||||
|
if data[i] == '<' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
linkOffset = i |
||||||
|
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
linkEnd = i |
||||||
|
if data[linkOffset] == '<' && data[linkEnd-1] == '>' { |
||||||
|
linkOffset++ |
||||||
|
linkEnd-- |
||||||
|
} |
||||||
|
|
||||||
|
// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
|
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') { |
||||||
|
i++ |
||||||
|
} |
||||||
|
if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// compute end-of-line
|
||||||
|
if i >= len(data) || data[i] == '\r' || data[i] == '\n' { |
||||||
|
lineEnd = i |
||||||
|
} |
||||||
|
if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' { |
||||||
|
lineEnd++ |
||||||
|
} |
||||||
|
|
||||||
|
// optional (space|tab)* spacer after a newline
|
||||||
|
if lineEnd > 0 { |
||||||
|
i = lineEnd + 1 |
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') { |
||||||
|
i++ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// optional title: any non-newline sequence enclosed in '"() alone on its line
|
||||||
|
if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') { |
||||||
|
i++ |
||||||
|
titleOffset = i |
||||||
|
|
||||||
|
// look for EOL
|
||||||
|
for i < len(data) && data[i] != '\n' && data[i] != '\r' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' { |
||||||
|
titleEnd = i + 1 |
||||||
|
} else { |
||||||
|
titleEnd = i |
||||||
|
} |
||||||
|
|
||||||
|
// step back
|
||||||
|
i-- |
||||||
|
for i > titleOffset && (data[i] == ' ' || data[i] == '\t') { |
||||||
|
i-- |
||||||
|
} |
||||||
|
if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') { |
||||||
|
lineEnd = titleEnd |
||||||
|
titleEnd = i |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// The first bit of this logic is the same as Parser.listItem, but the rest
|
||||||
|
// is much simpler. This function simply finds the entire block and shifts it
|
||||||
|
// over by one tab if it is indeed a block (just returns the line if it's not).
|
||||||
|
// blockEnd is the end of the section in the input buffer, and contents is the
|
||||||
|
// extracted text that was shifted over one tab. It will need to be rendered at
|
||||||
|
// the end of the document.
|
||||||
|
func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) { |
||||||
|
if i == 0 || len(data) == 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// skip leading whitespace on first line
|
||||||
|
for i < len(data) && data[i] == ' ' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
|
||||||
|
blockStart = i |
||||||
|
|
||||||
|
// find the end of the line
|
||||||
|
blockEnd = i |
||||||
|
for i < len(data) && data[i-1] != '\n' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
|
||||||
|
// get working buffer
|
||||||
|
var raw bytes.Buffer |
||||||
|
|
||||||
|
// put the first line into the working buffer
|
||||||
|
raw.Write(data[blockEnd:i]) |
||||||
|
blockEnd = i |
||||||
|
|
||||||
|
// process the following lines
|
||||||
|
containsBlankLine := false |
||||||
|
|
||||||
|
gatherLines: |
||||||
|
for blockEnd < len(data) { |
||||||
|
i++ |
||||||
|
|
||||||
|
// find the end of this line
|
||||||
|
for i < len(data) && data[i-1] != '\n' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
|
||||||
|
// if it is an empty line, guess that it is part of this item
|
||||||
|
// and move on to the next line
|
||||||
|
if p.isEmpty(data[blockEnd:i]) > 0 { |
||||||
|
containsBlankLine = true |
||||||
|
blockEnd = i |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
n := 0 |
||||||
|
if n = isIndented(data[blockEnd:i], indentSize); n == 0 { |
||||||
|
// this is the end of the block.
|
||||||
|
// we don't want to include this last line in the index.
|
||||||
|
break gatherLines |
||||||
|
} |
||||||
|
|
||||||
|
// if there were blank lines before this one, insert a new one now
|
||||||
|
if containsBlankLine { |
||||||
|
raw.WriteByte('\n') |
||||||
|
containsBlankLine = false |
||||||
|
} |
||||||
|
|
||||||
|
// get rid of that first tab, write to buffer
|
||||||
|
raw.Write(data[blockEnd+n : i]) |
||||||
|
hasBlock = true |
||||||
|
|
||||||
|
blockEnd = i |
||||||
|
} |
||||||
|
|
||||||
|
if data[blockEnd-1] != '\n' { |
||||||
|
raw.WriteByte('\n') |
||||||
|
} |
||||||
|
|
||||||
|
contents = raw.Bytes() |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Miscellaneous helper functions
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
// Test if a character is a punctuation symbol.
|
||||||
|
// Taken from a private function in regexp in the stdlib.
|
||||||
|
func ispunct(c byte) bool { |
||||||
|
for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") { |
||||||
|
if c == r { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Test if a character is a whitespace character.
|
||||||
|
func isspace(c byte) bool { |
||||||
|
return ishorizontalspace(c) || isverticalspace(c) |
||||||
|
} |
||||||
|
|
||||||
|
// Test if a character is a horizontal whitespace character.
|
||||||
|
func ishorizontalspace(c byte) bool { |
||||||
|
return c == ' ' || c == '\t' |
||||||
|
} |
||||||
|
|
||||||
|
// Test if a character is a vertical character.
|
||||||
|
func isverticalspace(c byte) bool { |
||||||
|
return c == '\n' || c == '\r' || c == '\f' || c == '\v' |
||||||
|
} |
||||||
|
|
||||||
|
// Test if a character is letter.
|
||||||
|
func isletter(c byte) bool { |
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') |
||||||
|
} |
||||||
|
|
||||||
|
// Test if a character is a letter or a digit.
|
||||||
|
// TODO: check when this is looking for ASCII alnum and when it should use unicode
|
||||||
|
func isalnum(c byte) bool { |
||||||
|
return (c >= '0' && c <= '9') || isletter(c) |
||||||
|
} |
||||||
|
|
||||||
|
// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
|
||||||
|
// always ends output with a newline
|
||||||
|
func expandTabs(out *bytes.Buffer, line []byte, tabSize int) { |
||||||
|
// first, check for common cases: no tabs, or only tabs at beginning of line
|
||||||
|
i, prefix := 0, 0 |
||||||
|
slowcase := false |
||||||
|
for i = 0; i < len(line); i++ { |
||||||
|
if line[i] == '\t' { |
||||||
|
if prefix == i { |
||||||
|
prefix++ |
||||||
|
} else { |
||||||
|
slowcase = true |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// no need to decode runes if all tabs are at the beginning of the line
|
||||||
|
if !slowcase { |
||||||
|
for i = 0; i < prefix*tabSize; i++ { |
||||||
|
out.WriteByte(' ') |
||||||
|
} |
||||||
|
out.Write(line[prefix:]) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// the slow case: we need to count runes to figure out how
|
||||||
|
// many spaces to insert for each tab
|
||||||
|
column := 0 |
||||||
|
i = 0 |
||||||
|
for i < len(line) { |
||||||
|
start := i |
||||||
|
for i < len(line) && line[i] != '\t' { |
||||||
|
_, size := utf8.DecodeRune(line[i:]) |
||||||
|
i += size |
||||||
|
column++ |
||||||
|
} |
||||||
|
|
||||||
|
if i > start { |
||||||
|
out.Write(line[start:i]) |
||||||
|
} |
||||||
|
|
||||||
|
if i >= len(line) { |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
for { |
||||||
|
out.WriteByte(' ') |
||||||
|
column++ |
||||||
|
if column%tabSize == 0 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
i++ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Find if a line counts as indented or not.
|
||||||
|
// Returns number of characters the indent is (0 = not indented).
|
||||||
|
func isIndented(data []byte, indentSize int) int { |
||||||
|
if len(data) == 0 { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
if data[0] == '\t' { |
||||||
|
return 1 |
||||||
|
} |
||||||
|
if len(data) < indentSize { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
for i := 0; i < indentSize; i++ { |
||||||
|
if data[i] != ' ' { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} |
||||||
|
return indentSize |
||||||
|
} |
||||||
|
|
||||||
|
// Create a url-safe slug for fragments
|
||||||
|
func slugify(in []byte) []byte { |
||||||
|
if len(in) == 0 { |
||||||
|
return in |
||||||
|
} |
||||||
|
out := make([]byte, 0, len(in)) |
||||||
|
sym := false |
||||||
|
|
||||||
|
for _, ch := range in { |
||||||
|
if isalnum(ch) { |
||||||
|
sym = false |
||||||
|
out = append(out, ch) |
||||||
|
} else if sym { |
||||||
|
continue |
||||||
|
} else { |
||||||
|
out = append(out, '-') |
||||||
|
sym = true |
||||||
|
} |
||||||
|
} |
||||||
|
var a, b int |
||||||
|
var ch byte |
||||||
|
for a, ch = range out { |
||||||
|
if ch != '-' { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
for b = len(out) - 1; b > 0; b-- { |
||||||
|
if out[b] != '-' { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
return out[a : b+1] |
||||||
|
} |
@ -0,0 +1,354 @@ |
|||||||
|
package blackfriday |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
// NodeType specifies a type of a single node of a syntax tree. Usually one
|
||||||
|
// node (and its type) corresponds to a single markdown feature, e.g. emphasis
|
||||||
|
// or code block.
|
||||||
|
type NodeType int |
||||||
|
|
||||||
|
// Constants for identifying different types of nodes. See NodeType.
|
||||||
|
const ( |
||||||
|
Document NodeType = iota |
||||||
|
BlockQuote |
||||||
|
List |
||||||
|
Item |
||||||
|
Paragraph |
||||||
|
Heading |
||||||
|
HorizontalRule |
||||||
|
Emph |
||||||
|
Strong |
||||||
|
Del |
||||||
|
Link |
||||||
|
Image |
||||||
|
Text |
||||||
|
HTMLBlock |
||||||
|
CodeBlock |
||||||
|
Softbreak |
||||||
|
Hardbreak |
||||||
|
Code |
||||||
|
HTMLSpan |
||||||
|
Table |
||||||
|
TableCell |
||||||
|
TableHead |
||||||
|
TableBody |
||||||
|
TableRow |
||||||
|
) |
||||||
|
|
||||||
|
var nodeTypeNames = []string{ |
||||||
|
Document: "Document", |
||||||
|
BlockQuote: "BlockQuote", |
||||||
|
List: "List", |
||||||
|
Item: "Item", |
||||||
|
Paragraph: "Paragraph", |
||||||
|
Heading: "Heading", |
||||||
|
HorizontalRule: "HorizontalRule", |
||||||
|
Emph: "Emph", |
||||||
|
Strong: "Strong", |
||||||
|
Del: "Del", |
||||||
|
Link: "Link", |
||||||
|
Image: "Image", |
||||||
|
Text: "Text", |
||||||
|
HTMLBlock: "HTMLBlock", |
||||||
|
CodeBlock: "CodeBlock", |
||||||
|
Softbreak: "Softbreak", |
||||||
|
Hardbreak: "Hardbreak", |
||||||
|
Code: "Code", |
||||||
|
HTMLSpan: "HTMLSpan", |
||||||
|
Table: "Table", |
||||||
|
TableCell: "TableCell", |
||||||
|
TableHead: "TableHead", |
||||||
|
TableBody: "TableBody", |
||||||
|
TableRow: "TableRow", |
||||||
|
} |
||||||
|
|
||||||
|
func (t NodeType) String() string { |
||||||
|
return nodeTypeNames[t] |
||||||
|
} |
||||||
|
|
||||||
|
// ListData contains fields relevant to a List and Item node type.
|
||||||
|
type ListData struct { |
||||||
|
ListFlags ListType |
||||||
|
Tight bool // Skip <p>s around list item data if true
|
||||||
|
BulletChar byte // '*', '+' or '-' in bullet lists
|
||||||
|
Delimiter byte // '.' or ')' after the number in ordered lists
|
||||||
|
RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
|
||||||
|
IsFootnotesList bool // This is a list of footnotes
|
||||||
|
} |
||||||
|
|
||||||
|
// LinkData contains fields relevant to a Link node type.
|
||||||
|
type LinkData struct { |
||||||
|
Destination []byte // Destination is what goes into a href
|
||||||
|
Title []byte // Title is the tooltip thing that goes in a title attribute
|
||||||
|
NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote
|
||||||
|
Footnote *Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil.
|
||||||
|
} |
||||||
|
|
||||||
|
// CodeBlockData contains fields relevant to a CodeBlock node type.
|
||||||
|
type CodeBlockData struct { |
||||||
|
IsFenced bool // Specifies whether it's a fenced code block or an indented one
|
||||||
|
Info []byte // This holds the info string
|
||||||
|
FenceChar byte |
||||||
|
FenceLength int |
||||||
|
FenceOffset int |
||||||
|
} |
||||||
|
|
||||||
|
// TableCellData contains fields relevant to a TableCell node type.
|
||||||
|
type TableCellData struct { |
||||||
|
IsHeader bool // This tells if it's under the header row
|
||||||
|
Align CellAlignFlags // This holds the value for align attribute
|
||||||
|
} |
||||||
|
|
||||||
|
// HeadingData contains fields relevant to a Heading node type.
|
||||||
|
type HeadingData struct { |
||||||
|
Level int // This holds the heading level number
|
||||||
|
HeadingID string // This might hold heading ID, if present
|
||||||
|
IsTitleblock bool // Specifies whether it's a title block
|
||||||
|
} |
||||||
|
|
||||||
|
// Node is a single element in the abstract syntax tree of the parsed document.
|
||||||
|
// It holds connections to the structurally neighboring nodes and, for certain
|
||||||
|
// types of nodes, additional information that might be needed when rendering.
|
||||||
|
type Node struct { |
||||||
|
Type NodeType // Determines the type of the node
|
||||||
|
Parent *Node // Points to the parent
|
||||||
|
FirstChild *Node // Points to the first child, if any
|
||||||
|
LastChild *Node // Points to the last child, if any
|
||||||
|
Prev *Node // Previous sibling; nil if it's the first child
|
||||||
|
Next *Node // Next sibling; nil if it's the last child
|
||||||
|
|
||||||
|
Literal []byte // Text contents of the leaf nodes
|
||||||
|
|
||||||
|
HeadingData // Populated if Type is Heading
|
||||||
|
ListData // Populated if Type is List
|
||||||
|
CodeBlockData // Populated if Type is CodeBlock
|
||||||
|
LinkData // Populated if Type is Link
|
||||||
|
TableCellData // Populated if Type is TableCell
|
||||||
|
|
||||||
|
content []byte // Markdown content of the block nodes
|
||||||
|
open bool // Specifies an open block node that has not been finished to process yet
|
||||||
|
} |
||||||
|
|
||||||
|
// NewNode allocates a node of a specified type.
|
||||||
|
func NewNode(typ NodeType) *Node { |
||||||
|
return &Node{ |
||||||
|
Type: typ, |
||||||
|
open: true, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (n *Node) String() string { |
||||||
|
ellipsis := "" |
||||||
|
snippet := n.Literal |
||||||
|
if len(snippet) > 16 { |
||||||
|
snippet = snippet[:16] |
||||||
|
ellipsis = "..." |
||||||
|
} |
||||||
|
return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis) |
||||||
|
} |
||||||
|
|
||||||
|
// Unlink removes node 'n' from the tree.
|
||||||
|
// It panics if the node is nil.
|
||||||
|
func (n *Node) Unlink() { |
||||||
|
if n.Prev != nil { |
||||||
|
n.Prev.Next = n.Next |
||||||
|
} else if n.Parent != nil { |
||||||
|
n.Parent.FirstChild = n.Next |
||||||
|
} |
||||||
|
if n.Next != nil { |
||||||
|
n.Next.Prev = n.Prev |
||||||
|
} else if n.Parent != nil { |
||||||
|
n.Parent.LastChild = n.Prev |
||||||
|
} |
||||||
|
n.Parent = nil |
||||||
|
n.Next = nil |
||||||
|
n.Prev = nil |
||||||
|
} |
||||||
|
|
||||||
|
// AppendChild adds a node 'child' as a child of 'n'.
|
||||||
|
// It panics if either node is nil.
|
||||||
|
func (n *Node) AppendChild(child *Node) { |
||||||
|
child.Unlink() |
||||||
|
child.Parent = n |
||||||
|
if n.LastChild != nil { |
||||||
|
n.LastChild.Next = child |
||||||
|
child.Prev = n.LastChild |
||||||
|
n.LastChild = child |
||||||
|
} else { |
||||||
|
n.FirstChild = child |
||||||
|
n.LastChild = child |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// InsertBefore inserts 'sibling' immediately before 'n'.
|
||||||
|
// It panics if either node is nil.
|
||||||
|
func (n *Node) InsertBefore(sibling *Node) { |
||||||
|
sibling.Unlink() |
||||||
|
sibling.Prev = n.Prev |
||||||
|
if sibling.Prev != nil { |
||||||
|
sibling.Prev.Next = sibling |
||||||
|
} |
||||||
|
sibling.Next = n |
||||||
|
n.Prev = sibling |
||||||
|
sibling.Parent = n.Parent |
||||||
|
if sibling.Prev == nil { |
||||||
|
sibling.Parent.FirstChild = sibling |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (n *Node) isContainer() bool { |
||||||
|
switch n.Type { |
||||||
|
case Document: |
||||||
|
fallthrough |
||||||
|
case BlockQuote: |
||||||
|
fallthrough |
||||||
|
case List: |
||||||
|
fallthrough |
||||||
|
case Item: |
||||||
|
fallthrough |
||||||
|
case Paragraph: |
||||||
|
fallthrough |
||||||
|
case Heading: |
||||||
|
fallthrough |
||||||
|
case Emph: |
||||||
|
fallthrough |
||||||
|
case Strong: |
||||||
|
fallthrough |
||||||
|
case Del: |
||||||
|
fallthrough |
||||||
|
case Link: |
||||||
|
fallthrough |
||||||
|
case Image: |
||||||
|
fallthrough |
||||||
|
case Table: |
||||||
|
fallthrough |
||||||
|
case TableHead: |
||||||
|
fallthrough |
||||||
|
case TableBody: |
||||||
|
fallthrough |
||||||
|
case TableRow: |
||||||
|
fallthrough |
||||||
|
case TableCell: |
||||||
|
return true |
||||||
|
default: |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (n *Node) canContain(t NodeType) bool { |
||||||
|
if n.Type == List { |
||||||
|
return t == Item |
||||||
|
} |
||||||
|
if n.Type == Document || n.Type == BlockQuote || n.Type == Item { |
||||||
|
return t != Item |
||||||
|
} |
||||||
|
if n.Type == Table { |
||||||
|
return t == TableHead || t == TableBody |
||||||
|
} |
||||||
|
if n.Type == TableHead || n.Type == TableBody { |
||||||
|
return t == TableRow |
||||||
|
} |
||||||
|
if n.Type == TableRow { |
||||||
|
return t == TableCell |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// WalkStatus allows NodeVisitor to have some control over the tree traversal.
|
||||||
|
// It is returned from NodeVisitor and different values allow Node.Walk to
|
||||||
|
// decide which node to go to next.
|
||||||
|
type WalkStatus int |
||||||
|
|
||||||
|
const ( |
||||||
|
// GoToNext is the default traversal of every node.
|
||||||
|
GoToNext WalkStatus = iota |
||||||
|
// SkipChildren tells walker to skip all children of current node.
|
||||||
|
SkipChildren |
||||||
|
// Terminate tells walker to terminate the traversal.
|
||||||
|
Terminate |
||||||
|
) |
||||||
|
|
||||||
|
// NodeVisitor is a callback to be called when traversing the syntax tree.
|
||||||
|
// Called twice for every node: once with entering=true when the branch is
|
||||||
|
// first visited, then with entering=false after all the children are done.
|
||||||
|
type NodeVisitor func(node *Node, entering bool) WalkStatus |
||||||
|
|
||||||
|
// Walk is a convenience method that instantiates a walker and starts a
|
||||||
|
// traversal of subtree rooted at n.
|
||||||
|
func (n *Node) Walk(visitor NodeVisitor) { |
||||||
|
w := newNodeWalker(n) |
||||||
|
for w.current != nil { |
||||||
|
status := visitor(w.current, w.entering) |
||||||
|
switch status { |
||||||
|
case GoToNext: |
||||||
|
w.next() |
||||||
|
case SkipChildren: |
||||||
|
w.entering = false |
||||||
|
w.next() |
||||||
|
case Terminate: |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type nodeWalker struct { |
||||||
|
current *Node |
||||||
|
root *Node |
||||||
|
entering bool |
||||||
|
} |
||||||
|
|
||||||
|
func newNodeWalker(root *Node) *nodeWalker { |
||||||
|
return &nodeWalker{ |
||||||
|
current: root, |
||||||
|
root: root, |
||||||
|
entering: true, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (nw *nodeWalker) next() { |
||||||
|
if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root { |
||||||
|
nw.current = nil |
||||||
|
return |
||||||
|
} |
||||||
|
if nw.entering && nw.current.isContainer() { |
||||||
|
if nw.current.FirstChild != nil { |
||||||
|
nw.current = nw.current.FirstChild |
||||||
|
nw.entering = true |
||||||
|
} else { |
||||||
|
nw.entering = false |
||||||
|
} |
||||||
|
} else if nw.current.Next == nil { |
||||||
|
nw.current = nw.current.Parent |
||||||
|
nw.entering = false |
||||||
|
} else { |
||||||
|
nw.current = nw.current.Next |
||||||
|
nw.entering = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func dump(ast *Node) { |
||||||
|
fmt.Println(dumpString(ast)) |
||||||
|
} |
||||||
|
|
||||||
|
func dumpR(ast *Node, depth int) string { |
||||||
|
if ast == nil { |
||||||
|
return "" |
||||||
|
} |
||||||
|
indent := bytes.Repeat([]byte("\t"), depth) |
||||||
|
content := ast.Literal |
||||||
|
if content == nil { |
||||||
|
content = ast.content |
||||||
|
} |
||||||
|
result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content) |
||||||
|
for n := ast.FirstChild; n != nil; n = n.Next { |
||||||
|
result += dumpR(n, depth+1) |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
func dumpString(ast *Node) string { |
||||||
|
return dumpR(ast, 0) |
||||||
|
} |
@ -0,0 +1,457 @@ |
|||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// SmartyPants rendering
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"io" |
||||||
|
) |
||||||
|
|
||||||
|
// SPRenderer is a struct containing state of a Smartypants renderer.
|
||||||
|
type SPRenderer struct { |
||||||
|
inSingleQuote bool |
||||||
|
inDoubleQuote bool |
||||||
|
callbacks [256]smartCallback |
||||||
|
} |
||||||
|
|
||||||
|
func wordBoundary(c byte) bool { |
||||||
|
return c == 0 || isspace(c) || ispunct(c) |
||||||
|
} |
||||||
|
|
||||||
|
func tolower(c byte) byte { |
||||||
|
if c >= 'A' && c <= 'Z' { |
||||||
|
return c - 'A' + 'a' |
||||||
|
} |
||||||
|
return c |
||||||
|
} |
||||||
|
|
||||||
|
func isdigit(c byte) bool { |
||||||
|
return c >= '0' && c <= '9' |
||||||
|
} |
||||||
|
|
||||||
|
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool { |
||||||
|
// edge of the buffer is likely to be a tag that we don't get to see,
|
||||||
|
// so we treat it like text sometimes
|
||||||
|
|
||||||
|
// enumerate all sixteen possibilities for (previousChar, nextChar)
|
||||||
|
// each can be one of {0, space, punct, other}
|
||||||
|
switch { |
||||||
|
case previousChar == 0 && nextChar == 0: |
||||||
|
// context is not any help here, so toggle
|
||||||
|
*isOpen = !*isOpen |
||||||
|
case isspace(previousChar) && nextChar == 0: |
||||||
|
// [ "] might be [ "<code>foo...]
|
||||||
|
*isOpen = true |
||||||
|
case ispunct(previousChar) && nextChar == 0: |
||||||
|
// [!"] hmm... could be [Run!"] or [("<code>...]
|
||||||
|
*isOpen = false |
||||||
|
case /* isnormal(previousChar) && */ nextChar == 0: |
||||||
|
// [a"] is probably a close
|
||||||
|
*isOpen = false |
||||||
|
case previousChar == 0 && isspace(nextChar): |
||||||
|
// [" ] might be [...foo</code>" ]
|
||||||
|
*isOpen = false |
||||||
|
case isspace(previousChar) && isspace(nextChar): |
||||||
|
// [ " ] context is not any help here, so toggle
|
||||||
|
*isOpen = !*isOpen |
||||||
|
case ispunct(previousChar) && isspace(nextChar): |
||||||
|
// [!" ] is probably a close
|
||||||
|
*isOpen = false |
||||||
|
case /* isnormal(previousChar) && */ isspace(nextChar): |
||||||
|
// [a" ] this is one of the easy cases
|
||||||
|
*isOpen = false |
||||||
|
case previousChar == 0 && ispunct(nextChar): |
||||||
|
// ["!] hmm... could be ["$1.95] or [</code>"!...]
|
||||||
|
*isOpen = false |
||||||
|
case isspace(previousChar) && ispunct(nextChar): |
||||||
|
// [ "!] looks more like [ "$1.95]
|
||||||
|
*isOpen = true |
||||||
|
case ispunct(previousChar) && ispunct(nextChar): |
||||||
|
// [!"!] context is not any help here, so toggle
|
||||||
|
*isOpen = !*isOpen |
||||||
|
case /* isnormal(previousChar) && */ ispunct(nextChar): |
||||||
|
// [a"!] is probably a close
|
||||||
|
*isOpen = false |
||||||
|
case previousChar == 0 /* && isnormal(nextChar) */ : |
||||||
|
// ["a] is probably an open
|
||||||
|
*isOpen = true |
||||||
|
case isspace(previousChar) /* && isnormal(nextChar) */ : |
||||||
|
// [ "a] this is one of the easy cases
|
||||||
|
*isOpen = true |
||||||
|
case ispunct(previousChar) /* && isnormal(nextChar) */ : |
||||||
|
// [!"a] is probably an open
|
||||||
|
*isOpen = true |
||||||
|
default: |
||||||
|
// [a'b] maybe a contraction?
|
||||||
|
*isOpen = false |
||||||
|
} |
||||||
|
|
||||||
|
// Note that with the limited lookahead, this non-breaking
|
||||||
|
// space will also be appended to single double quotes.
|
||||||
|
if addNBSP && !*isOpen { |
||||||
|
out.WriteString(" ") |
||||||
|
} |
||||||
|
|
||||||
|
out.WriteByte('&') |
||||||
|
if *isOpen { |
||||||
|
out.WriteByte('l') |
||||||
|
} else { |
||||||
|
out.WriteByte('r') |
||||||
|
} |
||||||
|
out.WriteByte(quote) |
||||||
|
out.WriteString("quo;") |
||||||
|
|
||||||
|
if addNBSP && *isOpen { |
||||||
|
out.WriteString(" ") |
||||||
|
} |
||||||
|
|
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
if len(text) >= 2 { |
||||||
|
t1 := tolower(text[1]) |
||||||
|
|
||||||
|
if t1 == '\'' { |
||||||
|
nextChar := byte(0) |
||||||
|
if len(text) >= 3 { |
||||||
|
nextChar = text[2] |
||||||
|
} |
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { |
||||||
|
return 1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) { |
||||||
|
out.WriteString("’") |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
if len(text) >= 3 { |
||||||
|
t2 := tolower(text[2]) |
||||||
|
|
||||||
|
if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) && |
||||||
|
(len(text) < 4 || wordBoundary(text[3])) { |
||||||
|
out.WriteString("’") |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
nextChar := byte(0) |
||||||
|
if len(text) > 1 { |
||||||
|
nextChar = text[1] |
||||||
|
} |
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
if len(text) >= 3 { |
||||||
|
t1 := tolower(text[1]) |
||||||
|
t2 := tolower(text[2]) |
||||||
|
|
||||||
|
if t1 == 'c' && t2 == ')' { |
||||||
|
out.WriteString("©") |
||||||
|
return 2 |
||||||
|
} |
||||||
|
|
||||||
|
if t1 == 'r' && t2 == ')' { |
||||||
|
out.WriteString("®") |
||||||
|
return 2 |
||||||
|
} |
||||||
|
|
||||||
|
if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' { |
||||||
|
out.WriteString("™") |
||||||
|
return 3 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
if len(text) >= 2 { |
||||||
|
if text[1] == '-' { |
||||||
|
out.WriteString("—") |
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
if wordBoundary(previousChar) && wordBoundary(text[1]) { |
||||||
|
out.WriteString("–") |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
if len(text) >= 3 && text[1] == '-' && text[2] == '-' { |
||||||
|
out.WriteString("—") |
||||||
|
return 2 |
||||||
|
} |
||||||
|
if len(text) >= 2 && text[1] == '-' { |
||||||
|
out.WriteString("–") |
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int { |
||||||
|
if bytes.HasPrefix(text, []byte(""")) { |
||||||
|
nextChar := byte(0) |
||||||
|
if len(text) >= 7 { |
||||||
|
nextChar = text[6] |
||||||
|
} |
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) { |
||||||
|
return 5 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if bytes.HasPrefix(text, []byte("�")) { |
||||||
|
return 3 |
||||||
|
} |
||||||
|
|
||||||
|
out.WriteByte('&') |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int { |
||||||
|
var quote byte = 'd' |
||||||
|
if angledQuotes { |
||||||
|
quote = 'a' |
||||||
|
} |
||||||
|
|
||||||
|
return func(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
return r.smartAmpVariant(out, previousChar, text, quote, addNBSP) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
if len(text) >= 3 && text[1] == '.' && text[2] == '.' { |
||||||
|
out.WriteString("…") |
||||||
|
return 2 |
||||||
|
} |
||||||
|
|
||||||
|
if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' { |
||||||
|
out.WriteString("…") |
||||||
|
return 4 |
||||||
|
} |
||||||
|
|
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
if len(text) >= 2 && text[1] == '`' { |
||||||
|
nextChar := byte(0) |
||||||
|
if len(text) >= 3 { |
||||||
|
nextChar = text[2] |
||||||
|
} |
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { |
||||||
|
return 1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { |
||||||
|
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
|
||||||
|
// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8)
|
||||||
|
// and avoid changing dates like 1/23/2005 into fractions.
|
||||||
|
numEnd := 0 |
||||||
|
for len(text) > numEnd && isdigit(text[numEnd]) { |
||||||
|
numEnd++ |
||||||
|
} |
||||||
|
if numEnd == 0 { |
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
denStart := numEnd + 1 |
||||||
|
if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 { |
||||||
|
denStart = numEnd + 3 |
||||||
|
} else if len(text) < numEnd+2 || text[numEnd] != '/' { |
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
denEnd := denStart |
||||||
|
for len(text) > denEnd && isdigit(text[denEnd]) { |
||||||
|
denEnd++ |
||||||
|
} |
||||||
|
if denEnd == denStart { |
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' { |
||||||
|
out.WriteString("<sup>") |
||||||
|
out.Write(text[:numEnd]) |
||||||
|
out.WriteString("</sup>⁄<sub>") |
||||||
|
out.Write(text[denStart:denEnd]) |
||||||
|
out.WriteString("</sub>") |
||||||
|
return denEnd - 1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { |
||||||
|
if text[0] == '1' && text[1] == '/' && text[2] == '2' { |
||||||
|
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' { |
||||||
|
out.WriteString("½") |
||||||
|
return 2 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if text[0] == '1' && text[1] == '/' && text[2] == '4' { |
||||||
|
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') { |
||||||
|
out.WriteString("¼") |
||||||
|
return 2 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if text[0] == '3' && text[1] == '/' && text[2] == '4' { |
||||||
|
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') { |
||||||
|
out.WriteString("¾") |
||||||
|
return 2 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
out.WriteByte(text[0]) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int { |
||||||
|
nextChar := byte(0) |
||||||
|
if len(text) > 1 { |
||||||
|
nextChar = text[1] |
||||||
|
} |
||||||
|
if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) { |
||||||
|
out.WriteString(""") |
||||||
|
} |
||||||
|
|
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
return r.smartDoubleQuoteVariant(out, previousChar, text, 'd') |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
return r.smartDoubleQuoteVariant(out, previousChar, text, 'a') |
||||||
|
} |
||||||
|
|
||||||
|
func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int { |
||||||
|
i := 0 |
||||||
|
|
||||||
|
for i < len(text) && text[i] != '>' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
|
||||||
|
out.Write(text[:i+1]) |
||||||
|
return i |
||||||
|
} |
||||||
|
|
||||||
|
type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int |
||||||
|
|
||||||
|
// NewSmartypantsRenderer constructs a Smartypants renderer object.
|
||||||
|
func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer { |
||||||
|
var ( |
||||||
|
r SPRenderer |
||||||
|
|
||||||
|
smartAmpAngled = r.smartAmp(true, false) |
||||||
|
smartAmpAngledNBSP = r.smartAmp(true, true) |
||||||
|
smartAmpRegular = r.smartAmp(false, false) |
||||||
|
smartAmpRegularNBSP = r.smartAmp(false, true) |
||||||
|
|
||||||
|
addNBSP = flags&SmartypantsQuotesNBSP != 0 |
||||||
|
) |
||||||
|
|
||||||
|
if flags&SmartypantsAngledQuotes == 0 { |
||||||
|
r.callbacks['"'] = r.smartDoubleQuote |
||||||
|
if !addNBSP { |
||||||
|
r.callbacks['&'] = smartAmpRegular |
||||||
|
} else { |
||||||
|
r.callbacks['&'] = smartAmpRegularNBSP |
||||||
|
} |
||||||
|
} else { |
||||||
|
r.callbacks['"'] = r.smartAngledDoubleQuote |
||||||
|
if !addNBSP { |
||||||
|
r.callbacks['&'] = smartAmpAngled |
||||||
|
} else { |
||||||
|
r.callbacks['&'] = smartAmpAngledNBSP |
||||||
|
} |
||||||
|
} |
||||||
|
r.callbacks['\''] = r.smartSingleQuote |
||||||
|
r.callbacks['('] = r.smartParens |
||||||
|
if flags&SmartypantsDashes != 0 { |
||||||
|
if flags&SmartypantsLatexDashes == 0 { |
||||||
|
r.callbacks['-'] = r.smartDash |
||||||
|
} else { |
||||||
|
r.callbacks['-'] = r.smartDashLatex |
||||||
|
} |
||||||
|
} |
||||||
|
r.callbacks['.'] = r.smartPeriod |
||||||
|
if flags&SmartypantsFractions == 0 { |
||||||
|
r.callbacks['1'] = r.smartNumber |
||||||
|
r.callbacks['3'] = r.smartNumber |
||||||
|
} else { |
||||||
|
for ch := '1'; ch <= '9'; ch++ { |
||||||
|
r.callbacks[ch] = r.smartNumberGeneric |
||||||
|
} |
||||||
|
} |
||||||
|
r.callbacks['<'] = r.smartLeftAngle |
||||||
|
r.callbacks['`'] = r.smartBacktick |
||||||
|
return &r |
||||||
|
} |
||||||
|
|
||||||
|
// Process is the entry point of the Smartypants renderer.
|
||||||
|
func (r *SPRenderer) Process(w io.Writer, text []byte) { |
||||||
|
mark := 0 |
||||||
|
for i := 0; i < len(text); i++ { |
||||||
|
if action := r.callbacks[text[i]]; action != nil { |
||||||
|
if i > mark { |
||||||
|
w.Write(text[mark:i]) |
||||||
|
} |
||||||
|
previousChar := byte(0) |
||||||
|
if i > 0 { |
||||||
|
previousChar = text[i-1] |
||||||
|
} |
||||||
|
var tmp bytes.Buffer |
||||||
|
i += action(&tmp, previousChar, text[i:]) |
||||||
|
w.Write(tmp.Bytes()) |
||||||
|
mark = i + 1 |
||||||
|
} |
||||||
|
} |
||||||
|
if mark < len(text) { |
||||||
|
w.Write(text[mark:]) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
sudo: false |
||||||
|
language: go |
||||||
|
go: |
||||||
|
- 1.x |
||||||
|
- master |
||||||
|
matrix: |
||||||
|
allow_failures: |
||||||
|
- go: master |
||||||
|
fast_finish: true |
||||||
|
install: |
||||||
|
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). |
||||||
|
script: |
||||||
|
- go get -t -v ./... |
||||||
|
- diff -u <(echo -n) <(gofmt -d -s .) |
||||||
|
- go tool vet . |
||||||
|
- go test -v -race ./... |
@ -0,0 +1,21 @@ |
|||||||
|
MIT License |
||||||
|
|
||||||
|
Copyright (c) 2015 Dmitri Shuralyov |
||||||
|
|
||||||
|
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,36 @@ |
|||||||
|
sanitized_anchor_name |
||||||
|
===================== |
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/shurcooL/sanitized_anchor_name.svg?branch=master)](https://travis-ci.org/shurcooL/sanitized_anchor_name) [![GoDoc](https://godoc.org/github.com/shurcooL/sanitized_anchor_name?status.svg)](https://godoc.org/github.com/shurcooL/sanitized_anchor_name) |
||||||
|
|
||||||
|
Package sanitized_anchor_name provides a func to create sanitized anchor names. |
||||||
|
|
||||||
|
Its logic can be reused by multiple packages to create interoperable anchor names |
||||||
|
and links to those anchors. |
||||||
|
|
||||||
|
At this time, it does not try to ensure that generated anchor names |
||||||
|
are unique, that responsibility falls on the caller. |
||||||
|
|
||||||
|
Installation |
||||||
|
------------ |
||||||
|
|
||||||
|
```bash |
||||||
|
go get -u github.com/shurcooL/sanitized_anchor_name |
||||||
|
``` |
||||||
|
|
||||||
|
Example |
||||||
|
------- |
||||||
|
|
||||||
|
```Go |
||||||
|
anchorName := sanitized_anchor_name.Create("This is a header") |
||||||
|
|
||||||
|
fmt.Println(anchorName) |
||||||
|
|
||||||
|
// Output: |
||||||
|
// this-is-a-header |
||||||
|
``` |
||||||
|
|
||||||
|
License |
||||||
|
------- |
||||||
|
|
||||||
|
- [MIT License](LICENSE) |
@ -0,0 +1,29 @@ |
|||||||
|
// Package sanitized_anchor_name provides a func to create sanitized anchor names.
|
||||||
|
//
|
||||||
|
// Its logic can be reused by multiple packages to create interoperable anchor names
|
||||||
|
// and links to those anchors.
|
||||||
|
//
|
||||||
|
// At this time, it does not try to ensure that generated anchor names
|
||||||
|
// are unique, that responsibility falls on the caller.
|
||||||
|
package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name"
|
||||||
|
|
||||||
|
import "unicode" |
||||||
|
|
||||||
|
// Create returns a sanitized anchor name for the given text.
|
||||||
|
func Create(text string) string { |
||||||
|
var anchorName []rune |
||||||
|
var futureDash = false |
||||||
|
for _, r := range text { |
||||||
|
switch { |
||||||
|
case unicode.IsLetter(r) || unicode.IsNumber(r): |
||||||
|
if futureDash && len(anchorName) > 0 { |
||||||
|
anchorName = append(anchorName, '-') |
||||||
|
} |
||||||
|
futureDash = false |
||||||
|
anchorName = append(anchorName, unicode.ToLower(r)) |
||||||
|
default: |
||||||
|
futureDash = true |
||||||
|
} |
||||||
|
} |
||||||
|
return string(anchorName) |
||||||
|
} |
@ -1,2 +1,3 @@ |
|||||||
*.coverprofile |
*.coverprofile |
||||||
node_modules/ |
node_modules/ |
||||||
|
vendor |
@ -1,27 +1,35 @@ |
|||||||
language: go |
language: go |
||||||
sudo: false |
sudo: false |
||||||
dist: trusty |
dist: bionic |
||||||
osx_image: xcode8.3 |
osx_image: xcode10 |
||||||
go: 1.8.x |
go: |
||||||
|
- 1.11.x |
||||||
|
- 1.12.x |
||||||
|
- 1.13.x |
||||||
|
|
||||||
os: |
os: |
||||||
- linux |
- linux |
||||||
- osx |
- osx |
||||||
|
|
||||||
|
env: |
||||||
|
GO111MODULE=on |
||||||
|
GOPROXY=https://proxy.golang.org |
||||||
|
|
||||||
cache: |
cache: |
||||||
directories: |
directories: |
||||||
- node_modules |
- node_modules |
||||||
|
|
||||||
before_script: |
before_script: |
||||||
- go get github.com/urfave/gfmrun/... || true |
- go get github.com/urfave/gfmrun/cmd/gfmrun |
||||||
- go get golang.org/x/tools/cmd/goimports |
- go get golang.org/x/tools/cmd/goimports |
||||||
- if [ ! -f node_modules/.bin/markdown-toc ] ; then |
- npm install markdown-toc |
||||||
npm install markdown-toc ; |
- go mod tidy |
||||||
fi |
|
||||||
|
|
||||||
script: |
script: |
||||||
- ./runtests gen |
- go run build.go vet |
||||||
- ./runtests vet |
- go run build.go test |
||||||
- ./runtests test |
- go run build.go gfmrun docs/v1/manual.md |
||||||
- ./runtests gfmrun |
- go run build.go toc docs/v1/manual.md |
||||||
- ./runtests toc |
|
||||||
|
after_success: |
||||||
|
- bash <(curl -s https://codecov.io/bash) |
||||||
|
@ -1,435 +0,0 @@ |
|||||||
# Change Log |
|
||||||
|
|
||||||
**ATTN**: This project uses [semantic versioning](http://semver.org/). |
|
||||||
|
|
||||||
## [Unreleased] |
|
||||||
|
|
||||||
## 1.20.0 - 2017-08-10 |
|
||||||
|
|
||||||
### Fixed |
|
||||||
|
|
||||||
* `HandleExitCoder` is now correctly iterates over all errors in |
|
||||||
a `MultiError`. The exit code is the exit code of the last error or `1` if |
|
||||||
there are no `ExitCoder`s in the `MultiError`. |
|
||||||
* Fixed YAML file loading on Windows (previously would fail validate the file path) |
|
||||||
* Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly |
|
||||||
propogated |
|
||||||
* `ErrWriter` is now passed downwards through command structure to avoid the |
|
||||||
need to redefine it |
|
||||||
* Pass `Command` context into `OnUsageError` rather than parent context so that |
|
||||||
all fields are avaiable |
|
||||||
* Errors occuring in `Before` funcs are no longer double printed |
|
||||||
* Use `UsageText` in the help templates for commands and subcommands if |
|
||||||
defined; otherwise build the usage as before (was previously ignoring this |
|
||||||
field) |
|
||||||
* `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if |
|
||||||
a program calls `Set` or `GlobalSet` directly after flag parsing (would |
|
||||||
previously only return `true` if the flag was set during parsing) |
|
||||||
|
|
||||||
### Changed |
|
||||||
|
|
||||||
* No longer exit the program on command/subcommand error if the error raised is |
|
||||||
not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was |
|
||||||
determined to be a regression in functionality. See [the |
|
||||||
PR](https://github.com/urfave/cli/pull/595) for discussion. |
|
||||||
|
|
||||||
### Added |
|
||||||
|
|
||||||
* `CommandsByName` type was added to make it easy to sort `Command`s by name, |
|
||||||
alphabetically |
|
||||||
* `altsrc` now handles loading of string and int arrays from TOML |
|
||||||
* Support for definition of custom help templates for `App` via |
|
||||||
`CustomAppHelpTemplate` |
|
||||||
* Support for arbitrary key/value fields on `App` to be used with |
|
||||||
`CustomAppHelpTemplate` via `ExtraInfo` |
|
||||||
* `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be |
|
||||||
`cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag` |
|
||||||
interface to be used. |
|
||||||
|
|
||||||
|
|
||||||
## [1.19.1] - 2016-11-21 |
|
||||||
|
|
||||||
### Fixed |
|
||||||
|
|
||||||
- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as |
|
||||||
the `Action` for a command would cause it to error rather than calling the |
|
||||||
function. Should not have a affected declarative cases using `func(c |
|
||||||
*cli.Context) err)`. |
|
||||||
- Shell completion now handles the case where the user specifies |
|
||||||
`--generate-bash-completion` immediately after a flag that takes an argument. |
|
||||||
Previously it call the application with `--generate-bash-completion` as the |
|
||||||
flag value. |
|
||||||
|
|
||||||
## [1.19.0] - 2016-11-19 |
|
||||||
### Added |
|
||||||
- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`) |
|
||||||
- A `Description` field was added to `App` for a more detailed description of |
|
||||||
the application (similar to the existing `Description` field on `Command`) |
|
||||||
- Flag type code generation via `go generate` |
|
||||||
- Write to stderr and exit 1 if action returns non-nil error |
|
||||||
- Added support for TOML to the `altsrc` loader |
|
||||||
- `SkipArgReorder` was added to allow users to skip the argument reordering. |
|
||||||
This is useful if you want to consider all "flags" after an argument as |
|
||||||
arguments rather than flags (the default behavior of the stdlib `flag` |
|
||||||
library). This is backported functionality from the [removal of the flag |
|
||||||
reordering](https://github.com/urfave/cli/pull/398) in the unreleased version |
|
||||||
2 |
|
||||||
- For formatted errors (those implementing `ErrorFormatter`), the errors will |
|
||||||
be formatted during output. Compatible with `pkg/errors`. |
|
||||||
|
|
||||||
### Changed |
|
||||||
- Raise minimum tested/supported Go version to 1.2+ |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Consider empty environment variables as set (previously environment variables |
|
||||||
with the equivalent of `""` would be skipped rather than their value used). |
|
||||||
- Return an error if the value in a given environment variable cannot be parsed |
|
||||||
as the flag type. Previously these errors were silently swallowed. |
|
||||||
- Print full error when an invalid flag is specified (which includes the invalid flag) |
|
||||||
- `App.Writer` defaults to `stdout` when `nil` |
|
||||||
- If no action is specified on a command or app, the help is now printed instead of `panic`ing |
|
||||||
- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized) |
|
||||||
- Correctly show help message if `-h` is provided to a subcommand |
|
||||||
- `context.(Global)IsSet` now respects environment variables. Previously it |
|
||||||
would return `false` if a flag was specified in the environment rather than |
|
||||||
as an argument |
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user |
|
||||||
- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This |
|
||||||
fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well |
|
||||||
as `altsrc` where Go would complain that the types didn't match |
|
||||||
|
|
||||||
## [1.18.1] - 2016-08-28 |
|
||||||
### Fixed |
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported) |
|
||||||
|
|
||||||
## [1.18.0] - 2016-06-27 |
|
||||||
### Added |
|
||||||
- `./runtests` test runner with coverage tracking by default |
|
||||||
- testing on OS X |
|
||||||
- testing on Windows |
|
||||||
- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code |
|
||||||
|
|
||||||
### Changed |
|
||||||
- Use spaces for alignment in help/usage output instead of tabs, making the |
|
||||||
output alignment consistent regardless of tab width |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Printing of command aliases in help text |
|
||||||
- Printing of visible flags for both struct and struct pointer flags |
|
||||||
- Display the `help` subcommand when using `CommandCategories` |
|
||||||
- No longer swallows `panic`s that occur within the `Action`s themselves when |
|
||||||
detecting the signature of the `Action` field |
|
||||||
|
|
||||||
## [1.17.1] - 2016-08-28 |
|
||||||
### Fixed |
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user |
|
||||||
|
|
||||||
## [1.17.0] - 2016-05-09 |
|
||||||
### Added |
|
||||||
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` |
|
||||||
- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool` |
|
||||||
- Support for hiding commands by setting `Hidden: true` -- this will hide the |
|
||||||
commands in help output |
|
||||||
|
|
||||||
### Changed |
|
||||||
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer |
|
||||||
quoted in help text output. |
|
||||||
- All flag types now include `(default: {value})` strings following usage when a |
|
||||||
default value can be (reasonably) detected. |
|
||||||
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent |
|
||||||
with non-slice flag types |
|
||||||
- Apps now exit with a code of 3 if an unknown subcommand is specified |
|
||||||
(previously they printed "No help topic for...", but still exited 0. This |
|
||||||
makes it easier to script around apps built using `cli` since they can trust |
|
||||||
that a 0 exit code indicated a successful execution. |
|
||||||
- cleanups based on [Go Report Card |
|
||||||
feedback](https://goreportcard.com/report/github.com/urfave/cli) |
|
||||||
|
|
||||||
## [1.16.1] - 2016-08-28 |
|
||||||
### Fixed |
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user |
|
||||||
|
|
||||||
## [1.16.0] - 2016-05-02 |
|
||||||
### Added |
|
||||||
- `Hidden` field on all flag struct types to omit from generated help text |
|
||||||
|
|
||||||
### Changed |
|
||||||
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from |
|
||||||
generated help text via the `Hidden` field |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- handling of error values in `HandleAction` and `HandleExitCoder` |
|
||||||
|
|
||||||
## [1.15.0] - 2016-04-30 |
|
||||||
### Added |
|
||||||
- This file! |
|
||||||
- Support for placeholders in flag usage strings |
|
||||||
- `App.Metadata` map for arbitrary data/state management |
|
||||||
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after |
|
||||||
parsing. |
|
||||||
- Support for nested lookup of dot-delimited keys in structures loaded from |
|
||||||
YAML. |
|
||||||
|
|
||||||
### Changed |
|
||||||
- The `App.Action` and `Command.Action` now prefer a return signature of |
|
||||||
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil |
|
||||||
`error` is returned, there may be two outcomes: |
|
||||||
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called |
|
||||||
automatically |
|
||||||
- Else the error is bubbled up and returned from `App.Run` |
|
||||||
- Specifying an `Action` with the legacy return signature of |
|
||||||
`func(*cli.Context)` will produce a deprecation message to stderr |
|
||||||
- Specifying an `Action` that is not a `func` type will produce a non-zero exit |
|
||||||
from `App.Run` |
|
||||||
- Specifying an `Action` func that has an invalid (input) signature will |
|
||||||
produce a non-zero exit from `App.Run` |
|
||||||
|
|
||||||
### Deprecated |
|
||||||
- <a name="deprecated-cli-app-runandexitonerror"></a> |
|
||||||
`cli.App.RunAndExitOnError`, which should now be done by returning an error |
|
||||||
that fulfills `cli.ExitCoder` to `cli.App.Run`. |
|
||||||
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for |
|
||||||
`cli.App.Action` of `func(*cli.Context)`, which should now have a return |
|
||||||
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Added missing `*cli.Context.GlobalFloat64` method |
|
||||||
|
|
||||||
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- Codebeat badge |
|
||||||
- Support for categorization via `CategorizedHelp` and `Categories` on app. |
|
||||||
|
|
||||||
### Changed |
|
||||||
- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Ensure version is not shown in help text when `HideVersion` set. |
|
||||||
|
|
||||||
## [1.13.0] - 2016-03-06 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- YAML file input support. |
|
||||||
- `NArg` method on context. |
|
||||||
|
|
||||||
## [1.12.0] - 2016-02-17 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- Custom usage error handling. |
|
||||||
- Custom text support in `USAGE` section of help output. |
|
||||||
- Improved help messages for empty strings. |
|
||||||
- AppVeyor CI configuration. |
|
||||||
|
|
||||||
### Changed |
|
||||||
- Removed `panic` from default help printer func. |
|
||||||
- De-duping and optimizations. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Correctly handle `Before`/`After` at command level when no subcommands. |
|
||||||
- Case of literal `-` argument causing flag reordering. |
|
||||||
- Environment variable hints on Windows. |
|
||||||
- Docs updates. |
|
||||||
|
|
||||||
## [1.11.1] - 2015-12-21 (backfilled 2016-04-25) |
|
||||||
### Changed |
|
||||||
- Use `path.Base` in `Name` and `HelpName` |
|
||||||
- Export `GetName` on flag types. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Flag parsing when skipping is enabled. |
|
||||||
- Test output cleanup. |
|
||||||
- Move completion check to account for empty input case. |
|
||||||
|
|
||||||
## [1.11.0] - 2015-11-15 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- Destination scan support for flags. |
|
||||||
- Testing against `tip` in Travis CI config. |
|
||||||
|
|
||||||
### Changed |
|
||||||
- Go version in Travis CI config. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Removed redundant tests. |
|
||||||
- Use correct example naming in tests. |
|
||||||
|
|
||||||
## [1.10.2] - 2015-10-29 (backfilled 2016-04-25) |
|
||||||
### Fixed |
|
||||||
- Remove unused var in bash completion. |
|
||||||
|
|
||||||
## [1.10.1] - 2015-10-21 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- Coverage and reference logos in README. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Use specified values in help and version parsing. |
|
||||||
- Only display app version and help message once. |
|
||||||
|
|
||||||
## [1.10.0] - 2015-10-06 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- More tests for existing functionality. |
|
||||||
- `ArgsUsage` at app and command level for help text flexibility. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Honor `HideHelp` and `HideVersion` in `App.Run`. |
|
||||||
- Remove juvenile word from README. |
|
||||||
|
|
||||||
## [1.9.0] - 2015-09-08 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- `FullName` on command with accompanying help output update. |
|
||||||
- Set default `$PROG` in bash completion. |
|
||||||
|
|
||||||
### Changed |
|
||||||
- Docs formatting. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Removed self-referential imports in tests. |
|
||||||
|
|
||||||
## [1.8.0] - 2015-06-30 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- Support for `Copyright` at app level. |
|
||||||
- `Parent` func at context level to walk up context lineage. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Global flag processing at top level. |
|
||||||
|
|
||||||
## [1.7.1] - 2015-06-11 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- Aggregate errors from `Before`/`After` funcs. |
|
||||||
- Doc comments on flag structs. |
|
||||||
- Include non-global flags when checking version and help. |
|
||||||
- Travis CI config updates. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Ensure slice type flags have non-nil values. |
|
||||||
- Collect global flags from the full command hierarchy. |
|
||||||
- Docs prose. |
|
||||||
|
|
||||||
## [1.7.0] - 2015-05-03 (backfilled 2016-04-25) |
|
||||||
### Changed |
|
||||||
- `HelpPrinter` signature includes output writer. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Specify go 1.1+ in docs. |
|
||||||
- Set `Writer` when running command as app. |
|
||||||
|
|
||||||
## [1.6.0] - 2015-03-23 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- Multiple author support. |
|
||||||
- `NumFlags` at context level. |
|
||||||
- `Aliases` at command level. |
|
||||||
|
|
||||||
### Deprecated |
|
||||||
- `ShortName` at command level. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Subcommand help output. |
|
||||||
- Backward compatible support for deprecated `Author` and `Email` fields. |
|
||||||
- Docs regarding `Names`/`Aliases`. |
|
||||||
|
|
||||||
## [1.5.0] - 2015-02-20 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- `After` hook func support at app and command level. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Use parsed context when running command as subcommand. |
|
||||||
- Docs prose. |
|
||||||
|
|
||||||
## [1.4.1] - 2015-01-09 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- Support for hiding `-h / --help` flags, but not `help` subcommand. |
|
||||||
- Stop flag parsing after `--`. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Help text for generic flags to specify single value. |
|
||||||
- Use double quotes in output for defaults. |
|
||||||
- Use `ParseInt` instead of `ParseUint` for int environment var values. |
|
||||||
- Use `0` as base when parsing int environment var values. |
|
||||||
|
|
||||||
## [1.4.0] - 2014-12-12 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- Support for environment variable lookup "cascade". |
|
||||||
- Support for `Stdout` on app for output redirection. |
|
||||||
|
|
||||||
### Fixed |
|
||||||
- Print command help instead of app help in `ShowCommandHelp`. |
|
||||||
|
|
||||||
## [1.3.1] - 2014-11-13 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- Docs and example code updates. |
|
||||||
|
|
||||||
### Changed |
|
||||||
- Default `-v / --version` flag made optional. |
|
||||||
|
|
||||||
## [1.3.0] - 2014-08-10 (backfilled 2016-04-25) |
|
||||||
### Added |
|
||||||
- `FlagNames` at context level. |
|
||||||
- Exposed `VersionPrinter` var for more control over version output. |
|
||||||
- Zsh completion hook. |
|
||||||
- `AUTHOR` section in default app help template. |
|
||||||
- Contribution guidelines. |
|
||||||
- `DurationFlag` type. |
|
||||||
|
|
||||||
## [1.2.0] - 2014-08-02 |
|
||||||
### Added |
|
||||||
- Support for environment variable defaults on flags plus tests. |
|
||||||
|
|
||||||
## [1.1.0] - 2014-07-15 |
|
||||||
### Added |
|
||||||
- Bash completion. |
|
||||||
- Optional hiding of built-in help command. |
|
||||||
- Optional skipping of flag parsing at command level. |
|
||||||
- `Author`, `Email`, and `Compiled` metadata on app. |
|
||||||
- `Before` hook func support at app and command level. |
|
||||||
- `CommandNotFound` func support at app level. |
|
||||||
- Command reference available on context. |
|
||||||
- `GenericFlag` type. |
|
||||||
- `Float64Flag` type. |
|
||||||
- `BoolTFlag` type. |
|
||||||
- `IsSet` flag helper on context. |
|
||||||
- More flag lookup funcs at context level. |
|
||||||
- More tests & docs. |
|
||||||
|
|
||||||
### Changed |
|
||||||
- Help template updates to account for presence/absence of flags. |
|
||||||
- Separated subcommand help template. |
|
||||||
- Exposed `HelpPrinter` var for more control over help output. |
|
||||||
|
|
||||||
## [1.0.0] - 2013-11-01 |
|
||||||
### Added |
|
||||||
- `help` flag in default app flag set and each command flag set. |
|
||||||
- Custom handling of argument parsing errors. |
|
||||||
- Command lookup by name at app level. |
|
||||||
- `StringSliceFlag` type and supporting `StringSlice` type. |
|
||||||
- `IntSliceFlag` type and supporting `IntSlice` type. |
|
||||||
- Slice type flag lookups by name at context level. |
|
||||||
- Export of app and command help functions. |
|
||||||
- More tests & docs. |
|
||||||
|
|
||||||
## 0.1.0 - 2013-07-22 |
|
||||||
### Added |
|
||||||
- Initial implementation. |
|
||||||
|
|
||||||
[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD |
|
||||||
[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0 |
|
||||||
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 |
|
||||||
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 |
|
||||||
[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0 |
|
||||||
[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0 |
|
||||||
[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0 |
|
||||||
[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0 |
|
||||||
[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1 |
|
||||||
[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0 |
|
||||||
[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2 |
|
||||||
[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1 |
|
||||||
[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0 |
|
||||||
[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0 |
|
||||||
[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0 |
|
||||||
[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1 |
|
||||||
[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0 |
|
||||||
[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0 |
|
||||||
[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0 |
|
||||||
[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1 |
|
||||||
[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0 |
|
||||||
[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1 |
|
||||||
[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0 |
|
||||||
[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0 |
|
||||||
[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0 |
|
||||||
[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0 |
|
@ -0,0 +1,74 @@ |
|||||||
|
# Contributor Covenant Code of Conduct |
||||||
|
|
||||||
|
## Our Pledge |
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as |
||||||
|
contributors and maintainers pledge to making participation in our project and |
||||||
|
our community a harassment-free experience for everyone, regardless of age, body |
||||||
|
size, disability, ethnicity, gender identity and expression, level of experience, |
||||||
|
education, socio-economic status, nationality, personal appearance, race, |
||||||
|
religion, or sexual identity and orientation. |
||||||
|
|
||||||
|
## Our Standards |
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment |
||||||
|
include: |
||||||
|
|
||||||
|
* Using welcoming and inclusive language |
||||||
|
* Being respectful of differing viewpoints and experiences |
||||||
|
* Gracefully accepting constructive criticism |
||||||
|
* Focusing on what is best for the community |
||||||
|
* Showing empathy towards other community members |
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include: |
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or |
||||||
|
advances |
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks |
||||||
|
* Public or private harassment |
||||||
|
* Publishing others' private information, such as a physical or electronic |
||||||
|
address, without explicit permission |
||||||
|
* Other conduct which could reasonably be considered inappropriate in a |
||||||
|
professional setting |
||||||
|
|
||||||
|
## Our Responsibilities |
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable |
||||||
|
behavior and are expected to take appropriate and fair corrective action in |
||||||
|
response to any instances of unacceptable behavior. |
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or |
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions |
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or |
||||||
|
permanently any contributor for other behaviors that they deem inappropriate, |
||||||
|
threatening, offensive, or harmful. |
||||||
|
|
||||||
|
## Scope |
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces |
||||||
|
when an individual is representing the project or its community. Examples of |
||||||
|
representing a project or community include using an official project e-mail |
||||||
|
address, posting via an official social media account, or acting as an appointed |
||||||
|
representative at an online or offline event. Representation of a project may be |
||||||
|
further defined and clarified by project maintainers. |
||||||
|
|
||||||
|
## Enforcement |
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be |
||||||
|
reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be |
||||||
|
reviewed and investigated and will result in a response that is deemed necessary |
||||||
|
and appropriate to the circumstances. The project team is obligated to maintain |
||||||
|
confidentiality with regard to the reporter of an incident. Further details of |
||||||
|
specific enforcement policies may be posted separately. |
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good |
||||||
|
faith may face temporary or permanent repercussions as determined by other |
||||||
|
members of the project's leadership. |
||||||
|
|
||||||
|
## Attribution |
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, |
||||||
|
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html |
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org |
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,148 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"sort" |
||||||
|
"strings" |
||||||
|
"text/template" |
||||||
|
|
||||||
|
"github.com/cpuguy83/go-md2man/v2/md2man" |
||||||
|
) |
||||||
|
|
||||||
|
// ToMarkdown creates a markdown string for the `*App`
|
||||||
|
// The function errors if either parsing or writing of the string fails.
|
||||||
|
func (a *App) ToMarkdown() (string, error) { |
||||||
|
var w bytes.Buffer |
||||||
|
if err := a.writeDocTemplate(&w); err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
return w.String(), nil |
||||||
|
} |
||||||
|
|
||||||
|
// ToMan creates a man page string for the `*App`
|
||||||
|
// The function errors if either parsing or writing of the string fails.
|
||||||
|
func (a *App) ToMan() (string, error) { |
||||||
|
var w bytes.Buffer |
||||||
|
if err := a.writeDocTemplate(&w); err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
man := md2man.Render(w.Bytes()) |
||||||
|
return string(man), nil |
||||||
|
} |
||||||
|
|
||||||
|
type cliTemplate struct { |
||||||
|
App *App |
||||||
|
Commands []string |
||||||
|
GlobalArgs []string |
||||||
|
SynopsisArgs []string |
||||||
|
} |
||||||
|
|
||||||
|
func (a *App) writeDocTemplate(w io.Writer) error { |
||||||
|
const name = "cli" |
||||||
|
t, err := template.New(name).Parse(MarkdownDocTemplate) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return t.ExecuteTemplate(w, name, &cliTemplate{ |
||||||
|
App: a, |
||||||
|
Commands: prepareCommands(a.Commands, 0), |
||||||
|
GlobalArgs: prepareArgsWithValues(a.Flags), |
||||||
|
SynopsisArgs: prepareArgsSynopsis(a.Flags), |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func prepareCommands(commands []Command, level int) []string { |
||||||
|
coms := []string{} |
||||||
|
for i := range commands { |
||||||
|
command := &commands[i] |
||||||
|
if command.Hidden { |
||||||
|
continue |
||||||
|
} |
||||||
|
usage := "" |
||||||
|
if command.Usage != "" { |
||||||
|
usage = command.Usage |
||||||
|
} |
||||||
|
|
||||||
|
prepared := fmt.Sprintf("%s %s\n\n%s\n", |
||||||
|
strings.Repeat("#", level+2), |
||||||
|
strings.Join(command.Names(), ", "), |
||||||
|
usage, |
||||||
|
) |
||||||
|
|
||||||
|
flags := prepareArgsWithValues(command.Flags) |
||||||
|
if len(flags) > 0 { |
||||||
|
prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n")) |
||||||
|
} |
||||||
|
|
||||||
|
coms = append(coms, prepared) |
||||||
|
|
||||||
|
// recursevly iterate subcommands
|
||||||
|
if len(command.Subcommands) > 0 { |
||||||
|
coms = append( |
||||||
|
coms, |
||||||
|
prepareCommands(command.Subcommands, level+1)..., |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return coms |
||||||
|
} |
||||||
|
|
||||||
|
func prepareArgsWithValues(flags []Flag) []string { |
||||||
|
return prepareFlags(flags, ", ", "**", "**", `""`, true) |
||||||
|
} |
||||||
|
|
||||||
|
func prepareArgsSynopsis(flags []Flag) []string { |
||||||
|
return prepareFlags(flags, "|", "[", "]", "[value]", false) |
||||||
|
} |
||||||
|
|
||||||
|
func prepareFlags( |
||||||
|
flags []Flag, |
||||||
|
sep, opener, closer, value string, |
||||||
|
addDetails bool, |
||||||
|
) []string { |
||||||
|
args := []string{} |
||||||
|
for _, f := range flags { |
||||||
|
flag, ok := f.(DocGenerationFlag) |
||||||
|
if !ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
modifiedArg := opener |
||||||
|
for _, s := range strings.Split(flag.GetName(), ",") { |
||||||
|
trimmed := strings.TrimSpace(s) |
||||||
|
if len(modifiedArg) > len(opener) { |
||||||
|
modifiedArg += sep |
||||||
|
} |
||||||
|
if len(trimmed) > 1 { |
||||||
|
modifiedArg += fmt.Sprintf("--%s", trimmed) |
||||||
|
} else { |
||||||
|
modifiedArg += fmt.Sprintf("-%s", trimmed) |
||||||
|
} |
||||||
|
} |
||||||
|
modifiedArg += closer |
||||||
|
if flag.TakesValue() { |
||||||
|
modifiedArg += fmt.Sprintf("=%s", value) |
||||||
|
} |
||||||
|
|
||||||
|
if addDetails { |
||||||
|
modifiedArg += flagDetails(flag) |
||||||
|
} |
||||||
|
|
||||||
|
args = append(args, modifiedArg+"\n") |
||||||
|
|
||||||
|
} |
||||||
|
sort.Strings(args) |
||||||
|
return args |
||||||
|
} |
||||||
|
|
||||||
|
// flagDetails returns a string containing the flags metadata
|
||||||
|
func flagDetails(flag DocGenerationFlag) string { |
||||||
|
description := flag.GetUsage() |
||||||
|
value := flag.GetValue() |
||||||
|
if value != "" { |
||||||
|
description += " (default: " + value + ")" |
||||||
|
} |
||||||
|
return ": " + description |
||||||
|
} |
@ -0,0 +1,194 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"strings" |
||||||
|
"text/template" |
||||||
|
) |
||||||
|
|
||||||
|
// ToFishCompletion creates a fish completion string for the `*App`
|
||||||
|
// The function errors if either parsing or writing of the string fails.
|
||||||
|
func (a *App) ToFishCompletion() (string, error) { |
||||||
|
var w bytes.Buffer |
||||||
|
if err := a.writeFishCompletionTemplate(&w); err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
return w.String(), nil |
||||||
|
} |
||||||
|
|
||||||
|
type fishCompletionTemplate struct { |
||||||
|
App *App |
||||||
|
Completions []string |
||||||
|
AllCommands []string |
||||||
|
} |
||||||
|
|
||||||
|
func (a *App) writeFishCompletionTemplate(w io.Writer) error { |
||||||
|
const name = "cli" |
||||||
|
t, err := template.New(name).Parse(FishCompletionTemplate) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
allCommands := []string{} |
||||||
|
|
||||||
|
// Add global flags
|
||||||
|
completions := a.prepareFishFlags(a.VisibleFlags(), allCommands) |
||||||
|
|
||||||
|
// Add help flag
|
||||||
|
if !a.HideHelp { |
||||||
|
completions = append( |
||||||
|
completions, |
||||||
|
a.prepareFishFlags([]Flag{HelpFlag}, allCommands)..., |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// Add version flag
|
||||||
|
if !a.HideVersion { |
||||||
|
completions = append( |
||||||
|
completions, |
||||||
|
a.prepareFishFlags([]Flag{VersionFlag}, allCommands)..., |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// Add commands and their flags
|
||||||
|
completions = append( |
||||||
|
completions, |
||||||
|
a.prepareFishCommands(a.VisibleCommands(), &allCommands, []string{})..., |
||||||
|
) |
||||||
|
|
||||||
|
return t.ExecuteTemplate(w, name, &fishCompletionTemplate{ |
||||||
|
App: a, |
||||||
|
Completions: completions, |
||||||
|
AllCommands: allCommands, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (a *App) prepareFishCommands(commands []Command, allCommands *[]string, previousCommands []string) []string { |
||||||
|
completions := []string{} |
||||||
|
for i := range commands { |
||||||
|
command := &commands[i] |
||||||
|
|
||||||
|
if command.Hidden { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
var completion strings.Builder |
||||||
|
completion.WriteString(fmt.Sprintf( |
||||||
|
"complete -r -c %s -n '%s' -a '%s'", |
||||||
|
a.Name, |
||||||
|
a.fishSubcommandHelper(previousCommands), |
||||||
|
strings.Join(command.Names(), " "), |
||||||
|
)) |
||||||
|
|
||||||
|
if command.Usage != "" { |
||||||
|
completion.WriteString(fmt.Sprintf(" -d '%s'", |
||||||
|
escapeSingleQuotes(command.Usage))) |
||||||
|
} |
||||||
|
|
||||||
|
if !command.HideHelp { |
||||||
|
completions = append( |
||||||
|
completions, |
||||||
|
a.prepareFishFlags([]Flag{HelpFlag}, command.Names())..., |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
*allCommands = append(*allCommands, command.Names()...) |
||||||
|
completions = append(completions, completion.String()) |
||||||
|
completions = append( |
||||||
|
completions, |
||||||
|
a.prepareFishFlags(command.Flags, command.Names())..., |
||||||
|
) |
||||||
|
|
||||||
|
// recursevly iterate subcommands
|
||||||
|
if len(command.Subcommands) > 0 { |
||||||
|
completions = append( |
||||||
|
completions, |
||||||
|
a.prepareFishCommands( |
||||||
|
command.Subcommands, allCommands, command.Names(), |
||||||
|
)..., |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return completions |
||||||
|
} |
||||||
|
|
||||||
|
func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string { |
||||||
|
completions := []string{} |
||||||
|
for _, f := range flags { |
||||||
|
flag, ok := f.(DocGenerationFlag) |
||||||
|
if !ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
completion := &strings.Builder{} |
||||||
|
completion.WriteString(fmt.Sprintf( |
||||||
|
"complete -c %s -n '%s'", |
||||||
|
a.Name, |
||||||
|
a.fishSubcommandHelper(previousCommands), |
||||||
|
)) |
||||||
|
|
||||||
|
fishAddFileFlag(f, completion) |
||||||
|
|
||||||
|
for idx, opt := range strings.Split(flag.GetName(), ",") { |
||||||
|
if idx == 0 { |
||||||
|
completion.WriteString(fmt.Sprintf( |
||||||
|
" -l %s", strings.TrimSpace(opt), |
||||||
|
)) |
||||||
|
} else { |
||||||
|
completion.WriteString(fmt.Sprintf( |
||||||
|
" -s %s", strings.TrimSpace(opt), |
||||||
|
)) |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if flag.TakesValue() { |
||||||
|
completion.WriteString(" -r") |
||||||
|
} |
||||||
|
|
||||||
|
if flag.GetUsage() != "" { |
||||||
|
completion.WriteString(fmt.Sprintf(" -d '%s'", |
||||||
|
escapeSingleQuotes(flag.GetUsage()))) |
||||||
|
} |
||||||
|
|
||||||
|
completions = append(completions, completion.String()) |
||||||
|
} |
||||||
|
|
||||||
|
return completions |
||||||
|
} |
||||||
|
|
||||||
|
func fishAddFileFlag(flag Flag, completion *strings.Builder) { |
||||||
|
switch f := flag.(type) { |
||||||
|
case GenericFlag: |
||||||
|
if f.TakesFile { |
||||||
|
return |
||||||
|
} |
||||||
|
case StringFlag: |
||||||
|
if f.TakesFile { |
||||||
|
return |
||||||
|
} |
||||||
|
case StringSliceFlag: |
||||||
|
if f.TakesFile { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
completion.WriteString(" -f") |
||||||
|
} |
||||||
|
|
||||||
|
func (a *App) fishSubcommandHelper(allCommands []string) string { |
||||||
|
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", a.Name) |
||||||
|
if len(allCommands) > 0 { |
||||||
|
fishHelper = fmt.Sprintf( |
||||||
|
"__fish_seen_subcommand_from %s", |
||||||
|
strings.Join(allCommands, " "), |
||||||
|
) |
||||||
|
} |
||||||
|
return fishHelper |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func escapeSingleQuotes(input string) string { |
||||||
|
return strings.Replace(input, `'`, `\'`, -1) |
||||||
|
} |
@ -1,93 +0,0 @@ |
|||||||
[ |
|
||||||
{ |
|
||||||
"name": "Bool", |
|
||||||
"type": "bool", |
|
||||||
"value": false, |
|
||||||
"context_default": "false", |
|
||||||
"parser": "strconv.ParseBool(f.Value.String())" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "BoolT", |
|
||||||
"type": "bool", |
|
||||||
"value": false, |
|
||||||
"doctail": " that is true by default", |
|
||||||
"context_default": "false", |
|
||||||
"parser": "strconv.ParseBool(f.Value.String())" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "Duration", |
|
||||||
"type": "time.Duration", |
|
||||||
"doctail": " (see https://golang.org/pkg/time/#ParseDuration)", |
|
||||||
"context_default": "0", |
|
||||||
"parser": "time.ParseDuration(f.Value.String())" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "Float64", |
|
||||||
"type": "float64", |
|
||||||
"context_default": "0", |
|
||||||
"parser": "strconv.ParseFloat(f.Value.String(), 64)" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "Generic", |
|
||||||
"type": "Generic", |
|
||||||
"dest": false, |
|
||||||
"context_default": "nil", |
|
||||||
"context_type": "interface{}" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "Int64", |
|
||||||
"type": "int64", |
|
||||||
"context_default": "0", |
|
||||||
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "Int", |
|
||||||
"type": "int", |
|
||||||
"context_default": "0", |
|
||||||
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)", |
|
||||||
"parser_cast": "int(parsed)" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "IntSlice", |
|
||||||
"type": "*IntSlice", |
|
||||||
"dest": false, |
|
||||||
"context_default": "nil", |
|
||||||
"context_type": "[]int", |
|
||||||
"parser": "(f.Value.(*IntSlice)).Value(), error(nil)" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "Int64Slice", |
|
||||||
"type": "*Int64Slice", |
|
||||||
"dest": false, |
|
||||||
"context_default": "nil", |
|
||||||
"context_type": "[]int64", |
|
||||||
"parser": "(f.Value.(*Int64Slice)).Value(), error(nil)" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "String", |
|
||||||
"type": "string", |
|
||||||
"context_default": "\"\"", |
|
||||||
"parser": "f.Value.String(), error(nil)" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "StringSlice", |
|
||||||
"type": "*StringSlice", |
|
||||||
"dest": false, |
|
||||||
"context_default": "nil", |
|
||||||
"context_type": "[]string", |
|
||||||
"parser": "(f.Value.(*StringSlice)).Value(), error(nil)" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "Uint64", |
|
||||||
"type": "uint64", |
|
||||||
"context_default": "0", |
|
||||||
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "Uint", |
|
||||||
"type": "uint", |
|
||||||
"context_default": "0", |
|
||||||
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)", |
|
||||||
"parser_cast": "uint(parsed)" |
|
||||||
} |
|
||||||
] |
|
@ -0,0 +1,109 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// BoolFlag is a flag with type bool
|
||||||
|
type BoolFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
Destination *bool |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f BoolFlag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f BoolFlag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f BoolFlag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f BoolFlag) TakesValue() bool { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f BoolFlag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f BoolFlag) GetValue() string { |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
// Bool looks up the value of a local BoolFlag, returns
|
||||||
|
// false if not found
|
||||||
|
func (c *Context) Bool(name string) bool { |
||||||
|
return lookupBool(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalBool looks up the value of a global BoolFlag, returns
|
||||||
|
// false if not found
|
||||||
|
func (c *Context) GlobalBool(name string) bool { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupBool(name, fs) |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f BoolFlag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
val := false |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
if envVal == "" { |
||||||
|
val = false |
||||||
|
} else { |
||||||
|
envValBool, err := strconv.ParseBool(envVal) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
val = envValBool |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Destination != nil { |
||||||
|
set.BoolVar(f.Destination, name, val, f.Usage) |
||||||
|
return |
||||||
|
} |
||||||
|
set.Bool(name, val, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupBool(name string, set *flag.FlagSet) bool { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := strconv.ParseBool(f.Value.String()) |
||||||
|
if err != nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
@ -0,0 +1,110 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// BoolTFlag is a flag with type bool that is true by default
|
||||||
|
type BoolTFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
Destination *bool |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f BoolTFlag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f BoolTFlag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f BoolTFlag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f BoolTFlag) TakesValue() bool { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f BoolTFlag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f BoolTFlag) GetValue() string { |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
// BoolT looks up the value of a local BoolTFlag, returns
|
||||||
|
// false if not found
|
||||||
|
func (c *Context) BoolT(name string) bool { |
||||||
|
return lookupBoolT(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalBoolT looks up the value of a global BoolTFlag, returns
|
||||||
|
// false if not found
|
||||||
|
func (c *Context) GlobalBoolT(name string) bool { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupBoolT(name, fs) |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f BoolTFlag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
val := true |
||||||
|
|
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
if envVal == "" { |
||||||
|
val = false |
||||||
|
} else { |
||||||
|
envValBool, err := strconv.ParseBool(envVal) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
val = envValBool |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Destination != nil { |
||||||
|
set.BoolVar(f.Destination, name, val, f.Usage) |
||||||
|
return |
||||||
|
} |
||||||
|
set.Bool(name, val, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupBoolT(name string, set *flag.FlagSet) bool { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := strconv.ParseBool(f.Value.String()) |
||||||
|
if err != nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
|
||||||
|
type DurationFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
Value time.Duration |
||||||
|
Destination *time.Duration |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f DurationFlag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f DurationFlag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f DurationFlag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f DurationFlag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f DurationFlag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f DurationFlag) GetValue() string { |
||||||
|
return f.Value.String() |
||||||
|
} |
||||||
|
|
||||||
|
// Duration looks up the value of a local DurationFlag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) Duration(name string) time.Duration { |
||||||
|
return lookupDuration(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalDuration looks up the value of a global DurationFlag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) GlobalDuration(name string) time.Duration { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupDuration(name, fs) |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f DurationFlag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
envValDuration, err := time.ParseDuration(envVal) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
|
||||||
|
f.Value = envValDuration |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Destination != nil { |
||||||
|
set.DurationVar(f.Destination, name, f.Value, f.Usage) |
||||||
|
return |
||||||
|
} |
||||||
|
set.Duration(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupDuration(name string, set *flag.FlagSet) time.Duration { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := time.ParseDuration(f.Value.String()) |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// Float64Flag is a flag with type float64
|
||||||
|
type Float64Flag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
Value float64 |
||||||
|
Destination *float64 |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f Float64Flag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f Float64Flag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f Float64Flag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f Float64Flag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f Float64Flag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f Float64Flag) GetValue() string { |
||||||
|
return fmt.Sprintf("%f", f.Value) |
||||||
|
} |
||||||
|
|
||||||
|
// Float64 looks up the value of a local Float64Flag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) Float64(name string) float64 { |
||||||
|
return lookupFloat64(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalFloat64 looks up the value of a global Float64Flag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) GlobalFloat64(name string) float64 { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupFloat64(name, fs) |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f Float64Flag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
envValFloat, err := strconv.ParseFloat(envVal, 10) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
|
||||||
|
f.Value = envValFloat |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Destination != nil { |
||||||
|
set.Float64Var(f.Destination, name, f.Value, f.Usage) |
||||||
|
return |
||||||
|
} |
||||||
|
set.Float64(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupFloat64(name string, set *flag.FlagSet) float64 { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := strconv.ParseFloat(f.Value.String(), 64) |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
@ -1,627 +0,0 @@ |
|||||||
package cli |
|
||||||
|
|
||||||
import ( |
|
||||||
"flag" |
|
||||||
"strconv" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
// WARNING: This file is generated!
|
|
||||||
|
|
||||||
// BoolFlag is a flag with type bool
|
|
||||||
type BoolFlag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Destination *bool |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f BoolFlag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f BoolFlag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// Bool looks up the value of a local BoolFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) Bool(name string) bool { |
|
||||||
return lookupBool(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalBool looks up the value of a global BoolFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) GlobalBool(name string) bool { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupBool(name, fs) |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
func lookupBool(name string, set *flag.FlagSet) bool { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := strconv.ParseBool(f.Value.String()) |
|
||||||
if err != nil { |
|
||||||
return false |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// BoolTFlag is a flag with type bool that is true by default
|
|
||||||
type BoolTFlag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Destination *bool |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f BoolTFlag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f BoolTFlag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// BoolT looks up the value of a local BoolTFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) BoolT(name string) bool { |
|
||||||
return lookupBoolT(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalBoolT looks up the value of a global BoolTFlag, returns
|
|
||||||
// false if not found
|
|
||||||
func (c *Context) GlobalBoolT(name string) bool { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupBoolT(name, fs) |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
func lookupBoolT(name string, set *flag.FlagSet) bool { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := strconv.ParseBool(f.Value.String()) |
|
||||||
if err != nil { |
|
||||||
return false |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
|
|
||||||
type DurationFlag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value time.Duration |
|
||||||
Destination *time.Duration |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f DurationFlag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f DurationFlag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// Duration looks up the value of a local DurationFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Duration(name string) time.Duration { |
|
||||||
return lookupDuration(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalDuration looks up the value of a global DurationFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalDuration(name string) time.Duration { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupDuration(name, fs) |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
func lookupDuration(name string, set *flag.FlagSet) time.Duration { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := time.ParseDuration(f.Value.String()) |
|
||||||
if err != nil { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
// Float64Flag is a flag with type float64
|
|
||||||
type Float64Flag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value float64 |
|
||||||
Destination *float64 |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Float64Flag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Float64Flag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// Float64 looks up the value of a local Float64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Float64(name string) float64 { |
|
||||||
return lookupFloat64(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalFloat64 looks up the value of a global Float64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalFloat64(name string) float64 { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupFloat64(name, fs) |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
func lookupFloat64(name string, set *flag.FlagSet) float64 { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := strconv.ParseFloat(f.Value.String(), 64) |
|
||||||
if err != nil { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
// GenericFlag is a flag with type Generic
|
|
||||||
type GenericFlag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value Generic |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f GenericFlag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f GenericFlag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// Generic looks up the value of a local GenericFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) Generic(name string) interface{} { |
|
||||||
return lookupGeneric(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalGeneric looks up the value of a global GenericFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalGeneric(name string) interface{} { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupGeneric(name, fs) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func lookupGeneric(name string, set *flag.FlagSet) interface{} { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := f.Value, error(nil) |
|
||||||
if err != nil { |
|
||||||
return nil |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Int64Flag is a flag with type int64
|
|
||||||
type Int64Flag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value int64 |
|
||||||
Destination *int64 |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Int64Flag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Int64Flag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// Int64 looks up the value of a local Int64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Int64(name string) int64 { |
|
||||||
return lookupInt64(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalInt64 looks up the value of a global Int64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalInt64(name string) int64 { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupInt64(name, fs) |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
func lookupInt64(name string, set *flag.FlagSet) int64 { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) |
|
||||||
if err != nil { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
// IntFlag is a flag with type int
|
|
||||||
type IntFlag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value int |
|
||||||
Destination *int |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f IntFlag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f IntFlag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// Int looks up the value of a local IntFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Int(name string) int { |
|
||||||
return lookupInt(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalInt looks up the value of a global IntFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalInt(name string) int { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupInt(name, fs) |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
func lookupInt(name string, set *flag.FlagSet) int { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) |
|
||||||
if err != nil { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return int(parsed) |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
// IntSliceFlag is a flag with type *IntSlice
|
|
||||||
type IntSliceFlag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value *IntSlice |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f IntSliceFlag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f IntSliceFlag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// IntSlice looks up the value of a local IntSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) IntSlice(name string) []int { |
|
||||||
return lookupIntSlice(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalIntSlice(name string) []int { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupIntSlice(name, fs) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func lookupIntSlice(name string, set *flag.FlagSet) []int { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil) |
|
||||||
if err != nil { |
|
||||||
return nil |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Int64SliceFlag is a flag with type *Int64Slice
|
|
||||||
type Int64SliceFlag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value *Int64Slice |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Int64SliceFlag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Int64SliceFlag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) Int64Slice(name string) []int64 { |
|
||||||
return lookupInt64Slice(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalInt64Slice(name string) []int64 { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupInt64Slice(name, fs) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil) |
|
||||||
if err != nil { |
|
||||||
return nil |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// StringFlag is a flag with type string
|
|
||||||
type StringFlag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value string |
|
||||||
Destination *string |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f StringFlag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f StringFlag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// String looks up the value of a local StringFlag, returns
|
|
||||||
// "" if not found
|
|
||||||
func (c *Context) String(name string) string { |
|
||||||
return lookupString(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalString looks up the value of a global StringFlag, returns
|
|
||||||
// "" if not found
|
|
||||||
func (c *Context) GlobalString(name string) string { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupString(name, fs) |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
func lookupString(name string, set *flag.FlagSet) string { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := f.Value.String(), error(nil) |
|
||||||
if err != nil { |
|
||||||
return "" |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
// StringSliceFlag is a flag with type *StringSlice
|
|
||||||
type StringSliceFlag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value *StringSlice |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f StringSliceFlag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f StringSliceFlag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// StringSlice looks up the value of a local StringSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) StringSlice(name string) []string { |
|
||||||
return lookupStringSlice(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
|
|
||||||
// nil if not found
|
|
||||||
func (c *Context) GlobalStringSlice(name string) []string { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupStringSlice(name, fs) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func lookupStringSlice(name string, set *flag.FlagSet) []string { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil) |
|
||||||
if err != nil { |
|
||||||
return nil |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Uint64Flag is a flag with type uint64
|
|
||||||
type Uint64Flag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value uint64 |
|
||||||
Destination *uint64 |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f Uint64Flag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f Uint64Flag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// Uint64 looks up the value of a local Uint64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Uint64(name string) uint64 { |
|
||||||
return lookupUint64(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalUint64 looks up the value of a global Uint64Flag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalUint64(name string) uint64 { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupUint64(name, fs) |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
func lookupUint64(name string, set *flag.FlagSet) uint64 { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) |
|
||||||
if err != nil { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return parsed |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
// UintFlag is a flag with type uint
|
|
||||||
type UintFlag struct { |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
Value uint |
|
||||||
Destination *uint |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f UintFlag) String() string { |
|
||||||
return FlagStringer(f) |
|
||||||
} |
|
||||||
|
|
||||||
// GetName returns the name of the flag
|
|
||||||
func (f UintFlag) GetName() string { |
|
||||||
return f.Name |
|
||||||
} |
|
||||||
|
|
||||||
// Uint looks up the value of a local UintFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) Uint(name string) uint { |
|
||||||
return lookupUint(name, c.flagSet) |
|
||||||
} |
|
||||||
|
|
||||||
// GlobalUint looks up the value of a global UintFlag, returns
|
|
||||||
// 0 if not found
|
|
||||||
func (c *Context) GlobalUint(name string) uint { |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||||
return lookupUint(name, fs) |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
func lookupUint(name string, set *flag.FlagSet) uint { |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil { |
|
||||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) |
|
||||||
if err != nil { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return uint(parsed) |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
@ -0,0 +1,110 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
// Generic is a generic parseable type identified by a specific flag
|
||||||
|
type Generic interface { |
||||||
|
Set(value string) error |
||||||
|
String() string |
||||||
|
} |
||||||
|
|
||||||
|
// GenericFlag is a flag with type Generic
|
||||||
|
type GenericFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
TakesFile bool |
||||||
|
Value Generic |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f GenericFlag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f GenericFlag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f GenericFlag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f GenericFlag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f GenericFlag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f GenericFlag) GetValue() string { |
||||||
|
if f.Value != nil { |
||||||
|
return f.Value.String() |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||||
|
// provided by the user for parsing by the flag
|
||||||
|
// Ignores parsing errors
|
||||||
|
func (f GenericFlag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
|
||||||
|
// provided by the user for parsing by the flag
|
||||||
|
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
val := f.Value |
||||||
|
if fileEnvVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
if err := val.Set(fileEnvVal); err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as value for flag %s: %s", fileEnvVal, f.Name, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
set.Var(f.Value, name, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Generic looks up the value of a local GenericFlag, returns
|
||||||
|
// nil if not found
|
||||||
|
func (c *Context) Generic(name string) interface{} { |
||||||
|
return lookupGeneric(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalGeneric looks up the value of a global GenericFlag, returns
|
||||||
|
// nil if not found
|
||||||
|
func (c *Context) GlobalGeneric(name string) interface{} { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupGeneric(name, fs) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupGeneric(name string, set *flag.FlagSet) interface{} { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := f.Value, error(nil) |
||||||
|
if err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// IntFlag is a flag with type int
|
||||||
|
type IntFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
Value int |
||||||
|
Destination *int |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f IntFlag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f IntFlag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f IntFlag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f IntFlag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f IntFlag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f IntFlag) GetValue() string { |
||||||
|
return fmt.Sprintf("%d", f.Value) |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f IntFlag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
envValInt, err := strconv.ParseInt(envVal, 0, 64) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
f.Value = int(envValInt) |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Destination != nil { |
||||||
|
set.IntVar(f.Destination, name, f.Value, f.Usage) |
||||||
|
return |
||||||
|
} |
||||||
|
set.Int(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Int looks up the value of a local IntFlag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) Int(name string) int { |
||||||
|
return lookupInt(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalInt looks up the value of a global IntFlag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) GlobalInt(name string) int { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupInt(name, fs) |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func lookupInt(name string, set *flag.FlagSet) int { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return int(parsed) |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// Int64Flag is a flag with type int64
|
||||||
|
type Int64Flag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
Value int64 |
||||||
|
Destination *int64 |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f Int64Flag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f Int64Flag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f Int64Flag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f Int64Flag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f Int64Flag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f Int64Flag) GetValue() string { |
||||||
|
return fmt.Sprintf("%d", f.Value) |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f Int64Flag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
envValInt, err := strconv.ParseInt(envVal, 0, 64) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
|
||||||
|
f.Value = envValInt |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Destination != nil { |
||||||
|
set.Int64Var(f.Destination, name, f.Value, f.Usage) |
||||||
|
return |
||||||
|
} |
||||||
|
set.Int64(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Int64 looks up the value of a local Int64Flag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) Int64(name string) int64 { |
||||||
|
return lookupInt64(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalInt64 looks up the value of a global Int64Flag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) GlobalInt64(name string) int64 { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupInt64(name, fs) |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func lookupInt64(name string, set *flag.FlagSet) int64 { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
@ -0,0 +1,141 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
||||||
|
type Int64Slice []int64 |
||||||
|
|
||||||
|
// Set parses the value into an integer and appends it to the list of values
|
||||||
|
func (f *Int64Slice) Set(value string) error { |
||||||
|
tmp, err := strconv.ParseInt(value, 10, 64) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
*f = append(*f, tmp) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *Int64Slice) String() string { |
||||||
|
return fmt.Sprintf("%#v", *f) |
||||||
|
} |
||||||
|
|
||||||
|
// Value returns the slice of ints set by this flag
|
||||||
|
func (f *Int64Slice) Value() []int64 { |
||||||
|
return *f |
||||||
|
} |
||||||
|
|
||||||
|
// Get returns the slice of ints set by this flag
|
||||||
|
func (f *Int64Slice) Get() interface{} { |
||||||
|
return *f |
||||||
|
} |
||||||
|
|
||||||
|
// Int64SliceFlag is a flag with type *Int64Slice
|
||||||
|
type Int64SliceFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
Value *Int64Slice |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f Int64SliceFlag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f Int64SliceFlag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f Int64SliceFlag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f Int64SliceFlag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f Int64SliceFlag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f Int64SliceFlag) GetValue() string { |
||||||
|
if f.Value != nil { |
||||||
|
return f.Value.String() |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f Int64SliceFlag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
newVal := &Int64Slice{} |
||||||
|
for _, s := range strings.Split(envVal, ",") { |
||||||
|
s = strings.TrimSpace(s) |
||||||
|
if err := newVal.Set(s); err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
} |
||||||
|
if f.Value == nil { |
||||||
|
f.Value = newVal |
||||||
|
} else { |
||||||
|
*f.Value = *newVal |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Value == nil { |
||||||
|
f.Value = &Int64Slice{} |
||||||
|
} |
||||||
|
set.Var(f.Value, name, f.Usage) |
||||||
|
}) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
||||||
|
// nil if not found
|
||||||
|
func (c *Context) Int64Slice(name string) []int64 { |
||||||
|
return lookupInt64Slice(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
|
||||||
|
// nil if not found
|
||||||
|
func (c *Context) GlobalInt64Slice(name string) []int64 { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupInt64Slice(name, fs) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil) |
||||||
|
if err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,142 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
||||||
|
type IntSlice []int |
||||||
|
|
||||||
|
// Set parses the value into an integer and appends it to the list of values
|
||||||
|
func (f *IntSlice) Set(value string) error { |
||||||
|
tmp, err := strconv.Atoi(value) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
*f = append(*f, tmp) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *IntSlice) String() string { |
||||||
|
return fmt.Sprintf("%#v", *f) |
||||||
|
} |
||||||
|
|
||||||
|
// Value returns the slice of ints set by this flag
|
||||||
|
func (f *IntSlice) Value() []int { |
||||||
|
return *f |
||||||
|
} |
||||||
|
|
||||||
|
// Get returns the slice of ints set by this flag
|
||||||
|
func (f *IntSlice) Get() interface{} { |
||||||
|
return *f |
||||||
|
} |
||||||
|
|
||||||
|
// IntSliceFlag is a flag with type *IntSlice
|
||||||
|
type IntSliceFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
Value *IntSlice |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f IntSliceFlag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f IntSliceFlag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f IntSliceFlag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f IntSliceFlag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f IntSliceFlag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f IntSliceFlag) GetValue() string { |
||||||
|
if f.Value != nil { |
||||||
|
return f.Value.String() |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f IntSliceFlag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
newVal := &IntSlice{} |
||||||
|
for _, s := range strings.Split(envVal, ",") { |
||||||
|
s = strings.TrimSpace(s) |
||||||
|
if err := newVal.Set(s); err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
} |
||||||
|
if f.Value == nil { |
||||||
|
f.Value = newVal |
||||||
|
} else { |
||||||
|
*f.Value = *newVal |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Value == nil { |
||||||
|
f.Value = &IntSlice{} |
||||||
|
} |
||||||
|
set.Var(f.Value, name, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// IntSlice looks up the value of a local IntSliceFlag, returns
|
||||||
|
// nil if not found
|
||||||
|
func (c *Context) IntSlice(name string) []int { |
||||||
|
return lookupIntSlice(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
|
||||||
|
// nil if not found
|
||||||
|
func (c *Context) GlobalIntSlice(name string) []int { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupIntSlice(name, fs) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupIntSlice(name string, set *flag.FlagSet) []int { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil) |
||||||
|
if err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,98 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import "flag" |
||||||
|
|
||||||
|
// StringFlag is a flag with type string
|
||||||
|
type StringFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
TakesFile bool |
||||||
|
Value string |
||||||
|
Destination *string |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f StringFlag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f StringFlag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f StringFlag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f StringFlag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f StringFlag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f StringFlag) GetValue() string { |
||||||
|
return f.Value |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f StringFlag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
f.Value = envVal |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Destination != nil { |
||||||
|
set.StringVar(f.Destination, name, f.Value, f.Usage) |
||||||
|
return |
||||||
|
} |
||||||
|
set.String(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// String looks up the value of a local StringFlag, returns
|
||||||
|
// "" if not found
|
||||||
|
func (c *Context) String(name string) string { |
||||||
|
return lookupString(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalString looks up the value of a global StringFlag, returns
|
||||||
|
// "" if not found
|
||||||
|
func (c *Context) GlobalString(name string) string { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupString(name, fs) |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
func lookupString(name string, set *flag.FlagSet) string { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := f.Value.String(), error(nil) |
||||||
|
if err != nil { |
||||||
|
return "" |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
@ -0,0 +1,138 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
|
||||||
|
type StringSlice []string |
||||||
|
|
||||||
|
// Set appends the string value to the list of values
|
||||||
|
func (f *StringSlice) Set(value string) error { |
||||||
|
*f = append(*f, value) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *StringSlice) String() string { |
||||||
|
return fmt.Sprintf("%s", *f) |
||||||
|
} |
||||||
|
|
||||||
|
// Value returns the slice of strings set by this flag
|
||||||
|
func (f *StringSlice) Value() []string { |
||||||
|
return *f |
||||||
|
} |
||||||
|
|
||||||
|
// Get returns the slice of strings set by this flag
|
||||||
|
func (f *StringSlice) Get() interface{} { |
||||||
|
return *f |
||||||
|
} |
||||||
|
|
||||||
|
// StringSliceFlag is a flag with type *StringSlice
|
||||||
|
type StringSliceFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
TakesFile bool |
||||||
|
Value *StringSlice |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f StringSliceFlag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f StringSliceFlag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f StringSliceFlag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f StringSliceFlag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f StringSliceFlag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f StringSliceFlag) GetValue() string { |
||||||
|
if f.Value != nil { |
||||||
|
return f.Value.String() |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f StringSliceFlag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
newVal := &StringSlice{} |
||||||
|
for _, s := range strings.Split(envVal, ",") { |
||||||
|
s = strings.TrimSpace(s) |
||||||
|
if err := newVal.Set(s); err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
} |
||||||
|
if f.Value == nil { |
||||||
|
f.Value = newVal |
||||||
|
} else { |
||||||
|
*f.Value = *newVal |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Value == nil { |
||||||
|
f.Value = &StringSlice{} |
||||||
|
} |
||||||
|
set.Var(f.Value, name, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// StringSlice looks up the value of a local StringSliceFlag, returns
|
||||||
|
// nil if not found
|
||||||
|
func (c *Context) StringSlice(name string) []string { |
||||||
|
return lookupStringSlice(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
|
||||||
|
// nil if not found
|
||||||
|
func (c *Context) GlobalStringSlice(name string) []string { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupStringSlice(name, fs) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupStringSlice(name string, set *flag.FlagSet) []string { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil) |
||||||
|
if err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// UintFlag is a flag with type uint
|
||||||
|
type UintFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
Value uint |
||||||
|
Destination *uint |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f UintFlag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f UintFlag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f UintFlag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f UintFlag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f UintFlag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f UintFlag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
envValInt, err := strconv.ParseUint(envVal, 0, 64) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
|
||||||
|
f.Value = uint(envValInt) |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Destination != nil { |
||||||
|
set.UintVar(f.Destination, name, f.Value, f.Usage) |
||||||
|
return |
||||||
|
} |
||||||
|
set.Uint(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f UintFlag) GetValue() string { |
||||||
|
return fmt.Sprintf("%d", f.Value) |
||||||
|
} |
||||||
|
|
||||||
|
// Uint looks up the value of a local UintFlag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) Uint(name string) uint { |
||||||
|
return lookupUint(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalUint looks up the value of a global UintFlag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) GlobalUint(name string) uint { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupUint(name, fs) |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func lookupUint(name string, set *flag.FlagSet) uint { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return uint(parsed) |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// Uint64Flag is a flag with type uint64
|
||||||
|
type Uint64Flag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
FilePath string |
||||||
|
Required bool |
||||||
|
Hidden bool |
||||||
|
Value uint64 |
||||||
|
Destination *uint64 |
||||||
|
} |
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults)
|
||||||
|
func (f Uint64Flag) String() string { |
||||||
|
return FlagStringer(f) |
||||||
|
} |
||||||
|
|
||||||
|
// GetName returns the name of the flag
|
||||||
|
func (f Uint64Flag) GetName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f Uint64Flag) IsRequired() bool { |
||||||
|
return f.Required |
||||||
|
} |
||||||
|
|
||||||
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
|
func (f Uint64Flag) TakesValue() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// GetUsage returns the usage string for the flag
|
||||||
|
func (f Uint64Flag) GetUsage() string { |
||||||
|
return f.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// GetValue returns the flags value as string representation and an empty
|
||||||
|
// string if the flag takes no value at all.
|
||||||
|
func (f Uint64Flag) GetValue() string { |
||||||
|
return fmt.Sprintf("%d", f.Value) |
||||||
|
} |
||||||
|
|
||||||
|
// Apply populates the flag given the flag set and environment
|
||||||
|
// Ignores errors
|
||||||
|
func (f Uint64Flag) Apply(set *flag.FlagSet) { |
||||||
|
_ = f.ApplyWithError(set) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyWithError populates the flag given the flag set and environment
|
||||||
|
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error { |
||||||
|
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
||||||
|
envValInt, err := strconv.ParseUint(envVal, 0, 64) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) |
||||||
|
} |
||||||
|
|
||||||
|
f.Value = envValInt |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
if f.Destination != nil { |
||||||
|
set.Uint64Var(f.Destination, name, f.Value, f.Usage) |
||||||
|
return |
||||||
|
} |
||||||
|
set.Uint64(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Uint64 looks up the value of a local Uint64Flag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) Uint64(name string) uint64 { |
||||||
|
return lookupUint64(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// GlobalUint64 looks up the value of a global Uint64Flag, returns
|
||||||
|
// 0 if not found
|
||||||
|
func (c *Context) GlobalUint64(name string) uint64 { |
||||||
|
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
||||||
|
return lookupUint64(name, fs) |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func lookupUint64(name string, set *flag.FlagSet) uint64 { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return parsed |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
@ -1,255 +0,0 @@ |
|||||||
#!/usr/bin/env python |
|
||||||
""" |
|
||||||
The flag types that ship with the cli library have many things in common, and |
|
||||||
so we can take advantage of the `go generate` command to create much of the |
|
||||||
source code from a list of definitions. These definitions attempt to cover |
|
||||||
the parts that vary between flag types, and should evolve as needed. |
|
||||||
|
|
||||||
An example of the minimum definition needed is: |
|
||||||
|
|
||||||
{ |
|
||||||
"name": "SomeType", |
|
||||||
"type": "sometype", |
|
||||||
"context_default": "nil" |
|
||||||
} |
|
||||||
|
|
||||||
In this example, the code generated for the `cli` package will include a type |
|
||||||
named `SomeTypeFlag` that is expected to wrap a value of type `sometype`. |
|
||||||
Fetching values by name via `*cli.Context` will default to a value of `nil`. |
|
||||||
|
|
||||||
A more complete, albeit somewhat redundant, example showing all available |
|
||||||
definition keys is: |
|
||||||
|
|
||||||
{ |
|
||||||
"name": "VeryMuchType", |
|
||||||
"type": "*VeryMuchType", |
|
||||||
"value": true, |
|
||||||
"dest": false, |
|
||||||
"doctail": " which really only wraps a []float64, oh well!", |
|
||||||
"context_type": "[]float64", |
|
||||||
"context_default": "nil", |
|
||||||
"parser": "parseVeryMuchType(f.Value.String())", |
|
||||||
"parser_cast": "[]float64(parsed)" |
|
||||||
} |
|
||||||
|
|
||||||
The meaning of each field is as follows: |
|
||||||
|
|
||||||
name (string) - The type "name", which will be suffixed with |
|
||||||
`Flag` when generating the type definition |
|
||||||
for `cli` and the wrapper type for `altsrc` |
|
||||||
type (string) - The type that the generated `Flag` type for `cli` |
|
||||||
is expected to "contain" as its `.Value` member |
|
||||||
value (bool) - Should the generated `cli` type have a `Value` |
|
||||||
member? |
|
||||||
dest (bool) - Should the generated `cli` type support a |
|
||||||
destination pointer? |
|
||||||
doctail (string) - Additional docs for the `cli` flag type comment |
|
||||||
context_type (string) - The literal type used in the `*cli.Context` |
|
||||||
reader func signature |
|
||||||
context_default (string) - The literal value used as the default by the |
|
||||||
`*cli.Context` reader funcs when no value is |
|
||||||
present |
|
||||||
parser (string) - Literal code used to parse the flag `f`, |
|
||||||
expected to have a return signature of |
|
||||||
(value, error) |
|
||||||
parser_cast (string) - Literal code used to cast the `parsed` value |
|
||||||
returned from the `parser` code |
|
||||||
""" |
|
||||||
|
|
||||||
from __future__ import print_function, unicode_literals |
|
||||||
|
|
||||||
import argparse |
|
||||||
import json |
|
||||||
import os |
|
||||||
import subprocess |
|
||||||
import sys |
|
||||||
import tempfile |
|
||||||
import textwrap |
|
||||||
|
|
||||||
|
|
||||||
class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter, |
|
||||||
argparse.RawDescriptionHelpFormatter): |
|
||||||
pass |
|
||||||
|
|
||||||
|
|
||||||
def main(sysargs=sys.argv[:]): |
|
||||||
parser = argparse.ArgumentParser( |
|
||||||
description='Generate flag type code!', |
|
||||||
formatter_class=_FancyFormatter) |
|
||||||
parser.add_argument( |
|
||||||
'package', |
|
||||||
type=str, default='cli', choices=_WRITEFUNCS.keys(), |
|
||||||
help='Package for which flag types will be generated' |
|
||||||
) |
|
||||||
parser.add_argument( |
|
||||||
'-i', '--in-json', |
|
||||||
type=argparse.FileType('r'), |
|
||||||
default=sys.stdin, |
|
||||||
help='Input JSON file which defines each type to be generated' |
|
||||||
) |
|
||||||
parser.add_argument( |
|
||||||
'-o', '--out-go', |
|
||||||
type=argparse.FileType('w'), |
|
||||||
default=sys.stdout, |
|
||||||
help='Output file/stream to which generated source will be written' |
|
||||||
) |
|
||||||
parser.epilog = __doc__ |
|
||||||
|
|
||||||
args = parser.parse_args(sysargs[1:]) |
|
||||||
_generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json) |
|
||||||
return 0 |
|
||||||
|
|
||||||
|
|
||||||
def _generate_flag_types(writefunc, output_go, input_json): |
|
||||||
types = json.load(input_json) |
|
||||||
|
|
||||||
tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False) |
|
||||||
writefunc(tmp, types) |
|
||||||
tmp.close() |
|
||||||
|
|
||||||
new_content = subprocess.check_output( |
|
||||||
['goimports', tmp.name] |
|
||||||
).decode('utf-8') |
|
||||||
|
|
||||||
print(new_content, file=output_go, end='') |
|
||||||
output_go.flush() |
|
||||||
os.remove(tmp.name) |
|
||||||
|
|
||||||
|
|
||||||
def _set_typedef_defaults(typedef): |
|
||||||
typedef.setdefault('doctail', '') |
|
||||||
typedef.setdefault('context_type', typedef['type']) |
|
||||||
typedef.setdefault('dest', True) |
|
||||||
typedef.setdefault('value', True) |
|
||||||
typedef.setdefault('parser', 'f.Value, error(nil)') |
|
||||||
typedef.setdefault('parser_cast', 'parsed') |
|
||||||
|
|
||||||
|
|
||||||
def _write_cli_flag_types(outfile, types): |
|
||||||
_fwrite(outfile, """\ |
|
||||||
package cli |
|
||||||
|
|
||||||
// WARNING: This file is generated! |
|
||||||
|
|
||||||
""") |
|
||||||
|
|
||||||
for typedef in types: |
|
||||||
_set_typedef_defaults(typedef) |
|
||||||
|
|
||||||
_fwrite(outfile, """\ |
|
||||||
// {name}Flag is a flag with type {type}{doctail} |
|
||||||
type {name}Flag struct {{ |
|
||||||
Name string |
|
||||||
Usage string |
|
||||||
EnvVar string |
|
||||||
Hidden bool |
|
||||||
""".format(**typedef)) |
|
||||||
|
|
||||||
if typedef['value']: |
|
||||||
_fwrite(outfile, """\ |
|
||||||
Value {type} |
|
||||||
""".format(**typedef)) |
|
||||||
|
|
||||||
if typedef['dest']: |
|
||||||
_fwrite(outfile, """\ |
|
||||||
Destination *{type} |
|
||||||
""".format(**typedef)) |
|
||||||
|
|
||||||
_fwrite(outfile, "\n}\n\n") |
|
||||||
|
|
||||||
_fwrite(outfile, """\ |
|
||||||
// String returns a readable representation of this value |
|
||||||
// (for usage defaults) |
|
||||||
func (f {name}Flag) String() string {{ |
|
||||||
return FlagStringer(f) |
|
||||||
}} |
|
||||||
|
|
||||||
// GetName returns the name of the flag |
|
||||||
func (f {name}Flag) GetName() string {{ |
|
||||||
return f.Name |
|
||||||
}} |
|
||||||
|
|
||||||
// {name} looks up the value of a local {name}Flag, returns |
|
||||||
// {context_default} if not found |
|
||||||
func (c *Context) {name}(name string) {context_type} {{ |
|
||||||
return lookup{name}(name, c.flagSet) |
|
||||||
}} |
|
||||||
|
|
||||||
// Global{name} looks up the value of a global {name}Flag, returns |
|
||||||
// {context_default} if not found |
|
||||||
func (c *Context) Global{name}(name string) {context_type} {{ |
|
||||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {{ |
|
||||||
return lookup{name}(name, fs) |
|
||||||
}} |
|
||||||
return {context_default} |
|
||||||
}} |
|
||||||
|
|
||||||
func lookup{name}(name string, set *flag.FlagSet) {context_type} {{ |
|
||||||
f := set.Lookup(name) |
|
||||||
if f != nil {{ |
|
||||||
parsed, err := {parser} |
|
||||||
if err != nil {{ |
|
||||||
return {context_default} |
|
||||||
}} |
|
||||||
return {parser_cast} |
|
||||||
}} |
|
||||||
return {context_default} |
|
||||||
}} |
|
||||||
""".format(**typedef)) |
|
||||||
|
|
||||||
|
|
||||||
def _write_altsrc_flag_types(outfile, types): |
|
||||||
_fwrite(outfile, """\ |
|
||||||
package altsrc |
|
||||||
|
|
||||||
import ( |
|
||||||
"gopkg.in/urfave/cli.v1" |
|
||||||
) |
|
||||||
|
|
||||||
// WARNING: This file is generated! |
|
||||||
|
|
||||||
""") |
|
||||||
|
|
||||||
for typedef in types: |
|
||||||
_set_typedef_defaults(typedef) |
|
||||||
|
|
||||||
_fwrite(outfile, """\ |
|
||||||
// {name}Flag is the flag type that wraps cli.{name}Flag to allow |
|
||||||
// for other values to be specified |
|
||||||
type {name}Flag struct {{ |
|
||||||
cli.{name}Flag |
|
||||||
set *flag.FlagSet |
|
||||||
}} |
|
||||||
|
|
||||||
// New{name}Flag creates a new {name}Flag |
|
||||||
func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{ |
|
||||||
return &{name}Flag{{{name}Flag: fl, set: nil}} |
|
||||||
}} |
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the |
|
||||||
// wrapped {name}Flag.Apply |
|
||||||
func (f *{name}Flag) Apply(set *flag.FlagSet) {{ |
|
||||||
f.set = set |
|
||||||
f.{name}Flag.Apply(set) |
|
||||||
}} |
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the |
|
||||||
// wrapped {name}Flag.ApplyWithError |
|
||||||
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{ |
|
||||||
f.set = set |
|
||||||
return f.{name}Flag.ApplyWithError(set) |
|
||||||
}} |
|
||||||
""".format(**typedef)) |
|
||||||
|
|
||||||
|
|
||||||
def _fwrite(outfile, text): |
|
||||||
print(textwrap.dedent(text), end='', file=outfile) |
|
||||||
|
|
||||||
|
|
||||||
_WRITEFUNCS = { |
|
||||||
'cli': _write_cli_flag_types, |
|
||||||
'altsrc': _write_altsrc_flag_types |
|
||||||
} |
|
||||||
|
|
||||||
if __name__ == '__main__': |
|
||||||
sys.exit(main()) |
|
@ -0,0 +1,89 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type iterativeParser interface { |
||||||
|
newFlagSet() (*flag.FlagSet, error) |
||||||
|
useShortOptionHandling() bool |
||||||
|
} |
||||||
|
|
||||||
|
// To enable short-option handling (e.g., "-it" vs "-i -t") we have to
|
||||||
|
// iteratively catch parsing errors. This way we achieve LR parsing without
|
||||||
|
// transforming any arguments. Otherwise, there is no way we can discriminate
|
||||||
|
// combined short options from common arguments that should be left untouched.
|
||||||
|
func parseIter(set *flag.FlagSet, ip iterativeParser, args []string) error { |
||||||
|
for { |
||||||
|
err := set.Parse(args) |
||||||
|
if !ip.useShortOptionHandling() || err == nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
errStr := err.Error() |
||||||
|
trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -") |
||||||
|
if errStr == trimmed { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// regenerate the initial args with the split short opts
|
||||||
|
argsWereSplit := false |
||||||
|
for i, arg := range args { |
||||||
|
// skip args that are not part of the error message
|
||||||
|
if name := strings.TrimLeft(arg, "-"); name != trimmed { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// if we can't split, the error was accurate
|
||||||
|
shortOpts := splitShortOptions(set, arg) |
||||||
|
if len(shortOpts) == 1 { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// swap current argument with the split version
|
||||||
|
args = append(args[:i], append(shortOpts, args[i+1:]...)...) |
||||||
|
argsWereSplit = true |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
// This should be an impossible to reach code path, but in case the arg
|
||||||
|
// splitting failed to happen, this will prevent infinite loops
|
||||||
|
if !argsWereSplit { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Since custom parsing failed, replace the flag set before retrying
|
||||||
|
newSet, err := ip.newFlagSet() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
*set = *newSet |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func splitShortOptions(set *flag.FlagSet, arg string) []string { |
||||||
|
shortFlagsExist := func(s string) bool { |
||||||
|
for _, c := range s[1:] { |
||||||
|
if f := set.Lookup(string(c)); f == nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
if !isSplittable(arg) || !shortFlagsExist(arg) { |
||||||
|
return []string{arg} |
||||||
|
} |
||||||
|
|
||||||
|
separated := make([]string, 0, len(arg)-1) |
||||||
|
for _, flagChar := range arg[1:] { |
||||||
|
separated = append(separated, "-"+string(flagChar)) |
||||||
|
} |
||||||
|
|
||||||
|
return separated |
||||||
|
} |
||||||
|
|
||||||
|
func isSplittable(flagArg string) bool { |
||||||
|
return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2 |
||||||
|
} |
@ -1,122 +0,0 @@ |
|||||||
#!/usr/bin/env python |
|
||||||
from __future__ import print_function |
|
||||||
|
|
||||||
import argparse |
|
||||||
import os |
|
||||||
import sys |
|
||||||
import tempfile |
|
||||||
|
|
||||||
from subprocess import check_call, check_output |
|
||||||
|
|
||||||
|
|
||||||
PACKAGE_NAME = os.environ.get( |
|
||||||
'CLI_PACKAGE_NAME', 'github.com/urfave/cli' |
|
||||||
) |
|
||||||
|
|
||||||
|
|
||||||
def main(sysargs=sys.argv[:]): |
|
||||||
targets = { |
|
||||||
'vet': _vet, |
|
||||||
'test': _test, |
|
||||||
'gfmrun': _gfmrun, |
|
||||||
'toc': _toc, |
|
||||||
'gen': _gen, |
|
||||||
} |
|
||||||
|
|
||||||
parser = argparse.ArgumentParser() |
|
||||||
parser.add_argument( |
|
||||||
'target', nargs='?', choices=tuple(targets.keys()), default='test' |
|
||||||
) |
|
||||||
args = parser.parse_args(sysargs[1:]) |
|
||||||
|
|
||||||
targets[args.target]() |
|
||||||
return 0 |
|
||||||
|
|
||||||
|
|
||||||
def _test(): |
|
||||||
if check_output('go version'.split()).split()[2] < 'go1.2': |
|
||||||
_run('go test -v .') |
|
||||||
return |
|
||||||
|
|
||||||
coverprofiles = [] |
|
||||||
for subpackage in ['', 'altsrc']: |
|
||||||
coverprofile = 'cli.coverprofile' |
|
||||||
if subpackage != '': |
|
||||||
coverprofile = '{}.coverprofile'.format(subpackage) |
|
||||||
|
|
||||||
coverprofiles.append(coverprofile) |
|
||||||
|
|
||||||
_run('go test -v'.split() + [ |
|
||||||
'-coverprofile={}'.format(coverprofile), |
|
||||||
('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/') |
|
||||||
]) |
|
||||||
|
|
||||||
combined_name = _combine_coverprofiles(coverprofiles) |
|
||||||
_run('go tool cover -func={}'.format(combined_name)) |
|
||||||
os.remove(combined_name) |
|
||||||
|
|
||||||
|
|
||||||
def _gfmrun(): |
|
||||||
go_version = check_output('go version'.split()).split()[2] |
|
||||||
if go_version < 'go1.3': |
|
||||||
print('runtests: skip on {}'.format(go_version), file=sys.stderr) |
|
||||||
return |
|
||||||
_run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md']) |
|
||||||
|
|
||||||
|
|
||||||
def _vet(): |
|
||||||
_run('go vet ./...') |
|
||||||
|
|
||||||
|
|
||||||
def _toc(): |
|
||||||
_run('node_modules/.bin/markdown-toc -i README.md') |
|
||||||
_run('git diff --exit-code') |
|
||||||
|
|
||||||
|
|
||||||
def _gen(): |
|
||||||
go_version = check_output('go version'.split()).split()[2] |
|
||||||
if go_version < 'go1.5': |
|
||||||
print('runtests: skip on {}'.format(go_version), file=sys.stderr) |
|
||||||
return |
|
||||||
|
|
||||||
_run('go generate ./...') |
|
||||||
_run('git diff --exit-code') |
|
||||||
|
|
||||||
|
|
||||||
def _run(command): |
|
||||||
if hasattr(command, 'split'): |
|
||||||
command = command.split() |
|
||||||
print('runtests: {}'.format(' '.join(command)), file=sys.stderr) |
|
||||||
check_call(command) |
|
||||||
|
|
||||||
|
|
||||||
def _gfmrun_count(): |
|
||||||
with open('README.md') as infile: |
|
||||||
lines = infile.read().splitlines() |
|
||||||
return len(filter(_is_go_runnable, lines)) |
|
||||||
|
|
||||||
|
|
||||||
def _is_go_runnable(line): |
|
||||||
return line.startswith('package main') |
|
||||||
|
|
||||||
|
|
||||||
def _combine_coverprofiles(coverprofiles): |
|
||||||
combined = tempfile.NamedTemporaryFile( |
|
||||||
suffix='.coverprofile', delete=False |
|
||||||
) |
|
||||||
combined.write('mode: set\n') |
|
||||||
|
|
||||||
for coverprofile in coverprofiles: |
|
||||||
with open(coverprofile, 'r') as infile: |
|
||||||
for line in infile.readlines(): |
|
||||||
if not line.startswith('mode: '): |
|
||||||
combined.write(line) |
|
||||||
|
|
||||||
combined.flush() |
|
||||||
name = combined.name |
|
||||||
combined.close() |
|
||||||
return name |
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__': |
|
||||||
sys.exit(main()) |
|
@ -0,0 +1,29 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import "unicode" |
||||||
|
|
||||||
|
// lexicographicLess compares strings alphabetically considering case.
|
||||||
|
func lexicographicLess(i, j string) bool { |
||||||
|
iRunes := []rune(i) |
||||||
|
jRunes := []rune(j) |
||||||
|
|
||||||
|
lenShared := len(iRunes) |
||||||
|
if lenShared > len(jRunes) { |
||||||
|
lenShared = len(jRunes) |
||||||
|
} |
||||||
|
|
||||||
|
for index := 0; index < lenShared; index++ { |
||||||
|
ir := iRunes[index] |
||||||
|
jr := jRunes[index] |
||||||
|
|
||||||
|
if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr { |
||||||
|
return lir < ljr |
||||||
|
} |
||||||
|
|
||||||
|
if ir != jr { |
||||||
|
return ir < jr |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return i < j |
||||||
|
} |
@ -0,0 +1,121 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
// AppHelpTemplate is the text template for the Default help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
var AppHelpTemplate = `NAME: |
||||||
|
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}} |
||||||
|
|
||||||
|
USAGE: |
||||||
|
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} |
||||||
|
|
||||||
|
VERSION: |
||||||
|
{{.Version}}{{end}}{{end}}{{if .Description}} |
||||||
|
|
||||||
|
DESCRIPTION: |
||||||
|
{{.Description}}{{end}}{{if len .Authors}} |
||||||
|
|
||||||
|
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: |
||||||
|
{{range $index, $author := .Authors}}{{if $index}} |
||||||
|
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} |
||||||
|
|
||||||
|
COMMANDS:{{range .VisibleCategories}}{{if .Name}} |
||||||
|
|
||||||
|
{{.Name}}:{{range .VisibleCommands}} |
||||||
|
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} |
||||||
|
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} |
||||||
|
|
||||||
|
GLOBAL OPTIONS: |
||||||
|
{{range $index, $option := .VisibleFlags}}{{if $index}} |
||||||
|
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} |
||||||
|
|
||||||
|
COPYRIGHT: |
||||||
|
{{.Copyright}}{{end}} |
||||||
|
` |
||||||
|
|
||||||
|
// CommandHelpTemplate is the text template for the command help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
var CommandHelpTemplate = `NAME: |
||||||
|
{{.HelpName}} - {{.Usage}} |
||||||
|
|
||||||
|
USAGE: |
||||||
|
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} |
||||||
|
|
||||||
|
CATEGORY: |
||||||
|
{{.Category}}{{end}}{{if .Description}} |
||||||
|
|
||||||
|
DESCRIPTION: |
||||||
|
{{.Description}}{{end}}{{if .VisibleFlags}} |
||||||
|
|
||||||
|
OPTIONS: |
||||||
|
{{range .VisibleFlags}}{{.}} |
||||||
|
{{end}}{{end}} |
||||||
|
` |
||||||
|
|
||||||
|
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
var SubcommandHelpTemplate = `NAME: |
||||||
|
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} |
||||||
|
|
||||||
|
USAGE: |
||||||
|
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} |
||||||
|
|
||||||
|
COMMANDS:{{range .VisibleCategories}}{{if .Name}} |
||||||
|
|
||||||
|
{{.Name}}:{{range .VisibleCommands}} |
||||||
|
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} |
||||||
|
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} |
||||||
|
|
||||||
|
OPTIONS: |
||||||
|
{{range .VisibleFlags}}{{.}} |
||||||
|
{{end}}{{end}} |
||||||
|
` |
||||||
|
|
||||||
|
var MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }} |
||||||
|
|
||||||
|
% {{ .App.Author }} |
||||||
|
|
||||||
|
# NAME |
||||||
|
|
||||||
|
{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }} |
||||||
|
|
||||||
|
# SYNOPSIS |
||||||
|
|
||||||
|
{{ .App.Name }} |
||||||
|
{{ if .SynopsisArgs }} |
||||||
|
` + "```" + ` |
||||||
|
{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + ` |
||||||
|
{{ end }}{{ if .App.UsageText }} |
||||||
|
# DESCRIPTION |
||||||
|
|
||||||
|
{{ .App.UsageText }} |
||||||
|
{{ end }} |
||||||
|
**Usage**: |
||||||
|
|
||||||
|
` + "```" + ` |
||||||
|
{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] |
||||||
|
` + "```" + ` |
||||||
|
{{ if .GlobalArgs }} |
||||||
|
# GLOBAL OPTIONS |
||||||
|
{{ range $v := .GlobalArgs }} |
||||||
|
{{ $v }}{{ end }} |
||||||
|
{{ end }}{{ if .Commands }} |
||||||
|
# COMMANDS |
||||||
|
{{ range $v := .Commands }} |
||||||
|
{{ $v }}{{ end }}{{ end }}` |
||||||
|
|
||||||
|
var FishCompletionTemplate = `# {{ .App.Name }} fish shell completion |
||||||
|
|
||||||
|
function __fish_{{ .App.Name }}_no_subcommand --description 'Test if there has been any subcommand yet' |
||||||
|
for i in (commandline -opc) |
||||||
|
if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }} |
||||||
|
return 1 |
||||||
|
end |
||||||
|
end |
||||||
|
return 0 |
||||||
|
end |
||||||
|
|
||||||
|
{{ range $v := .Completions }}{{ $v }} |
||||||
|
{{ end }}` |
@ -0,0 +1,149 @@ |
|||||||
|
// Copyright 2021 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.
|
||||||
|
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package unix |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"unsafe" |
||||||
|
) |
||||||
|
|
||||||
|
// Helpers for dealing with ifreq since it contains a union and thus requires a
|
||||||
|
// lot of unsafe.Pointer casts to use properly.
|
||||||
|
|
||||||
|
// An Ifreq is a type-safe wrapper around the raw ifreq struct. An Ifreq
|
||||||
|
// contains an interface name and a union of arbitrary data which can be
|
||||||
|
// accessed using the Ifreq's methods. To create an Ifreq, use the NewIfreq
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
// Use the Name method to access the stored interface name. The union data
|
||||||
|
// fields can be get and set using the following methods:
|
||||||
|
// - Uint16/SetUint16: flags
|
||||||
|
// - Uint32/SetUint32: ifindex, metric, mtu
|
||||||
|
type Ifreq struct{ raw ifreq } |
||||||
|
|
||||||
|
// NewIfreq creates an Ifreq with the input network interface name after
|
||||||
|
// validating the name does not exceed IFNAMSIZ-1 (trailing NULL required)
|
||||||
|
// bytes.
|
||||||
|
func NewIfreq(name string) (*Ifreq, error) { |
||||||
|
// Leave room for terminating NULL byte.
|
||||||
|
if len(name) >= IFNAMSIZ { |
||||||
|
return nil, EINVAL |
||||||
|
} |
||||||
|
|
||||||
|
var ifr ifreq |
||||||
|
copy(ifr.Ifrn[:], name) |
||||||
|
|
||||||
|
return &Ifreq{raw: ifr}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// TODO(mdlayher): get/set methods for hardware address sockaddr, char array, etc.
|
||||||
|
|
||||||
|
// Name returns the interface name associated with the Ifreq.
|
||||||
|
func (ifr *Ifreq) Name() string { |
||||||
|
// BytePtrToString requires a NULL terminator or the program may crash. If
|
||||||
|
// one is not present, just return the empty string.
|
||||||
|
if !bytes.Contains(ifr.raw.Ifrn[:], []byte{0x00}) { |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
return BytePtrToString(&ifr.raw.Ifrn[0]) |
||||||
|
} |
||||||
|
|
||||||
|
// According to netdevice(7), only AF_INET addresses are returned for numerous
|
||||||
|
// sockaddr ioctls. For convenience, we expose these as Inet4Addr since the Port
|
||||||
|
// field and other data is always empty.
|
||||||
|
|
||||||
|
// Inet4Addr returns the Ifreq union data from an embedded sockaddr as a C
|
||||||
|
// in_addr/Go []byte (4-byte IPv4 address) value. If the sockaddr family is not
|
||||||
|
// AF_INET, an error is returned.
|
||||||
|
func (ifr *Ifreq) Inet4Addr() ([]byte, error) { |
||||||
|
raw := *(*RawSockaddrInet4)(unsafe.Pointer(&ifr.raw.Ifru[:SizeofSockaddrInet4][0])) |
||||||
|
if raw.Family != AF_INET { |
||||||
|
// Cannot safely interpret raw.Addr bytes as an IPv4 address.
|
||||||
|
return nil, EINVAL |
||||||
|
} |
||||||
|
|
||||||
|
return raw.Addr[:], nil |
||||||
|
} |
||||||
|
|
||||||
|
// SetInet4Addr sets a C in_addr/Go []byte (4-byte IPv4 address) value in an
|
||||||
|
// embedded sockaddr within the Ifreq's union data. v must be 4 bytes in length
|
||||||
|
// or an error will be returned.
|
||||||
|
func (ifr *Ifreq) SetInet4Addr(v []byte) error { |
||||||
|
if len(v) != 4 { |
||||||
|
return EINVAL |
||||||
|
} |
||||||
|
|
||||||
|
var addr [4]byte |
||||||
|
copy(addr[:], v) |
||||||
|
|
||||||
|
ifr.clear() |
||||||
|
*(*RawSockaddrInet4)( |
||||||
|
unsafe.Pointer(&ifr.raw.Ifru[:SizeofSockaddrInet4][0]), |
||||||
|
) = RawSockaddrInet4{ |
||||||
|
// Always set IP family as ioctls would require it anyway.
|
||||||
|
Family: AF_INET, |
||||||
|
Addr: addr, |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Uint16 returns the Ifreq union data as a C short/Go uint16 value.
|
||||||
|
func (ifr *Ifreq) Uint16() uint16 { |
||||||
|
return *(*uint16)(unsafe.Pointer(&ifr.raw.Ifru[:2][0])) |
||||||
|
} |
||||||
|
|
||||||
|
// SetUint16 sets a C short/Go uint16 value as the Ifreq's union data.
|
||||||
|
func (ifr *Ifreq) SetUint16(v uint16) { |
||||||
|
ifr.clear() |
||||||
|
*(*uint16)(unsafe.Pointer(&ifr.raw.Ifru[:2][0])) = v |
||||||
|
} |
||||||
|
|
||||||
|
// Uint32 returns the Ifreq union data as a C int/Go uint32 value.
|
||||||
|
func (ifr *Ifreq) Uint32() uint32 { |
||||||
|
return *(*uint32)(unsafe.Pointer(&ifr.raw.Ifru[:4][0])) |
||||||
|
} |
||||||
|
|
||||||
|
// SetUint32 sets a C int/Go uint32 value as the Ifreq's union data.
|
||||||
|
func (ifr *Ifreq) SetUint32(v uint32) { |
||||||
|
ifr.clear() |
||||||
|
*(*uint32)(unsafe.Pointer(&ifr.raw.Ifru[:4][0])) = v |
||||||
|
} |
||||||
|
|
||||||
|
// clear zeroes the ifreq's union field to prevent trailing garbage data from
|
||||||
|
// being sent to the kernel if an ifreq is reused.
|
||||||
|
func (ifr *Ifreq) clear() { |
||||||
|
for i := range ifr.raw.Ifru { |
||||||
|
ifr.raw.Ifru[i] = 0 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO(mdlayher): export as IfreqData? For now we can provide helpers such as
|
||||||
|
// IoctlGetEthtoolDrvinfo which use these APIs under the hood.
|
||||||
|
|
||||||
|
// An ifreqData is an Ifreq which carries pointer data. To produce an ifreqData,
|
||||||
|
// use the Ifreq.withData method.
|
||||||
|
type ifreqData struct { |
||||||
|
name [IFNAMSIZ]byte |
||||||
|
// A type separate from ifreq is required in order to comply with the
|
||||||
|
// unsafe.Pointer rules since the "pointer-ness" of data would not be
|
||||||
|
// preserved if it were cast into the byte array of a raw ifreq.
|
||||||
|
data unsafe.Pointer |
||||||
|
// Pad to the same size as ifreq.
|
||||||
|
_ [len(ifreq{}.Ifru) - SizeofPtr]byte |
||||||
|
} |
||||||
|
|
||||||
|
// withData produces an ifreqData with the pointer p set for ioctls which require
|
||||||
|
// arbitrary pointer data.
|
||||||
|
func (ifr Ifreq) withData(p unsafe.Pointer) ifreqData { |
||||||
|
return ifreqData{ |
||||||
|
name: ifr.raw.Ifrn, |
||||||
|
data: p, |
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue