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 |
||||
|
||||
//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 |
||||
node_modules/ |
||||
vendor |
@ -1,27 +1,35 @@ |
||||
language: go |
||||
sudo: false |
||||
dist: trusty |
||||
osx_image: xcode8.3 |
||||
go: 1.8.x |
||||
dist: bionic |
||||
osx_image: xcode10 |
||||
go: |
||||
- 1.11.x |
||||
- 1.12.x |
||||
- 1.13.x |
||||
|
||||
os: |
||||
- linux |
||||
- osx |
||||
- linux |
||||
- osx |
||||
|
||||
env: |
||||
GO111MODULE=on |
||||
GOPROXY=https://proxy.golang.org |
||||
|
||||
cache: |
||||
directories: |
||||
- node_modules |
||||
- node_modules |
||||
|
||||
before_script: |
||||
- go get github.com/urfave/gfmrun/... || true |
||||
- go get golang.org/x/tools/cmd/goimports |
||||
- if [ ! -f node_modules/.bin/markdown-toc ] ; then |
||||
npm install markdown-toc ; |
||||
fi |
||||
- go get github.com/urfave/gfmrun/cmd/gfmrun |
||||
- go get golang.org/x/tools/cmd/goimports |
||||
- npm install markdown-toc |
||||
- go mod tidy |
||||
|
||||
script: |
||||
- ./runtests gen |
||||
- ./runtests vet |
||||
- ./runtests test |
||||
- ./runtests gfmrun |
||||
- ./runtests toc |
||||
- go run build.go vet |
||||
- go run build.go test |
||||
- go run build.go gfmrun docs/v1/manual.md |
||||
- go run build.go toc docs/v1/manual.md |
||||
|
||||
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