<commit_before>// Copyright (c) 2013-2014 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

func (e *Evaluator) analyzeMaterial() {
	counters := &e.position.count
	for _, piece := range []Piece{Pawn, Knight, Bishop, Rook, Queen} {
		count := counters[piece] - counters[piece|Black]
		midgame, endgame := piece.value()
		e.midgame += midgame * count
		e.endgame += endgame * count
	}
}

func (e *Evaluator) analyzeCoordination() {
	var white, black [4]Score

	maskSafe := ^e.position.pawnAttacks(Black) 	// Squares not attacked by Black pawns.
	maskEnemy := e.position.outposts[Black]  	// Squares occupied by Black pieces.
	white[0] = e.knights(White, maskSafe, maskEnemy)
	white[1] = e.bishops(White, maskSafe, maskEnemy)
	white[2] = e.rooks(White, maskSafe, maskEnemy)
	white[3] = e.queens(White, maskSafe, maskEnemy)

	maskSafe = ^e.position.pawnAttacks(White) 	// Squares not attacked by White pawns.
	maskEnemy = e.position.outposts[White]  	// Squares occupied by White pieces.
	black[0] = e.knights(Black, maskSafe, maskEnemy)
	black[1] = e.bishops(Black, maskSafe, maskEnemy)
	black[2] = e.rooks(Black, maskSafe, maskEnemy)
	black[3] = e.queens(Black, maskSafe, maskEnemy)

	e.midgame += white[0].midgame + white[1].midgame + white[2].midgame + white[3].midgame -
	             black[0].midgame - black[1].midgame - black[2].midgame - black[3].midgame

     	e.endgame += white[0].endgame + white[1].endgame + white[2].endgame + white[3].endgame -
     	             black[0].endgame - black[1].endgame - black[2].endgame - black[3].endgame
}

func (e *Evaluator) knights(color int, maskSafe, maskEnemy Bitmask) (score Score) {
	p := e.position
	outposts := p.outposts[knight(color)]

	for outposts != 0 {
		square := outposts.pop()
		targets := p.targets(square)

		// Attacks.
		attacks := (targets & maskEnemy).count()
		score.midgame += attacks * attackForce.midgame
		score.endgame += attacks * attackForce.endgame

		// Mobility
		mobility := mobilityKnight[(targets & maskSafe).count()]
		score.midgame += mobility.midgame
		score.midgame += mobility.midgame

		// Placement.
		square = Flip(color, square)
		score.midgame += bonusKnight[0][square]
		score.endgame += bonusKnight[1][square]

		// Penalty a knight is attacked by enemy's pawn.
		if maskPawn[color^1][square] & p.outposts[pawn(color^1)] != 0 {
			score.midgame -= penaltyPawnThreat[2].midgame
			score.endgame -= penaltyPawnThreat[2].endgame
		}

		// Bonus if a knight is behind friendly pawn.
		if RelRow(color, square) < 4 && p.outposts[pawn(color)].isSet(square + eight[color]) {
			score.midgame += behindPawn.midgame
			score.endgame += behindPawn.endgame
		}
	}
	return
}

func (e *Evaluator) bishops(color int, maskSafe, maskEnemy Bitmask) (score Score) {
	p := e.position
	outposts := p.outposts[bishop(color)]

	for outposts != 0 {
		square := outposts.pop()
		targets := p.xrayTargets(square)

		// Attacks.
		attacks := (targets & maskEnemy).count()
		score.midgame += attacks * attackForce.midgame
		score.endgame += attacks * attackForce.endgame

		// Mobility
		mobility := mobilityBishop[(targets & maskSafe).count()]
		score.midgame += mobility.midgame
		score.midgame += mobility.midgame

		// Placement.
		square = Flip(color, square)
		score.midgame += bonusBishop[0][square]
		score.endgame += bonusBishop[1][square]

		// Penalty for light/dark square bishop and matching pawns.
		if count := (SameColor(square) & p.outposts[pawn(color)]).count(); count > 0 {
			score.midgame -= bishopPawns.midgame
			score.midgame -= bishopPawns.midgame
		}

		// Penalty a bishop is attacked by enemy's pawn.
		if maskPawn[color^1][square] & p.outposts[pawn(color^1)] != 0 {
			score.midgame -= penaltyPawnThreat[3].midgame
			score.endgame -= penaltyPawnThreat[3].endgame
		}

		// Bonus if a bishop is behind friendly pawn.
		if RelRow(color, square) < 4 && p.outposts[pawn(color)].isSet(square + eight[color]) {
			score.midgame += behindPawn.midgame
			score.endgame += behindPawn.endgame
		}
	}

	// Bonus for the pair of bishops.
	if bishops := p.count[bishop(color)]; bishops >= 2 {
		e.midgame += bishopPair.midgame
		e.endgame += bishopPair.endgame
	}
	return
}

func (e *Evaluator) rooks(color int, maskSafe, maskEnemy Bitmask) (score Score) {
	p := e.position
	hisPawns := p.outposts[pawn(color)]
	herPawns := p.outposts[pawn(color^1)]
	outposts := p.outposts[rook(color)]

	// Bonus if rook is on 7th rank and enemy's king trapped on 8th.
	if count := (outposts & mask7th[color]).count(); count > 0 && p.outposts[king(color^1)] & mask8th[color] != 0 {
		score.midgame += count * rookOn7th.midgame
		score.endgame += count * rookOn7th.endgame
	}
	for outposts != 0 {
		square := outposts.pop()
		targets := p.xrayTargets(square)

		// Attacks.
		attacks := (targets & maskEnemy).count()
		score.midgame += attacks * attackForce.midgame
		score.endgame += attacks * attackForce.endgame

		// Mobility
		mobility := mobilityRook[(targets & maskSafe).count()]
		score.midgame += mobility.midgame
		score.midgame += mobility.midgame

		// Placement.
		square = Flip(color, square)
		score.midgame += bonusRook[0][square]
		score.endgame += bonusRook[1][square]

		// Penalty a rook is attacked by enemy's pawn.
		if maskPawn[color^1][square] & p.outposts[pawn(color^1)] != 0 {
			score.midgame -= penaltyPawnThreat[4].midgame
			score.endgame -= penaltyPawnThreat[4].endgame
		}

		// Bonus if rook is attacking enemy's pawns.
		if count := (targets & p.outposts[pawn(color^1)]).count(); count > 0 {
			score.midgame += count * rookOnPawn.midgame
			score.endgame += count * rookOnPawn.endgame
		}

		// Bonuses if rook is on open or semi-open file.
		column := Col(square)
		if hisPawns & maskFile[column] == 0 {
			if herPawns & maskFile[column] == 0 {
				score.midgame += rookOnOpen.midgame
				score.endgame += rookOnOpen.endgame
			} else {
				score.midgame += rookOnSemiOpen.midgame
				score.endgame += rookOnSemiOpen.endgame
			}
		}

		// Middle game penalty if a rook is boxed. Extra penalty if castle
		// rights have been lost.
		if bit[square] & rookBoxA[color] != 0 && p.outposts[king(color)] & castleQueen[color] != 0 {
			score.midgame -= rookBoxed.midgame
			if p.castles & castleQueenside[color] == 0 {
				score.midgame -= rookBoxed.midgame
			}
		}
		if bit[square] & rookBoxH[color] != 0 && p.outposts[king(color)] & castleKing[color] != 0 {
			score.midgame -= rookBoxed.midgame
			if p.castles & castleKingside[color] == 0 {
				score.midgame -= rookBoxed.midgame
			}
		}
	}
	return
}

func (e *Evaluator) queens(color int, maskSafe, maskEnemy Bitmask) (score Score) {
	p := e.position
	outposts := p.outposts[queen(color)]

	// Bonus if queen is on 7th rank and enemy's king trapped on 8th.
	if count := (outposts & mask7th[color]).count(); count > 0 && p.outposts[king(color^1)] & mask8th[color] != 0 {
		score.midgame += count * queenOn7th.midgame
		score.endgame += count * queenOn7th.endgame
	}
	for outposts != 0 {
		square := outposts.pop()
		targets := p.targets(square)

		// Attacks.
		attacks := (targets & maskEnemy).count()
		score.midgame += attacks * attackForce.midgame
		score.endgame += attacks * attackForce.endgame

		// Mobility
		mobility := mobilityQueen[Max(15, (targets & maskSafe).count())]
		score.midgame += mobility.midgame
		score.midgame += mobility.midgame

		// Placement.
		square = Flip(color, square)
		score.midgame += bonusQueen[0][square]
		score.endgame += bonusQueen[1][square]

		// Penalty if queen is attacked by enemy's pawn.
		if maskPawn[color^1][square] & p.outposts[pawn(color^1)] != 0 {
			score.midgame -= penaltyPawnThreat[5].midgame
			score.endgame -= penaltyPawnThreat[5].endgame
		}

		// Bonus if queen is out and attacking enemy's pawns.
		if count := (targets & p.outposts[pawn(color^1)]).count(); count > 0 && RelRow(color, square) > 3 {
			score.midgame += count * queenOnPawn.midgame
			score.endgame += count * queenOnPawn.endgame
		}
	}
	return
}
<commit_msg>More thorough piece evaluation<commit_after>// Copyright (c) 2013-2014 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

func (e *Evaluator) analyzePieces() {
	var white, black [4]Score

	maskSafe := ^e.position.pawnAttacks(Black) // Squares not attacked by Black pawns.
	white[0] = e.knights(White, maskSafe)
	white[1] = e.bishops(White, maskSafe)
	white[2] = e.rooks(White, maskSafe)
	white[3] = e.queens(White, maskSafe)

	maskSafe = ^e.position.pawnAttacks(White) // Squares not attacked by White pawns.
	black[0] = e.knights(Black, maskSafe)
	black[1] = e.bishops(Black, maskSafe)
	black[2] = e.rooks(Black, maskSafe)
	black[3] = e.queens(Black, maskSafe)

	e.midgame += white[0].midgame + white[1].midgame + white[2].midgame + white[3].midgame -
	             black[0].midgame - black[1].midgame - black[2].midgame - black[3].midgame

     	e.endgame += white[0].endgame + white[1].endgame + white[2].endgame + white[3].endgame -
     	             black[0].endgame - black[1].endgame - black[2].endgame - black[3].endgame
}

func (e *Evaluator) knights(color int, maskSafe Bitmask) (score Score) {
	p := e.position
	outposts := p.outposts[knight(color)]

	for outposts != 0 {
		square := outposts.pop()
		targets := p.targets(square)

		// Bonus for knight's mobility.
		mobility := mobilityKnight[(targets & maskSafe).count()]
		score.midgame += mobility.midgame
		score.endgame += mobility.endgame

		// Penalty if knight is attacked by enemy's pawn.
		if maskPawn[color^1][square] & p.outposts[pawn(color^1)] != 0 {
			score.midgame -= penaltyPawnThreat[Knight/2].midgame
			score.endgame -= penaltyPawnThreat[Knight/2].endgame
		}

		// Bonus if knight is behind friendly pawn.
		if RelRow(color, square) < 4 && p.outposts[pawn(color)].isSet(square + eight[color]) {
			score.midgame += behindPawn.midgame
			score.endgame += behindPawn.endgame
		}

		// Track if knight attacks squares around enemy's king.
		if targets & p.kingAttacks(color^1) != 0 {
			e.attacks[color]++
			e.threats[color] += bonusKingThreat[Knight/2]
		}

		// Bonus for knight's board position.
		flip := Flip(color, square)
		score.midgame += bonusKnight[0][flip]
		score.endgame += bonusKnight[1][flip]

		// Extra bonus if knight is in the center. Increase the extra
		// bonus if the knight is supported by a pawn and can't be
		// exchanged.
		if extra := extraKnight[flip]; extra > 0 {
			if p.pawnAttacks(color).isSet(square) {
				if p.count[knight(color^1)] == 0 {
					extra *= 2 // No knights to exchange.
				}
				extra += extra / 2 // Supported by a pawn.
			}
			score.midgame += extra
			score.endgame += extra
		}
	}
	return
}

func (e *Evaluator) bishops(color int, maskSafe Bitmask) (score Score) {
	p := e.position
	outposts := p.outposts[bishop(color)]

	for outposts != 0 {
		square := outposts.pop()
		targets := p.xrayTargets(square)

		// Bonus for bishop's mobility
		mobility := mobilityBishop[(targets & maskSafe).count()]
		score.midgame += mobility.midgame
		score.endgame += mobility.endgame

		// Penalty for light/dark square bishop and matching pawns.
		if count := (SameColor(square) & p.outposts[pawn(color)]).count(); count > 0 {
			score.midgame -= bishopPawns.midgame
			score.endgame -= bishopPawns.endgame
		}

		// Penalty if bishop is attacked by enemy's pawn.
		if maskPawn[color^1][square] & p.outposts[pawn(color^1)] != 0 {
			score.midgame -= penaltyPawnThreat[Bishop/2].midgame
			score.endgame -= penaltyPawnThreat[Bishop/2].endgame
		}

		// Bonus if bishop is behind friendly pawn.
		if RelRow(color, square) < 4 && p.outposts[pawn(color)].isSet(square + eight[color]) {
			score.midgame += behindPawn.midgame
			score.endgame += behindPawn.endgame
		}

		// Middle game penalty for boxed bishop.
		if color == White {
			if (square == C1 && p.pieces[D2].isPawn() && p.pieces[D3] != 0) ||
			   (square == F1 && p.pieces[E2].isPawn() && p.pieces[E3] != 0) {
				score.midgame -= bishopBoxed.midgame
			}
		} else {
			if (square == C8 && p.pieces[D7].isPawn() && p.pieces[D6] != 0) ||
			   (square == F8 && p.pieces[E7].isPawn() && p.pieces[E6] != 0) {
				score.midgame -= bishopBoxed.midgame
			}
		}

		// Track if bishop attacks squares around enemy's king.
		if targets & p.kingAttacks(color^1) != 0 {
			e.attacks[color]++
			e.threats[color] += bonusKingThreat[Bishop/2]
		}

		// Bonus for bishop's board position.
		flip := Flip(color, square)
		score.midgame += bonusBishop[0][flip]
		score.endgame += bonusBishop[1][flip]

		// Extra bonus if bishop is in the center. Increase the extra
		// bonus if the bishop is supported by a pawn and can't be
		// exchanged.
		if extra := extraBishop[flip]; extra > 0 {
			if p.pawnAttacks(color).isSet(square) {
				if p.count[bishop(color^1)] == 0 {
					extra *= 2 // No bishops to exchange.
				}
				extra += extra / 2 // Supported by a pawn.
			}
			score.midgame += extra
			score.endgame += extra
		}
	}

	// Bonus for the pair of bishops.
	if bishops := p.count[bishop(color)]; bishops >= 2 {
		score.midgame += bishopPair.midgame
		score.endgame += bishopPair.endgame
	}
	return
}

func (e *Evaluator) rooks(color int, maskSafe Bitmask) (score Score) {
	p := e.position
	hisPawns := p.outposts[pawn(color)]
	herPawns := p.outposts[pawn(color^1)]
	outposts := p.outposts[rook(color)]

	// Bonus if rook is on 7th rank and enemy's king trapped on 8th.
	if count := (outposts & mask7th[color]).count(); count > 0 && p.outposts[king(color^1)] & mask8th[color] != 0 {
		score.midgame += count * rookOn7th.midgame
		score.endgame += count * rookOn7th.endgame
	}
	for outposts != 0 {
		square := outposts.pop()
		targets := p.xrayTargets(square)

		// Bonus for rook's mobility
		mobility := mobilityRook[(targets & maskSafe).count()]
		score.midgame += mobility.midgame
		score.endgame += mobility.endgame

		// Penalty if rook is attacked by enemy's pawn.
		if maskPawn[color^1][square] & p.outposts[pawn(color^1)] != 0 {
			score.midgame -= penaltyPawnThreat[Rook/2].midgame
			score.endgame -= penaltyPawnThreat[Rook/2].endgame
		}

		// Bonus if rook is attacking enemy's pawns.
		if count := (targets & p.outposts[pawn(color^1)]).count(); count > 0 {
			score.midgame += count * rookOnPawn.midgame
			score.endgame += count * rookOnPawn.endgame
		}

		// Bonuses if rook is on open or semi-open file.
		column := Col(square)
		if hisPawns & maskFile[column] == 0 {
			if herPawns & maskFile[column] == 0 {
				score.midgame += rookOnOpen.midgame
				score.endgame += rookOnOpen.endgame
			} else {
				score.midgame += rookOnSemiOpen.midgame
				score.endgame += rookOnSemiOpen.endgame
			}
		}

		// Middle game penalty if a rook is boxed. Extra penalty if castle
		// rights have been lost.
		if bit[square] & rookBoxA[color] != 0 && p.outposts[king(color)] & castleQueen[color] != 0 {
			score.midgame -= rookBoxed.midgame
			if p.castles & castleQueenside[color] == 0 {
				score.midgame -= rookBoxed.midgame
			}
		}
		if bit[square] & rookBoxH[color] != 0 && p.outposts[king(color)] & castleKing[color] != 0 {
			score.midgame -= rookBoxed.midgame
			if p.castles & castleKingside[color] == 0 {
				score.midgame -= rookBoxed.midgame
			}
		}

		// Track if rook attacks squares around enemy's king.
		if targets & p.kingAttacks(color^1) != 0 {
			e.attacks[color]++
			e.threats[color] += bonusKingThreat[Rook/2]
		}

		// Bonus for rook's board position.
		flip := Flip(color, square)
		score.midgame += bonusRook[0][flip]
		score.endgame += bonusRook[1][flip]
	}
	return
}

func (e *Evaluator) queens(color int, maskSafe Bitmask) (score Score) {
	p := e.position
	outposts := p.outposts[queen(color)]

	// Bonus if queen is on 7th rank and enemy's king trapped on 8th.
	if count := (outposts & mask7th[color]).count(); count > 0 && p.outposts[king(color^1)] & mask8th[color] != 0 {
		score.midgame += count * queenOn7th.midgame
		score.endgame += count * queenOn7th.endgame
	}
	for outposts != 0 {
		square := outposts.pop()
		targets := p.targets(square)

		// Bonus for queen's mobility
		mobility := mobilityQueen[Min(15, (targets & maskSafe).count())]
		score.midgame += mobility.midgame
		score.endgame += mobility.endgame

		// Penalty if queen is attacked by enemy's pawn.
		if maskPawn[color^1][square] & p.outposts[pawn(color^1)] != 0 {
			score.midgame -= penaltyPawnThreat[Queen/2].midgame
			score.endgame -= penaltyPawnThreat[Queen/2].endgame
		}

		// Bonus if queen is out and attacking enemy's pawns.
		if count := (targets & p.outposts[pawn(color^1)]).count(); count > 0 && RelRow(color, square) > 3 {
			score.midgame += count * queenOnPawn.midgame
			score.endgame += count * queenOnPawn.endgame
		}

		// Track if queen attacks squares around enemy's king.
		if targets & p.kingAttacks(color^1) != 0 {
			e.attacks[color]++
			e.threats[color] += bonusKingThreat[Queen/2]
		}

		// Bonus for queen's board position.
		flip := Flip(color, square)
		score.midgame += bonusQueen[0][flip]
		score.endgame += bonusQueen[1][flip]
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"text/tabwriter"
	"time"

	konfig "koding/kites/config"
	"koding/kites/config/configstore"
	"koding/kites/kloud/utils/object"
	"koding/klient/storage"
	"koding/klientctl/config"
	cfg "koding/klientctl/endpoint/config"

	"github.com/codegangsta/cli"
	"github.com/koding/logging"
)

const (
	// KlientDirectory is full path to directory that holds klient.
	KlientDirectory = "/opt/kite/klient"

	// KlientctlDirectory is full path to directory that holds klientctl.
	KlientctlDirectory = "/usr/local/bin"

	// KlientctlBinName is the bin named that will be stored in the KlientctlDirectory.
	KlientctlBinName = "kd"

	// CommandAttempts is the number of attempts to try commands like start, stop
	// etc.
	CommandAttempts = 30

	// CommandWaitTime is how long to wait for commands like start, stop to
	// complete
	CommandWaitTime = 1 * time.Second
)

var (
	// ConfigFolder is folder where config and other related info are stored.
	ConfigFolder string
)

func init() {
	var err error
	if ConfigFolder, err = createFolderAtHome(".config", "koding"); err != nil {
		panic(err)
	}
}

func createFolderAtHome(cf ...string) (string, error) {
	args := []string{konfig.CurrentUser.HomeDir}
	args = append(args, cf...)

	folderName := filepath.Join(args...)
	if err := os.MkdirAll(folderName, 0755); err != nil {
		return "", err
	}

	return folderName, nil
}

var b = &object.Builder{
	Tag:           "json",
	Sep:           ".",
	Recursive:     true,
	FlatStringers: true,
}

func ConfigShow(c *cli.Context, log logging.Logger, _ string) (int, error) {
	used := config.Konfig

	if !c.Bool("defaults") {
		k, err := cfg.Used()
		if err != nil && err != storage.ErrKeyNotFound {
			return 1, err
		}
		if err == nil {
			used = k
		}
	}

	if c.Bool("json") {
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "\t")
		enc.Encode(used)

		return 0, nil
	}

	printKonfig(used)

	return 0, nil
}

func ConfigSet(c *cli.Context, log logging.Logger, _ string) (int, error) {
	if len(c.Args()) != 2 {
		cli.ShowCommandHelp(c, "set")
		return 1, nil
	}

	if err := configstore.Set(c.Args().Get(0), c.Args().Get(1)); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1, err
	}

	return 0, nil
}

func ConfigUnset(c *cli.Context, log logging.Logger, _ string) (int, error) {
	if len(c.Args()) != 1 {
		cli.ShowCommandHelp(c, "unset")
		return 1, nil
	}

	if err := configstore.Set(c.Args().Get(0), ""); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1, err
	}

	return 0, nil
}

func ConfigList(c *cli.Context, log logging.Logger, _ string) (int, error) {
	konfigs := configstore.List()

	if c.Bool("json") {
		p, err := json.MarshalIndent(konfigs, "", "\t")
		if err != nil {
			return 1, err
		}

		fmt.Printf("%s\n", p)

		return 0, nil
	}

	printKonfigs(konfigs.Slice())

	return 0, nil
}

func ConfigUse(c *cli.Context, log logging.Logger, _ string) (int, error) {
	if len(c.Args()) != 1 {
		cli.ShowCommandHelp(c, "use")
		return 1, nil
	}

	arg := c.Args().Get(0)

	k, ok := configstore.List()[arg]
	if !ok {
		fmt.Fprintf(os.Stderr, "Configuration %q was not found. Please use \"kd config list"+
			"\" to list available configurations.\n", arg)
		return 1, nil
	}

	if err := configstore.Use(k); err != nil {
		fmt.Fprintln(os.Stderr, "Error switching configuration:", err)
		return 1, err
	}

	fmt.Printf("Switched to %s.\n\nPlease run \"sudo kd restart\" for the new configuration to take effect.\n", k.KodingPublic())

	return 0, nil
}

func ConfigReset(c *cli.Context, log logging.Logger, _ string) (int, error) {
	if err := cfg.Reset(); err != nil {
		fmt.Fprintln(os.Stderr, "Error resetting configuration:", err)
		return 1, err
	}

	fmt.Printf("Reset %s.\n\nPlease run \"sudo kd restart\" for the new configuration to take effect.\n", config.Konfig.KodingPublic())

	return 0, nil
}

func printKonfigs(konfigs []*konfig.Konfig) {
	w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
	defer w.Flush()

	fmt.Fprintln(w, "ID\tKODING URL")

	for _, konfig := range konfigs {
		fmt.Fprintf(w, "%s\t%s\n", konfig.ID(), konfig.KodingPublic())
	}
}

func printKonfig(konfig *konfig.Konfig) {
	w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
	defer w.Flush()

	fmt.Fprintln(w, "KEY\tVALUE")

	obj := b.Build(konfig, "kiteKey", "kontrolURL", "tunnelURL")

	for _, key := range obj.Keys() {
		value := obj[key]
		if value == nil || fmt.Sprintf("%v", value) == "" {
			value = "-"
		}
		fmt.Fprintf(w, "%s\t%v\n", key, value)
	}
}
<commit_msg>kd: extend ignored field list for "config show" command<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"text/tabwriter"
	"time"

	konfig "koding/kites/config"
	"koding/kites/config/configstore"
	"koding/kites/kloud/utils/object"
	"koding/klient/storage"
	"koding/klientctl/config"
	cfg "koding/klientctl/endpoint/config"

	"github.com/codegangsta/cli"
	"github.com/koding/logging"
)

const (
	// KlientDirectory is full path to directory that holds klient.
	KlientDirectory = "/opt/kite/klient"

	// KlientctlDirectory is full path to directory that holds klientctl.
	KlientctlDirectory = "/usr/local/bin"

	// KlientctlBinName is the bin named that will be stored in the KlientctlDirectory.
	KlientctlBinName = "kd"

	// CommandAttempts is the number of attempts to try commands like start, stop
	// etc.
	CommandAttempts = 30

	// CommandWaitTime is how long to wait for commands like start, stop to
	// complete
	CommandWaitTime = 1 * time.Second
)

var (
	// ConfigFolder is folder where config and other related info are stored.
	ConfigFolder string
)

func init() {
	var err error
	if ConfigFolder, err = createFolderAtHome(".config", "koding"); err != nil {
		panic(err)
	}
}

func createFolderAtHome(cf ...string) (string, error) {
	args := []string{konfig.CurrentUser.HomeDir}
	args = append(args, cf...)

	folderName := filepath.Join(args...)
	if err := os.MkdirAll(folderName, 0755); err != nil {
		return "", err
	}

	return folderName, nil
}

var b = &object.Builder{
	Tag:           "json",
	Sep:           ".",
	Recursive:     true,
	FlatStringers: true,
}

func ConfigShow(c *cli.Context, log logging.Logger, _ string) (int, error) {
	used := config.Konfig

	if !c.Bool("defaults") {
		k, err := cfg.Used()
		if err != nil && err != storage.ErrKeyNotFound {
			return 1, err
		}
		if err == nil {
			used = k
		}
	}

	if c.Bool("json") {
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "\t")
		enc.Encode(used)

		return 0, nil
	}

	printKonfig(used)

	return 0, nil
}

func ConfigSet(c *cli.Context, log logging.Logger, _ string) (int, error) {
	if len(c.Args()) != 2 {
		cli.ShowCommandHelp(c, "set")
		return 1, nil
	}

	if err := configstore.Set(c.Args().Get(0), c.Args().Get(1)); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1, err
	}

	return 0, nil
}

func ConfigUnset(c *cli.Context, log logging.Logger, _ string) (int, error) {
	if len(c.Args()) != 1 {
		cli.ShowCommandHelp(c, "unset")
		return 1, nil
	}

	if err := configstore.Set(c.Args().Get(0), ""); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1, err
	}

	return 0, nil
}

func ConfigList(c *cli.Context, log logging.Logger, _ string) (int, error) {
	konfigs := configstore.List()

	if c.Bool("json") {
		p, err := json.MarshalIndent(konfigs, "", "\t")
		if err != nil {
			return 1, err
		}

		fmt.Printf("%s\n", p)

		return 0, nil
	}

	printKonfigs(konfigs.Slice())

	return 0, nil
}

func ConfigUse(c *cli.Context, log logging.Logger, _ string) (int, error) {
	if len(c.Args()) != 1 {
		cli.ShowCommandHelp(c, "use")
		return 1, nil
	}

	arg := c.Args().Get(0)

	k, ok := configstore.List()[arg]
	if !ok {
		fmt.Fprintf(os.Stderr, "Configuration %q was not found. Please use \"kd config list"+
			"\" to list available configurations.\n", arg)
		return 1, nil
	}

	if err := configstore.Use(k); err != nil {
		fmt.Fprintln(os.Stderr, "Error switching configuration:", err)
		return 1, err
	}

	fmt.Printf("Switched to %s.\n\nPlease run \"sudo kd restart\" for the new configuration to take effect.\n", k.KodingPublic())

	return 0, nil
}

func ConfigReset(c *cli.Context, log logging.Logger, _ string) (int, error) {
	if err := cfg.Reset(); err != nil {
		fmt.Fprintln(os.Stderr, "Error resetting configuration:", err)
		return 1, err
	}

	fmt.Printf("Reset %s.\n\nPlease run \"sudo kd restart\" for the new configuration to take effect.\n", config.Konfig.KodingPublic())

	return 0, nil
}

func printKonfigs(konfigs []*konfig.Konfig) {
	w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
	defer w.Flush()

	fmt.Fprintln(w, "ID\tKODING URL")

	for _, konfig := range konfigs {
		fmt.Fprintf(w, "%s\t%s\n", konfig.ID(), konfig.KodingPublic())
	}
}

// ignoredFields are not displayed on "kd config show"
// as they are either for internal purpose or are
// deprecated ones.
var ignoredFields = []string{
	"kiteKey",
	"kiteKeyFile",
	"kontrolURL", // deprecated
	"tunnelURL",  // deprecated
	"environment",
	"publicBucketName",
	"publicBucketRegion",
	"tunnelID",
}

func printKonfig(konfig *konfig.Konfig) {
	w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
	defer w.Flush()

	fmt.Fprintln(w, "KEY\tVALUE")

	obj := b.Build(konfig, ignoredFields...)

	for _, key := range obj.Keys() {
		value := obj[key]
		if value == nil || fmt.Sprintf("%v", value) == "" {
			value = "-"
		}
		fmt.Fprintf(w, "%s\t%v\n", key, value)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"compress/gzip"
	"fmt"
	"io"
	"koding/klientctl/config"
	"net/http"
	"os"
	"path/filepath"

	"github.com/codegangsta/cli"
	"github.com/koding/logging"
)

// UpdateCommand updates this binary if there's an update available.
func UpdateCommand(c *cli.Context, log logging.Logger, _ string) int {
	if len(c.Args()) != 0 {
		cli.ShowCommandHelp(c, "update")
		return 1
	}

	// Create and open the log file, to be safe in case it's missing.
	f, err := createLogFile(LogFilePath)
	if err != nil {
		fmt.Println(`Error: Unable to open log files.`)
	} else {
		log.SetHandler(logging.NewWriterHandler(f))
		log.Info("Update created log file at %q", LogFilePath)
	}

	checkUpdate := NewCheckUpdate()

	// by pass random checking to force checking for update
	checkUpdate.ForceCheck = true

	yesUpdate, err := checkUpdate.IsUpdateAvailable()
	if err != nil {
		log.Error("Error checking if update is available. err:%s", err)
		fmt.Println(FailedCheckingUpdateAvailable)
		return 1
	}

	if !yesUpdate {
		fmt.Println("No update available.")
		return 0
	}

	s, err := newService(nil)
	if err != nil {
		log.Error("Error creating Service. err:%s", err)
		fmt.Println(GenericInternalError)
		return 1
	}

	fmt.Printf("An update is available...\n")
	fmt.Printf("Stopping %s...\n", config.KlientName)

	// stop klient before we update it
	if err := s.Stop(); err != nil {
		log.Error("Error stopping Service. err:%s", err)
		fmt.Println(FailedStopKlient)
		return 1
	}

	klientVersion, err := latestVersion(config.S3KlientLatest)
	if err != nil {
		log.Error("Error checking if update is available. err: %s", err)
		fmt.Println(FailedCheckingUpdateAvailable)
		return 1
	}

	klientctlVersion, err := latestVersion(config.S3KlientctlLatest)
	if err != nil {
		log.Error("Error checking if update is available. err: %s", err)
		fmt.Println(FailedCheckingUpdateAvailable)
		return 1
	}

	// download klient and kd to approprite place
	dlPaths := map[string]string{
		// /opt/kite/klient/klient
		filepath.Join(KlientDirectory, "klient"): config.S3Klient(klientVersion),

		// /usr/local/bin/kd
		filepath.Join(KlientctlDirectory, "kd"): config.S3Klientctl(klientctlVersion),
	}

	fmt.Println("Updating...")

	for localPath, remotePath := range dlPaths {
		if err := downloadRemoteToLocal(remotePath, localPath); err != nil {
			log.Error("Error updating. err:%s", err)
			fmt.Println(FailedDownloadUpdate)
			return 1
		}
	}

	// start klient now that it's done updating
	if err := s.Start(); err != nil {
		log.Error("Error starting Service. err:%s", err)
		fmt.Println(FailedStartKlient)
		return 1
	}

	fmt.Printf("Successfully updated to latest version of %s.\n", config.Name)
	return 0
}

func downloadRemoteToLocal(remotePath, destPath string) error {
	// create the destination dir, if needed.
	if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
		return err
	}

	// open file in specified path to write to
	perms := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
	binFile, err := os.OpenFile(destPath, perms, 0755)
	if err != nil {
		if binFile != nil {
			binFile.Close()
		}

		return nil
	}

	// get from remote
	res, err := http.Get(remotePath)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	var body io.Reader = res.Body
	if r, err := gzip.NewReader(res.Body); err == nil {
		// If body contains gzip header, it means the payload was compressed.
		// Relying solely on Content-Type == "application/gzip" check
		// was not reliable.
		body = r
	}

	// copy remote file to destination path
	if _, err := io.Copy(binFile, body); err != nil {
		return err
	}

	return binFile.Close()
}
<commit_msg>klientctl: fix for TestDownload test<commit_after>package main

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"koding/klientctl/config"
	"net/http"
	"os"
	"path/filepath"

	"github.com/codegangsta/cli"
	"github.com/koding/logging"
)

// UpdateCommand updates this binary if there's an update available.
func UpdateCommand(c *cli.Context, log logging.Logger, _ string) int {
	if len(c.Args()) != 0 {
		cli.ShowCommandHelp(c, "update")
		return 1
	}

	// Create and open the log file, to be safe in case it's missing.
	f, err := createLogFile(LogFilePath)
	if err != nil {
		fmt.Println(`Error: Unable to open log files.`)
	} else {
		log.SetHandler(logging.NewWriterHandler(f))
		log.Info("Update created log file at %q", LogFilePath)
	}

	checkUpdate := NewCheckUpdate()

	// by pass random checking to force checking for update
	checkUpdate.ForceCheck = true

	yesUpdate, err := checkUpdate.IsUpdateAvailable()
	if err != nil {
		log.Error("Error checking if update is available. err:%s", err)
		fmt.Println(FailedCheckingUpdateAvailable)
		return 1
	}

	if !yesUpdate {
		fmt.Println("No update available.")
		return 0
	}

	s, err := newService(nil)
	if err != nil {
		log.Error("Error creating Service. err:%s", err)
		fmt.Println(GenericInternalError)
		return 1
	}

	fmt.Printf("An update is available...\n")
	fmt.Printf("Stopping %s...\n", config.KlientName)

	// stop klient before we update it
	if err := s.Stop(); err != nil {
		log.Error("Error stopping Service. err:%s", err)
		fmt.Println(FailedStopKlient)
		return 1
	}

	klientVersion, err := latestVersion(config.S3KlientLatest)
	if err != nil {
		log.Error("Error checking if update is available. err: %s", err)
		fmt.Println(FailedCheckingUpdateAvailable)
		return 1
	}

	klientctlVersion, err := latestVersion(config.S3KlientctlLatest)
	if err != nil {
		log.Error("Error checking if update is available. err: %s", err)
		fmt.Println(FailedCheckingUpdateAvailable)
		return 1
	}

	// download klient and kd to approprite place
	dlPaths := map[string]string{
		// /opt/kite/klient/klient
		filepath.Join(KlientDirectory, "klient"): config.S3Klient(klientVersion),

		// /usr/local/bin/kd
		filepath.Join(KlientctlDirectory, "kd"): config.S3Klientctl(klientctlVersion),
	}

	fmt.Println("Updating...")

	for localPath, remotePath := range dlPaths {
		if err := downloadRemoteToLocal(remotePath, localPath); err != nil {
			log.Error("Error updating. err:%s", err)
			fmt.Println(FailedDownloadUpdate)
			return 1
		}
	}

	// start klient now that it's done updating
	if err := s.Start(); err != nil {
		log.Error("Error starting Service. err:%s", err)
		fmt.Println(FailedStartKlient)
		return 1
	}

	fmt.Printf("Successfully updated to latest version of %s.\n", config.Name)
	return 0
}

func downloadRemoteToLocal(remotePath, destPath string) error {
	// create the destination dir, if needed.
	if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
		return err
	}

	// open file in specified path to write to
	perms := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
	binFile, err := os.OpenFile(destPath, perms, 0755)
	if err != nil {
		if binFile != nil {
			binFile.Close()
		}

		return nil
	}

	// get from remote
	res, err := http.Get(remotePath)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	var buf bytes.Buffer // to restore begining of a response body consumed by gzip.NewReader
	var body io.Reader = io.MultiReader(&buf, res.Body)

	// If body contains gzip header, it means the payload was compressed.
	// Relying solely on Content-Type == "application/gzip" check
	// was not reliable.
	if r, err := gzip.NewReader(io.TeeReader(res.Body, &buf)); err == nil {
		if r, err = gzip.NewReader(body); err == nil {
			body = r
		}
	}

	// copy remote file to destination path
	if _, err := io.Copy(binFile, body); err != nil {
		return err
	}

	return binFile.Close()
}
<|endoftext|>


<commit_before><commit_msg>More things<commit_after><|endoftext|>


<commit_before>/*
	Original copyright by GitHub as follows. Additions by the Vitess authors as follows.
*/
/*
   Copyright 2016 GitHub Inc.
	 See https://github.com/github/gh-ost/blob/master/LICENSE
*/
/*
Copyright 2021 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package onlineddl

import (
	"context"
	"fmt"
	"math"
	"strconv"
	"strings"

	"vitess.io/vitess/go/sqltypes"
	"vitess.io/vitess/go/vt/binlog/binlogplayer"
	"vitess.io/vitess/go/vt/dbconnpool"
	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
	"vitess.io/vitess/go/vt/sqlparser"
	"vitess.io/vitess/go/vt/vttablet/onlineddl/vrepl"
	"vitess.io/vitess/go/vt/vttablet/tabletmanager/vreplication"
)

// VReplStream represents a row in _vt.vreplication table
type VReplStream struct {
	id                   int64
	workflow             string
	source               string
	pos                  string
	timeUpdated          int64
	transactionTimestamp int64
	state                string
	message              string
	bls                  *binlogdatapb.BinlogSource
}

// VRepl is an online DDL helper for VReplication based migrations (ddl_strategy="online")
type VRepl struct {
	workflow    string
	keyspace    string
	shard       string
	dbName      string
	sourceTable string
	targetTable string

	sharedPKColumns *vrepl.ColumnList

	sourceSharedColumns *vrepl.ColumnList
	targetSharedColumns *vrepl.ColumnList
	sharedColumnsMap    map[string]string

	filterQuery string
	bls         *binlogdatapb.BinlogSource

	parser *vrepl.AlterTableParser
}

// NewVRepl creates a VReplication handler for Online DDL
func NewVRepl(workflow, keyspace, shard, dbName, sourceTable, targetTable string) *VRepl {
	return &VRepl{
		workflow:    workflow,
		keyspace:    keyspace,
		shard:       shard,
		dbName:      dbName,
		sourceTable: sourceTable,
		targetTable: targetTable,
		parser:      vrepl.NewAlterTableParser(),
	}
}

// getCandidateUniqueKeys investigates a table and returns the list of unique keys
// candidate for chunking
func (v *VRepl) getCandidateUniqueKeys(ctx context.Context, conn *dbconnpool.DBConnection, tableName string) (uniqueKeys [](*vrepl.UniqueKey), err error) {

	query, err := sqlparser.ParseAndBind(sqlShowColumnsFrom,
		sqltypes.StringBindVariable(v.dbName),
		sqltypes.StringBindVariable(tableName),
		sqltypes.StringBindVariable(v.dbName),
		sqltypes.StringBindVariable(tableName),
	)
	if err != nil {
		return uniqueKeys, err
	}

	rs, err := conn.ExecuteFetch(query, math.MaxInt64, true)
	if err != nil {
		return nil, err
	}
	for _, row := range rs.Named().Rows {
		uniqueKey := &vrepl.UniqueKey{
			Name:            row.AsString("INDEX_NAME", ""),
			Columns:         *vrepl.ParseColumnList(row.AsString("COLUMN_NAMES", "")),
			HasNullable:     row.AsBool("has_nullable", false),
			IsAutoIncrement: row.AsBool("is_auto_increment", false),
		}
		uniqueKeys = append(uniqueKeys, uniqueKey)
	}
	return uniqueKeys, nil
}

// readTableColumns reads column list from given table
func (v *VRepl) readTableColumns(ctx context.Context, conn *dbconnpool.DBConnection, tableName string) (columns *vrepl.ColumnList, virtualColumns *vrepl.ColumnList, pkColumns *vrepl.ColumnList, err error) {
	parsed := sqlparser.BuildParsedQuery(sqlShowColumnsFrom, tableName)
	rs, err := conn.ExecuteFetch(parsed.Query, math.MaxInt64, true)
	if err != nil {
		return nil, nil, nil, err
	}
	columnNames := []string{}
	virtualColumnNames := []string{}
	pkColumnNames := []string{}
	for _, row := range rs.Named().Rows {
		columnName := row.AsString("Field", "")
		columnNames = append(columnNames, columnName)

		extra := row.AsString("Extra", "")
		if strings.Contains(extra, " GENERATED") {
			virtualColumnNames = append(virtualColumnNames, columnName)
		}

		key := row.AsString("Key", "")
		if key == "PRI" {
			pkColumnNames = append(pkColumnNames, columnName)
		}
	}
	if len(columnNames) == 0 {
		return nil, nil, nil, fmt.Errorf("Found 0 columns on `%s`", tableName)
	}
	return vrepl.NewColumnList(columnNames), vrepl.NewColumnList(virtualColumnNames), vrepl.NewColumnList(pkColumnNames), nil
}

// getSharedColumns returns the intersection of two lists of columns in same order as the first list
func (v *VRepl) getSharedColumns(sourceColumns, targetColumns *vrepl.ColumnList, sourceVirtualColumns, targetVirtualColumns *vrepl.ColumnList, columnRenameMap map[string]string) (
	sourceSharedColumns *vrepl.ColumnList, targetSharedColumns *vrepl.ColumnList, sharedColumnsMap map[string]string,
) {
	sharedColumnNames := []string{}
	for _, sourceColumn := range sourceColumns.Names() {
		isSharedColumn := false
		for _, targetColumn := range targetColumns.Names() {
			if strings.EqualFold(sourceColumn, targetColumn) {
				// both tables have this column. Good start.
				isSharedColumn = true
				break
			}
			if strings.EqualFold(columnRenameMap[sourceColumn], targetColumn) {
				// column in source is renamed in target
				isSharedColumn = true
				break
			}
		}
		for _, virtualColumn := range sourceVirtualColumns.Names() {
			// virtual/generated columns on source are silently skipped
			if strings.EqualFold(sourceColumn, virtualColumn) {
				isSharedColumn = false
			}
		}
		for _, virtualColumn := range targetVirtualColumns.Names() {
			// virtual/generated columns on target are silently skipped
			if strings.EqualFold(sourceColumn, virtualColumn) {
				isSharedColumn = false
			}
		}
		if isSharedColumn {
			sharedColumnNames = append(sharedColumnNames, sourceColumn)
		}
	}
	sharedColumnsMap = map[string]string{}
	for _, columnName := range sharedColumnNames {
		if mapped, ok := columnRenameMap[columnName]; ok {
			sharedColumnsMap[columnName] = mapped
		} else {
			sharedColumnsMap[columnName] = columnName
		}
	}
	mappedSharedColumnNames := []string{}
	for _, columnName := range sharedColumnNames {
		mappedSharedColumnNames = append(mappedSharedColumnNames, sharedColumnsMap[columnName])
	}
	return vrepl.NewColumnList(sharedColumnNames), vrepl.NewColumnList(mappedSharedColumnNames), sharedColumnsMap
}

// getSharedPKColumns returns the intersection of PRIMARY KEY columns (taking renaming into consideration) etween source and target tables
func (v *VRepl) getSharedPKColumns(sourcePKColumns, targetPKColumns *vrepl.ColumnList, columnRenameMap map[string]string) (
	sharedPKColumns *vrepl.ColumnList,
) {
	sharedColumnNames := []string{}
	for _, sourceColumn := range sourcePKColumns.Names() {
		isSharedColumn := false
		for _, targetColumn := range targetPKColumns.Names() {
			if strings.EqualFold(sourceColumn, targetColumn) {
				// both tables have this column. Good start.
				isSharedColumn = true
				break
			}
			if strings.EqualFold(columnRenameMap[sourceColumn], targetColumn) {
				// column in source is renamed in target
				isSharedColumn = true
				break
			}
		}
		if isSharedColumn {
			sharedColumnNames = append(sharedColumnNames, sourceColumn)
		}
	}
	return vrepl.NewColumnList(sharedColumnNames)
}

// getSharedUniqueKeys returns the intersection of two given unique keys,
// testing by list of columns
func (v *VRepl) getSharedUniqueKeys(sourceUniqueKeys, targetUniqueKeys [](*vrepl.UniqueKey)) (uniqueKeys [](*vrepl.UniqueKey), err error) {
	// We actually do NOT rely on key name, just on the set of columns. This is because maybe
	// the ALTER is on the name itself...
	for _, sourceUniqueKey := range sourceUniqueKeys {
		for _, targetUniqueKey := range targetUniqueKeys {
			if sourceUniqueKey.Columns.EqualsByNames(&targetUniqueKey.Columns) {
				uniqueKeys = append(uniqueKeys, sourceUniqueKey)
			}
		}
	}
	return uniqueKeys, nil
}

func (v *VRepl) analyzeAlter(ctx context.Context, alterOptions string) error {
	if err := v.parser.ParseAlterStatement(alterOptions); err != nil {
		return err
	}
	if v.parser.IsRenameTable() {
		return fmt.Errorf("Renaming the table is not aupported in ALTER TABLE: %s", alterOptions)
	}
	return nil
}

func (v *VRepl) analyzeTables(ctx context.Context, conn *dbconnpool.DBConnection) error {
	// columns:
	sourceColumns, sourceVirtualColumns, sourcePKColumns, err := v.readTableColumns(ctx, conn, v.sourceTable)
	if err != nil {
		return err
	}
	targetColumns, targetVirtualColumns, targetPKColumns, err := v.readTableColumns(ctx, conn, v.targetTable)
	if err != nil {
		return err
	}
	v.sourceSharedColumns, v.targetSharedColumns, v.sharedColumnsMap = v.getSharedColumns(sourceColumns, targetColumns, sourceVirtualColumns, targetVirtualColumns, v.parser.ColumnRenameMap())

	v.sharedPKColumns = v.getSharedPKColumns(sourcePKColumns, targetPKColumns, v.parser.ColumnRenameMap())
	if v.sharedPKColumns.Len() == 0 {
		// TODO(shlomi): need to carefully examine what happens when we extend/reduce a PRIMARY KEY
		// is a column subset OK?
		return fmt.Errorf("Found no shared PRIMARY KEY columns between `%s` and `%s`", v.sourceTable, v.targetTable)
	}

	return nil
}

// generateFilterQuery creates a SELECT query used by vreplication as a filter. It SELECTs all
// non-generated columns between source & target tables, and takes care of column renames.
func (v *VRepl) generateFilterQuery(ctx context.Context) error {
	if v.sourceSharedColumns.Len() == 0 {
		return fmt.Errorf("Empty column list")
	}
	var sb strings.Builder
	sb.WriteString("select ")
	for i, name := range v.sourceSharedColumns.Names() {
		targetName := v.sharedColumnsMap[name]

		if i > 0 {
			sb.WriteString(", ")
		}
		sb.WriteString(escapeName(name))
		sb.WriteString(" as ")
		sb.WriteString(escapeName(targetName))
	}
	sb.WriteString(" from ")
	sb.WriteString(escapeName(v.sourceTable))

	v.filterQuery = sb.String()
	return nil
}

func (v *VRepl) analyzeBinlogSource(ctx context.Context) {
	bls := &binlogdatapb.BinlogSource{
		Keyspace:      v.keyspace,
		Shard:         v.shard,
		Filter:        &binlogdatapb.Filter{},
		StopAfterCopy: false,
	}
	rule := &binlogdatapb.Rule{
		Match:  v.targetTable,
		Filter: v.filterQuery,
	}
	bls.Filter.Rules = append(bls.Filter.Rules, rule)
	v.bls = bls
}

func (v *VRepl) analyze(ctx context.Context, conn *dbconnpool.DBConnection, alterOptions string) error {
	if err := v.analyzeAlter(ctx, alterOptions); err != nil {
		return err
	}
	if err := v.analyzeTables(ctx, conn); err != nil {
		return err
	}
	if err := v.generateFilterQuery(ctx); err != nil {
		return err
	}
	v.analyzeBinlogSource(ctx)
	return nil
}

// generateInsertStatement generates the INSERT INTO _vt.replication stataement that creates the vreplication workflow
func (v *VRepl) generateInsertStatement(ctx context.Context) (string, error) {
	ig := vreplication.NewInsertGenerator(binlogplayer.BlpStopped, v.dbName)
	ig.AddRow(v.workflow, v.bls, "", "", "MASTER")

	return ig.String(), nil
}

// generateStartStatement Generates the statement to start VReplication running on the workflow
func (v *VRepl) generateStartStatement(ctx context.Context) (string, error) {
	return sqlparser.ParseAndBind(sqlStartVReplStream,
		sqltypes.StringBindVariable(v.dbName),
		sqltypes.StringBindVariable(v.workflow),
	)
}

// escapeName will escape a db/table/column/... name by wrapping with backticks.
// It is not fool proof. I'm just trying to do the right thing here, not solving
// SQL injection issues, which should be irrelevant for this tool.
func escapeName(name string) string {
	if unquoted, err := strconv.Unquote(name); err == nil {
		name = unquoted
	}
	return fmt.Sprintf("`%s`", name)
}
<commit_msg>typo<commit_after>/*
	Original copyright by GitHub as follows. Additions by the Vitess authors as follows.
*/
/*
   Copyright 2016 GitHub Inc.
	 See https://github.com/github/gh-ost/blob/master/LICENSE
*/
/*
Copyright 2021 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package onlineddl

import (
	"context"
	"fmt"
	"math"
	"strconv"
	"strings"

	"vitess.io/vitess/go/sqltypes"
	"vitess.io/vitess/go/vt/binlog/binlogplayer"
	"vitess.io/vitess/go/vt/dbconnpool"
	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
	"vitess.io/vitess/go/vt/sqlparser"
	"vitess.io/vitess/go/vt/vttablet/onlineddl/vrepl"
	"vitess.io/vitess/go/vt/vttablet/tabletmanager/vreplication"
)

// VReplStream represents a row in _vt.vreplication table
type VReplStream struct {
	id                   int64
	workflow             string
	source               string
	pos                  string
	timeUpdated          int64
	transactionTimestamp int64
	state                string
	message              string
	bls                  *binlogdatapb.BinlogSource
}

// VRepl is an online DDL helper for VReplication based migrations (ddl_strategy="online")
type VRepl struct {
	workflow    string
	keyspace    string
	shard       string
	dbName      string
	sourceTable string
	targetTable string

	sharedPKColumns *vrepl.ColumnList

	sourceSharedColumns *vrepl.ColumnList
	targetSharedColumns *vrepl.ColumnList
	sharedColumnsMap    map[string]string

	filterQuery string
	bls         *binlogdatapb.BinlogSource

	parser *vrepl.AlterTableParser
}

// NewVRepl creates a VReplication handler for Online DDL
func NewVRepl(workflow, keyspace, shard, dbName, sourceTable, targetTable string) *VRepl {
	return &VRepl{
		workflow:    workflow,
		keyspace:    keyspace,
		shard:       shard,
		dbName:      dbName,
		sourceTable: sourceTable,
		targetTable: targetTable,
		parser:      vrepl.NewAlterTableParser(),
	}
}

// getCandidateUniqueKeys investigates a table and returns the list of unique keys
// candidate for chunking
func (v *VRepl) getCandidateUniqueKeys(ctx context.Context, conn *dbconnpool.DBConnection, tableName string) (uniqueKeys [](*vrepl.UniqueKey), err error) {

	query, err := sqlparser.ParseAndBind(sqlShowColumnsFrom,
		sqltypes.StringBindVariable(v.dbName),
		sqltypes.StringBindVariable(tableName),
		sqltypes.StringBindVariable(v.dbName),
		sqltypes.StringBindVariable(tableName),
	)
	if err != nil {
		return uniqueKeys, err
	}

	rs, err := conn.ExecuteFetch(query, math.MaxInt64, true)
	if err != nil {
		return nil, err
	}
	for _, row := range rs.Named().Rows {
		uniqueKey := &vrepl.UniqueKey{
			Name:            row.AsString("INDEX_NAME", ""),
			Columns:         *vrepl.ParseColumnList(row.AsString("COLUMN_NAMES", "")),
			HasNullable:     row.AsBool("has_nullable", false),
			IsAutoIncrement: row.AsBool("is_auto_increment", false),
		}
		uniqueKeys = append(uniqueKeys, uniqueKey)
	}
	return uniqueKeys, nil
}

// readTableColumns reads column list from given table
func (v *VRepl) readTableColumns(ctx context.Context, conn *dbconnpool.DBConnection, tableName string) (columns *vrepl.ColumnList, virtualColumns *vrepl.ColumnList, pkColumns *vrepl.ColumnList, err error) {
	parsed := sqlparser.BuildParsedQuery(sqlShowColumnsFrom, tableName)
	rs, err := conn.ExecuteFetch(parsed.Query, math.MaxInt64, true)
	if err != nil {
		return nil, nil, nil, err
	}
	columnNames := []string{}
	virtualColumnNames := []string{}
	pkColumnNames := []string{}
	for _, row := range rs.Named().Rows {
		columnName := row.AsString("Field", "")
		columnNames = append(columnNames, columnName)

		extra := row.AsString("Extra", "")
		if strings.Contains(extra, " GENERATED") {
			virtualColumnNames = append(virtualColumnNames, columnName)
		}

		key := row.AsString("Key", "")
		if key == "PRI" {
			pkColumnNames = append(pkColumnNames, columnName)
		}
	}
	if len(columnNames) == 0 {
		return nil, nil, nil, fmt.Errorf("Found 0 columns on `%s`", tableName)
	}
	return vrepl.NewColumnList(columnNames), vrepl.NewColumnList(virtualColumnNames), vrepl.NewColumnList(pkColumnNames), nil
}

// getSharedColumns returns the intersection of two lists of columns in same order as the first list
func (v *VRepl) getSharedColumns(sourceColumns, targetColumns *vrepl.ColumnList, sourceVirtualColumns, targetVirtualColumns *vrepl.ColumnList, columnRenameMap map[string]string) (
	sourceSharedColumns *vrepl.ColumnList, targetSharedColumns *vrepl.ColumnList, sharedColumnsMap map[string]string,
) {
	sharedColumnNames := []string{}
	for _, sourceColumn := range sourceColumns.Names() {
		isSharedColumn := false
		for _, targetColumn := range targetColumns.Names() {
			if strings.EqualFold(sourceColumn, targetColumn) {
				// both tables have this column. Good start.
				isSharedColumn = true
				break
			}
			if strings.EqualFold(columnRenameMap[sourceColumn], targetColumn) {
				// column in source is renamed in target
				isSharedColumn = true
				break
			}
		}
		for _, virtualColumn := range sourceVirtualColumns.Names() {
			// virtual/generated columns on source are silently skipped
			if strings.EqualFold(sourceColumn, virtualColumn) {
				isSharedColumn = false
			}
		}
		for _, virtualColumn := range targetVirtualColumns.Names() {
			// virtual/generated columns on target are silently skipped
			if strings.EqualFold(sourceColumn, virtualColumn) {
				isSharedColumn = false
			}
		}
		if isSharedColumn {
			sharedColumnNames = append(sharedColumnNames, sourceColumn)
		}
	}
	sharedColumnsMap = map[string]string{}
	for _, columnName := range sharedColumnNames {
		if mapped, ok := columnRenameMap[columnName]; ok {
			sharedColumnsMap[columnName] = mapped
		} else {
			sharedColumnsMap[columnName] = columnName
		}
	}
	mappedSharedColumnNames := []string{}
	for _, columnName := range sharedColumnNames {
		mappedSharedColumnNames = append(mappedSharedColumnNames, sharedColumnsMap[columnName])
	}
	return vrepl.NewColumnList(sharedColumnNames), vrepl.NewColumnList(mappedSharedColumnNames), sharedColumnsMap
}

// getSharedPKColumns returns the intersection of PRIMARY KEY columns (taking renaming into consideration) between source and target tables
func (v *VRepl) getSharedPKColumns(sourcePKColumns, targetPKColumns *vrepl.ColumnList, columnRenameMap map[string]string) (
	sharedPKColumns *vrepl.ColumnList,
) {
	sharedColumnNames := []string{}
	for _, sourceColumn := range sourcePKColumns.Names() {
		isSharedColumn := false
		for _, targetColumn := range targetPKColumns.Names() {
			if strings.EqualFold(sourceColumn, targetColumn) {
				// both tables have this column. Good start.
				isSharedColumn = true
				break
			}
			if strings.EqualFold(columnRenameMap[sourceColumn], targetColumn) {
				// column in source is renamed in target
				isSharedColumn = true
				break
			}
		}
		if isSharedColumn {
			sharedColumnNames = append(sharedColumnNames, sourceColumn)
		}
	}
	return vrepl.NewColumnList(sharedColumnNames)
}

// getSharedUniqueKeys returns the intersection of two given unique keys,
// testing by list of columns
func (v *VRepl) getSharedUniqueKeys(sourceUniqueKeys, targetUniqueKeys [](*vrepl.UniqueKey)) (uniqueKeys [](*vrepl.UniqueKey), err error) {
	// We actually do NOT rely on key name, just on the set of columns. This is because maybe
	// the ALTER is on the name itself...
	for _, sourceUniqueKey := range sourceUniqueKeys {
		for _, targetUniqueKey := range targetUniqueKeys {
			if sourceUniqueKey.Columns.EqualsByNames(&targetUniqueKey.Columns) {
				uniqueKeys = append(uniqueKeys, sourceUniqueKey)
			}
		}
	}
	return uniqueKeys, nil
}

func (v *VRepl) analyzeAlter(ctx context.Context, alterOptions string) error {
	if err := v.parser.ParseAlterStatement(alterOptions); err != nil {
		return err
	}
	if v.parser.IsRenameTable() {
		return fmt.Errorf("Renaming the table is not aupported in ALTER TABLE: %s", alterOptions)
	}
	return nil
}

func (v *VRepl) analyzeTables(ctx context.Context, conn *dbconnpool.DBConnection) error {
	// columns:
	sourceColumns, sourceVirtualColumns, sourcePKColumns, err := v.readTableColumns(ctx, conn, v.sourceTable)
	if err != nil {
		return err
	}
	targetColumns, targetVirtualColumns, targetPKColumns, err := v.readTableColumns(ctx, conn, v.targetTable)
	if err != nil {
		return err
	}
	v.sourceSharedColumns, v.targetSharedColumns, v.sharedColumnsMap = v.getSharedColumns(sourceColumns, targetColumns, sourceVirtualColumns, targetVirtualColumns, v.parser.ColumnRenameMap())

	v.sharedPKColumns = v.getSharedPKColumns(sourcePKColumns, targetPKColumns, v.parser.ColumnRenameMap())
	if v.sharedPKColumns.Len() == 0 {
		// TODO(shlomi): need to carefully examine what happens when we extend/reduce a PRIMARY KEY
		// is a column subset OK?
		return fmt.Errorf("Found no shared PRIMARY KEY columns between `%s` and `%s`", v.sourceTable, v.targetTable)
	}

	return nil
}

// generateFilterQuery creates a SELECT query used by vreplication as a filter. It SELECTs all
// non-generated columns between source & target tables, and takes care of column renames.
func (v *VRepl) generateFilterQuery(ctx context.Context) error {
	if v.sourceSharedColumns.Len() == 0 {
		return fmt.Errorf("Empty column list")
	}
	var sb strings.Builder
	sb.WriteString("select ")
	for i, name := range v.sourceSharedColumns.Names() {
		targetName := v.sharedColumnsMap[name]

		if i > 0 {
			sb.WriteString(", ")
		}
		sb.WriteString(escapeName(name))
		sb.WriteString(" as ")
		sb.WriteString(escapeName(targetName))
	}
	sb.WriteString(" from ")
	sb.WriteString(escapeName(v.sourceTable))

	v.filterQuery = sb.String()
	return nil
}

func (v *VRepl) analyzeBinlogSource(ctx context.Context) {
	bls := &binlogdatapb.BinlogSource{
		Keyspace:      v.keyspace,
		Shard:         v.shard,
		Filter:        &binlogdatapb.Filter{},
		StopAfterCopy: false,
	}
	rule := &binlogdatapb.Rule{
		Match:  v.targetTable,
		Filter: v.filterQuery,
	}
	bls.Filter.Rules = append(bls.Filter.Rules, rule)
	v.bls = bls
}

func (v *VRepl) analyze(ctx context.Context, conn *dbconnpool.DBConnection, alterOptions string) error {
	if err := v.analyzeAlter(ctx, alterOptions); err != nil {
		return err
	}
	if err := v.analyzeTables(ctx, conn); err != nil {
		return err
	}
	if err := v.generateFilterQuery(ctx); err != nil {
		return err
	}
	v.analyzeBinlogSource(ctx)
	return nil
}

// generateInsertStatement generates the INSERT INTO _vt.replication stataement that creates the vreplication workflow
func (v *VRepl) generateInsertStatement(ctx context.Context) (string, error) {
	ig := vreplication.NewInsertGenerator(binlogplayer.BlpStopped, v.dbName)
	ig.AddRow(v.workflow, v.bls, "", "", "MASTER")

	return ig.String(), nil
}

// generateStartStatement Generates the statement to start VReplication running on the workflow
func (v *VRepl) generateStartStatement(ctx context.Context) (string, error) {
	return sqlparser.ParseAndBind(sqlStartVReplStream,
		sqltypes.StringBindVariable(v.dbName),
		sqltypes.StringBindVariable(v.workflow),
	)
}

// escapeName will escape a db/table/column/... name by wrapping with backticks.
// It is not fool proof. I'm just trying to do the right thing here, not solving
// SQL injection issues, which should be irrelevant for this tool.
func escapeName(name string) string {
	if unquoted, err := strconv.Unquote(name); err == nil {
		name = unquoted
	}
	return fmt.Sprintf("`%s`", name)
}
<|endoftext|>


<commit_before>package eval

import (
	"fmt"
	"monkey/ast"
)

var (
	TRUE  = &Boolean{Value: true}
	FALSE = &Boolean{Value: false}
	NULL  = &Null{}
)

func Eval(node ast.Node, scope *Scope) Object {
	switch node := node.(type) {
	case *ast.Program:
		return evalProgram(node, scope)

	case *ast.ExpressionStatement:
		return Eval(node.Expression, scope)

	case *ast.LetStatement:
		val := Eval(node.Value, scope)
		if val.Type() == ERROR_OBJ {
			return val
		}
		return scope.Set(node.Name.String(), val)

	case *ast.ReturnStatement:
		value := Eval(node.ReturnValue, scope)
		if value != nil {
			return &ReturnValue{Value: value}
		}
		return NULL

	case *ast.Boolean:
		return nativeBoolToBooleanObject(node.Value)

	case *ast.IntegerLiteral:
		return &Integer{Value: node.Value}

	case *ast.StringLiteral:
		return &String{Value: node.Value}

	case *ast.ArrayLiteral:
		return evalArrayLiteral(node, scope)

	case *ast.HashLiteral:
		return evalHashLiteral(node, scope)

	case *ast.FunctionLiteral:
		return &Function{Literal: node, Scope: scope}

	case *ast.Identifier:
		if val, ok := scope.Get(node.String()); ok {
			return val
		}
		return &Error{Message: fmt.Sprintf("unknown identifier: %s", node.String())}

	case *ast.PrefixExpression:
		right := Eval(node.Right, scope)
		if right.Type() == ERROR_OBJ {
			return right
		}
		return evalPrefixExpression(node.Operator, right)

	case *ast.InfixExpression:
		left := Eval(node.Left, scope)
		right := Eval(node.Right, scope)
		if left.Type() == ERROR_OBJ {
			return left
		} else if right.Type() == ERROR_OBJ {
			return right
		}
		return evalInfixExpression(node.Operator, left, right)

	case *ast.IfExpression:
		condition := Eval(node.Condition, scope)
		if isTrue(condition) {
			return evalBlockStatements(node.Consequence.Statements, scope)
		} else if node.Alternative != nil {
			return evalBlockStatements(node.Alternative.Statements, scope)
		}
		return NULL

	case *ast.BlockStatement:
		return evalBlockStatements(node.Statements, scope)

	case *ast.CallExpression:
		f_scope := NewScope(scope)
		fn, ok := scope.Get(node.Function.String())
		if !ok {
			if f, ok := node.Function.(*ast.FunctionLiteral); ok {
				fn = &Function{Literal: f, Scope: scope}
				scope.Set(node.Function.String(), fn)
			}
			if builtin, ok := builtins[node.Function.String()]; ok {
				return builtin.Fn(evalArgs(node.Arguments, scope)...)
			}
		}
		return evalFunctionCall(node, f_scope)

	case *ast.MethodCallExpression:
		return evalMethodCallExpression(node, scope)

	case *ast.IndexExpression:
		left := Eval(node.Left, scope)
		index := Eval(node.Index, scope)
		return evalIndexExpression(left, index)
	}
	return nil
}

func evalIndexExpression(left, index Object) Object {
	var errMsg string
	switch iterable := left.(type) {
	case *Array:
		return evalArrayIndex(iterable, index)
	case *Hash:
		return evalHashKeyIndex(iterable, index)
	default:
		errMsg = fmt.Sprintf("index not supported for type: %s", left.Type())
	}
	return &Error{Message: errMsg}
}

func evalHashKeyIndex(hash *Hash, key Object) Object {
	hashable, ok := key.(Hashable)
	if !ok {
		return &Error{Message: fmt.Sprintf("key error: %s not hashable", key.Type())}
	}
	hashPair, ok := hash.Pairs[hashable.HashKey()]
	if !ok {
		return NULL
	}
	return hashPair.Value
}

func evalArrayIndex(array *Array, index Object) Object {
	var errMsg string
	idx, ok := index.(*Integer)
	if !ok {
		if err, ok := index.(*Error); ok {
			errMsg = fmt.Sprintf("type error: index not integer. got=Err: %s", err.Message)
		} else {
			errMsg = fmt.Sprintf("type error: index not integer. got=%s", index.Type())
		}
		return &Error{Message: errMsg}
	}
	length := int64(len(array.Members))
	if idx.Value > length-1 || idx.Value < -length {
		return &Error{Message: fmt.Sprintf("index %d out of range", idx.Value)}
	}
	if idx.Value < 0 {
		return array.Members[(length)+idx.Value]
	}
	return array.Members[idx.Value]
}

func isTrue(obj Object) bool {
	switch obj {
	case TRUE:
		return true
	case FALSE:
		return false
	case NULL:
		return false
	default:
		return true
	}
}

func nativeBoolToBooleanObject(input bool) *Boolean {
	if input {
		return TRUE
	}
	return FALSE
}

func evalBlockStatements(block []ast.Statement, scope *Scope) Object {
	var results Object

	for _, statement := range block {
		results = Eval(statement, scope)
		if results != nil && results.Type() == RETURN_VALUE_OBJ {
			return results
		}
	}
	return results
}

func evalProgram(program *ast.Program, scope *Scope) Object {
	var results Object

	for _, statement := range program.Statements {
		results = Eval(statement, scope)
		switch s := results.(type) {
		case *ReturnValue:
			return s.Value
		case *Error:
			return s
		}
	}

	return results
}

func evalInfixExpression(operator string, left Object, right Object) Object {
	var errMsg string
	switch {
	case left.Type() == INTEGER_OBJ && right.Type() == INTEGER_OBJ:
		return evalIntInfixExpression(operator, left, right)
	case left.Type() == STRING_OBJ && right.Type() == STRING_OBJ:
		return evalStringInfixExpression(operator, left, right)
	case operator == "==":
		return nativeBoolToBooleanObject(left == right)
	case operator == "!=":
		return nativeBoolToBooleanObject(left != right)
	case left.Type() != right.Type():
		errMsg = fmt.Sprintf("type mismatch: %s %s %s", left.Type(), operator, right.Type())
	default:
		errMsg = fmt.Sprintf("unknown operator: %s %s %s", left.Type(), operator, right.Type())
	}
	if errMsg != "" {
		return &Error{Message: errMsg}
	}
	return NULL
}

func evalStringInfixExpression(operator string, left Object, right Object) Object {
	l := left.(*String)
	r := right.(*String)

	switch operator {
	case "==":
		return nativeBoolToBooleanObject(l.Value == r.Value)
	case "!=":
		return nativeBoolToBooleanObject(l.Value != r.Value)
	case "+":
		// TODO: "Hello, + "World" causes some sort of infinite loop
		return &String{Value: l.Value + r.Value}
	}
	return &Error{Message: fmt.Sprintf("unknown operator: %s %s %s", left.Type(), operator, left.Type())}
}

func evalIntInfixExpression(operator string, left Object, right Object) Object {
	l := left.(*Integer)
	r := right.(*Integer)

	switch operator {
	case "+":
		return &Integer{Value: l.Value + r.Value}
	case "-":
		return &Integer{Value: l.Value - r.Value}
	case "*":
		return &Integer{Value: l.Value * r.Value}
	case "/":
		return &Integer{Value: l.Value / r.Value}
	case ">":
		return nativeBoolToBooleanObject(l.Value > r.Value)
	case "<":
		return nativeBoolToBooleanObject(l.Value < r.Value)
	case "==":
		return nativeBoolToBooleanObject(l.Value == r.Value)
	case "!=":
		return nativeBoolToBooleanObject(l.Value != r.Value)
	}
	return NULL
}

func evalPrefixExpression(operator string, right Object) Object {
	switch operator {
	case "!":
		return evalBangOperatorExpression(right)
	case "-":
		if i, ok := right.(*Integer); ok {
			i.Value = -i.Value
			return right
		}
		msg := fmt.Sprintf("unknown operator: %s%s", operator, right.Type())
		return &Error{Message: msg}
	default:
		return NULL
	}
}

func evalBangOperatorExpression(right Object) Object {
	switch right {
	case TRUE:
		return FALSE
	case FALSE:
		return TRUE
	case NULL:
		return TRUE
	default:
		return FALSE
	}
}

func evalArrayLiteral(a *ast.ArrayLiteral, scope *Scope) Object {
	return &Array{Members: evalArgs(a.Members, scope)}
}

func evalHashLiteral(hl *ast.HashLiteral, scope *Scope) Object {
	hashMap := make(map[HashKey]HashPair)
	// TODO: { 1 -> true, 2 -> "five", "three"-> "four } causes some sort of infinite loop
	for key, value := range hl.Pairs {
		key := Eval(key, scope)
		hashable, ok := key.(Hashable)
		if !ok {
			return &Error{Message: fmt.Sprintf("key error: %T not hashable", key.Type())}
		}
		hashMap[hashable.HashKey()] = HashPair{Key: key, Value: Eval(value, scope)}

	}
	return &Hash{Pairs: hashMap}
}

func evalMethodCallExpression(call *ast.MethodCallExpression, scope *Scope) Object {
	obj := Eval(call.Object, scope)
	method, ok := call.Call.(*ast.CallExpression)
	if !ok {
		return &Error{Message: fmt.Sprintf("Method call not *ast.CallExpression. got=%T", call.Call)}
	}
	args := evalArgs(method.Arguments, scope)
	return obj.CallMethod(method.Function.String(), args)
}

func evalFunctionCall(call *ast.CallExpression, scope *Scope) Object {
	f, ok := scope.Get(call.Function.String())
	if !ok {
		return &Error{Message: fmt.Sprintf("unknown identifier: %s", call.Function.String())}
	}
	fn := f.(*Function)
	fn.Scope = scope
	args := evalArgs(call.Arguments, scope)
	// TODO: If the wrong number of params is passed a panic occurs
	for i, v := range fn.Literal.Parameters {
		scope.Set(v.String(), args[i])
	}
	r := Eval(fn.Literal.Body, scope)
	if obj, ok := r.(*ReturnValue); ok {
		return obj.Value
	}
	return r
}

func evalArgs(args []ast.Expression, scope *Scope) []Object {
	e := []Object{}
	for _, v := range args {
		e = append(e, Eval(v, scope))
	}
	return e
}
<commit_msg>cleaned up and refactored main eval loop<commit_after>package eval

import (
	"fmt"
	"monkey/ast"
)

var (
	TRUE  = &Boolean{Value: true}
	FALSE = &Boolean{Value: false}
	NULL  = &Null{}
)

func Eval(node ast.Node, scope *Scope) Object {
	switch node := node.(type) {
	case *ast.Program:
		return evalProgram(node, scope)
	case *ast.ExpressionStatement:
		return Eval(node.Expression, scope)
	case *ast.LetStatement:
		return evalLetStatement(node, scope)
	case *ast.ReturnStatement:
		return evalReturnStatment(node, scope)
	case *ast.Boolean:
		return nativeBoolToBooleanObject(node.Value)
	case *ast.IntegerLiteral:
		return evalIntegerLiteral(node)
	case *ast.StringLiteral:
		return evalStringLiteral(node)
	case *ast.Identifier:
		return evalIdentifier(node, scope)
	case *ast.ArrayLiteral:
		return evalArrayLiteral(node, scope)
	case *ast.HashLiteral:
		return evalHashLiteral(node, scope)
	case *ast.FunctionLiteral:
		return evalFunctionLiteral(node, scope)
	case *ast.PrefixExpression:
		return evalPrefixExpression(node, scope)
	case *ast.InfixExpression:
		return evalInfixExpression(node, scope)
	case *ast.IfExpression:
		return evalIfExpression(node, scope)
	case *ast.BlockStatement:
		return evalBlockStatements(node.Statements, scope)
	case *ast.CallExpression:
		return evalFunctionCall(node, scope)
	case *ast.MethodCallExpression:
		return evalMethodCallExpression(node, scope)
	case *ast.IndexExpression:
		return evalIndexExpression(node, scope)
	}
	return nil
}

func evalLetStatement(l *ast.LetStatement, scope *Scope) (val Object) {
	if val = Eval(l.Value, scope); val.Type() != ERROR_OBJ {
		return scope.Set(l.Name.String(), val)
	}
	return
}

func evalReturnStatment(r *ast.ReturnStatement, scope *Scope) Object {
	if value := Eval(r.ReturnValue, scope); value != nil {
		return &ReturnValue{Value: value}
	}
	return NULL
}

func nativeBoolToBooleanObject(input bool) *Boolean {
	if input {
		return TRUE
	}
	return FALSE
}

func evalIntegerLiteral(i *ast.IntegerLiteral) Object {
	return &Integer{Value: i.Value}
}

func evalStringLiteral(s *ast.StringLiteral) Object {
	return &String{Value: s.Value}
}

func evalArrayLiteral(a *ast.ArrayLiteral, scope *Scope) Object {
	return &Array{Members: evalArgs(a.Members, scope)}
}

func evalIdentifier(i *ast.Identifier, scope *Scope) Object {
	if val, ok := scope.Get(i.String()); ok {
		return val
	}
	return &Error{Message: fmt.Sprintf("unknown identifier: %s", i.String())}
}

func evalHashLiteral(hl *ast.HashLiteral, scope *Scope) Object {
	hashMap := make(map[HashKey]HashPair)
	// TODO: { 1 -> true, 2 -> "five", "three"-> "four } causes some sort of infinite loop
	for key, value := range hl.Pairs {
		key := Eval(key, scope)
		if hashable, ok := key.(Hashable); ok {
			hashMap[hashable.HashKey()] = HashPair{Key: key, Value: Eval(value, scope)}
		} else {
			return &Error{Message: fmt.Sprintf("key error: %T not hashable", key.Type())}
		}
	}
	return &Hash{Pairs: hashMap}
}

func evalFunctionLiteral(fl *ast.FunctionLiteral, scope *Scope) Object {
	return &Function{Literal: fl, Scope: scope}
}

func evalPrefixExpression(p *ast.PrefixExpression, s *Scope) Object {
	right := Eval(p.Right, s)
	if right.Type() == ERROR_OBJ {
		return right
	}
	switch p.Operator {
	case "!":
		return evalBangOperatorExpression(right)
	case "-":
		if i, ok := right.(*Integer); ok {
			i.Value = -i.Value
			return i
		}
	}
	return &Error{Message: fmt.Sprintf("unknown operator: %s%s", p.Operator, right.Type())}
}

func evalInfixExpression(i *ast.InfixExpression, s *Scope) Object {
	left := Eval(i.Left, s)
	right := Eval(i.Right, s)
	if left.Type() == ERROR_OBJ {
		return left
	} else if right.Type() == ERROR_OBJ {
		return right
	}

	var errMsg string
	switch {
	case left.Type() == INTEGER_OBJ && right.Type() == INTEGER_OBJ:
		return evalIntInfixExpression(i.Operator, left, right)
	case left.Type() == STRING_OBJ && right.Type() == STRING_OBJ:
		return evalStringInfixExpression(i.Operator, left, right)
	case i.Operator == "==":
		return nativeBoolToBooleanObject(left == right)
	case i.Operator == "!=":
		return nativeBoolToBooleanObject(left != right)
	case left.Type() != right.Type():
		errMsg = fmt.Sprintf("type mismatch: %s %s %s", left.Type(), i.Operator, right.Type())
	default:
		errMsg = fmt.Sprintf("unknown operator: %s %s %s", left.Type(), i.Operator, right.Type())
	}
	return &Error{Message: errMsg}
}

func evalIfExpression(ie *ast.IfExpression, s *Scope) Object {
	condition := Eval(ie.Condition, s)
	if isTrue(condition) {
		return evalBlockStatements(ie.Consequence.Statements, s)
	} else if ie.Alternative != nil {
		return evalBlockStatements(ie.Alternative.Statements, s)
	}
	return NULL
}

func evalBlockStatements(block []ast.Statement, scope *Scope) (results Object) {
	for _, statement := range block {
		results = Eval(statement, scope)
		if results != nil && results.Type() == RETURN_VALUE_OBJ {
			return
		}
	}
	return
}

func evalFunctionCall(call *ast.CallExpression, s *Scope) Object {
	fn, ok := s.Get(call.Function.String())
	if !ok {
		if f, ok := call.Function.(*ast.FunctionLiteral); ok {
			fn = &Function{Literal: f, Scope: s}
			s.Set(call.Function.String(), fn)
		} else if builtin, ok := builtins[call.Function.String()]; ok {
			return builtin.Fn(evalArgs(call.Arguments, s)...)
		} else {
			return &Error{Message: fmt.Sprintf("unknown identifier: %s", call.Function.String())}
		}
	}
	f := fn.(*Function)
	f.Scope = NewScope(s)
	args := evalArgs(call.Arguments, f.Scope)
	// TODO: If the wrong number of params is passed a panic occurs
	for i, v := range f.Literal.Parameters {
		f.Scope.Set(v.String(), args[i])
	}
	r := Eval(f.Literal.Body, f.Scope)
	if obj, ok := r.(*ReturnValue); ok {
		return obj.Value
	}
	return r
}

func evalMethodCallExpression(call *ast.MethodCallExpression, scope *Scope) Object {
	obj := Eval(call.Object, scope)
	method, ok := call.Call.(*ast.CallExpression)
	if !ok {
		return &Error{Message: fmt.Sprintf("Method call not *ast.CallExpression. got=%T", call.Call)}
	}
	args := evalArgs(method.Arguments, scope)
	return obj.CallMethod(method.Function.String(), args)
}

func evalArgs(args []ast.Expression, scope *Scope) []Object {
	e := []Object{}
	for _, v := range args {
		e = append(e, Eval(v, scope))
	}
	return e
}

func evalProgram(program *ast.Program, scope *Scope) (results Object) {
	for _, statement := range program.Statements {
		results = Eval(statement, scope)
		switch s := results.(type) {
		case *ReturnValue:
			return s.Value
		case *Error:
			return s
		}
	}
	return
}

func evalIndexExpression(ie *ast.IndexExpression, s *Scope) Object {
	left := Eval(ie.Left, s)
	index := Eval(ie.Index, s)
	if index.Type() == ERROR_OBJ {
		return index
	}
	switch iterable := left.(type) {
	case *Array:
		return evalArrayIndex(iterable, index)
	case *Hash:
		return evalHashKeyIndex(iterable, index)
	}
	return &Error{Message: fmt.Sprintf("index not supported for type: %s", left.Type())}
}

func evalHashKeyIndex(hash *Hash, key Object) Object {
	hashable, ok := key.(Hashable)
	if !ok {
		return &Error{Message: fmt.Sprintf("key error: %s not hashable", key.Type())}
	}
	hashPair, ok := hash.Pairs[hashable.HashKey()]
	if !ok {
		return NULL
	}
	return hashPair.Value
}

func evalArrayIndex(array *Array, index Object) Object {
	idx, ok := index.(*Integer)
	if !ok {
		return &Error{Message: fmt.Sprintf("type error: index not integer. got=%s", index.Type())}
	}
	length := int64(len(array.Members))
	if idx.Value > length-1 || idx.Value < -length {
		return &Error{Message: fmt.Sprintf("index %d out of range", idx.Value)}
	}
	if idx.Value < 0 {
		return array.Members[(length)+idx.Value]
	}
	return array.Members[idx.Value]
}

func isTrue(obj Object) bool {
	switch obj {
	case TRUE:
		return true
	case FALSE:
		return false
	case NULL:
		return false
	default:
		return true
	}
}

func evalStringInfixExpression(operator string, left Object, right Object) Object {
	l := left.(*String)
	r := right.(*String)

	switch operator {
	case "==":
		return nativeBoolToBooleanObject(l.Value == r.Value)
	case "!=":
		return nativeBoolToBooleanObject(l.Value != r.Value)
	case "+":
		// TODO: "Hello, + "World" causes some sort of infinite loop
		return &String{Value: l.Value + r.Value}
	}
	return &Error{Message: fmt.Sprintf("unknown operator: %s %s %s", left.Type(), operator, left.Type())}
}

func evalIntInfixExpression(operator string, left Object, right Object) Object {
	l := left.(*Integer)
	r := right.(*Integer)

	switch operator {
	case "+":
		return &Integer{Value: l.Value + r.Value}
	case "-":
		return &Integer{Value: l.Value - r.Value}
	case "*":
		return &Integer{Value: l.Value * r.Value}
	case "/":
		return &Integer{Value: l.Value / r.Value}
	case ">":
		return nativeBoolToBooleanObject(l.Value > r.Value)
	case "<":
		return nativeBoolToBooleanObject(l.Value < r.Value)
	case "==":
		return nativeBoolToBooleanObject(l.Value == r.Value)
	case "!=":
		return nativeBoolToBooleanObject(l.Value != r.Value)
	}
	return NULL
}

func evalBangOperatorExpression(right Object) Object {
	switch right {
	case TRUE:
		return FALSE
	case FALSE:
		return TRUE
	case NULL:
		return TRUE
	default:
		return FALSE
	}
}
<|endoftext|>


<commit_before>package eapi

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
)

type Parameters struct {
	Version int      `json:"version"`
	Cmds    []string `json:"cmds"`
	Format  string   `json:"format"`
}

type Request struct {
	Jsonrpc string     `json:"jsonrpc"`
	Method  string     `json:"method"`
	Params  Parameters `json:"params"`
	Id      string     `json:"id"`
}

type JsonRpcResponse struct {
	Jsonrpc string                   `json:"jsonrpc"`
	Result  []map[string]interface{} `json:"result"`
	Error   map[string]interface{}   `json:"error"`
	Id      string                   `json:"id"`
}

type ShowVersion struct {
	ModelName        string  `json:"modelName"`
	InternalVersion  string  `json:"internalVersion"`
	SystemMacAddress string  `json:"systemMacAddress"`
	SerialNumber     string  `json:"serialNumber"`
	MemTotal         float64 `json:"memTotal"`
	BootupTimestap   float64 `json:"bootupTimestamp"`
	MemFree          float64 `json:"memFree"`
	Version          string  `json:"version"`
	Architecture     string  `json:"architecture"`
	InternalBuildId  string  `json:"internalBuildId"`
	HardwareRevision string  `json:"hardwareRevision"`
}

type ShowLldpNeighbors struct {
    TablesDeletes   int
    TablesAgeOuts   int
    TablesDrops     int
    TablesInserts   int
    LldpNeighbors   []LldpNeighbor
}

type LldpNeighbor struct{
    NeighborDevice  string
    NeighborPort    string
    Port            string
    Ttl             int
}

//TODO: May need to replace Interface with a generic interface
type ShowInterfaces struct {
	Interfaces map[string]Interface `json:"interfaces"`
}

// TODO: Account for different interface types, this is specific to Eth
type Interface struct {
	Bandwidth                 int
	BurnedInAddress           string
	Description               string //`json:"description"`
	ForwardingModel           string
	Hardware                  string
	InterfaceAddress          []InterfaceAddress
	InterfaceCounters         EthInterfaceCounters
	InterfaceMembership       string
	InterfaceStatistics       InterfaceStatistics
	InterfaceStatus           string
	L2Mtu                     int
	LastStatusChangeTimestamp float64
	LineProtocolStatus        string
	Mtu                       int
	Name                      string
	PhysicalAddress           string
}

type InterfaceAddress struct {
	BroadcastAddress       string
	PrimaryIp              IpAddress
	SecondaryIps           interface{}
	SecondaryIpOrderedList []IpAddress
	VirtualIp              IpAddress
}

type IpAddress struct {
	Address string
	MaskLen int
}

type EthInterfaceCounters struct {
	CounterRefreshTime float64
	InBroadcastPkts    int
	InDiscards         int
	InMulticastPkts    int
	InOctets           int
	InUcastPkts        int
	InputErrorsDetail  PhysicalInputErrors
	LastClear          float64
	LinkStatusChanges  int
	OutBroadcastPkts   int
	OutDiscards        int
	OutMulticastPkts   int
	OutOctets          int
	OutUcastPkts       int
	OutErrorsDetail    PhysicalOutputErrors
	TotalInErrors      int
	TotalOutErrors     int
}

type PhysicalInputErrors struct {
	AlignmentErrots int
	FcsErrors       int
	GiantFrames     int
	RuntFrames      int
	RxPause         int
	SymbolErrors    int
}

type PhysicalOutputErrors struct {
	Collisions            int
	DeferredTransmissions int
	LateCollisions        int
	TxPause               int
}

type InterfaceStatistics struct {
	InBitsRate     float64
	OutBitsRate    float64
	InPktsRate     float64
	UpdateInterval int
	OutPktsRate    float64
}

func Call(url string, cmds []string, format string) JsonRpcResponse {
	p := Parameters{1, cmds, format}
	req := Request{"2.0", "runCmds", p, "1"}
	buf, err := json.Marshal(req)
	resp := new(http.Response)
	if err != nil {
		panic(err)
	}
	client := &http.Client{}
	if strings.HasPrefix(url, "https") {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
		client = &http.Client{Transport: tr}
	}
	resp, err = client.Post(url, "application/json", bytes.NewReader(buf))
	defer resp.Body.Close()

	if err != nil {
		fmt.Println(err)
		panic(err)
	}
	return decodeEapiResponse(resp)
}

func decodeEapiResponse(resp *http.Response) JsonRpcResponse {
	dec := json.NewDecoder(resp.Body)
	var v JsonRpcResponse
	if err := dec.Decode(&v); err != nil {
		log.Println(err)
	}
	return v
}
<commit_msg>Added raw call<commit_after>package eapi

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
    "io/ioutil"
)

type Parameters struct {
	Version int      `json:"version"`
	Cmds    []string `json:"cmds"`
	Format  string   `json:"format"`
}

type Request struct {
	Jsonrpc string     `json:"jsonrpc"`
	Method  string     `json:"method"`
	Params  Parameters `json:"params"`
	Id      string     `json:"id"`
}

type JsonRpcResponse struct {
	Jsonrpc string                   `json:"jsonrpc"`
	Result  []map[string]interface{} `json:"result"`
	Error   map[string]interface{}   `json:"error"`
	Id      string                   `json:"id"`
}

type ShowVersion struct {
	ModelName        string  `json:"modelName"`
	InternalVersion  string  `json:"internalVersion"`
	SystemMacAddress string  `json:"systemMacAddress"`
	SerialNumber     string  `json:"serialNumber"`
	MemTotal         float64 `json:"memTotal"`
	BootupTimestap   float64 `json:"bootupTimestamp"`
	MemFree          float64 `json:"memFree"`
	Version          string  `json:"version"`
	Architecture     string  `json:"architecture"`
	InternalBuildId  string  `json:"internalBuildId"`
	HardwareRevision string  `json:"hardwareRevision"`
}

type ShowLldpNeighbors struct {
    TablesDeletes   int `json:"tablesDeletes"`
    TablesAgeOuts   int `json:"tablesAgeOuts"`
    TablesDrops     int `json:"tablesDrops"`
    TablesInserts   int `json:"tablesInserts"`
    TablesLastChangeTime   int `json:"tablesLastChangeTime"`
    LldpNeighbors   []LldpNeighbor `json:"lldpNeighbors"`
}

type LldpNeighbor struct{
    NeighborDevice  string
    NeighborPort    string
    Port            string
    Ttl             int
}

//TODO: May need to replace Interface with a generic interface
type ShowInterfaces struct {
	Interfaces map[string]Interface `json:"interfaces"`
}

// TODO: Account for different interface types, this is specific to Eth
type Interface struct {
	Bandwidth                 int
	BurnedInAddress           string
	Description               string //`json:"description"`
	ForwardingModel           string
	Hardware                  string
	InterfaceAddress          []InterfaceAddress
	InterfaceCounters         EthInterfaceCounters
	InterfaceMembership       string
	InterfaceStatistics       InterfaceStatistics
	InterfaceStatus           string
	L2Mtu                     int
	LastStatusChangeTimestamp float64
	LineProtocolStatus        string
	Mtu                       int
	Name                      string
	PhysicalAddress           string
}

type InterfaceAddress struct {
	BroadcastAddress       string
	PrimaryIp              IpAddress
	SecondaryIps           interface{}
	SecondaryIpOrderedList []IpAddress
	VirtualIp              IpAddress
}

type IpAddress struct {
	Address string
	MaskLen int
}

type EthInterfaceCounters struct {
	CounterRefreshTime float64
	InBroadcastPkts    int
	InDiscards         int
	InMulticastPkts    int
	InOctets           int
	InUcastPkts        int
	InputErrorsDetail  PhysicalInputErrors
	LastClear          float64
	LinkStatusChanges  int
	OutBroadcastPkts   int
	OutDiscards        int
	OutMulticastPkts   int
	OutOctets          int
	OutUcastPkts       int
	OutErrorsDetail    PhysicalOutputErrors
	TotalInErrors      int
	TotalOutErrors     int
}

type PhysicalInputErrors struct {
	AlignmentErrots int
	FcsErrors       int
	GiantFrames     int
	RuntFrames      int
	RxPause         int
	SymbolErrors    int
}

type PhysicalOutputErrors struct {
	Collisions            int
	DeferredTransmissions int
	LateCollisions        int
	TxPause               int
}

type InterfaceStatistics struct {
	InBitsRate     float64
	OutBitsRate    float64
	InPktsRate     float64
	UpdateInterval int
	OutPktsRate    float64
}

func call(url string, cmds []string, format string) *http.Response {
   p := Parameters{1, cmds, format}
    req := Request{"2.0", "runCmds", p, "1"}
    buf, err := json.Marshal(req)
    resp := new(http.Response)
    if err != nil {
        panic(err)
    }
    client := &http.Client{}
    if strings.HasPrefix(url, "https") {
        tr := &http.Transport{
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        }
        client = &http.Client{Transport: tr}
    }
    resp, err = client.Post(url, "application/json", bytes.NewReader(buf))

    if err != nil {
        fmt.Println(err)
        panic(err)
    }
    return resp
}

func RawCall(url string, cmds []string, format string) []byte {
    resp := call(url, cmds, format)
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error calling")
        fmt.Println(err)
    }
    return body
}

func Call(url string, cmds []string, format string) JsonRpcResponse {
	p := Parameters{1, cmds, format}
	req := Request{"2.0", "runCmds", p, "1"}
	buf, err := json.Marshal(req)
	resp := new(http.Response)
	if err != nil {
		panic(err)
	}
	client := &http.Client{}
	if strings.HasPrefix(url, "https") {
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
		client = &http.Client{Transport: tr}
	}
	resp, err = client.Post(url, "application/json", bytes.NewReader(buf))
	defer resp.Body.Close()

	if err != nil {
		fmt.Println(err)
		panic(err)
	}
	return decodeEapiResponse(resp)
}

func decodeEapiResponse(resp *http.Response) JsonRpcResponse {
	dec := json.NewDecoder(resp.Body)
	var v JsonRpcResponse
	if err := dec.Decode(&v); err != nil {
		log.Println(err)
	}
	return v
}
<|endoftext|>


<commit_before>// Copyright 2014 The gocui 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 gocui

import "github.com/nsf/termbox-go"

const MaxInt = int(^uint(0) >> 1)

// handleEdit manages the edition mode. We do not handle errors here because if
// an error happens, it is enough to keep the view without modifications.
func (g *Gui) handleEdit(v *View, ev *termbox.Event) {
	switch {
	case ev.Ch != 0 && ev.Mod == 0:
		v.editWrite(ev.Ch)
	case ev.Key == termbox.KeySpace:
		v.editWrite(' ')
	case ev.Key == termbox.KeyBackspace || ev.Key == termbox.KeyBackspace2:
		v.editDelete(true)
	case ev.Key == termbox.KeyDelete:
		v.editDelete(false)
	case ev.Key == termbox.KeyInsert:
		v.overwrite = !v.overwrite
	case ev.Key == termbox.KeyEnter:
		v.editNewLine()
	case ev.Key == termbox.KeyArrowDown:
		v.moveCursor(0, 1, false)
	case ev.Key == termbox.KeyArrowUp:
		v.moveCursor(0, -1, false)
	case ev.Key == termbox.KeyArrowLeft:
		v.moveCursor(-1, 0, false)
	case ev.Key == termbox.KeyArrowRight:
		v.moveCursor(1, 0, false)
	}
}

// editWrite writes a rune at the cursor position.
func (v *View) editWrite(ch rune) {
	v.writeRune(v.cx, v.cy, ch)
	v.moveCursor(1, 0, true)
}

// editDelete deletes a rune at the cursor position. back determines
// the direction.
func (v *View) editDelete(back bool) {
	if back {
		if v.cx == 0 {
			v.mergeLines(v.cy - 1)
		} else {
			v.deleteRune(v.cx-1, v.cy)
		}
		v.moveCursor(-1, 0, true)
	} else {
		y := v.oy + v.cy
		if y >= 0 && y < len(v.viewLines) && v.cx == len(v.viewLines[y].line) {
			v.mergeLines(v.cy)
		} else {
			v.deleteRune(v.cx, v.cy)
		}
	}
}

// editNewLine inserts a new line under the cursor.
func (v *View) editNewLine() {
	v.breakLine(v.cx, v.cy)

	y := v.oy + v.cy
	if y >= len(v.viewLines) || (y >= 0 && y < len(v.viewLines) &&
		!(v.Wrap && v.cx == 0 && v.viewLines[y].linesX > 0)) {
		// new line at the end of the buffer or
		// cursor is not at the beginning of a wrapped line
		v.ox = 0
		v.cx = 0
		v.moveCursor(0, 1, true)
	}
}

// moveCursor moves the cursor taking into account the line or view widths and
// moves the origin when necessary. If writeMode is false, the cursor jumps to
// the next line when it reaches the end of the line, otherwise it jumps when
// the cursor reaches the width of the view.
func (v *View) moveCursor(dx, dy int, writeMode bool) {
	maxX, maxY := v.Size()
	cx, cy := v.cx+dx, v.cy+dy
	x, y := v.ox+cx, v.oy+cy

	var curLineWidth, prevLineWidth int
	// get the width of the current line
	if writeMode {
		if v.Wrap {
			curLineWidth = maxX - 1
		} else {
			curLineWidth = MaxInt
		}
	} else {
		if y >= 0 && y < len(v.viewLines) {
			curLineWidth = len(v.viewLines[y].line)
			if v.Wrap && curLineWidth >= maxX {
				curLineWidth = maxX - 1
			}
		} else {
			curLineWidth = 0
		}
	}
	// get the width of the previous line
	if y-1 >= 0 && y-1 < len(v.viewLines) {
		prevLineWidth = len(v.viewLines[y-1].line)
	} else {
		prevLineWidth = 0
	}

	// adjust cursor's x position and view's x origin
	if x > curLineWidth { // move to next line
		if dx > 0 { // horizontal movement
			if !v.Wrap {
				v.ox = 0
			}
			v.cx = 0
			cy += 1
		} else { // vertical movement
			if curLineWidth > 0 { // move cursor to the EOF
				if v.Wrap {
					v.cx = curLineWidth
				} else {
					ncx := curLineWidth - v.ox
					if ncx < 0 {
						v.ox += ncx
						if v.ox < 0 {
							v.ox = 0
						}
						v.cx = 0
					} else {
						v.cx = ncx
					}
				}
			} else {
				if !v.Wrap {
					v.ox = 0
				}
				v.cx = 0
			}
		}
	} else if cx < 0 {
		if !v.Wrap && v.ox > 0 { // move origin to the left
			v.ox -= 1
		} else { // move to previous line
			if prevLineWidth > 0 {
				if !v.Wrap { // set origin so the EOL is visible
					nox := prevLineWidth - maxX + 1
					if nox < 0 {
						v.ox = 0
					} else {
						v.ox = nox
					}
				}
				v.cx = prevLineWidth
			} else {
				if !v.Wrap {
					v.ox = 0
				}
				v.cx = 0
			}
			cy -= 1
		}
	} else { // stay on the same line
		if v.Wrap {
			v.cx = cx
		} else {
			if cx >= maxX {
				v.ox += 1
			} else {
				v.cx = cx
			}
		}
	}

	// adjust cursor's y position and view's y origin
	if cy >= maxY {
		v.oy += 1
	} else if cy < 0 {
		if v.oy > 0 {
			v.oy -= 1
		}
	} else {
		v.cy = cy
	}
}
<commit_msg>Fix cursor behaviour of View.editDelete()<commit_after>// Copyright 2014 The gocui 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 gocui

import "github.com/nsf/termbox-go"

const MaxInt = int(^uint(0) >> 1)

// handleEdit manages the edition mode. We do not handle errors here because if
// an error happens, it is enough to keep the view without modifications.
func (g *Gui) handleEdit(v *View, ev *termbox.Event) {
	switch {
	case ev.Ch != 0 && ev.Mod == 0:
		v.editWrite(ev.Ch)
	case ev.Key == termbox.KeySpace:
		v.editWrite(' ')
	case ev.Key == termbox.KeyBackspace || ev.Key == termbox.KeyBackspace2:
		v.editDelete(true)
	case ev.Key == termbox.KeyDelete:
		v.editDelete(false)
	case ev.Key == termbox.KeyInsert:
		v.overwrite = !v.overwrite
	case ev.Key == termbox.KeyEnter:
		v.editNewLine()
	case ev.Key == termbox.KeyArrowDown:
		v.moveCursor(0, 1, false)
	case ev.Key == termbox.KeyArrowUp:
		v.moveCursor(0, -1, false)
	case ev.Key == termbox.KeyArrowLeft:
		v.moveCursor(-1, 0, false)
	case ev.Key == termbox.KeyArrowRight:
		v.moveCursor(1, 0, false)
	}
}

// editWrite writes a rune at the cursor position.
func (v *View) editWrite(ch rune) {
	v.writeRune(v.cx, v.cy, ch)
	v.moveCursor(1, 0, true)
}

// editDelete deletes a rune at the cursor position. back determines
// the direction.
func (v *View) editDelete(back bool) {
	y := v.oy + v.cy
	if y < 0 {
		return
	} else if y >= len(v.viewLines) {
		v.moveCursor(-1, 0, true)
		return
	}

	maxX, _ := v.Size()
	if back {
		if v.cx == 0 { // start of the line
			if y < 1 {
				return
			}

			var maxPrevWidth int
			if v.Wrap {
				maxPrevWidth = maxX
			} else {
				maxPrevWidth = MaxInt
			}

			if v.viewLines[y].linesX == 0 { // regular line
				v.mergeLines(v.cy - 1)
				if len(v.viewLines[y-1].line) < maxPrevWidth {
					v.moveCursor(-1, 0, true)
				}
			} else { // wrapped line
				v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
				v.moveCursor(-1, 0, true)
			}
		} else {
			v.deleteRune(v.cx-1, v.cy)
			v.moveCursor(-1, 0, true)
		}
	} else { // middle/end of the line
		if v.cx == len(v.viewLines[y].line) {
			v.mergeLines(v.cy)
		} else {
			v.deleteRune(v.cx, v.cy)
		}
	}
}

// editNewLine inserts a new line under the cursor.
func (v *View) editNewLine() {
	v.breakLine(v.cx, v.cy)

	y := v.oy + v.cy
	if y >= len(v.viewLines) || (y >= 0 && y < len(v.viewLines) &&
		!(v.Wrap && v.cx == 0 && v.viewLines[y].linesX > 0)) {
		// new line at the end of the buffer or
		// cursor is not at the beginning of a wrapped line
		v.ox = 0
		v.cx = 0
		v.moveCursor(0, 1, true)
	}
}

// moveCursor moves the cursor taking into account the line or view widths and
// moves the origin when necessary.
func (v *View) moveCursor(dx, dy int, writeMode bool) {
	maxX, maxY := v.Size()
	cx, cy := v.cx+dx, v.cy+dy
	x, y := v.ox+cx, v.oy+cy

	var curLineWidth, prevLineWidth int
	// get the width of the current line
	if writeMode {
		if v.Wrap {
			curLineWidth = maxX - 1
		} else {
			curLineWidth = MaxInt
		}
	} else {
		if y >= 0 && y < len(v.viewLines) {
			curLineWidth = len(v.viewLines[y].line)
			if v.Wrap && curLineWidth >= maxX {
				curLineWidth = maxX - 1
			}
		} else {
			curLineWidth = 0
		}
	}
	// get the width of the previous line
	if y-1 >= 0 && y-1 < len(v.viewLines) {
		prevLineWidth = len(v.viewLines[y-1].line)
	} else {
		prevLineWidth = 0
	}

	// adjust cursor's x position and view's x origin
	if x > curLineWidth { // move to next line
		if dx > 0 { // horizontal movement
			if !v.Wrap {
				v.ox = 0
			}
			v.cx = 0
			cy += 1
		} else { // vertical movement
			if curLineWidth > 0 { // move cursor to the EOF
				if v.Wrap {
					v.cx = curLineWidth
				} else {
					ncx := curLineWidth - v.ox
					if ncx < 0 {
						v.ox += ncx
						if v.ox < 0 {
							v.ox = 0
						}
						v.cx = 0
					} else {
						v.cx = ncx
					}
				}
			} else {
				if !v.Wrap {
					v.ox = 0
				}
				v.cx = 0
			}
		}
	} else if cx < 0 {
		if !v.Wrap && v.ox > 0 { // move origin to the left
			v.ox -= 1
		} else { // move to previous line
			if prevLineWidth > 0 {
				if !v.Wrap { // set origin so the EOL is visible
					nox := prevLineWidth - maxX + 1
					if nox < 0 {
						v.ox = 0
					} else {
						v.ox = nox
					}
				}
				v.cx = prevLineWidth
			} else {
				if !v.Wrap {
					v.ox = 0
				}
				v.cx = 0
			}
			cy -= 1
		}
	} else { // stay on the same line
		if v.Wrap {
			v.cx = cx
		} else {
			if cx >= maxX {
				v.ox += 1
			} else {
				v.cx = cx
			}
		}
	}

	// adjust cursor's y position and view's y origin
	if cy >= maxY {
		v.oy += 1
	} else if cy < 0 {
		if v.oy > 0 {
			v.oy -= 1
		}
	} else {
		v.cy = cy
	}
}
<|endoftext|>