<commit_before>package main

import (
	"bytes"
	"io"
	"mime"
	"net/http"
	"strings"
)

// Filtering by MIME type.

// The action to take for files of each type.
// If it is not specified here, types beginning with "text" will be
// content-filtered, and others will be allowed without scanning the content.
var mimeActions = map[string]action{}

// We use the action type from categories.go, but instead of BLOCK, IGNORE, and
// ALLOW, here it is BLOCK, FILTER, and ALLOW.
const FILTER = IGNORE

var mimeAllow = newActiveFlag("mime-allow", "", "content type to allow without phrase scan",
	func(t string) error {
		mimeActions[t] = ALLOW
		return nil
	})

var mimeFilter = newActiveFlag("mime-filter", "", "content type to filter",
	func(t string) error {
		mimeActions[t] = FILTER
		return nil
	})

var mimeBlock = newActiveFlag("mime-block", "", "content type to block",
	func(t string) error {
		mimeActions[t] = BLOCK
		return nil
	})

// checkContentType examines the request's Content-Type header, and potentially
// its content as well, to determine the content's MIME type.
// Then it decides, based on the MIME type, whether it should be allowed,
// filtered, or blocked.
func checkContentType(resp *http.Response) (contentType string, a action) {
	ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
	if err != nil || !strings.Contains(ct, "/") {
		ct = ""
	}

	switch ct {
	case "text/plain", "text/html", "unknown/unknown", "application/unknown", "*/*", "", "application/octet-stream":
		if resp.Header.Get("Content-Encoding") == "" {
			// These types tend to be used for content whose type is unknown,
			// so we should try to second-guess them.
			// But we don't bother if the content is gzipped; then we'd get application/gzip.
			// We can hope (probably in vain) that a server smart enough to compress the
			// content is smart enough to give us a correct media type.
			preview := make([]byte, 512)
			n, _ := resp.Body.Read(preview)
			preview = preview[:n]

			if n > 0 {
				ct, _, _ = mime.ParseMediaType(http.DetectContentType(preview))

				// Make the preview data available for re-reading.
				var rc struct {
					io.Reader
					io.Closer
				}
				rc.Reader = io.MultiReader(bytes.NewBuffer(preview), resp.Body)
				rc.Closer = resp.Body
				resp.Body = rc
			}
		}
	}

	if a, ok := mimeActions[ct]; ok {
		return ct, a
	}
	if strings.HasPrefix(ct, "text/") {
		return ct, FILTER
	}
	return ct, ALLOW
}

// baseType strips off any modifiers (such as charset) and returns the simple
// MIME type.
func baseType(t string) string {
	if semicolon := strings.Index(t, ";"); semicolon != -1 {
		t = t[:semicolon]
	}
	return strings.TrimSpace(t)
}
<commit_msg>Don't filter stuff with strange Content-Encodings<commit_after>package main

import (
	"bytes"
	"io"
	"log"
	"mime"
	"net/http"
	"strings"
)

// Filtering by MIME type.

// The action to take for files of each type.
// If it is not specified here, types beginning with "text" will be
// content-filtered, and others will be allowed without scanning the content.
var mimeActions = map[string]action{}

// We use the action type from categories.go, but instead of BLOCK, IGNORE, and
// ALLOW, here it is BLOCK, FILTER, and ALLOW.
const FILTER = IGNORE

var mimeAllow = newActiveFlag("mime-allow", "", "content type to allow without phrase scan",
	func(t string) error {
		mimeActions[t] = ALLOW
		return nil
	})

var mimeFilter = newActiveFlag("mime-filter", "", "content type to filter",
	func(t string) error {
		mimeActions[t] = FILTER
		return nil
	})

var mimeBlock = newActiveFlag("mime-block", "", "content type to block",
	func(t string) error {
		mimeActions[t] = BLOCK
		return nil
	})

// checkContentType examines the request's Content-Type header, and potentially
// its content as well, to determine the content's MIME type.
// Then it decides, based on the MIME type, whether it should be allowed,
// filtered, or blocked.
func checkContentType(resp *http.Response) (contentType string, a action) {
	ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
	if err != nil || !strings.Contains(ct, "/") {
		ct = ""
	}

	if ce := resp.Header.Get("Content-Encoding"); ce != "" && ce != "gzip" {
		// If the server is using a Content-Encoding that we don't understand,
		// we can't decode the content to filter it.
		log.Println("unknown Content-Encoding", ce, "for", resp.Request.URL)
		return ct, ALLOW
	}

	switch ct {
	case "text/plain", "text/html", "unknown/unknown", "application/unknown", "*/*", "", "application/octet-stream":
		if resp.Header.Get("Content-Encoding") == "" {
			// These types tend to be used for content whose type is unknown,
			// so we should try to second-guess them.
			// But we don't bother if the content is gzipped; then we'd get application/gzip.
			// We can hope (probably in vain) that a server smart enough to compress the
			// content is smart enough to give us a correct media type.
			preview := make([]byte, 512)
			n, _ := resp.Body.Read(preview)
			preview = preview[:n]

			if n > 0 {
				ct, _, _ = mime.ParseMediaType(http.DetectContentType(preview))

				// Make the preview data available for re-reading.
				var rc struct {
					io.Reader
					io.Closer
				}
				rc.Reader = io.MultiReader(bytes.NewBuffer(preview), resp.Body)
				rc.Closer = resp.Body
				resp.Body = rc
			}
		}
	}

	if a, ok := mimeActions[ct]; ok {
		return ct, a
	}
	if strings.HasPrefix(ct, "text/") {
		return ct, FILTER
	}
	return ct, ALLOW
}

// baseType strips off any modifiers (such as charset) and returns the simple
// MIME type.
func baseType(t string) string {
	if semicolon := strings.Index(t, ";"); semicolon != -1 {
		t = t[:semicolon]
	}
	return strings.TrimSpace(t)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"sync"
	"time"
)

// TODO testable

const versionExpiry = 10 * time.Minute

// A versionMux handles routing requests to the various versions available for
// a db. It handles two specific problems that crop up:
//
// First, it has the concept of "prepared" versions - versions that can serve
// local requests, but for which the cluster doesn't forward requests to by
// default. To make any version upgrade monotonic, we need to be able to (for a
// very short time) handle requests for version N-1 to outside clients, while
// responding to requests for version N if they are proxied from other nodes who
// have upgraded before us - and vice versa for clients who upgrade after us.
//
// Second, it has a scheme for making sure that all requests are finished to
// a version before closing it out. This is implemented with a timer and a
// reference count. The timer starts when we mark a version "ready to delete"
// and is reset every time a request comes in, so that a version still serving
// frequent requests will never be closed. Even once we remove a version from
// the mux, in-flight requests might have a pointer to the version, so we need
// to increment a reference count to the version when we pass one out and
// decrement it after the request is done.
type versionMux struct {
	versions       map[string]versionReferenceCount
	currentVersion versionReferenceCount
	lock           sync.RWMutex
}

type versionReferenceCount struct {
	*version
	count      *sync.WaitGroup
	closeTimer *time.Timer
	removing   bool
}

func newVersionMux() *versionMux {
	return &versionMux{versions: make(map[string]versionReferenceCount)}
}

// serveKey is the entrypoint for HTTP requests.
func (mux *versionMux) serveKey(w http.ResponseWriter, r *http.Request, key string) {
	proxyVersion := r.URL.Query().Get("proxy")
	var vs *version

	if proxyVersion != "" {
		vs = mux.getVersion(proxyVersion)

		// If this is a proxy request, we need to indicate to the peer that we don't
		// have the version they are asking for (although this should never happen,
		// since they should only route to us if we're publishing that we *do* have
		// the version).
		if vs == nil {
			w.WriteHeader(http.StatusNotImplemented)
			return
		}
	} else {
		vs = mux.getCurrent()
		if vs == nil {
			w.WriteHeader(http.StatusNotFound)
			return
		}
	}

	vs.serveKey(w, r, key)
	mux.release(vs)
}

// getCurrent returns the current version and increments the reference count
// for it. It returns nil if there is no prepared version.
func (mux *versionMux) getCurrent() *version {
	mux.lock.RLock()
	defer mux.lock.RUnlock()

	vs := mux.currentVersion
	if vs.version != nil {
		vs.count.Add(1)
		if vs.closeTimer != nil {
			vs.closeTimer.Reset(versionExpiry)
		}
	}

	return vs.version
}

// getVersion returns the version that matches the given name, and increments
// the reference count for it. It returns nil if there is no version matching
// that name.
func (mux *versionMux) getVersion(name string) *version {
	mux.lock.RLock()
	defer mux.lock.RUnlock()

	vs := mux.versions[name]
	if vs.version != nil {
		vs.count.Add(1)
		if vs.closeTimer != nil {
			vs.closeTimer.Reset(versionExpiry)
		}
	}

	return vs.version
}

// getAll returns a snapshot of all known versions.
func (mux *versionMux) getAll() []*version {
	mux.lock.RLock()
	defer mux.lock.RUnlock()

	versions := make([]*version, 0, len(mux.versions))
	for _, vs := range mux.versions {
		versions = append(versions, vs.version)
	}

	return versions
}

// release signifies that a request is done with a version, decrementing the
// reference count.
func (mux *versionMux) release(version *version) {
	if version == nil {
		return
	}

	mux.lock.RLock()
	defer mux.lock.RUnlock()

	if vs, ok := mux.versions[version.name]; ok {
		vs.count.Done()
	}
}

// prepare puts a new version in the wings, allowing requests to be routed to it
// but not setting it as the default. If the passed version is already prepared,
// this method will panic.
func (mux *versionMux) prepare(version *version) {
	mux.lock.Lock()
	defer mux.lock.Unlock()

	if _, ok := mux.versions[version.name]; ok {
		panic(fmt.Sprintf("version already prepared: %s", version.name))
	}

	mux.versions[version.name] = versionReferenceCount{
		version: version,
		count:   new(sync.WaitGroup),
	}
}

// upgrade switches the given version to the current default. If the given
// version hasn't been prepared, or if it currently has a different version
// by the same name, this method will panic.
func (mux *versionMux) upgrade(version *version) {
	mux.lock.Lock()
	defer mux.lock.Unlock()

	vs := mux.mustGet(version)
	mux.currentVersion = vs
}

// remove starts a timer that will remove a version from the mux. Any time a
// proxied request comes in for the version, we reset the timer, so it must
// be completely unused for the full period of time before it will get removed
// from the mux (if shouldWait is false, this step is skipped). Once we remove
// it from the mux, we make extra sure that nothing is using it by waiting for
// the reference count to drop to zero. Finally, remove returns the version
// that was removed.
//
// It's idempotent to call remove for the same version multiple times. If
// the version is already being removed, remove will return nil.
func (mux *versionMux) remove(version *version, shouldWait bool) *version {
	if version == nil {
		return nil
	}

	mux.lock.Lock()
	vs, ok := mux.versions[version.name]
	alreadyRemoving := vs.removing
	vs.removing = true
	mux.lock.Unlock()

	// The version has already been removed, or is already in the process
	// of being removed
	if !ok || alreadyRemoving {
		return nil
	}

	// Set the timer, then wait for it. Any request from here on will reset the
	// timer.
	if shouldWait {
		mux.lock.Lock()
		timer := time.NewTimer(versionExpiry)
		vs.closeTimer = timer
		mux.lock.Unlock()

		// Wait for the timer, which is reset on every request.
		<-timer.C
	}

	mux.lock.Lock()
	delete(mux.versions, version.name)
	mux.lock.Unlock()

	// Wait for the reference count to drop to zero.
	vs.count.Wait()
	return version
}

func (mux *versionMux) mustGet(version *version) versionReferenceCount {
	vs, ok := mux.versions[version.name]
	if !ok {
		panic(fmt.Sprintf("version doesn't exist: %s", version.name))
	} else if vs.version != version {
		panic(fmt.Sprintf("somehow got another reference to the same version: %s", version.name))
	}

	return vs
}
<commit_msg>Do our best to return data for misproxied requests<commit_after>package main

import (
	"fmt"
	"net/http"
	"sync"
	"time"
)

// TODO testable

const versionExpiry = 10 * time.Minute

// A versionMux handles routing requests to the various versions available for
// a db. It handles two specific problems that crop up:
//
// First, it has the concept of "prepared" versions - versions that can serve
// local requests, but for which the cluster doesn't forward requests to by
// default. To make any version upgrade monotonic, we need to be able to (for a
// very short time) handle requests for version N-1 to outside clients, while
// responding to requests for version N if they are proxied from other nodes who
// have upgraded before us - and vice versa for clients who upgrade after us.
//
// Second, it has a scheme for making sure that all requests are finished to
// a version before closing it out. This is implemented with a timer and a
// reference count. The timer starts when we mark a version "ready to delete"
// and is reset every time a request comes in, so that a version still serving
// frequent requests will never be closed. Even once we remove a version from
// the mux, in-flight requests might have a pointer to the version, so we need
// to increment a reference count to the version when we pass one out and
// decrement it after the request is done.
type versionMux struct {
	versions       map[string]versionReferenceCount
	currentVersion versionReferenceCount
	lock           sync.RWMutex
}

type versionReferenceCount struct {
	*version
	count      *sync.WaitGroup
	closeTimer *time.Timer
	removing   bool
}

func newVersionMux() *versionMux {
	return &versionMux{versions: make(map[string]versionReferenceCount)}
}

// serveKey is the entrypoint for HTTP requests.
func (mux *versionMux) serveKey(w http.ResponseWriter, r *http.Request, key string) {
	proxyVersion := r.URL.Query().Get("proxy")
	var vs *version

	if proxyVersion != "" {
		vs = mux.getVersion(proxyVersion)

		// If this is a proxy request, but we don't have the version the peer is
		// asking for, just return what we have. This should never happen unless
		// sharding is broken, since peers should only route requests for versions
		// that we expressly make available.
		if vs == nil {
			vs = mux.getCurrent()

			// If we don't have *any* version, we need to indicate that, rather than
			// returning a 404, which might indicate that we do have the dataset but
			// that key doesn't exist. We use http 501 for this.
			if vs == nil {
				w.WriteHeader(http.StatusNotImplemented)
				return
			}
		}
	} else {
		vs = mux.getCurrent()
		if vs == nil {
			w.WriteHeader(http.StatusNotFound)
			return
		}
	}

	vs.serveKey(w, r, key)
	mux.release(vs)
}

// getCurrent returns the current version and increments the reference count
// for it. It returns nil if there is no prepared version.
func (mux *versionMux) getCurrent() *version {
	mux.lock.RLock()
	defer mux.lock.RUnlock()

	vs := mux.currentVersion
	if vs.version != nil {
		vs.count.Add(1)
		if vs.closeTimer != nil {
			vs.closeTimer.Reset(versionExpiry)
		}
	}

	return vs.version
}

// getVersion returns the version that matches the given name, and increments
// the reference count for it. It returns nil if there is no version matching
// that name.
func (mux *versionMux) getVersion(name string) *version {
	mux.lock.RLock()
	defer mux.lock.RUnlock()

	vs := mux.versions[name]
	if vs.version != nil {
		vs.count.Add(1)
		if vs.closeTimer != nil {
			vs.closeTimer.Reset(versionExpiry)
		}
	}

	return vs.version
}

// getAll returns a snapshot of all known versions.
func (mux *versionMux) getAll() []*version {
	mux.lock.RLock()
	defer mux.lock.RUnlock()

	versions := make([]*version, 0, len(mux.versions))
	for _, vs := range mux.versions {
		versions = append(versions, vs.version)
	}

	return versions
}

// release signifies that a request is done with a version, decrementing the
// reference count.
func (mux *versionMux) release(version *version) {
	if version == nil {
		return
	}

	mux.lock.RLock()
	defer mux.lock.RUnlock()

	if vs, ok := mux.versions[version.name]; ok {
		vs.count.Done()
	}
}

// prepare puts a new version in the wings, allowing requests to be routed to it
// but not setting it as the default. If the passed version is already prepared,
// this method will panic.
func (mux *versionMux) prepare(version *version) {
	mux.lock.Lock()
	defer mux.lock.Unlock()

	if _, ok := mux.versions[version.name]; ok {
		panic(fmt.Sprintf("version already prepared: %s", version.name))
	}

	mux.versions[version.name] = versionReferenceCount{
		version: version,
		count:   new(sync.WaitGroup),
	}
}

// upgrade switches the given version to the current default. If the given
// version hasn't been prepared, or if it currently has a different version
// by the same name, this method will panic.
func (mux *versionMux) upgrade(version *version) {
	mux.lock.Lock()
	defer mux.lock.Unlock()

	vs := mux.mustGet(version)
	mux.currentVersion = vs
}

// remove starts a timer that will remove a version from the mux. Any time a
// proxied request comes in for the version, we reset the timer, so it must
// be completely unused for the full period of time before it will get removed
// from the mux (if shouldWait is false, this step is skipped). Once we remove
// it from the mux, we make extra sure that nothing is using it by waiting for
// the reference count to drop to zero. Finally, remove returns the version
// that was removed.
//
// It's idempotent to call remove for the same version multiple times. If
// the version is already being removed, remove will return nil.
func (mux *versionMux) remove(version *version, shouldWait bool) *version {
	if version == nil {
		return nil
	}

	mux.lock.Lock()
	vs, ok := mux.versions[version.name]
	alreadyRemoving := vs.removing
	vs.removing = true
	mux.lock.Unlock()

	// The version has already been removed, or is already in the process
	// of being removed
	if !ok || alreadyRemoving {
		return nil
	}

	// Set the timer, then wait for it. Any request from here on will reset the
	// timer.
	if shouldWait {
		mux.lock.Lock()
		timer := time.NewTimer(versionExpiry)
		vs.closeTimer = timer
		mux.lock.Unlock()

		// Wait for the timer, which is reset on every request.
		<-timer.C
	}

	mux.lock.Lock()
	delete(mux.versions, version.name)
	mux.lock.Unlock()

	// Wait for the reference count to drop to zero.
	vs.count.Wait()
	return version
}

func (mux *versionMux) mustGet(version *version) versionReferenceCount {
	vs, ok := mux.versions[version.name]
	if !ok {
		panic(fmt.Sprintf("version doesn't exist: %s", version.name))
	} else if vs.version != version {
		panic(fmt.Sprintf("somehow got another reference to the same version: %s", version.name))
	}

	return vs
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.

package donna

import (
        `fmt`
        `regexp`
)

type Move struct {
        from     int
        to       int
        score    int
        piece    Piece
        captured Piece
        promoted Piece
}

func NewMove(p *Position, from, to int) *Move {
        move := new(Move)

        move.from = from
        move.to = to
        move.piece = p.pieces[from]
        move.captured = p.pieces[to]

        if p.flags.enpassant != 0 && to == p.flags.enpassant {
                move.captured = Pawn(p.color^1)
        }

        if move.captured == 0 {
                move.score = move.calculateScore(p)
        } else {
                move.score = move.calculateValue()
        }

        return move
}

func NewMoveFromString(e2e4 string, p *Position) (move *Move) {
	re := regexp.MustCompile(`([KkQqRrBbNn]?)([a-h])([1-8])-?([a-h])([1-8])([QqRrBbNn]?)`)
	arr := re.FindStringSubmatch(e2e4)

	if len(arr) > 0 {
		name  := arr[1]
		from  := Square(int(arr[3][0]-'1'), int(arr[2][0]-'a'))
		to    := Square(int(arr[5][0]-'1'), int(arr[4][0]-'a'))
		promo := arr[6]

		var piece Piece
		switch name {
		case `K`, `k`:
			piece = King(p.color)
		case `Q`, `q`:
			piece = Queen(p.color)
		case `R`, `r`:
			piece = Rook(p.color)
		case `B`, `b`:
			piece = Bishop(p.color)
		case `N`, `n`:
			piece = Knight(p.color)
		default:
			piece = Pawn(p.color)
		}
                if (p.pieces[from] != piece) || (p.targets[from] & Bit(to) == 0) {
                        move = nil
                } else {
                        move = NewMove(p, from, to)
                        if len(promo) > 0 {
                                switch promo {
                                case `Q`, `q`:
                                        move.promote(QUEEN)
                                case `R`, `r`:
                                        move.promote(ROOK)
                                case `B`, `b`:
                                        move.promote(BISHOP)
                                case `N`, `n`:
                                        move.promote(KNIGHT)
                                default:
                                        move = nil
                                }
                        }
                }
	} else if e2e4 == `0-0` || e2e4 == `0-0-0` {
                from := p.outposts[King(p.color)].firstSet()
                to := G1
                if e2e4 == `0-0-0` {
                        to = C1
                }
                if p.color == Black {
                        to += 56
                }
                move = NewMove(p, from, to)
                if !move.isCastle() {
                        move = nil
                }
	}
	return
}

func (m *Move) promote(kind int) *Move {
        m.promoted = Piece(kind | m.piece.color())

        return m
}

func (m *Move) is(move *Move) bool {
        return m.from == move.from  &&
                 m.to == move.to    &&
              m.piece == move.piece &&
           m.captured == m.captured &&
           m.promoted == m.promoted
}

func (m *Move) calculateScore(position *Position) int {
	var midgame, endgame int
	square := flip[m.piece.color()][m.to]

	switch m.piece.kind() {
        case PAWN:
                midgame += bonusPawn[0][square]
                endgame += bonusPawn[1][square]
        case KNIGHT:
                midgame += bonusKnight[0][square]
                endgame += bonusKnight[1][square]
        case BISHOP:
                midgame += bonusBishop[0][square]
                endgame += bonusBishop[1][square]
        case ROOK:
                midgame += bonusRook[0][square]
                endgame += bonusRook[1][square]
        case QUEEN:
                midgame += bonusQueen[0][square]
                endgame += bonusQueen[1][square]
        case KING:
                midgame += bonusKing[0][square]
                endgame += bonusKing[1][square]
        }

	return (midgame * position.stage + endgame * (256 - position.stage)) / 256
}

// PxQ, NxQ, BxQ, RxQ, QxQ, KxQ => where => QUEEN  = 5 << 1 // 10
// PxR, NxR, BxR, RxR, QxR, KxR             ROOK   = 4 << 1 // 8
// PxB, NxB, BxB, RxB, QxB, KxB             BISHOP = 3 << 1 // 6
// PxN, NxN, BxN, RxN, QxN, KxN             KNIGHT = 2 << 1 // 4
// PxP, NxP, BxP, RxP, QxP, KxP             PAWN   = 1 << 1 // 2
func (m *Move) calculateValue() int {
        if m.captured == 0 || m.captured.kind() == KING {
                return 0
        }

        victim := (QUEEN - m.captured.kind()) / PAWN
        attacker := m.piece.kind() / PAWN - 1

        return victimAttacker[victim][attacker]
}

func (m *Move) isKingSideCastle() bool {
        return m.piece.isKing() && ((m.piece.isWhite() && m.from == E1 && m.to == G1) || (m.piece.isBlack() && m.from == E8 && m.to == G8))
}

func (m *Move) isQueenSideCastle() bool {
        return m.piece.isKing() && ((m.piece.isWhite() && m.from == E1 && m.to == C1) || (m.piece.isBlack() && m.from == E8 && m.to == C8))
}

func (m *Move) isCastle() bool {
        return m.isKingSideCastle() || m.isQueenSideCastle()
}

func (m *Move) isEnpassant(opponentPawns Bitmask) bool {
        color := m.piece.color()

        if m.piece.isPawn() && Row(m.from) == [2]int{1,6}[color] && Row(m.to) == [2]int{3,4}[color] {
                switch col := Col(m.to); col {
                case 0:
                        return opponentPawns.isSet(m.to + 1)
                case 7:
                        return opponentPawns.isSet(m.to - 1)
                default:
                        return opponentPawns.isSet(m.to + 1) || opponentPawns.isSet(m.to - 1)
                }
        }
        return false
}

func (m *Move) isEnpassantCapture(enpassant int) bool {
        return m.piece.isPawn() && m.to == enpassant
}

func (m *Move) String() string {

        if !m.isCastle() {
                col := [2]int{ Col(m.from) + 'a', Col(m.to) + 'a' }
                row := [2]int{ Row(m.from) + 1, Row(m.to) + 1 }

                capture := '-'
                if m.captured != 0 {
                        capture = 'x'
                }
                piece, promoted := m.piece.String(), m.promoted.String()
                format := `%c%d%c%c%d%s`

                if m.piece.isPawn() { // Skip piece name if it's a pawn.
                        return fmt.Sprintf(format, col[0], row[0], capture, col[1], row[1], promoted)
                } else {
                        if Settings.Fancy { // Fancy notation is more readable with extra space.
                                format = `%s ` + format
                        } else {
                                format = `%s` + format
                        }
                        return fmt.Sprintf(format, piece, col[0], row[0], capture, col[1], row[1], promoted)
                }
        } else if m.isKingSideCastle() {
                return `0-0`
        } else {
                return `0-0-0`
        }
}<commit_msg>Less code<commit_after>// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.

package donna

import (
        `fmt`
        `regexp`
)

type Move struct {
        from     int
        to       int
        score    int
        piece    Piece
        captured Piece
        promoted Piece
}

func NewMove(p *Position, from, to int) *Move {
        move := &Move{ from: from, to: to, piece: p.pieces[from], captured: p.pieces[to] }

        if p.flags.enpassant != 0 && to == p.flags.enpassant {
                move.captured = Pawn(p.color^1)
        }

        if move.captured == 0 {
                move.score = move.calculateScore(p)
        } else {
                move.score = move.calculateValue()
        }

        return move
}

func NewMoveFromString(e2e4 string, p *Position) (move *Move) {
	re := regexp.MustCompile(`([KkQqRrBbNn]?)([a-h])([1-8])-?([a-h])([1-8])([QqRrBbNn]?)`)
	arr := re.FindStringSubmatch(e2e4)

	if len(arr) > 0 {
		name  := arr[1]
		from  := Square(int(arr[3][0]-'1'), int(arr[2][0]-'a'))
		to    := Square(int(arr[5][0]-'1'), int(arr[4][0]-'a'))
		promo := arr[6]

		var piece Piece
		switch name {
		case `K`, `k`:
			piece = King(p.color)
		case `Q`, `q`:
			piece = Queen(p.color)
		case `R`, `r`:
			piece = Rook(p.color)
		case `B`, `b`:
			piece = Bishop(p.color)
		case `N`, `n`:
			piece = Knight(p.color)
		default:
			piece = Pawn(p.color)
		}
                if (p.pieces[from] != piece) || (p.targets[from] & Bit(to) == 0) {
                        move = nil
                } else {
                        move = NewMove(p, from, to)
                        if len(promo) > 0 {
                                switch promo {
                                case `Q`, `q`:
                                        move.promote(QUEEN)
                                case `R`, `r`:
                                        move.promote(ROOK)
                                case `B`, `b`:
                                        move.promote(BISHOP)
                                case `N`, `n`:
                                        move.promote(KNIGHT)
                                default:
                                        move = nil
                                }
                        }
                }
	} else if e2e4 == `0-0` || e2e4 == `0-0-0` {
                from := p.outposts[King(p.color)].firstSet()
                to := G1
                if e2e4 == `0-0-0` {
                        to = C1
                }
                if p.color == Black {
                        to += 56
                }
                move = NewMove(p, from, to)
                if !move.isCastle() {
                        move = nil
                }
	}
	return
}

func (m *Move) promote(kind int) *Move {
        m.promoted = Piece(kind | m.piece.color())

        return m
}

func (m *Move) is(move *Move) bool {
        return m.from == move.from  &&
                 m.to == move.to    &&
              m.piece == move.piece &&
           m.captured == m.captured &&
           m.promoted == m.promoted
}

func (m *Move) calculateScore(position *Position) int {
	square := flip[m.piece.color()][m.to]
        midgame, endgame := m.piece.bonus(square)

	return (midgame * position.stage + endgame * (256 - position.stage)) / 256
}

// PxQ, NxQ, BxQ, RxQ, QxQ, KxQ => where => QUEEN  = 5 << 1 // 10
// PxR, NxR, BxR, RxR, QxR, KxR             ROOK   = 4 << 1 // 8
// PxB, NxB, BxB, RxB, QxB, KxB             BISHOP = 3 << 1 // 6
// PxN, NxN, BxN, RxN, QxN, KxN             KNIGHT = 2 << 1 // 4
// PxP, NxP, BxP, RxP, QxP, KxP             PAWN   = 1 << 1 // 2
func (m *Move) calculateValue() int {
        if m.captured == 0 || m.captured.isKing() {
                return 0
        }

        victim := (QUEEN - m.captured.kind()) / PAWN
        attacker := m.piece.kind() / PAWN - 1

        return victimAttacker[victim][attacker]
}

func (m *Move) is00() bool {
        return (m.piece == King(White) && m.from == E1 && m.to == G1) ||
               (m.piece == King(Black) && m.from == E8 && m.to == G8)
}

func (m *Move) is000() bool {
        return (m.piece == King(White) && m.from == E1 && m.to == C1) ||
               (m.piece == King(Black) && m.from == E8 && m.to == C8)
}

func (m *Move) isCastle() bool {
        return m.is00() || m.is000()
}

func (m *Move) isEnpassant(opponentPawns Bitmask) bool {
        color := m.piece.color()

        if m.piece.isPawn() && Row(m.from) == [2]int{1,6}[color] && Row(m.to) == [2]int{3,4}[color] {
                switch col := Col(m.to); col {
                case 0:
                        return opponentPawns.isSet(m.to + 1)
                case 7:
                        return opponentPawns.isSet(m.to - 1)
                default:
                        return opponentPawns.isSet(m.to + 1) || opponentPawns.isSet(m.to - 1)
                }
        }
        return false
}

func (m *Move) isEnpassantCapture(enpassant int) bool {
        return m.piece.isPawn() && m.to == enpassant
}

func (m *Move) String() string {

        if !m.isCastle() {
                col := [2]int{ Col(m.from) + 'a', Col(m.to) + 'a' }
                row := [2]int{ Row(m.from) + 1, Row(m.to) + 1 }

                capture := '-'
                if m.captured != 0 {
                        capture = 'x'
                }
                piece, promoted := m.piece.String(), m.promoted.String()
                format := `%c%d%c%c%d%s`

                if m.piece.isPawn() { // Skip piece name if it's a pawn.
                        return fmt.Sprintf(format, col[0], row[0], capture, col[1], row[1], promoted)
                } else {
                        if Settings.Fancy { // Fancy notation is more readable with extra space.
                                format = `%s ` + format
                        } else {
                                format = `%s` + format
                        }
                        return fmt.Sprintf(format, piece, col[0], row[0], capture, col[1], row[1], promoted)
                }
        } else if m.is00() {
                return `0-0`
        } else {
                return `0-0-0`
        }
}<|endoftext|>


<commit_before>package GoSDK

import (
	"crypto/tls"
	"errors"
	mqttTypes "github.com/clearblade/mqtt_parsing"
	mqtt "github.com/eclipse/paho.mqtt.golang"
	"time"
)

const (
	//Mqtt QOS 0
	QOS_AtMostOnce = iota
	//Mqtt QOS 1
	QOS_AtLeastOnce
	//Mqtt QOS 2
	QOS_PreciselyOnce
)

//LastWillPacket is a type to represent the Last Will and Testament packet
type LastWillPacket struct {
	Topic  string
	Body   string
	Qos    int
	Retain bool
}

//herein we use the same trick we used for http clients

//InitializeMQTT allocates the mqtt client for the user. an empty string can be passed as the second argument for the user client
func (u *UserClient) InitializeMQTT(clientid string, ignore string, timeout int, ssl *tls.Config, lastWill *LastWillPacket) error {
	mqc, err := newMqttClient(u.UserToken, u.SystemKey, u.SystemSecret, clientid, timeout, u.MqttAddr, ssl, lastWill)
	if err != nil {
		return err
	}
	u.MQTTClient = mqc
	return nil
}

//InitializeMQTT allocates the mqtt client for the developer. the second argument is a
//the systemkey you wish to use for authenticating with the message broker
//topics are isolated across systems, so in order to communicate with a specific
//system, you must supply the system key
func (d *DevClient) InitializeMQTT(clientid, systemkey string, timeout int, ssl *tls.Config, lastWill *LastWillPacket) error {
	mqc, err := newMqttClient(d.DevToken, systemkey, "", clientid, timeout, d.MqttAddr, ssl, lastWill)
	if err != nil {
		return err
	}
	d.MQTTClient = mqc
	return nil
}

//InitializeMQTT allocates the mqtt client for the user. an empty string can be passed as the second argument for the user client
func (d *DeviceClient) InitializeMQTT(clientid string, ignore string, timeout int, ssl *tls.Config, lastWill *LastWillPacket) error {
	mqc, err := newMqttClient(d.DeviceToken, d.SystemKey, d.SystemSecret, clientid, timeout, d.MqttAddr, ssl, lastWill)
	if err != nil {
		return err
	}
	d.MQTTClient = mqc
	return nil
}

//Publish publishes a message to the specified mqtt topic
func (u *UserClient) Publish(topic string, message []byte, qos int) error {
	return publish(u.MQTTClient, topic, message, qos, u.getMessageId())
}

//Publish publishes a message to the specified mqtt topic
func (d *DeviceClient) Publish(topic string, message []byte, qos int) error {
	return publish(d.MQTTClient, topic, message, qos, d.getMessageId())
}

//Publish publishes a message to the specified mqtt topic
func (d *DevClient) Publish(topic string, message []byte, qos int) error {
	return publish(d.MQTTClient, topic, message, qos, d.getMessageId())
}

//Subscribe subscribes a user to a topic. Incoming messages will be sent over the channel.
func (u *UserClient) Subscribe(topic string, qos int) (<-chan *mqttTypes.Publish, error) {
	return subscribe(u.MQTTClient, topic, qos)
}

//Subscribe subscribes a device to a topic. Incoming messages will be sent over the channel.
func (d *DeviceClient) Subscribe(topic string, qos int) (<-chan *mqttTypes.Publish, error) {
	return subscribe(d.MQTTClient, topic, qos)
}

//Subscribe subscribes a user to a topic. Incoming messages will be sent over the channel.
func (d *DevClient) Subscribe(topic string, qos int) (<-chan *mqttTypes.Publish, error) {
	return subscribe(d.MQTTClient, topic, qos)
}

//Unsubscribe stops the flow of messages over the corresponding subscription chan
func (u *UserClient) Unsubscribe(topic string) error {
	return unsubscribe(u.MQTTClient, topic)
}

//Unsubscribe stops the flow of messages over the corresponding subscription chan
func (d *DeviceClient) Unsubscribe(topic string) error {
	return unsubscribe(d.MQTTClient, topic)
}

//Unsubscribe stops the flow of messages over the corresponding subscription chan
func (d *DevClient) Unsubscribe(topic string) error {
	return unsubscribe(d.MQTTClient, topic)
}

//Disconnect stops the TCP connection and unsubscribes the client from any remaining topics
func (u *UserClient) Disconnect() error {
	return disconnect(u.MQTTClient)
}

//Disconnect stops the TCP connection and unsubscribes the client from any remaining topics
func (d *DeviceClient) Disconnect() error {
	return disconnect(d.MQTTClient)
}

//Disconnect stops the TCP connection and unsubscribes the client from any remaining topics
func (d *DevClient) Disconnect() error {
	return disconnect(d.MQTTClient)
}

//Below are a series of convience functions to allow the user to only need to import
//the clearblade go-sdk
type mqttBaseClient struct {
	mqtt.Client
	address                                  string
	token, systemKey, systemSecret, clientID string
	timeout                                  int
}

//InitializeMqttClient allocates a mqtt client.
//the values for initialization are drawn from the client struct
//with the exception of the timeout and client id, which is mqtt specific.
// timeout refers to broker connect timeout
func newMqttClient(token, systemkey, systemsecret, clientid string, timeout int, address string, ssl *tls.Config, lastWill *LastWillPacket) (MqttClient, error) {
	o := mqtt.NewClientOptions()
	o.SetAutoReconnect(true)
	o.AddBroker("tcp://" + address)
	o.SetClientID(clientid)
	o.SetUsername(token)
	o.SetPassword(systemkey)
	o.SetConnectTimeout(time.Duration(timeout) * time.Second)
	if ssl != nil {
		o.SetTLSConfig(ssl)
	}
	if lastWill != nil {
		o.SetWill(lastWill.Topic, lastWill.Body, uint8(lastWill.Qos), lastWill.Retain)
	}
	cli := mqtt.NewClient(o)
	mqc := &mqttBaseClient{cli, address, token, systemkey, systemsecret, clientid, timeout}
	ret := mqc.Connect()
	ret.Wait()
	return mqc, ret.Error()
}

func publish(c MqttClient, topic string, data []byte, qos int, mid uint16) error {
	if c == nil {
		return errors.New("MQTTClient is uninitialized")
	}
	ret := c.Publish(topic, uint8(qos), false, data)
	ret.Wait()
	return ret.Error()
}

func subscribe(c MqttClient, topic string, qos int) (<-chan *mqttTypes.Publish, error) {
	if c == nil {
		return nil, errors.New("MQTTClient is uninitialized")
	}
	pubs := make(chan *mqttTypes.Publish, 50)
	ret := c.Subscribe(topic, uint8(qos), func(client mqtt.Client, msg mqtt.Message) {
		path, _ := mqttTypes.NewTopicPath(msg.Topic())
		pubs <- &mqttTypes.Publish{Topic: path, Payload: msg.Payload()}
	})
	ret.Wait()
	return pubs, ret.Error()
}

func unsubscribe(c MqttClient, topic string) error {
	if c == nil {
		return errors.New("MQTTClient is uninitialized")
	}
	ret := c.Unsubscribe(topic)
	ret.Wait()
	return ret.Error()
}

func disconnect(c MqttClient) error {
	if c == nil {
		return errors.New("MQTTClient is uninitialized")
	}
	c.Disconnect(0)
	return nil
}
<commit_msg>remove blocking call from publishes<commit_after>package GoSDK

import (
	"crypto/tls"
	"errors"
	mqttTypes "github.com/clearblade/mqtt_parsing"
	mqtt "github.com/eclipse/paho.mqtt.golang"
	"time"
)

const (
	//Mqtt QOS 0
	QOS_AtMostOnce = iota
	//Mqtt QOS 1
	QOS_AtLeastOnce
	//Mqtt QOS 2
	QOS_PreciselyOnce
)

//LastWillPacket is a type to represent the Last Will and Testament packet
type LastWillPacket struct {
	Topic  string
	Body   string
	Qos    int
	Retain bool
}

//herein we use the same trick we used for http clients

//InitializeMQTT allocates the mqtt client for the user. an empty string can be passed as the second argument for the user client
func (u *UserClient) InitializeMQTT(clientid string, ignore string, timeout int, ssl *tls.Config, lastWill *LastWillPacket) error {
	mqc, err := newMqttClient(u.UserToken, u.SystemKey, u.SystemSecret, clientid, timeout, u.MqttAddr, ssl, lastWill)
	if err != nil {
		return err
	}
	u.MQTTClient = mqc
	return nil
}

//InitializeMQTT allocates the mqtt client for the developer. the second argument is a
//the systemkey you wish to use for authenticating with the message broker
//topics are isolated across systems, so in order to communicate with a specific
//system, you must supply the system key
func (d *DevClient) InitializeMQTT(clientid, systemkey string, timeout int, ssl *tls.Config, lastWill *LastWillPacket) error {
	mqc, err := newMqttClient(d.DevToken, systemkey, "", clientid, timeout, d.MqttAddr, ssl, lastWill)
	if err != nil {
		return err
	}
	d.MQTTClient = mqc
	return nil
}

//InitializeMQTT allocates the mqtt client for the user. an empty string can be passed as the second argument for the user client
func (d *DeviceClient) InitializeMQTT(clientid string, ignore string, timeout int, ssl *tls.Config, lastWill *LastWillPacket) error {
	mqc, err := newMqttClient(d.DeviceToken, d.SystemKey, d.SystemSecret, clientid, timeout, d.MqttAddr, ssl, lastWill)
	if err != nil {
		return err
	}
	d.MQTTClient = mqc
	return nil
}

//Publish publishes a message to the specified mqtt topic
func (u *UserClient) Publish(topic string, message []byte, qos int) error {
	return publish(u.MQTTClient, topic, message, qos, u.getMessageId())
}

//Publish publishes a message to the specified mqtt topic
func (d *DeviceClient) Publish(topic string, message []byte, qos int) error {
	return publish(d.MQTTClient, topic, message, qos, d.getMessageId())
}

//Publish publishes a message to the specified mqtt topic
func (d *DevClient) Publish(topic string, message []byte, qos int) error {
	return publish(d.MQTTClient, topic, message, qos, d.getMessageId())
}

//Subscribe subscribes a user to a topic. Incoming messages will be sent over the channel.
func (u *UserClient) Subscribe(topic string, qos int) (<-chan *mqttTypes.Publish, error) {
	return subscribe(u.MQTTClient, topic, qos)
}

//Subscribe subscribes a device to a topic. Incoming messages will be sent over the channel.
func (d *DeviceClient) Subscribe(topic string, qos int) (<-chan *mqttTypes.Publish, error) {
	return subscribe(d.MQTTClient, topic, qos)
}

//Subscribe subscribes a user to a topic. Incoming messages will be sent over the channel.
func (d *DevClient) Subscribe(topic string, qos int) (<-chan *mqttTypes.Publish, error) {
	return subscribe(d.MQTTClient, topic, qos)
}

//Unsubscribe stops the flow of messages over the corresponding subscription chan
func (u *UserClient) Unsubscribe(topic string) error {
	return unsubscribe(u.MQTTClient, topic)
}

//Unsubscribe stops the flow of messages over the corresponding subscription chan
func (d *DeviceClient) Unsubscribe(topic string) error {
	return unsubscribe(d.MQTTClient, topic)
}

//Unsubscribe stops the flow of messages over the corresponding subscription chan
func (d *DevClient) Unsubscribe(topic string) error {
	return unsubscribe(d.MQTTClient, topic)
}

//Disconnect stops the TCP connection and unsubscribes the client from any remaining topics
func (u *UserClient) Disconnect() error {
	return disconnect(u.MQTTClient)
}

//Disconnect stops the TCP connection and unsubscribes the client from any remaining topics
func (d *DeviceClient) Disconnect() error {
	return disconnect(d.MQTTClient)
}

//Disconnect stops the TCP connection and unsubscribes the client from any remaining topics
func (d *DevClient) Disconnect() error {
	return disconnect(d.MQTTClient)
}

//Below are a series of convience functions to allow the user to only need to import
//the clearblade go-sdk
type mqttBaseClient struct {
	mqtt.Client
	address                                  string
	token, systemKey, systemSecret, clientID string
	timeout                                  int
}

//InitializeMqttClient allocates a mqtt client.
//the values for initialization are drawn from the client struct
//with the exception of the timeout and client id, which is mqtt specific.
// timeout refers to broker connect timeout
func newMqttClient(token, systemkey, systemsecret, clientid string, timeout int, address string, ssl *tls.Config, lastWill *LastWillPacket) (MqttClient, error) {
	o := mqtt.NewClientOptions()
	o.SetAutoReconnect(true)
	o.AddBroker("tcp://" + address)
	o.SetClientID(clientid)
	o.SetUsername(token)
	o.SetPassword(systemkey)
	o.SetConnectTimeout(time.Duration(timeout) * time.Second)
	if ssl != nil {
		o.SetTLSConfig(ssl)
	}
	if lastWill != nil {
		o.SetWill(lastWill.Topic, lastWill.Body, uint8(lastWill.Qos), lastWill.Retain)
	}
	cli := mqtt.NewClient(o)
	mqc := &mqttBaseClient{cli, address, token, systemkey, systemsecret, clientid, timeout}
	ret := mqc.Connect()
	ret.Wait()
	return mqc, ret.Error()
}

func publish(c MqttClient, topic string, data []byte, qos int, mid uint16) error {
	if c == nil {
		return errors.New("MQTTClient is uninitialized")
	}
	ret := c.Publish(topic, uint8(qos), false, data)
	return ret.Error()
}

func subscribe(c MqttClient, topic string, qos int) (<-chan *mqttTypes.Publish, error) {
	if c == nil {
		return nil, errors.New("MQTTClient is uninitialized")
	}
	pubs := make(chan *mqttTypes.Publish, 50)
	ret := c.Subscribe(topic, uint8(qos), func(client mqtt.Client, msg mqtt.Message) {
		path, _ := mqttTypes.NewTopicPath(msg.Topic())
		pubs <- &mqttTypes.Publish{Topic: path, Payload: msg.Payload()}
	})
	ret.Wait()
	return pubs, ret.Error()
}

func unsubscribe(c MqttClient, topic string) error {
	if c == nil {
		return errors.New("MQTTClient is uninitialized")
	}
	ret := c.Unsubscribe(topic)
	ret.Wait()
	return ret.Error()
}

func disconnect(c MqttClient) error {
	if c == nil {
		return errors.New("MQTTClient is uninitialized")
	}
	c.Disconnect(250)
	return nil
}
<|endoftext|>


<commit_before>package main

import (
  "flag"
  "fmt"
  "net"
  "os"
)

func getAddresses() (addrs []net.Addr) {
  addrs, err := net.InterfaceAddrs()
  if err != nil {
    os.Exit(1)
  }
  return
}

func succeed(addr string) {
  fmt.Println(addr)
  os.Exit(0)
}

func main() {
  only6 := flag.Bool("6", false, "Limit to IPv6")
  flag.Parse()

  addrs := getAddresses()
  for _, addr := range addrs {
    network, ok := addr.(*net.IPNet)
    if !ok || network.IP.IsLoopback() {
      continue
    }

    // skip IPv6 unless we want it
    is6 := network.IP.To4() == nil
    if (!*only6 && is6) || (*only6 && !is6) {
      continue
    }

    // skip Docker address
    if network.Contains(net.IPv4(172, 17, 0, 1)) {
      continue
    }

    succeed(network.IP.String())
  }

  // print the IPv6 or Docker address if it's the only one
  if len(addrs) > 0 {
    succeed(addrs[0].String())
  }

  os.Exit(1)
}
<commit_msg>Adds "remote" IP via benizi.com script<commit_after>package main

import (
  "flag"
  "fmt"
  "io/ioutil"
  "net"
  "net/http"
  "os"
)

func getAddresses(external bool) (addrs []net.Addr) {
  if external {
    addrs = getExternal()
  } else {
    got, err := net.InterfaceAddrs()
    if err != nil {
      os.Exit(1)
    }
    addrs = got
  }
  return
}

func getExternal() []net.Addr {
  res, err := http.Get("http://benizi.com/ip?raw=1")
  if err != nil {
    os.Exit(1)
  }
  defer res.Body.Close()

  ip, err := ioutil.ReadAll(res.Body)
  if err != nil {
    os.Exit(1)
  }

  _, network, err := net.ParseCIDR(fmt.Sprintf("%s/32", ip[0:len(ip)-1]))
  if err != nil {
    os.Exit(1)
  }

  return []net.Addr{network}
}

func succeed(addr string) {
  fmt.Println(addr)
  os.Exit(0)
}

func main() {
  only6 := flag.Bool("6", false, "Limit to IPv6")
  external := flag.Bool("x", false, "Fetch external address")
  flag.Parse()

  addrs := getAddresses(*external)
  for _, addr := range addrs {
    network, ok := addr.(*net.IPNet)
    if !ok || network.IP.IsLoopback() {
      continue
    }

    // skip IPv6 unless we want it
    is6 := network.IP.To4() == nil
    if (!*only6 && is6) || (*only6 && !is6) {
      continue
    }

    // skip Docker address
    if network.Contains(net.IPv4(172, 17, 0, 1)) {
      continue
    }

    succeed(network.IP.String())
  }

  // print the IPv6 or Docker address if it's the only one
  if len(addrs) > 0 {
    succeed(addrs[0].String())
  }

  os.Exit(1)
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"v.io/x/lib/cmdline"
)

var cmdMadbName = &cmdline.Command{
	Children: []*cmdline.Command{cmdMadbNameSet, cmdMadbNameUnset, cmdMadbNameList, cmdMadbNameClearAll},
	Name:     "name",
	Short:    "Manage device nicknames",
	Long: `
Manages device nicknames, which are meant to be more human-friendly compared to
the device serials provided by adb tool.
`,
}

var cmdMadbNameSet = &cmdline.Command{
	Runner: runnerFuncWithFilepath(runMadbNameSet),
	Name:   "set",
	Short:  "Set a nickname to be used in place of the device serial.",
	Long: `
Sets a human-friendly nickname that can be used when specifying the device in
any madb commands.

The device serial can be obtain using the 'adb devices -l' command.
For example, consider the following example output:

    HT4BVWV00023           device usb:3-3.4.2 product:volantisg model:Nexus_9 device:flounder_lte

The first value, 'HT4BVWV00023', is the device serial.
To assign a nickname for this device, run the following command:

    madb name set HT4BVWV00023 MyTablet

and it will assign the 'MyTablet' nickname to the device serial 'HT4BVWV00023'.
The alternative device specifiers (e.g., 'usb:3-3.4.2', 'product:volantisg')
can also have nicknames.

When a nickname is set for a device serial, the nickname can be used to specify
the device within madb commands.

There can only be one nickname for a device serial.
When the 'madb name set' command is invoked with a device serial with an already
assigned nickname, the old one will be replaced with the newly provided one.
`,
	ArgsName: "<device_serial> <nickname>",
	ArgsLong: `
<device_serial> is a device serial (e.g., 'HT4BVWV00023') or an alternative device specifier (e.g., 'usb:3-3.4.2') obtained from 'adb devices -l' command
<nickname> is an alpha-numeric string with no special characters or spaces.
`,
}

func runMadbNameSet(env *cmdline.Env, args []string, filename string) error {
	// Check if the arguments are valid.
	if len(args) != 2 {
		return env.UsageErrorf("There must be exactly two arguments.")
	}

	serial, nickname := args[0], args[1]
	if !isValidDeviceSerial(serial) {
		return env.UsageErrorf("Not a valid device serial: %v", serial)
	}

	if !isValidNickname(nickname) {
		return env.UsageErrorf("Not a valid nickname: %v", nickname)
	}

	nsm, err := readNicknameSerialMap(filename)
	if err != nil {
		return err
	}

	// If the nickname is already in use, don't allow it at all.
	if _, present := nsm[nickname]; present {
		return fmt.Errorf("The provided nickname %q is already in use.", nickname)
	}

	// If the serial number already has an assigned nickname, delete it first.
	// Need to do this check, because the nickname-serial map should be a one-to-one mapping.
	if nn, present := reverseMap(nsm)[serial]; present {
		delete(nsm, nn)
	}

	// Add the nickname serial mapping.
	nsm[nickname] = serial

	return writeNicknameSerialMap(nsm, filename)
}

var cmdMadbNameUnset = &cmdline.Command{
	Runner: runnerFuncWithFilepath(runMadbNameUnset),
	Name:   "unset",
	Short:  "Unset a nickname set by the 'madb name set' command.",
	Long: `
Unsets a nickname assigned by the 'madb name set' command. Either the device
serial or the assigned nickname can be specified to remove the mapping.
`,
	ArgsName: "<device_serial | nickname>",
	ArgsLong: `
There should be only one argument, which is either the device serial or the nickname.
`,
}

func runMadbNameUnset(env *cmdline.Env, args []string, filename string) error {
	// Check if the arguments are valid.
	if len(args) != 1 {
		return env.UsageErrorf("There must be exactly one argument.")
	}

	name := args[0]
	if !isValidDeviceSerial(name) && !isValidNickname(name) {
		return env.UsageErrorf("Not a valid device serial or name: %v", name)
	}

	nsm, err := readNicknameSerialMap(filename)
	if err != nil {
		return err
	}

	found := false
	for nickname, serial := range nsm {
		if nickname == name || serial == name {
			delete(nsm, nickname)
			found = true
			break
		}
	}

	if !found {
		return fmt.Errorf("The provided argument is neither a known nickname nor a device serial.")
	}

	return writeNicknameSerialMap(nsm, filename)
}

var cmdMadbNameList = &cmdline.Command{
	Runner: runnerFuncWithFilepath(runMadbNameList),
	Name:   "list",
	Short:  "List all the existing nicknames.",
	Long: `
Lists all the currently stored nicknames of device serials.
`,
}

func runMadbNameList(env *cmdline.Env, args []string, filename string) error {
	nsm, err := readNicknameSerialMap(filename)
	if err != nil {
		return err
	}

	// TODO(youngseokyoon): pretty print this.
	fmt.Println("Serial          Nickname")
	fmt.Println("========================")

	for nickname, serial := range nsm {
		fmt.Printf("%v\t%v\n", serial, nickname)
	}

	return nil
}

var cmdMadbNameClearAll = &cmdline.Command{
	Runner: runnerFuncWithFilepath(runMadbNameClearAll),
	Name:   "clear-all",
	Short:  "Clear all the existing nicknames.",
	Long: `
Clears all the currently stored nicknames of device serials.
`,
}

func runMadbNameClearAll(env *cmdline.Env, args []string, filename string) error {
	return os.Remove(filename)
}

func getDefaultNameFilePath() (string, error) {
	configDir, err := getConfigDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(configDir, "nicknames"), nil
}

func isValidDeviceSerial(serial string) bool {
	r := regexp.MustCompile(`^([A-Za-z0-9:\-\._]+|@\d+)$`)
	return r.MatchString(serial)
}

func isValidNickname(nickname string) bool {
	r := regexp.MustCompile(`^\w+$`)
	return r.MatchString(nickname)
}

// reverseMap returns a new map which contains reversed key, value pairs in the original map.
// The source map is assumed to be a one-to-one mapping between keys and values.
func reverseMap(source map[string]string) map[string]string {
	if source == nil {
		return nil
	}

	reversed := make(map[string]string, len(source))
	for k, v := range source {
		reversed[v] = k
	}

	return reversed
}

// readNicknameSerialMap reads the provided file and reconstructs the nickname => serial map.
// The mapping is written one per each line, in the form of "<nickname> <serial>".
func readNicknameSerialMap(filename string) (map[string]string, error) {
	result := make(map[string]string)

	f, err := os.Open(filename)
	if err != nil {
		// Nickname file may not exist when there are no nicknames assigned, and it is not an error.
		if os.IsNotExist(err) {
			return nil, nil
		}

		return nil, err
	}
	defer f.Close()

	// TODO(youngseokyoon): use encoding/json for serialization and deserialization.
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		fields := strings.Fields(line)
		if len(fields) == 2 {
			result[fields[0]] = fields[1]
		} else {
			return nil, fmt.Errorf("Unexpected number of columns in the nickname file.")
		}
	}

	return result, nil
}

// writeNicknameSerialmap takes a nickname => serial map and writes it into the provided file name.
// The mapping is written one per each line, in the form of "<nickname> <serial>".
func writeNicknameSerialMap(nsm map[string]string, filename string) error {
	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	for nickname, serial := range nsm {
		fmt.Fprintln(f, nickname, serial)
	}

	return nil
}

// runnerFuncWithFilepath is an adapter that turns the madb name subcommand functions into cmdline.Runners.
type runnerFuncWithFilepath func(*cmdline.Env, []string, string) error

// Run implements the cmdline.Runner interface by providing the default name file path
// as the third string argument of the underlying run function.
func (f runnerFuncWithFilepath) Run(env *cmdline.Env, args []string) error {
	p, err := getDefaultNameFilePath()
	if err != nil {
		return err
	}

	return f(env, args, p)
}
<commit_msg>TBR: devtools/madb: fix crash when setting a nickname for the first time.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"v.io/x/lib/cmdline"
)

var cmdMadbName = &cmdline.Command{
	Children: []*cmdline.Command{cmdMadbNameSet, cmdMadbNameUnset, cmdMadbNameList, cmdMadbNameClearAll},
	Name:     "name",
	Short:    "Manage device nicknames",
	Long: `
Manages device nicknames, which are meant to be more human-friendly compared to
the device serials provided by adb tool.
`,
}

var cmdMadbNameSet = &cmdline.Command{
	Runner: runnerFuncWithFilepath(runMadbNameSet),
	Name:   "set",
	Short:  "Set a nickname to be used in place of the device serial.",
	Long: `
Sets a human-friendly nickname that can be used when specifying the device in
any madb commands.

The device serial can be obtain using the 'adb devices -l' command.
For example, consider the following example output:

    HT4BVWV00023           device usb:3-3.4.2 product:volantisg model:Nexus_9 device:flounder_lte

The first value, 'HT4BVWV00023', is the device serial.
To assign a nickname for this device, run the following command:

    madb name set HT4BVWV00023 MyTablet

and it will assign the 'MyTablet' nickname to the device serial 'HT4BVWV00023'.
The alternative device specifiers (e.g., 'usb:3-3.4.2', 'product:volantisg')
can also have nicknames.

When a nickname is set for a device serial, the nickname can be used to specify
the device within madb commands.

There can only be one nickname for a device serial.
When the 'madb name set' command is invoked with a device serial with an already
assigned nickname, the old one will be replaced with the newly provided one.
`,
	ArgsName: "<device_serial> <nickname>",
	ArgsLong: `
<device_serial> is a device serial (e.g., 'HT4BVWV00023') or an alternative device specifier (e.g., 'usb:3-3.4.2') obtained from 'adb devices -l' command
<nickname> is an alpha-numeric string with no special characters or spaces.
`,
}

func runMadbNameSet(env *cmdline.Env, args []string, filename string) error {
	// Check if the arguments are valid.
	if len(args) != 2 {
		return env.UsageErrorf("There must be exactly two arguments.")
	}

	serial, nickname := args[0], args[1]
	if !isValidDeviceSerial(serial) {
		return env.UsageErrorf("Not a valid device serial: %v", serial)
	}

	if !isValidNickname(nickname) {
		return env.UsageErrorf("Not a valid nickname: %v", nickname)
	}

	nsm, err := readNicknameSerialMap(filename)
	if err != nil {
		return err
	}

	// If the nickname is already in use, don't allow it at all.
	if _, present := nsm[nickname]; present {
		return fmt.Errorf("The provided nickname %q is already in use.", nickname)
	}

	// If the serial number already has an assigned nickname, delete it first.
	// Need to do this check, because the nickname-serial map should be a one-to-one mapping.
	if nn, present := reverseMap(nsm)[serial]; present {
		delete(nsm, nn)
	}

	// Add the nickname serial mapping.
	nsm[nickname] = serial

	return writeNicknameSerialMap(nsm, filename)
}

var cmdMadbNameUnset = &cmdline.Command{
	Runner: runnerFuncWithFilepath(runMadbNameUnset),
	Name:   "unset",
	Short:  "Unset a nickname set by the 'madb name set' command.",
	Long: `
Unsets a nickname assigned by the 'madb name set' command. Either the device
serial or the assigned nickname can be specified to remove the mapping.
`,
	ArgsName: "<device_serial | nickname>",
	ArgsLong: `
There should be only one argument, which is either the device serial or the nickname.
`,
}

func runMadbNameUnset(env *cmdline.Env, args []string, filename string) error {
	// Check if the arguments are valid.
	if len(args) != 1 {
		return env.UsageErrorf("There must be exactly one argument.")
	}

	name := args[0]
	if !isValidDeviceSerial(name) && !isValidNickname(name) {
		return env.UsageErrorf("Not a valid device serial or name: %v", name)
	}

	nsm, err := readNicknameSerialMap(filename)
	if err != nil {
		return err
	}

	found := false
	for nickname, serial := range nsm {
		if nickname == name || serial == name {
			delete(nsm, nickname)
			found = true
			break
		}
	}

	if !found {
		return fmt.Errorf("The provided argument is neither a known nickname nor a device serial.")
	}

	return writeNicknameSerialMap(nsm, filename)
}

var cmdMadbNameList = &cmdline.Command{
	Runner: runnerFuncWithFilepath(runMadbNameList),
	Name:   "list",
	Short:  "List all the existing nicknames.",
	Long: `
Lists all the currently stored nicknames of device serials.
`,
}

func runMadbNameList(env *cmdline.Env, args []string, filename string) error {
	nsm, err := readNicknameSerialMap(filename)
	if err != nil {
		return err
	}

	// TODO(youngseokyoon): pretty print this.
	fmt.Println("Serial          Nickname")
	fmt.Println("========================")

	for nickname, serial := range nsm {
		fmt.Printf("%v\t%v\n", serial, nickname)
	}

	return nil
}

var cmdMadbNameClearAll = &cmdline.Command{
	Runner: runnerFuncWithFilepath(runMadbNameClearAll),
	Name:   "clear-all",
	Short:  "Clear all the existing nicknames.",
	Long: `
Clears all the currently stored nicknames of device serials.
`,
}

func runMadbNameClearAll(env *cmdline.Env, args []string, filename string) error {
	return os.Remove(filename)
}

func getDefaultNameFilePath() (string, error) {
	configDir, err := getConfigDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(configDir, "nicknames"), nil
}

func isValidDeviceSerial(serial string) bool {
	r := regexp.MustCompile(`^([A-Za-z0-9:\-\._]+|@\d+)$`)
	return r.MatchString(serial)
}

func isValidNickname(nickname string) bool {
	r := regexp.MustCompile(`^\w+$`)
	return r.MatchString(nickname)
}

// reverseMap returns a new map which contains reversed key, value pairs in the original map.
// The source map is assumed to be a one-to-one mapping between keys and values.
func reverseMap(source map[string]string) map[string]string {
	if source == nil {
		return nil
	}

	reversed := make(map[string]string, len(source))
	for k, v := range source {
		reversed[v] = k
	}

	return reversed
}

// readNicknameSerialMap reads the provided file and reconstructs the nickname => serial map.
// The mapping is written one per each line, in the form of "<nickname> <serial>".
func readNicknameSerialMap(filename string) (map[string]string, error) {
	result := make(map[string]string)

	f, err := os.Open(filename)
	if err != nil {
		// Nickname file may not exist when there are no nicknames assigned, and it is not an error.
		if os.IsNotExist(err) {
			return result, nil
		}

		return nil, err
	}
	defer f.Close()

	// TODO(youngseokyoon): use encoding/json for serialization and deserialization.
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		fields := strings.Fields(line)
		if len(fields) == 2 {
			result[fields[0]] = fields[1]
		} else {
			return nil, fmt.Errorf("Unexpected number of columns in the nickname file.")
		}
	}

	return result, nil
}

// writeNicknameSerialmap takes a nickname => serial map and writes it into the provided file name.
// The mapping is written one per each line, in the form of "<nickname> <serial>".
func writeNicknameSerialMap(nsm map[string]string, filename string) error {
	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	for nickname, serial := range nsm {
		fmt.Fprintln(f, nickname, serial)
	}

	return nil
}

// runnerFuncWithFilepath is an adapter that turns the madb name subcommand functions into cmdline.Runners.
type runnerFuncWithFilepath func(*cmdline.Env, []string, string) error

// Run implements the cmdline.Runner interface by providing the default name file path
// as the third string argument of the underlying run function.
func (f runnerFuncWithFilepath) Run(env *cmdline.Env, args []string) error {
	p, err := getDefaultNameFilePath()
	if err != nil {
		return err
	}

	return f(env, args, p)
}
<|endoftext|>


<commit_before>// Copyright 2014 The Go Authors.
// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
// Licensed under the same terms as Go itself:
// https://code.google.com/p/go/source/browse/LICENSE

package http2

import (
	"bytes"
	"encoding/xml"
	"flag"
	"fmt"
	"io"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"testing"
)

var coverSpec = flag.Bool("coverspec", false, "Run spec coverage tests")

// The global map of sentence coverage for the http2 spec.
var defaultSpecCoverage specCoverage

func init() {
	f, err := os.Open("testdata/draft-ietf-httpbis-http2.xml")
	if err != nil {
		panic(err)
	}
	defaultSpecCoverage = readSpecCov(f)
}

// specCover marks all sentences for section sec in defaultSpecCoverage. Sentences not
// "covered" will be included in report outputed by TestSpecCoverage.
func specCover(sec, sentences string) {
	defaultSpecCoverage.cover(sec, sentences)
}

type specPart struct {
	section  string
	sentence string
}

func (ss specPart) Less(oo specPart) bool {
	atoi := func(s string) int {
		n, err := strconv.Atoi(s)
		if err != nil {
			panic(err)
		}
		return n
	}
	a := strings.Split(ss.section, ".")
	b := strings.Split(oo.section, ".")
	for i := 0; i < len(a); i++ {
		if i >= len(b) {
			return false
		}
		x, y := atoi(a[i]), atoi(b[i])
		if x < y {
			return true
		}
	}
	return false
}

type bySpecSection []specPart

func (a bySpecSection) Len() int           { return len(a) }
func (a bySpecSection) Less(i, j int) bool { return a[i].Less(a[j]) }
func (a bySpecSection) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

type specCoverage struct {
	m map[specPart]bool
	d *xml.Decoder
}

func joinSection(sec []int) string {
	s := fmt.Sprintf("%d", sec[0])
	for _, n := range sec[1:] {
		s = fmt.Sprintf("%s.%d", s, n)
	}
	return s
}

func (sc specCoverage) readSection(sec []int) {
	var (
		buf = new(bytes.Buffer)
		sub = 0
	)
	for {
		tk, err := sc.d.Token()
		if err != nil {
			if err == io.EOF {
				return
			}
			panic(err)
		}
		switch v := tk.(type) {
		case xml.StartElement:
			if skipElement(v) {
				if err := sc.d.Skip(); err != nil {
					panic(err)
				}
				break
			}
			if v.Name.Local == "section" {
				sub++
				sc.readSection(append(sec, sub))
			}
		case xml.CharData:
			if len(sec) == 0 {
				break
			}
			buf.Write(v)
		case xml.EndElement:
			if v.Name.Local == "section" {
				sc.addSentences(joinSection(sec), buf.String())
				return
			}
		}
	}
}

var skipAnchor = map[string]bool{
	"intro":    true,
	"Overview": true,
}

var skipTitle = map[string]bool{
	"Acknowledgements":            true,
	"Change Log":                  true,
	"Document Organization":       true,
	"Conventions and Terminology": true,
}

func skipElement(s xml.StartElement) bool {
	switch s.Name.Local {
	case "artwork":
		return true
	case "section":
		for _, attr := range s.Attr {
			switch attr.Name.Local {
			case "anchor":
				if skipAnchor[attr.Value] || strings.HasPrefix(attr.Value, "changes.since.") {
					return true
				}
			case "title":
				if skipTitle[attr.Value] {
					return true
				}
			}
		}
	}
	return false
}

func readSpecCov(r io.Reader) specCoverage {
	sc := specCoverage{
		m: map[specPart]bool{},
		d: xml.NewDecoder(r)}
	sc.readSection(nil)
	return sc
}

func (sc specCoverage) addSentences(sec string, sentence string) {
	for _, s := range parseSentences(sentence) {
		sc.m[specPart{sec, s}] = false
	}
}

func (sc specCoverage) cover(sec string, sentence string) {
	for _, s := range parseSentences(sentence) {
		p := specPart{sec, s}
		if _, ok := sc.m[p]; !ok {
			panic(fmt.Sprintf("Not found in spec: %q, %q", sec, s))
		}
		sc.m[specPart{sec, s}] = true
	}

}

func (sc specCoverage) uncovered() []specPart {
	var a []specPart
	for p, covered := range sc.m {
		if !covered {
			a = append(a, p)
		}
	}
	return a
}

var whitespaceRx = regexp.MustCompile(`\s+`)

func parseSentences(sens string) []string {
	sens = strings.TrimSpace(sens)
	if sens == "" {
		return nil
	}
	ss := strings.Split(whitespaceRx.ReplaceAllString(sens, " "), ". ")
	for i, s := range ss {
		s = strings.TrimSpace(s)
		if !strings.HasSuffix(s, ".") {
			s += "."
		}
		ss[i] = s
	}
	return ss
}

func TestSpecParseSentences(t *testing.T) {
	tests := []struct {
		ss   string
		want []string
	}{
		{"Sentence 1. Sentence 2.",
			[]string{
				"Sentence 1.",
				"Sentence 2.",
			}},
		{"Sentence 1.  \nSentence 2.\tSentence 3.",
			[]string{
				"Sentence 1.",
				"Sentence 2.",
				"Sentence 3.",
			}},
	}

	for i, tt := range tests {
		got := parseSentences(tt.ss)
		if !reflect.DeepEqual(got, tt.want) {
			t.Errorf("%d: got = %q, want %q", i, got, tt.want)
		}
	}
}

func TestSpecBuildCoverageTable(t *testing.T) {
	testdata := `
<rfc>
  <middle>

      <t>Foo.</t>
      <t><t>Sentence 1.
      Sentence 2
   .	Sentence 3.</t></t>

      <t>Bar.</t>

	  <t>Baz.</t>

  </middle>
</rfc>`
	got := readSpecCov(strings.NewReader(testdata))
	want := specCoverage{
		m: map[specPart]bool{
			specPart{"1", "Foo."}:        false,
			specPart{"1", "Sentence 1."}: false,
			specPart{"1", "Sentence 2."}: false,
			specPart{"1", "Sentence 3."}: false,
			specPart{"2", "Bar."}:        false,
			specPart{"2.1", "Baz."}:      false,
		},
	}

	if !reflect.DeepEqual(got, want) {
		t.Errorf("got = %+v, want %+v", got, want)
	}
}

func TestSpecUncovered(t *testing.T) {
	testdata := `
<rfc>
  <middle>

	<t>Foo.</t>
	<t><t>Sentence 1.</t></t>

  </middle>
</rfc>`
	sp := readSpecCov(strings.NewReader(testdata))
	sp.cover("1", "Foo. Sentence 1.")

	want := specCoverage{
		m: map[specPart]bool{
			specPart{"1", "Foo."}:        true,
			specPart{"1", "Sentence 1."}: true,
		},
	}

	if !reflect.DeepEqual(sp, want) {
		t.Errorf("got = %+v, want %+v", sp, want)
	}

	defer func() {
		if err := recover(); err == nil {
			t.Error("expected panic")
		}
	}()

	sp.cover("1", "Not in spec.")
}

func TestSpecCoverage(t *testing.T) {
	uncovered := defaultSpecCoverage.uncovered()
	if len(uncovered) == 0 {
		return
	}
	sort.Sort(bySpecSection(uncovered))

	const shortLen = 5
	if testing.Short() && len(uncovered) > shortLen {
		uncovered = uncovered[:shortLen]
	}
	t.Logf("COVER REPORT:")
	fails := 0
	for _, p := range uncovered {
		t.Errorf("\tSECTION %s: %s", p.section, p.sentence)
		fails++
	}
	t.Logf("%d sections not covered", fails)
}
<commit_msg>parse spec xrefs<commit_after>// Copyright 2014 The Go Authors.
// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
// Licensed under the same terms as Go itself:
// https://code.google.com/p/go/source/browse/LICENSE

package http2

import (
	"bytes"
	"encoding/xml"
	"flag"
	"fmt"
	"io"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"testing"
)

var coverSpec = flag.Bool("coverspec", false, "Run spec coverage tests")

// The global map of sentence coverage for the http2 spec.
var defaultSpecCoverage specCoverage

func init() {
	f, err := os.Open("testdata/draft-ietf-httpbis-http2.xml")
	if err != nil {
		panic(err)
	}
	defaultSpecCoverage = readSpecCov(f)
}

// specCover marks all sentences for section sec in defaultSpecCoverage. Sentences not
// "covered" will be included in report outputed by TestSpecCoverage.
func specCover(sec, sentences string) {
	defaultSpecCoverage.cover(sec, sentences)
}

type specPart struct {
	section  string
	sentence string
}

func (ss specPart) Less(oo specPart) bool {
	atoi := func(s string) int {
		n, err := strconv.Atoi(s)
		if err != nil {
			panic(err)
		}
		return n
	}
	a := strings.Split(ss.section, ".")
	b := strings.Split(oo.section, ".")
	for i := 0; i < len(a); i++ {
		if i >= len(b) {
			return false
		}
		x, y := atoi(a[i]), atoi(b[i])
		if x < y {
			return true
		}
	}
	return false
}

type bySpecSection []specPart

func (a bySpecSection) Len() int           { return len(a) }
func (a bySpecSection) Less(i, j int) bool { return a[i].Less(a[j]) }
func (a bySpecSection) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

type specCoverage struct {
	m map[specPart]bool
	d *xml.Decoder
}

func joinSection(sec []int) string {
	s := fmt.Sprintf("%d", sec[0])
	for _, n := range sec[1:] {
		s = fmt.Sprintf("%s.%d", s, n)
	}
	return s
}

func (sc specCoverage) readSection(sec []int) {
	var (
		buf = new(bytes.Buffer)
		sub = 0
	)
	for {
		tk, err := sc.d.Token()
		if err != nil {
			if err == io.EOF {
				return
			}
			panic(err)
		}
		switch v := tk.(type) {
		case xml.StartElement:
			if skipElement(v) {
				if err := sc.d.Skip(); err != nil {
					panic(err)
				}
				break
			}
			switch v.Name.Local {
			case "section":
				sub++
				sc.readSection(append(sec, sub))
			case "xref":
				buf.Write(sc.readXRef(v))
			}
		case xml.CharData:
			if len(sec) == 0 {
				break
			}
			buf.Write(v)
		case xml.EndElement:
			if v.Name.Local == "section" {
				sc.addSentences(joinSection(sec), buf.String())
				return
			}
		}
	}
}

func (sc specCoverage) readXRef(se xml.StartElement) []byte {
	var b []byte
	for {
		tk, err := sc.d.Token()
		if err != nil {
			panic(err)
		}
		switch v := tk.(type) {
		case xml.CharData:
			if b != nil {
				panic("unexpected CharData")
			}
			b = []byte(v)
		case xml.EndElement:
			if v.Name.Local != "xref" {
				panic("expected </xref>")
			}
			if b != nil {
				return b
			}
			return []byte(fmt.Sprintf("%#v", se))
		default:
			panic(fmt.Sprintf("unexpected tag %q", v))
		}
	}
}

var skipAnchor = map[string]bool{
	"intro":    true,
	"Overview": true,
}

var skipTitle = map[string]bool{
	"Acknowledgements":            true,
	"Change Log":                  true,
	"Document Organization":       true,
	"Conventions and Terminology": true,
}

func skipElement(s xml.StartElement) bool {
	switch s.Name.Local {
	case "artwork":
		return true
	case "section":
		for _, attr := range s.Attr {
			switch attr.Name.Local {
			case "anchor":
				if skipAnchor[attr.Value] || strings.HasPrefix(attr.Value, "changes.since.") {
					return true
				}
			case "title":
				if skipTitle[attr.Value] {
					return true
				}
			}
		}
	}
	return false
}

func readSpecCov(r io.Reader) specCoverage {
	sc := specCoverage{
		m: map[specPart]bool{},
		d: xml.NewDecoder(r)}
	sc.readSection(nil)
	return sc
}

func (sc specCoverage) addSentences(sec string, sentence string) {
	for _, s := range parseSentences(sentence) {
		sc.m[specPart{sec, s}] = false
	}
}

func (sc specCoverage) cover(sec string, sentence string) {
	for _, s := range parseSentences(sentence) {
		p := specPart{sec, s}
		if _, ok := sc.m[p]; !ok {
			panic(fmt.Sprintf("Not found in spec: %q, %q", sec, s))
		}
		sc.m[specPart{sec, s}] = true
	}

}

func (sc specCoverage) uncovered() []specPart {
	var a []specPart
	for p, covered := range sc.m {
		if !covered {
			a = append(a, p)
		}
	}
	return a
}

var whitespaceRx = regexp.MustCompile(`\s+`)

func parseSentences(sens string) []string {
	sens = strings.TrimSpace(sens)
	if sens == "" {
		return nil
	}
	ss := strings.Split(whitespaceRx.ReplaceAllString(sens, " "), ". ")
	for i, s := range ss {
		s = strings.TrimSpace(s)
		if !strings.HasSuffix(s, ".") {
			s += "."
		}
		ss[i] = s
	}
	return ss
}

func TestSpecParseSentences(t *testing.T) {
	tests := []struct {
		ss   string
		want []string
	}{
		{"Sentence 1. Sentence 2.",
			[]string{
				"Sentence 1.",
				"Sentence 2.",
			}},
		{"Sentence 1.  \nSentence 2.\tSentence 3.",
			[]string{
				"Sentence 1.",
				"Sentence 2.",
				"Sentence 3.",
			}},
	}

	for i, tt := range tests {
		got := parseSentences(tt.ss)
		if !reflect.DeepEqual(got, tt.want) {
			t.Errorf("%d: got = %q, want %q", i, got, tt.want)
		}
	}
}

func TestSpecBuildCoverageTable(t *testing.T) {
	testdata := `
<rfc>
  <middle>

      <t>Foo.</t>
      <t><t>Sentence 1.
      Sentence 2
   .	Sentence 3.</t></t>

      <t>Bar.</t>

	  <t>Baz.</t>

  </middle>
</rfc>`
	got := readSpecCov(strings.NewReader(testdata))
	want := specCoverage{
		m: map[specPart]bool{
			specPart{"1", "Foo."}:        false,
			specPart{"1", "Sentence 1."}: false,
			specPart{"1", "Sentence 2."}: false,
			specPart{"1", "Sentence 3."}: false,
			specPart{"2", "Bar."}:        false,
			specPart{"2.1", "Baz."}:      false,
		},
	}

	if !reflect.DeepEqual(got, want) {
		t.Errorf("got = %+v, want %+v", got, want)
	}
}

func TestSpecUncovered(t *testing.T) {
	testdata := `
<rfc>
  <middle>

	<t>Foo.</t>
	<t><t>Sentence 1.</t></t>

  </middle>
</rfc>`
	sp := readSpecCov(strings.NewReader(testdata))
	sp.cover("1", "Foo. Sentence 1.")

	want := specCoverage{
		m: map[specPart]bool{
			specPart{"1", "Foo."}:        true,
			specPart{"1", "Sentence 1."}: true,
		},
	}

	if !reflect.DeepEqual(sp, want) {
		t.Errorf("got = %+v, want %+v", sp, want)
	}

	defer func() {
		if err := recover(); err == nil {
			t.Error("expected panic")
		}
	}()

	sp.cover("1", "Not in spec.")
}

func TestSpecCoverage(t *testing.T) {
	uncovered := defaultSpecCoverage.uncovered()
	if len(uncovered) == 0 {
		return
	}
	sort.Sort(bySpecSection(uncovered))

	const shortLen = 5
	if testing.Short() && len(uncovered) > shortLen {
		uncovered = uncovered[:shortLen]
	}
	t.Logf("COVER REPORT:")
	fails := 0
	for _, p := range uncovered {
		t.Errorf("\tSECTION %s: %s", p.section, p.sentence)
		fails++
	}
	t.Logf("%d sections not covered", fails)
}
<|endoftext|>