<commit_before>package readline

import "context"

const (
	K_BACKSPACE     = "BACKSPACE"
	K_CAPSLOCK      = "CAPSLOCK"
	K_CLEAR         = "CLEAR"
	K_CTRL          = "CTRL"
	K_CTRL_A        = "C_A"
	K_CTRL_B        = "C_B"
	K_CTRL_BREAK    = "C_BREAK"
	K_CTRL_C        = "C_C"
	K_CTRL_D        = "C_D"
	K_CTRL_E        = "C_E"
	K_CTRL_F        = "C_F"
	K_CTRL_G        = "C_G"
	K_CTRL_H        = "C_H"
	K_CTRL_I        = "C_I"
	K_CTRL_J        = "C_J"
	K_CTRL_K        = "C_K"
	K_CTRL_L        = "C_L"
	K_CTRL_M        = "C_M"
	K_CTRL_N        = "C_N"
	K_CTRL_O        = "C_O"
	K_CTRL_P        = "C_P"
	K_CTRL_Q        = "C_Q"
	K_CTRL_R        = "C_R"
	K_CTRL_S        = "C_S"
	K_CTRL_T        = "C_T"
	K_CTRL_U        = "C_U"
	K_CTRL_V        = "C_V"
	K_CTRL_W        = "C_W"
	K_CTRL_X        = "C_X"
	K_CTRL_Y        = "C_Y"
	K_CTRL_Z        = "C_Z"
	K_DELETE        = "DEL"
	K_DOWN          = "DOWN"
	K_CTRL_DOWN     = "C_DOWN"
	K_END           = "END"
	K_ENTER         = "ENTER"
	K_ESCAPE        = "ESCAPE"
	K_F1            = "F1"
	K_F10           = "F10"
	K_F11           = "F11"
	K_F12           = "F12"
	K_F13           = "F13"
	K_F14           = "F14"
	K_F15           = "F15"
	K_F16           = "F16"
	K_F17           = "F17"
	K_F18           = "F18"
	K_F19           = "F19"
	K_F2            = "F2"
	K_F20           = "F20"
	K_F21           = "F21"
	K_F22           = "F22"
	K_F23           = "F23"
	K_F24           = "F24"
	K_F3            = "F3"
	K_F4            = "F4"
	K_F5            = "F5"
	K_F6            = "F6"
	K_F7            = "F7"
	K_F8            = "F8"
	K_F9            = "F9"
	K_HOME          = "HOME"
	K_LEFT          = "LEFT"
	K_CTRL_LEFT     = "C_LEFT"
	K_PAGEDOWN      = "PAGEDOWN"
	K_PAGEUP        = "PAGEUP"
	K_PAUSE         = "PAUSE"
	K_RIGHT         = "RIGHT"
	K_CTRL_RIGHT    = "C_RIGHT"
	K_SHIFT         = "SHIFT"
	K_UP            = "UP"
	K_CTRL_UP       = "C_UP"
	K_ALT_A         = "M_A"
	K_ALT_B         = "M_B"
	K_ALT_BACKSPACE = "M_BACKSPACE"
	K_ALT_BREAK     = "M_BREAK"
	K_ALT_C         = "M_C"
	K_ALT_D         = "M_D"
	K_ALT_E         = "M_E"
	K_ALT_F         = "M_F"
	K_ALT_G         = "M_G"
	K_ALT_H         = "M_H"
	K_ALT_I         = "M_I"
	K_ALT_J         = "M_J"
	K_ALT_K         = "M_K"
	K_ALT_L         = "M_L"
	K_ALT_M         = "M_M"
	K_ALT_N         = "M_N"
	K_ALT_O         = "M_O"
	K_ALT_P         = "M_P"
	K_ALT_Q         = "M_Q"
	K_ALT_R         = "M_R"
	K_ALT_S         = "M_S"
	K_ALT_T         = "M_T"
	K_ALT_U         = "M_U"
	K_ALT_V         = "M_V"
	K_ALT_W         = "M_W"
	K_ALT_X         = "M_X"
	K_ALT_Y         = "M_Y"
	K_ALT_Z         = "M_Z"
	K_ALT_OEM_2     = "M_OEM_2"
)

const (
	F_ACCEPT_LINE          = "ACCEPT_LINE"
	F_BACKWARD_CHAR        = "BACKWARD_CHAR"
	F_BACKWARD_WORD        = "BACKWARD_WORD"
	F_BACKWARD_DELETE_CHAR = "BACKWARD_DELETE_CHAR"
	F_BEGINNING_OF_LINE    = "BEGINNING_OF_LINE"
	F_CLEAR_SCREEN         = "CLEAR_SCREEN"
	F_DELETE_CHAR          = "DELETE_CHAR"
	F_DELETE_OR_ABORT      = "DELETE_OR_ABORT"
	F_END_OF_LINE          = "END_OF_LINE"
	F_FORWARD_CHAR         = "FORWARD_CHAR"
	F_FORWARD_WORD         = "FORWARD_WORD"
	F_HISTORY_DOWN         = "HISTORY_DOWN" // for compatible
	F_HISTORY_UP           = "HISTORY_UP"   // for compatible
	F_NEXT_HISTORY         = "NEXT_HISTORY"
	F_PREVIOUS_HISTORY     = "PREVIOUS_HISTORY"
	F_INTR                 = "INTR"
	F_ISEARCH_BACKWARD     = "ISEARCH_BACKWARD"
	F_KILL_LINE            = "KILL_LINE"
	F_KILL_WHOLE_LINE      = "KILL_WHOLE_LINE"
	F_PASS                 = "PASS"
	F_QUOTED_INSERT        = "QUOTED_INSERT"
	F_REPAINT_ON_NEWLINE   = "REPAINT_ON_NEWLINE"
	F_SWAPCHAR             = "SWAPCHAR"
	F_UNIX_LINE_DISCARD    = "UNIX_LINE_DISCARD"
	F_UNIX_WORD_RUBOUT     = "UNIX_WORD_RUBOUT"
	F_YANK                 = "YANK"
	F_YANK_WITH_QUOTE      = "YANK_WITH_QUOTE"
	F_UNDO                 = "UNDO"
)

var name2char = map[string]string{
	K_BACKSPACE: "\x7F",
	K_CTRL_A:    "\x01",
	K_CTRL_B:    "\x02",
	K_CTRL_C:    "\x03",
	K_CTRL_D:    "\x04",
	K_CTRL_E:    "\x05",
	K_CTRL_F:    "\x06",
	K_CTRL_G:    "\x07",
	K_CTRL_H:    "\x08",
	K_CTRL_I:    "\x09",
	K_CTRL_J:    "\x0A",
	K_CTRL_K:    "\x0B",
	K_CTRL_L:    "\x0C",
	K_CTRL_M:    "\x0D",
	K_CTRL_N:    "\x0E",
	K_CTRL_O:    "\x0F",
	K_CTRL_P:    "\x10",
	K_CTRL_Q:    "\x11",
	K_CTRL_R:    "\x12",
	K_CTRL_S:    "\x13",
	K_CTRL_T:    "\x14",
	K_CTRL_U:    "\x15",
	K_CTRL_V:    "\x16",
	K_CTRL_W:    "\x17",
	K_CTRL_X:    "\x18",
	K_CTRL_Y:    "\x19",
	K_CTRL_Z:    "\x1A",
	// K_DELETE:        "\x7F",
	K_ENTER:         "\r",
	K_ESCAPE:        "\x1B",
	K_ALT_A:         "\x1Ba",
	K_ALT_B:         "\x1Bb",
	K_ALT_BACKSPACE: "\x1B\b",
	K_ALT_C:         "\x1Bc",
	K_ALT_D:         "\x1Bd",
	K_ALT_E:         "\x1Be",
	K_ALT_F:         "\x1Bf",
	K_ALT_G:         "\x1Bg",
	K_ALT_H:         "\x1Bh",
	K_ALT_I:         "\x1Bi",
	K_ALT_J:         "\x1Bj",
	K_ALT_K:         "\x1Bk",
	K_ALT_L:         "\x1Bl",
	K_ALT_M:         "\x1Bm",
	K_ALT_N:         "\x1Bn",
	K_ALT_O:         "\x1Bo",
	K_ALT_P:         "\x1Bp",
	K_ALT_Q:         "\x1Bq",
	K_ALT_R:         "\x1Br",
	K_ALT_S:         "\x1Bs",
	K_ALT_T:         "\x1Bt",
	K_ALT_U:         "\x1Bu",
	K_ALT_V:         "\x1Bv",
	K_ALT_W:         "\x1Bw",
	K_ALT_X:         "\x1Bx",
	K_ALT_Y:         "\x1By",
	K_ALT_Z:         "\x1Bz",
	K_CLEAR:         "0x0C",
	K_CTRL:          "0x11",
	K_CTRL_BREAK:    "0x03",
	K_DELETE:        "\x1B[3~",
	K_DOWN:          "\x1B[B",
	K_CTRL_DOWN:     "\x1B[1;5B",
	K_END:           "\x1B[F",
	K_F10:           "\x1B[21~",
	K_F11:           "\x1B[23~",
	K_F12:           "\x1B[24~",
	K_F13:           "0x7C",
	K_F14:           "0x7D",
	K_F15:           "0x7E",
	K_F16:           "0x7F",
	K_F17:           "0x80",
	K_F18:           "0x81",
	K_F19:           "0x82",
	K_F1:            "\x1B[OP",
	K_F20:           "0x83",
	K_F21:           "0x84",
	K_F22:           "0x85",
	K_F23:           "0x86",
	K_F24:           "0x87",
	K_F2:            "\x1B[OQ",
	K_F3:            "\x1B[OR",
	K_F4:            "\x1B[OS",
	K_F5:            "\x1B[15~",
	K_F6:            "\x1B[16~",
	K_F7:            "\x1B[17~",
	K_F8:            "\x1B[18~",
	K_F9:            "\x1B[20~",
	K_HOME:          "\x1B[H",
	K_LEFT:          "\x1B[D",
	K_CTRL_LEFT:     "\x1B[1;5D",
	K_PAGEDOWN:      "\x1B[6~",
	K_PAGEUP:        "\x1B[5~",
	K_PAUSE:         "0x13",
	K_RIGHT:         "\x1B[C",
	K_CTRL_RIGHT:    "\x1B[1;5C",
	K_UP:            "\x1B[A",
	K_CTRL_UP:       "\x1B[1;5A",
}

// KeyCode from
// http://msdn.microsoft.com/ja-jp/library/windows/desktop/dd375731(v=vs.85).aspx

var NAME2FUNC = map[string]func(context.Context, *Buffer) Result{
	F_ACCEPT_LINE:          keyFuncEnter,
	F_BACKWARD_CHAR:        keyFuncBackward,
	F_BACKWARD_WORD:        keyFuncBackwardWord,
	F_BACKWARD_DELETE_CHAR: keyFuncBackSpace,
	F_BEGINNING_OF_LINE:    keyFuncHead,
	F_CLEAR_SCREEN:         keyFuncCLS,
	F_DELETE_CHAR:          keyFuncDelete,
	F_DELETE_OR_ABORT:      keyFuncDeleteOrAbort,
	F_END_OF_LINE:          keyFuncTail,
	F_FORWARD_CHAR:         keyFuncForward,
	F_FORWARD_WORD:         keyFuncForwardWord,
	F_HISTORY_DOWN:         keyFuncHistoryDown, // for compatible
	F_HISTORY_UP:           keyFuncHistoryUp,   // for compatible
	F_NEXT_HISTORY:         keyFuncHistoryDown,
	F_PREVIOUS_HISTORY:     keyFuncHistoryUp,
	F_INTR:                 keyFuncIntr,
	F_ISEARCH_BACKWARD:     keyFuncIncSearch,
	F_KILL_LINE:            keyFuncClearAfter,
	F_KILL_WHOLE_LINE:      keyFuncClear,
	F_PASS:                 nil,
	F_QUOTED_INSERT:        keyFuncQuotedInsert,
	F_UNIX_LINE_DISCARD:    keyFuncClearBefore,
	F_UNIX_WORD_RUBOUT:     keyFuncWordRubout,
	F_YANK:                 keyFuncPaste,
	F_YANK_WITH_QUOTE:      keyFuncPasteQuote,
	F_SWAPCHAR:             keyFuncSwapChar,
	F_REPAINT_ON_NEWLINE:   keyFuncRepaintOnNewline,
	F_UNDO:                 keyFuncUndo,
}

func name2func(keyName string) KeyFuncT {
	if p, ok := NAME2FUNC[keyName]; ok {
		return &KeyGoFuncT{
			Func: p,
			Name: keyName,
		}
	} else {
		return nil
	}
}
<commit_msg>readline: add key-name K_CTRL_UNDERBAR, ...<commit_after>package readline

import "context"

const (
	K_BACKSPACE      = "BACKSPACE"
	K_CAPSLOCK       = "CAPSLOCK"
	K_CLEAR          = "CLEAR"
	K_CTRL           = "CTRL"
	K_CTRL_A         = "C_A"
	K_CTRL_B         = "C_B"
	K_CTRL_BREAK     = "C_BREAK"
	K_CTRL_C         = "C_C"
	K_CTRL_D         = "C_D"
	K_CTRL_E         = "C_E"
	K_CTRL_F         = "C_F"
	K_CTRL_G         = "C_G"
	K_CTRL_H         = "C_H"
	K_CTRL_I         = "C_I"
	K_CTRL_J         = "C_J"
	K_CTRL_K         = "C_K"
	K_CTRL_L         = "C_L"
	K_CTRL_M         = "C_M"
	K_CTRL_N         = "C_N"
	K_CTRL_O         = "C_O"
	K_CTRL_P         = "C_P"
	K_CTRL_Q         = "C_Q"
	K_CTRL_R         = "C_R"
	K_CTRL_S         = "C_S"
	K_CTRL_T         = "C_T"
	K_CTRL_U         = "C_U"
	K_CTRL_V         = "C_V"
	K_CTRL_W         = "C_W"
	K_CTRL_X         = "C_X"
	K_CTRL_Y         = "C_Y"
	K_CTRL_Z         = "C_Z"
	K_CTRL_UNDERBAR  = "C_UNDERBAR"
	K_CTRL_LBRACKET  = "C_["
	K_CTRL_RBRACKET  = "C_]"
	K_CTRL_BACKSLASH = "C_\\"
	K_CTRL_CARET     = "C_^"
	K_DELETE         = "DEL"
	K_DOWN           = "DOWN"
	K_CTRL_DOWN      = "C_DOWN"
	K_END            = "END"
	K_ENTER          = "ENTER"
	K_ESCAPE         = "ESCAPE"
	K_F1             = "F1"
	K_F10            = "F10"
	K_F11            = "F11"
	K_F12            = "F12"
	K_F13            = "F13"
	K_F14            = "F14"
	K_F15            = "F15"
	K_F16            = "F16"
	K_F17            = "F17"
	K_F18            = "F18"
	K_F19            = "F19"
	K_F2             = "F2"
	K_F20            = "F20"
	K_F21            = "F21"
	K_F22            = "F22"
	K_F23            = "F23"
	K_F24            = "F24"
	K_F3             = "F3"
	K_F4             = "F4"
	K_F5             = "F5"
	K_F6             = "F6"
	K_F7             = "F7"
	K_F8             = "F8"
	K_F9             = "F9"
	K_HOME           = "HOME"
	K_LEFT           = "LEFT"
	K_CTRL_LEFT      = "C_LEFT"
	K_PAGEDOWN       = "PAGEDOWN"
	K_PAGEUP         = "PAGEUP"
	K_PAUSE          = "PAUSE"
	K_RIGHT          = "RIGHT"
	K_CTRL_RIGHT     = "C_RIGHT"
	K_SHIFT          = "SHIFT"
	K_UP             = "UP"
	K_CTRL_UP        = "C_UP"
	K_ALT_A          = "M_A"
	K_ALT_B          = "M_B"
	K_ALT_BACKSPACE  = "M_BACKSPACE"
	K_ALT_BREAK      = "M_BREAK"
	K_ALT_C          = "M_C"
	K_ALT_D          = "M_D"
	K_ALT_E          = "M_E"
	K_ALT_F          = "M_F"
	K_ALT_G          = "M_G"
	K_ALT_H          = "M_H"
	K_ALT_I          = "M_I"
	K_ALT_J          = "M_J"
	K_ALT_K          = "M_K"
	K_ALT_L          = "M_L"
	K_ALT_M          = "M_M"
	K_ALT_N          = "M_N"
	K_ALT_O          = "M_O"
	K_ALT_P          = "M_P"
	K_ALT_Q          = "M_Q"
	K_ALT_R          = "M_R"
	K_ALT_S          = "M_S"
	K_ALT_T          = "M_T"
	K_ALT_U          = "M_U"
	K_ALT_V          = "M_V"
	K_ALT_W          = "M_W"
	K_ALT_X          = "M_X"
	K_ALT_Y          = "M_Y"
	K_ALT_Z          = "M_Z"
	K_ALT_OEM_2      = "M_OEM_2"
)

const (
	F_ACCEPT_LINE          = "ACCEPT_LINE"
	F_BACKWARD_CHAR        = "BACKWARD_CHAR"
	F_BACKWARD_WORD        = "BACKWARD_WORD"
	F_BACKWARD_DELETE_CHAR = "BACKWARD_DELETE_CHAR"
	F_BEGINNING_OF_LINE    = "BEGINNING_OF_LINE"
	F_CLEAR_SCREEN         = "CLEAR_SCREEN"
	F_DELETE_CHAR          = "DELETE_CHAR"
	F_DELETE_OR_ABORT      = "DELETE_OR_ABORT"
	F_END_OF_LINE          = "END_OF_LINE"
	F_FORWARD_CHAR         = "FORWARD_CHAR"
	F_FORWARD_WORD         = "FORWARD_WORD"
	F_HISTORY_DOWN         = "HISTORY_DOWN" // for compatible
	F_HISTORY_UP           = "HISTORY_UP"   // for compatible
	F_NEXT_HISTORY         = "NEXT_HISTORY"
	F_PREVIOUS_HISTORY     = "PREVIOUS_HISTORY"
	F_INTR                 = "INTR"
	F_ISEARCH_BACKWARD     = "ISEARCH_BACKWARD"
	F_KILL_LINE            = "KILL_LINE"
	F_KILL_WHOLE_LINE      = "KILL_WHOLE_LINE"
	F_PASS                 = "PASS"
	F_QUOTED_INSERT        = "QUOTED_INSERT"
	F_REPAINT_ON_NEWLINE   = "REPAINT_ON_NEWLINE"
	F_SWAPCHAR             = "SWAPCHAR"
	F_UNIX_LINE_DISCARD    = "UNIX_LINE_DISCARD"
	F_UNIX_WORD_RUBOUT     = "UNIX_WORD_RUBOUT"
	F_YANK                 = "YANK"
	F_YANK_WITH_QUOTE      = "YANK_WITH_QUOTE"
	F_UNDO                 = "UNDO"
)

var name2char = map[string]string{
	K_BACKSPACE:      "\x7F",
	K_CTRL_A:         "\x01",
	K_CTRL_B:         "\x02",
	K_CTRL_C:         "\x03",
	K_CTRL_D:         "\x04",
	K_CTRL_E:         "\x05",
	K_CTRL_F:         "\x06",
	K_CTRL_G:         "\x07",
	K_CTRL_H:         "\x08",
	K_CTRL_I:         "\x09",
	K_CTRL_J:         "\x0A",
	K_CTRL_K:         "\x0B",
	K_CTRL_L:         "\x0C",
	K_CTRL_M:         "\x0D",
	K_CTRL_N:         "\x0E",
	K_CTRL_O:         "\x0F",
	K_CTRL_P:         "\x10",
	K_CTRL_Q:         "\x11",
	K_CTRL_R:         "\x12",
	K_CTRL_S:         "\x13",
	K_CTRL_T:         "\x14",
	K_CTRL_U:         "\x15",
	K_CTRL_V:         "\x16",
	K_CTRL_W:         "\x17",
	K_CTRL_X:         "\x18",
	K_CTRL_Y:         "\x19",
	K_CTRL_Z:         "\x1A",
	K_CTRL_LBRACKET:  "\x1B", // C-[
	K_CTRL_BACKSLASH: "\x1C", // C-\
	K_CTRL_RBRACKET:  "\x1D", // C-]
	K_CTRL_CARET:     "\x1E", // C-^
	K_CTRL_UNDERBAR:  "\x1F",
	// K_DELETE:        "\x7F",
	K_ENTER:         "\r",
	K_ESCAPE:        "\x1B",
	K_ALT_A:         "\x1Ba",
	K_ALT_B:         "\x1Bb",
	K_ALT_BACKSPACE: "\x1B\b",
	K_ALT_C:         "\x1Bc",
	K_ALT_D:         "\x1Bd",
	K_ALT_E:         "\x1Be",
	K_ALT_F:         "\x1Bf",
	K_ALT_G:         "\x1Bg",
	K_ALT_H:         "\x1Bh",
	K_ALT_I:         "\x1Bi",
	K_ALT_J:         "\x1Bj",
	K_ALT_K:         "\x1Bk",
	K_ALT_L:         "\x1Bl",
	K_ALT_M:         "\x1Bm",
	K_ALT_N:         "\x1Bn",
	K_ALT_O:         "\x1Bo",
	K_ALT_P:         "\x1Bp",
	K_ALT_Q:         "\x1Bq",
	K_ALT_R:         "\x1Br",
	K_ALT_S:         "\x1Bs",
	K_ALT_T:         "\x1Bt",
	K_ALT_U:         "\x1Bu",
	K_ALT_V:         "\x1Bv",
	K_ALT_W:         "\x1Bw",
	K_ALT_X:         "\x1Bx",
	K_ALT_Y:         "\x1By",
	K_ALT_Z:         "\x1Bz",
	K_CLEAR:         "0x0C",
	K_CTRL:          "0x11",
	K_CTRL_BREAK:    "0x03",
	K_DELETE:        "\x1B[3~",
	K_DOWN:          "\x1B[B",
	K_CTRL_DOWN:     "\x1B[1;5B",
	K_END:           "\x1B[F",
	K_F10:           "\x1B[21~",
	K_F11:           "\x1B[23~",
	K_F12:           "\x1B[24~",
	K_F13:           "0x7C",
	K_F14:           "0x7D",
	K_F15:           "0x7E",
	K_F16:           "0x7F",
	K_F17:           "0x80",
	K_F18:           "0x81",
	K_F19:           "0x82",
	K_F1:            "\x1B[OP",
	K_F20:           "0x83",
	K_F21:           "0x84",
	K_F22:           "0x85",
	K_F23:           "0x86",
	K_F24:           "0x87",
	K_F2:            "\x1B[OQ",
	K_F3:            "\x1B[OR",
	K_F4:            "\x1B[OS",
	K_F5:            "\x1B[15~",
	K_F6:            "\x1B[16~",
	K_F7:            "\x1B[17~",
	K_F8:            "\x1B[18~",
	K_F9:            "\x1B[20~",
	K_HOME:          "\x1B[H",
	K_LEFT:          "\x1B[D",
	K_CTRL_LEFT:     "\x1B[1;5D",
	K_PAGEDOWN:      "\x1B[6~",
	K_PAGEUP:        "\x1B[5~",
	K_PAUSE:         "0x13",
	K_RIGHT:         "\x1B[C",
	K_CTRL_RIGHT:    "\x1B[1;5C",
	K_UP:            "\x1B[A",
	K_CTRL_UP:       "\x1B[1;5A",
}

// KeyCode from
// http://msdn.microsoft.com/ja-jp/library/windows/desktop/dd375731(v=vs.85).aspx

var NAME2FUNC = map[string]func(context.Context, *Buffer) Result{
	F_ACCEPT_LINE:          keyFuncEnter,
	F_BACKWARD_CHAR:        keyFuncBackward,
	F_BACKWARD_WORD:        keyFuncBackwardWord,
	F_BACKWARD_DELETE_CHAR: keyFuncBackSpace,
	F_BEGINNING_OF_LINE:    keyFuncHead,
	F_CLEAR_SCREEN:         keyFuncCLS,
	F_DELETE_CHAR:          keyFuncDelete,
	F_DELETE_OR_ABORT:      keyFuncDeleteOrAbort,
	F_END_OF_LINE:          keyFuncTail,
	F_FORWARD_CHAR:         keyFuncForward,
	F_FORWARD_WORD:         keyFuncForwardWord,
	F_HISTORY_DOWN:         keyFuncHistoryDown, // for compatible
	F_HISTORY_UP:           keyFuncHistoryUp,   // for compatible
	F_NEXT_HISTORY:         keyFuncHistoryDown,
	F_PREVIOUS_HISTORY:     keyFuncHistoryUp,
	F_INTR:                 keyFuncIntr,
	F_ISEARCH_BACKWARD:     keyFuncIncSearch,
	F_KILL_LINE:            keyFuncClearAfter,
	F_KILL_WHOLE_LINE:      keyFuncClear,
	F_PASS:                 nil,
	F_QUOTED_INSERT:        keyFuncQuotedInsert,
	F_UNIX_LINE_DISCARD:    keyFuncClearBefore,
	F_UNIX_WORD_RUBOUT:     keyFuncWordRubout,
	F_YANK:                 keyFuncPaste,
	F_YANK_WITH_QUOTE:      keyFuncPasteQuote,
	F_SWAPCHAR:             keyFuncSwapChar,
	F_REPAINT_ON_NEWLINE:   keyFuncRepaintOnNewline,
	F_UNDO:                 keyFuncUndo,
}

func name2func(keyName string) KeyFuncT {
	if p, ok := NAME2FUNC[keyName]; ok {
		return &KeyGoFuncT{
			Func: p,
			Name: keyName,
		}
	} else {
		return nil
	}
}
<|endoftext|>


<commit_before>// Copyright 2013 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.

package commands

import (
	. "github.com/limetext/lime/backend"
	. "github.com/quarnster/util/text"
	"reflect"
	"testing"
)

func TestMove(t *testing.T) {
	ed := GetEditor()

	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()
	v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	v.EndEdit(e)

	type Test struct {
		in      []Region
		by      string
		extend  bool
		forward bool
		exp     []Region
		args    Args
	}

	tests := []Test{
		{
			[]Region{{1, 1}, {3, 3}, {6, 6}},
			"characters",
			false,
			true,
			[]Region{{2, 2}, {4, 4}, {7, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {6, 6}},
			"characters",
			false,
			false,
			[]Region{{0, 0}, {2, 2}, {5, 5}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			false,
			true,
			[]Region{{2, 2}, {4, 4}, {7, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			false,
			false,
			[]Region{{0, 0}, {2, 2}, {5, 5}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			true,
			true,
			[]Region{{1, 2}, {3, 4}, {10, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			true,
			false,
			[]Region{{1, 0}, {3, 2}, {10, 5}},
			nil,
		},
		{
			[]Region{{1, 3}, {3, 5}, {10, 7}},
			"characters",
			true,
			true,
			[]Region{{1, 6}, {10, 8}},
			nil,
		},
		{
			[]Region{{1, 1}},
			"stops",
			true,
			true,
			[]Region{{1, 5}},
			Args{"word_end": true},
		},
		{
			[]Region{{1, 1}},
			"stops",
			false,
			true,
			[]Region{{6, 6}},
			Args{"word_begin": true},
		},
		{
			[]Region{{6, 6}},
			"stops",
			false,
			false,
			[]Region{{0, 0}},
			Args{"word_begin": true},
		},
		{
			[]Region{{34, 34}},
			"lines",
			false,
			false,
			[]Region{{23, 23}},
			nil,
		},
		{
			[]Region{{23, 23}},
			"lines",
			false,
			false,
			[]Region{{10, 10}},
			nil,
		},
		{
			[]Region{{100, 100}},
			"lines",
			false,
			false,
			[]Region{{24, 24}},
			nil,
		},
	}
	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"by": test.by, "extend": test.extend, "forward": test.forward}
		if test.args != nil {
			for k, v := range test.args {
				args[k] = v
			}
		}
		ed.CommandHandler().RunTextCommand(v, "move", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}

	e = v.BeginEdit()
	v.Insert(e, v.Buffer().Size(), "abc")
	v.EndEdit(e)

	tests = []Test{
		{
			[]Region{{100, 100}},
			"lines",
			false,
			false,
			[]Region{{27, 27}},
			nil,
		},
	}
	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"by": test.by, "extend": test.extend, "forward": test.forward}
		if test.args != nil {
			for k, v := range test.args {
				args[k] = v
			}
		}

		ed.CommandHandler().RunTextCommand(v, "move", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}
}

func TestMoveTo(t *testing.T) {
	/*
	   Correct behavior of MoveTo:
	       - Moves each cursor directly to the indicated position.
	       - If extend, the selection will be extended in the direction of movement
	*/

	ed := GetEditor()
	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()

	v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	v.EndEdit(e)

	type MoveToTest struct {
		in     []Region
		to     string
		extend bool
		exp    []Region
	}
	vbufflen := v.Buffer().Size()
	tests := []MoveToTest{
		{
			[]Region{{vbufflen, vbufflen}},
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {vbufflen - 2, vbufflen - 2}},
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			[]Region{{vbufflen, vbufflen}, {3, 3}},
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			[]Region{{vbufflen, vbufflen}},
			"bof",
			true,
			[]Region{{vbufflen, 0}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {vbufflen - 2, vbufflen - 2}},
			"bof",
			true,
			[]Region{{vbufflen - 1, 0}},
		},
		{
			[]Region{{vbufflen, vbufflen}, {3, 3}},
			"bof",
			true,
			[]Region{{vbufflen, 0}},
		},
		{
			[]Region{{0, 0}},
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			[]Region{{0, 0}, {3, 3}},
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			[]Region{{0, 0}, {vbufflen - 4, vbufflen - 4}},
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			[]Region{{0, 0}},
			"eof",
			true,
			[]Region{{0, vbufflen}},
		},
		{
			[]Region{{0, 0}, {3, 3}},
			"eof",
			true,
			[]Region{{0, vbufflen}},
		},
		{
			[]Region{{0, 0}, {vbufflen - 4, vbufflen - 4}},
			"eof",
			true,
			[]Region{{0, vbufflen}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}},
			"bol",
			false,
			[]Region{{24, 24}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {vbufflen - 3, vbufflen - 3}},
			"bol",
			false,
			[]Region{{24, 24}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {3, 3}},
			"bol",
			false,
			[]Region{{24, 24}, {0, 0}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}},
			"bol",
			true,
			[]Region{{vbufflen - 1, 24}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {vbufflen - 3, vbufflen - 3}},
			"bol",
			true,
			[]Region{{vbufflen - 1, 24}},
		},
		{
			[]Region{{vbufflen - 1, vbufflen - 1}, {3, 3}},
			"bol",
			true,
			[]Region{{vbufflen - 1, 24}, {3, 0}},
		},
		{
			[]Region{{0, 0}},
			"eol",
			false,
			[]Region{{12, 12}},
		},
		{
			[]Region{{0, 0}, {3, 3}},
			"eol",
			false,
			[]Region{{12, 12}},
		},
		{
			[]Region{{0, 0}, {vbufflen - 4, vbufflen - 4}},
			"eol",
			false,
			[]Region{{12, 12}, {vbufflen - 1, vbufflen - 1}},
		},
		{
			[]Region{{0, 0}},
			"eol",
			true,
			[]Region{{0, 12}},
		},
		{
			[]Region{{0, 0}, {3, 3}},
			"eol",
			true,
			[]Region{{0, 12}},
		},
		{
			[]Region{{0, 0}, {vbufflen - 4, vbufflen - 4}},
			"eol",
			true,
			[]Region{{0, 12}, {vbufflen - 4, vbufflen - 1}},
		},
	}

	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"to": test.to, "extend": test.extend}
		ed.CommandHandler().RunTextCommand(v, "move_to", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}
}

type scfe struct {
	DummyFrontend
	show Region
}

func (f *scfe) VisibleRegion(v *View) Region {
	s := v.Buffer().Line(v.Buffer().TextPoint(3*3, 1))
	e := v.Buffer().Line(v.Buffer().TextPoint(6*3, 1))
	return Region{s.Begin(), e.End()}
}

func (f *scfe) Show(v *View, r Region) {
	f.show = r
}

func TestScrollLines(t *testing.T) {
	var fe scfe
	ed := GetEditor()
	ed.SetFrontend(&fe)
	ch := ed.CommandHandler()
	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()
	for i := 0; i < 10; i++ {
		v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	}
	v.EndEdit(e)
	ch.RunTextCommand(v, "scroll_lines", Args{"amount": 0})

	if c := v.Buffer().Line(v.Buffer().TextPoint(3*3, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}

	ch.RunTextCommand(v, "scroll_lines", Args{"amount": 1})
	if c := v.Buffer().Line(v.Buffer().TextPoint(3*3-1, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}
	t.Log(fe.VisibleRegion(v), v.Buffer().Line(v.Buffer().TextPoint(6*3+1, 1)))
	ch.RunTextCommand(v, "scroll_lines", Args{"amount": -1})
	if c := v.Buffer().Line(v.Buffer().TextPoint(6*3+1, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}
}
<commit_msg>Improve readability and add some MoveTo tests.<commit_after>// Copyright 2013 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.

package commands

import (
	. "github.com/limetext/lime/backend"
	. "github.com/quarnster/util/text"
	"reflect"
	"testing"
)

func TestMove(t *testing.T) {
	ed := GetEditor()

	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()
	v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	v.EndEdit(e)

	type Test struct {
		in      []Region
		by      string
		extend  bool
		forward bool
		exp     []Region
		args    Args
	}

	tests := []Test{
		{
			[]Region{{1, 1}, {3, 3}, {6, 6}},
			"characters",
			false,
			true,
			[]Region{{2, 2}, {4, 4}, {7, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {6, 6}},
			"characters",
			false,
			false,
			[]Region{{0, 0}, {2, 2}, {5, 5}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			false,
			true,
			[]Region{{2, 2}, {4, 4}, {7, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			false,
			false,
			[]Region{{0, 0}, {2, 2}, {5, 5}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			true,
			true,
			[]Region{{1, 2}, {3, 4}, {10, 7}},
			nil,
		},
		{
			[]Region{{1, 1}, {3, 3}, {10, 6}},
			"characters",
			true,
			false,
			[]Region{{1, 0}, {3, 2}, {10, 5}},
			nil,
		},
		{
			[]Region{{1, 3}, {3, 5}, {10, 7}},
			"characters",
			true,
			true,
			[]Region{{1, 6}, {10, 8}},
			nil,
		},
		{
			[]Region{{1, 1}},
			"stops",
			true,
			true,
			[]Region{{1, 5}},
			Args{"word_end": true},
		},
		{
			[]Region{{1, 1}},
			"stops",
			false,
			true,
			[]Region{{6, 6}},
			Args{"word_begin": true},
		},
		{
			[]Region{{6, 6}},
			"stops",
			false,
			false,
			[]Region{{0, 0}},
			Args{"word_begin": true},
		},
		{
			[]Region{{34, 34}},
			"lines",
			false,
			false,
			[]Region{{23, 23}},
			nil,
		},
		{
			[]Region{{23, 23}},
			"lines",
			false,
			false,
			[]Region{{10, 10}},
			nil,
		},
		{
			[]Region{{100, 100}},
			"lines",
			false,
			false,
			[]Region{{24, 24}},
			nil,
		},
	}
	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"by": test.by, "extend": test.extend, "forward": test.forward}
		if test.args != nil {
			for k, v := range test.args {
				args[k] = v
			}
		}
		ed.CommandHandler().RunTextCommand(v, "move", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}

	e = v.BeginEdit()
	v.Insert(e, v.Buffer().Size(), "abc")
	v.EndEdit(e)

	tests = []Test{
		{
			[]Region{{100, 100}},
			"lines",
			false,
			false,
			[]Region{{27, 27}},
			nil,
		},
	}
	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"by": test.by, "extend": test.extend, "forward": test.forward}
		if test.args != nil {
			for k, v := range test.args {
				args[k] = v
			}
		}

		ed.CommandHandler().RunTextCommand(v, "move", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}
}

func TestMoveTo(t *testing.T) {
	/*
	   Correct behavior of MoveTo:
	       - Moves each cursor directly to the indicated position.
	       - If extend, the selection will be extended in the direction of movement
	*/

	ed := GetEditor()
	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()

	v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	v.EndEdit(e)

	type MoveToTest struct {
		in     []Region
		to     string
		extend bool
		exp    []Region
	}

	singleCursor 			:= []Region{{16, 16}}
	sameLineCursors 		:= []Region{{16, 16}, {17, 17}}
	sameLineCursorsReversed := []Region{{17, 17}, {16, 16}}
	diffLineCursors 		:= []Region{{3, 3},   {17, 17}}
	diffLineCursorsReversed := []Region{{17, 17}, {3, 3}}

	vbufflen := v.Buffer().Size()

	tests := []MoveToTest{
		// BOF move
		{
			singleCursor,
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			sameLineCursors,
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			sameLineCursorsReversed,
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			diffLineCursors,
			"bof",
			false,
			[]Region{{0, 0}},
		},
		{
			diffLineCursorsReversed,
			"bof",
			false,
			[]Region{{0, 0}},
		},

		// BOF extend
		{
			singleCursor,
			"bof",
			true,
			[]Region{{16, 0}},
		},
		{
			sameLineCursors,
			"bof",
			true,
			[]Region{{17, 0}},
		},
		{
			sameLineCursorsReversed,
			"bof",
			true,
			[]Region{{17, 0}},
		},
		{
			diffLineCursors,
			"bof",
			true,
			[]Region{{17, 0}},
		},
		{
			diffLineCursorsReversed,
			"bof",
			true,
			[]Region{{17, 0}},
		},

		// EOF move
		{
			singleCursor,
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			sameLineCursors,
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			sameLineCursorsReversed,
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			diffLineCursors,
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},
		{
			diffLineCursorsReversed,
			"eof",
			false,
			[]Region{{vbufflen, vbufflen}},
		},

		// EOF extend
		{
			singleCursor,
			"eof",
			true,
			[]Region{{16, vbufflen}},
		},
		{
			sameLineCursors,
			"eof",
			true,
			[]Region{{16, vbufflen}},
		},
		{
			sameLineCursorsReversed,
			"eof",
			true,
			[]Region{{16, vbufflen}},
		},
		{
			diffLineCursors,
			"eof",
			true,
			[]Region{{3, vbufflen}},
		},
		{
			diffLineCursorsReversed,
			"eof",
			true,
			[]Region{{3, vbufflen}},
		},

		// BOL move
		{
			singleCursor,
			"bol",
			false,
			[]Region{{13, 13}},
		},
		{
			sameLineCursors,
			"bol",
			false,
			[]Region{{13, 13}},
		},
		{
			sameLineCursorsReversed,
			"bol",
			false,
			[]Region{{13, 13}},
		},
		{
			diffLineCursors,
			"bol",
			false,
			[]Region{{0, 0}, {13, 13}},
		},
		{
			diffLineCursorsReversed,
			"bol",
			false,
			[]Region{{13, 13}, {0, 0}},
		},

		// BOL extend
		{
			singleCursor,
			"bol",
			true,
			[]Region{{16, 13}},
		},
		{
			sameLineCursors,
			"bol",
			true,
			[]Region{{17, 13}},
		},
		{
			sameLineCursorsReversed,
			"bol",
			true,
			[]Region{{17, 13}},
		},
		{
			diffLineCursors,
			"bol",
			true,
			[]Region{{3, 0}, {17, 13}},
		},
		{
			diffLineCursorsReversed,
			"bol",
			true,
			[]Region{{17, 13}, {3, 0}},
		},

		// EOL move
		{
			singleCursor,
			"eol",
			false,
			[]Region{{23, 23}},
		},
		{
			sameLineCursors,
			"eol",
			false,
			[]Region{{23, 23}},
		},
		{
			sameLineCursorsReversed,
			"eol",
			false,
			[]Region{{23, 23}},
		},
		{
			diffLineCursors,
			"eol",
			false,
			[]Region{{12, 12}, {23, 23}},
		},
		{
			diffLineCursorsReversed,
			"eol",
			false,
			[]Region{{23, 23}, {12, 12}},
		},

		// EOL extend
		{
			singleCursor,
			"eol",
			true,
			[]Region{{16, 23}},
		},
		{
			sameLineCursors,
			"eol",
			true,
			[]Region{{16, 23}},
		},
		{
			sameLineCursorsReversed,
			"eol",
			true,
			[]Region{{16, 23}},
		},
		{
			diffLineCursors,
			"eol",
			true,
			[]Region{{3, 12}, {17, 23}},
		},
		{
			diffLineCursorsReversed,
			"eol",
			true,
			[]Region{{17, 23}, {3, 12}},
		},
	}

	for i, test := range tests {
		v.Sel().Clear()
		for _, r := range test.in {
			v.Sel().Add(r)
		}
		args := Args{"to": test.to, "extend": test.extend}
		ed.CommandHandler().RunTextCommand(v, "move_to", args)
		if sr := v.Sel().Regions(); !reflect.DeepEqual(sr, test.exp) {
			t.Errorf("Test %d failed. Expected %v, but got %v: %+v", i, test.exp, sr, test)
		}
	}
}

type scfe struct {
	DummyFrontend
	show Region
}

func (f *scfe) VisibleRegion(v *View) Region {
	s := v.Buffer().Line(v.Buffer().TextPoint(3*3, 1))
	e := v.Buffer().Line(v.Buffer().TextPoint(6*3, 1))
	return Region{s.Begin(), e.End()}
}

func (f *scfe) Show(v *View, r Region) {
	f.show = r
}

func TestScrollLines(t *testing.T) {
	var fe scfe
	ed := GetEditor()
	ed.SetFrontend(&fe)
	ch := ed.CommandHandler()
	w := ed.NewWindow()
	v := w.NewFile()
	e := v.BeginEdit()
	for i := 0; i < 10; i++ {
		v.Insert(e, 0, "Hello World!\nTest123123\nAbrakadabra\n")
	}
	v.EndEdit(e)
	ch.RunTextCommand(v, "scroll_lines", Args{"amount": 0})

	if c := v.Buffer().Line(v.Buffer().TextPoint(3*3, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}

	ch.RunTextCommand(v, "scroll_lines", Args{"amount": 1})
	if c := v.Buffer().Line(v.Buffer().TextPoint(3*3-1, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}
	t.Log(fe.VisibleRegion(v), v.Buffer().Line(v.Buffer().TextPoint(6*3+1, 1)))
	ch.RunTextCommand(v, "scroll_lines", Args{"amount": -1})
	if c := v.Buffer().Line(v.Buffer().TextPoint(6*3+1, 1)); fe.show.Begin() != c.Begin() {
		t.Errorf("Expected %v, but got %v", c, fe.show)
	}
}
<|endoftext|>


<commit_before>package integration_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"

	"github.com/onsi/gomega/gexec"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const simpleRequest = `
{
	"source": {
	"uri": "%s",
	"branch": "master",
	"pool": "lock-pool"
	}
}`

type locksResponse map[string]string

var _ = Describe("Check", func() {
	var (
		gitRepo string
	)

	BeforeEach(func() {
		var err error

		gitRepo, err = ioutil.TempDir("", "git-repo")
		Expect(err).NotTo(HaveOccurred())
	})

	AfterEach(func() {
		err := os.RemoveAll(gitRepo)
		Expect(err).NotTo(HaveOccurred())
	})

	Context("when the repo is at HEAD", func() {
		FIt("succesfully checks", func() {
			setupGitRepo(gitRepo)

			cmd := exec.Command(builtCheck)
			cmd.Stdin = bytes.NewBufferString(fmt.Sprintf(simpleRequest, gitRepo))

			session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())

			<-session.Exited

			Expect(session.ExitCode()).To(Equal(0))

			var result []locksResponse
			err = json.NewDecoder(bytes.NewReader(session.Out.Contents())).Decode(&result)
			Expect(err).NotTo(HaveOccurred())

			gitVersion := exec.Command("git", "rev-parse", "HEAD")
			gitVersion.Dir = gitRepo

			shaStr, err := gitVersion.Output()
			Expect(err).NotTo(HaveOccurred())

			sha := strings.TrimSpace(string(shaStr))

			Expect(result).To(HaveLen(1))
			Expect(result[0]["version"]).To(Equal(sha))
		})
	})

	Context("when the repo is at a particular ref", func() {
		It("succesfully checks", func() {
		})
	})

	Context("when provided a bogus SHA", func() {
		It("succesfully checks", func() {
		})
	})

	Context("when given a particular pool", func() {
		It("succesfully checks", func() {
		})
	})

	Context("when the repo cannot not be fast-fowarded", func() {
		It("succesfully checks", func() {
		})
	})

	Context("when provided with credentials", func() {
		It("adds to the .netrc to successfully check", func() {
		})
	})

	Context("when credentials no longer required", func() {
		It("clears out the .netrc", func() {
		})
	})
})
<commit_msg>handle bogus sha use case<commit_after>package integration_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"

	"github.com/onsi/gomega/gexec"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const simpleRequest = `
{
	"source": {
	"uri": "file://%s",
	"branch": "master",
	"pool": "lock-pool"
	}
}`

const versionRequest = `
{
	"source": {
	"uri": "file://%s",
	"branch": "master",
	"pool": "lock-pool"
	},
	"version": {"ref": %q}
}`

const poolRequest = `
{
	"source": {
	"uri": "file://%s",
	"branch": "master",
	"pool": %q
	},
	"version": {"ref": %q}
}`

type locksResponse map[string]string

var _ = Describe("Check", func() {
	var gitRepo string

	BeforeEach(func() {
		var err error

		gitRepo, err = ioutil.TempDir("", "git-repo")
		Expect(err).NotTo(HaveOccurred())

		setupGitRepo(gitRepo)
	})

	AfterEach(func() {
		err := os.RemoveAll(gitRepo)
		Expect(err).NotTo(HaveOccurred())
	})

	Context("when the repo is at HEAD", func() {
		It("succesfully checks", func() {
			cmd := exec.Command(builtCheck)
			cmd.Stdin = bytes.NewBufferString(fmt.Sprintf(simpleRequest, gitRepo))

			session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())

			<-session.Exited

			Expect(session.ExitCode()).To(Equal(0))

			var result []locksResponse
			err = json.NewDecoder(bytes.NewReader(session.Out.Contents())).Decode(&result)
			Expect(err).NotTo(HaveOccurred())

			gitVersion := exec.Command("git", "rev-parse", "HEAD")
			gitVersion.Dir = gitRepo

			shaStr, err := gitVersion.Output()
			Expect(err).NotTo(HaveOccurred())

			sha := strings.TrimSpace(string(shaStr))

			Expect(result).To(HaveLen(1))
			Expect(result[0]["ref"]).To(Equal(sha))
		})
	})

	Context("when the repo is at a particular ref", func() {
		It("succesfully checks", func() {
			addLockCommit(gitRepo)
			refTwo := addLockCommit(gitRepo)
			refThree := addLockCommit(gitRepo)

			checkData := fmt.Sprintf(versionRequest, gitRepo, refTwo)
			cmd := exec.Command(builtCheck)
			cmd.Stdin = bytes.NewBufferString(checkData)

			session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())

			<-session.Exited

			Expect(session.ExitCode()).To(Equal(0))

			var result []locksResponse
			err = json.NewDecoder(bytes.NewReader(session.Out.Contents())).Decode(&result)
			Expect(err).NotTo(HaveOccurred())

			Expect(result).To(HaveLen(2))
			Expect(result[0]["ref"]).To(Equal(refTwo))
			Expect(result[1]["ref"]).To(Equal(refThree))
		})
	})

	Context("when provided a bogus SHA", func() {
		It("succesfully checks", func() {
			addLockCommit(gitRepo)
			lastRef := addLockCommit(gitRepo)

			checkData := fmt.Sprintf(versionRequest, gitRepo, "bogus reference")
			cmd := exec.Command(builtCheck)
			cmd.Stdin = bytes.NewBufferString(checkData)

			session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())

			<-session.Exited

			Expect(session.ExitCode()).To(Equal(0))

			var result []locksResponse
			err = json.NewDecoder(bytes.NewReader(session.Out.Contents())).Decode(&result)
			Expect(err).NotTo(HaveOccurred())

			Expect(result).To(HaveLen(1))
			Expect(result[0]["ref"]).To(Equal(lastRef))
		})
	})

	Context("when given a particular pool", func() {
		It("succesfully checks", func() {
		})
	})

	Context("when the repo cannot not be fast-fowarded", func() {
		It("succesfully checks", func() {
		})
	})

	Context("when provided with credentials", func() {
		It("adds to the .netrc to successfully check", func() {
		})
	})

	Context("when credentials no longer required", func() {
		It("clears out the .netrc", func() {
		})
	})
})
<|endoftext|>


<commit_before>/*
 Copyright 2015 Bluek404

 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 downloader

import (
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"time"
)

var (
	// 最大线程数量
	MaxThread = 5
	// 缓冲区大小
	CacheSize = 1024

	// 文件名获取错误，用于精细错误处理
	GetFileNameErr = errors.New("can not get file name")

	// 用于获取文件名
	getName = regexp.MustCompile(`^.{0,}?([^/&=]+)$`)
	// 随机数生成器，生成ID用。放在全局以不用多次生成生成器
	r = rand.New(rand.NewSource(time.Now().UnixNano()))
)

// 创建新的文件下载
//
// 如果想定义其他属性，应手动创建 *FileDl
// 然后以ID为key添加到 DownloaderList
func NewFileDl(url string, storeDir string) (*FileDl, error) {
	// 获取文件信息
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	buf := getName.FindStringSubmatch(url)
	if len(buf) == 0 {
		return nil, GetFileNameErr
	}
	name := buf[1]

	size := resp.ContentLength

	id := newID()

	f := &FileDl{
		File: FileInfo{
			Name: name,
			Url:  url,
			Size: size,
		},
		ID:       id,
		StoreDir: storeDir,
	}

	return f, nil
}

func newID() string { return strconv.FormatInt(r.Int63(), 36) }

type FileDl struct {
	File     FileInfo
	ID       string // 任务ID
	StoreDir string // 保存到的目录

	onStart  func()
	onPause  func()
	onResume func()
	onDelete func()
	onFinish func()
	onError  func(errCode int, errStr string)

	paused bool
	status Status
}

// 开始下载
func (f *FileDl) Start() {
	go func() {
		err := f.openFile(true)
		if err != nil {
			f.touchOnError(0, err.Error())
			return
		}

		if f.File.Size <= 0 {
			f.File.blockList = append(f.File.blockList, block{0, -1})
		} else {
			blockSize := f.File.Size / int64(MaxThread)
			var begin int64
			// 数据平均分配给各个线程
			for i := 0; i < MaxThread; i++ {
				var end = (int64(i) + 1) * blockSize
				f.File.blockList = append(f.File.blockList, block{begin, end})
				begin = end + 1
			}
			// 将余出数据分配给最后一个线程
			f.File.blockList[MaxThread-1].End += f.File.Size - f.File.blockList[MaxThread-1].End
		}

		// 开始下载
		err = f.download()
		if err != nil {
			f.touchOnError(0, err.Error())
			return
		}
	}()

	f.touch(f.onStart)
}

// 用于创建文件
// 如果文件已存在则打开文件
// rewrite为true则直接覆盖文件
func (f *FileDl) openFile(rewrite bool) error {
	if f.File.f != nil {
		// 文件已经打开
		return nil
	}
	dirInfo, err := os.Stat(f.StoreDir)
	if err != nil {
		err = os.MkdirAll(f.StoreDir, 0644)
		if err != nil {
			return err
		}
	}
	if !dirInfo.IsDir() {
		return errors.New(f.StoreDir + " is a file")
	}

	var file *os.File
	filePath := f.StoreDir + string(os.PathSeparator) + f.File.Name

	fileInfo, err := os.Stat(filePath)
	if err != nil {
		// 文件不存在，创建
		file, err = os.Create(filePath)
		if err != nil {
			return err
		}
	}
	if fileInfo.IsDir() {
		return errors.New(filePath + " is a dir")
	}

	// 文件已存在，是否重写
	if rewrite {
		file, err = os.Create(filePath)
		if err != nil {
			return err
		}
	} else {
		file, err = os.Open(filePath)
		if err != nil {
			return err
		}
	}
	f.File.f = file

	return nil
}

func (f *FileDl) download() error {
	f.startGetSpeeds()

	ok := make(chan bool, MaxThread)
	for i := range f.File.blockList {
		// TODO: 断点续传支持
		go func(id int) {
			defer func() {
				ok <- true
			}()

			err := f.downloadBlock(id)
			if err != nil {
				// TODO: 自动重新下载块
			}
		}(i)
	}

	for i := 0; i < MaxThread; i++ {
		<-ok
	}
	// 检查是否为暂停
	if !f.paused {
		f.paused = true
		err := os.Remove(f.StoreDir + string(os.PathSeparator) + f.File.Name + ".dl")
		if err != nil {
			return err
		}
		f.touch(f.onFinish)
	}
	return nil
}

// 文件块下载器
// 根据线程ID获取下载块的起始位置
func (f *FileDl) downloadBlock(id int) error {
	request, err := http.NewRequest("GET", f.File.Url, nil)
	if err != nil {
		return err
	}
	begin := f.File.blockList[id].Begin
	end := f.File.blockList[id].End
	if end != -1 {
		request.Header.Set(
			"Range",
			"bytes="+strconv.FormatInt(begin, 10)+"-"+strconv.FormatInt(end, 10),
		)
	}

	resp, err := http.DefaultClient.Do(request)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var buf = make([]byte, CacheSize)
	for {
		switch {
		case f.paused == true:
			return nil
		default:
			n, e := resp.Body.Read(buf)

			func() {
				// 将缓冲数据写入硬盘
				f.File.f.WriteAt(buf[:n], f.File.blockList[id].Begin)
			}()

			// 保存块的下载信息。用于断点续传
			byt, err := json.Marshal(f.File.blockList)
			if err != nil {
				return err
			}
			err = ioutil.WriteFile(f.StoreDir+string(os.PathSeparator)+f.File.Name+".dl", byt, 0644)
			if err != nil {
				return err
			}

			f.status.Downloaded += int64(len(buf[:n]))
			f.File.blockList[id].Begin += int64(len(buf[:n]))

			if e != nil {
				if e == io.EOF {
					return nil
				}
				return e
			}
		}
	}

	return nil
}

func (f *FileDl) startGetSpeeds() {
	go func() {
		var old = f.status.Downloaded
		for {
			if f.paused {
				f.status.Speeds = 0
				return
			} else {
				time.Sleep(time.Second * 1)
				f.status.Speeds = f.status.Downloaded - old
				old = f.status.Downloaded
			}
		}
	}()
}

// 获取下载统计信息
func (f FileDl) GetStatus() Status {
	return f.status
}

// 暂停下载
func (f *FileDl) Pause() {
	f.paused = true
	f.touch(f.onPause)
}

// 继续下载
func (f *FileDl) Resume() {
	f.paused = false
	go func() {
		err := f.openFile(false)
		if err != nil {
			f.touchOnError(0, err.Error())
			return
		}

		if f.File.blockList == nil {
			byt, err := ioutil.ReadFile(f.StoreDir + string(os.PathSeparator) + f.File.Name + ".dl")
			if err != nil {
				f.touchOnError(0, err.Error())
				return
			}

			err = json.Unmarshal(byt, f.File.blockList)
			if err != nil {
				f.touchOnError(0, err.Error())
				return
			}
		}

		err = f.download()
		if err != nil {
			f.touchOnError(0, err.Error())
			return
		}
	}()
	f.touch(f.onResume)
}

// 任务开始时触发的事件
func (f *FileDl) OnStart(fn func()) {
	f.onStart = fn
}

// 任务暂停时触发的事件
func (f *FileDl) OnPause(fn func()) {
	f.onPause = fn
}

// 任务继续时触发的事件
func (f *FileDl) OnResume(fn func()) {
	f.onResume = fn
}

// 任务完成时触发的事件
func (f *FileDl) OnFinish(fn func()) {
	f.onFinish = fn
}

// 任务出错时触发的事件
//
// errCode为错误码，errStr为错误描述
func (f *FileDl) OnError(fn func(errCode int, errStr string)) {
	f.onError = fn
}

// 用于触发事件
func (f FileDl) touch(fn func()) {
	if fn != nil {
		go fn()
	}
}

// 触发Error事件
func (f FileDl) touchOnError(errCode int, errStr string) {
	if f.onError != nil {
		go f.onError(errCode, errStr)
	}
}

type FileInfo struct {
	Name string // 想要保存的文件名
	Url  string // 下载地址
	Size int64  // 文件大小

	f         *os.File
	blockList []block // 用于记录未下载的文件块起始位置
}

type Status struct {
	Downloaded int64
	Speeds     int64
}

type block struct {
	Begin int64 `json:"begin"`
	End   int64 `json:"end"`
}
<commit_msg>delete old noto<commit_after>/*
 Copyright 2015 Bluek404

 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 downloader

import (
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"time"
)

var (
	// 最大线程数量
	MaxThread = 5
	// 缓冲区大小
	CacheSize = 1024

	// 文件名获取错误，用于精细错误处理
	GetFileNameErr = errors.New("can not get file name")

	// 用于获取文件名
	getName = regexp.MustCompile(`^.{0,}?([^/&=]+)$`)
	// 随机数生成器，生成ID用。放在全局以不用多次生成生成器
	r = rand.New(rand.NewSource(time.Now().UnixNano()))
)

// 创建新的文件下载
//
// 如果想定义其他属性，应手动创建 *FileDl
func NewFileDl(url string, storeDir string) (*FileDl, error) {
	// 获取文件信息
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	buf := getName.FindStringSubmatch(url)
	if len(buf) == 0 {
		return nil, GetFileNameErr
	}
	name := buf[1]

	size := resp.ContentLength

	id := newID()

	f := &FileDl{
		File: FileInfo{
			Name: name,
			Url:  url,
			Size: size,
		},
		ID:       id,
		StoreDir: storeDir,
	}

	return f, nil
}

func newID() string { return strconv.FormatInt(r.Int63(), 36) }

type FileDl struct {
	File     FileInfo
	ID       string // 任务ID
	StoreDir string // 保存到的目录

	onStart  func()
	onPause  func()
	onResume func()
	onDelete func()
	onFinish func()
	onError  func(errCode int, errStr string)

	paused bool
	status Status
}

// 开始下载
func (f *FileDl) Start() {
	go func() {
		err := f.openFile(true)
		if err != nil {
			f.touchOnError(0, err.Error())
			return
		}

		if f.File.Size <= 0 {
			f.File.blockList = append(f.File.blockList, block{0, -1})
		} else {
			blockSize := f.File.Size / int64(MaxThread)
			var begin int64
			// 数据平均分配给各个线程
			for i := 0; i < MaxThread; i++ {
				var end = (int64(i) + 1) * blockSize
				f.File.blockList = append(f.File.blockList, block{begin, end})
				begin = end + 1
			}
			// 将余出数据分配给最后一个线程
			f.File.blockList[MaxThread-1].End += f.File.Size - f.File.blockList[MaxThread-1].End
		}

		// 开始下载
		err = f.download()
		if err != nil {
			f.touchOnError(0, err.Error())
			return
		}
	}()

	f.touch(f.onStart)
}

// 用于创建文件
// 如果文件已存在则打开文件
// rewrite为true则直接覆盖文件
func (f *FileDl) openFile(rewrite bool) error {
	if f.File.f != nil {
		// 文件已经打开
		return nil
	}
	dirInfo, err := os.Stat(f.StoreDir)
	if err != nil {
		err = os.MkdirAll(f.StoreDir, 0644)
		if err != nil {
			return err
		}
	}
	if !dirInfo.IsDir() {
		return errors.New(f.StoreDir + " is a file")
	}

	var file *os.File
	filePath := f.StoreDir + string(os.PathSeparator) + f.File.Name

	fileInfo, err := os.Stat(filePath)
	if err != nil {
		// 文件不存在，创建
		file, err = os.Create(filePath)
		if err != nil {
			return err
		}
	}
	if fileInfo.IsDir() {
		return errors.New(filePath + " is a dir")
	}

	// 文件已存在，是否重写
	if rewrite {
		file, err = os.Create(filePath)
		if err != nil {
			return err
		}
	} else {
		file, err = os.Open(filePath)
		if err != nil {
			return err
		}
	}
	f.File.f = file

	return nil
}

func (f *FileDl) download() error {
	f.startGetSpeeds()

	ok := make(chan bool, MaxThread)
	for i := range f.File.blockList {
		// TODO: 断点续传支持
		go func(id int) {
			defer func() {
				ok <- true
			}()

			err := f.downloadBlock(id)
			if err != nil {
				// TODO: 自动重新下载块
			}
		}(i)
	}

	for i := 0; i < MaxThread; i++ {
		<-ok
	}
	// 检查是否为暂停
	if !f.paused {
		f.paused = true
		err := os.Remove(f.StoreDir + string(os.PathSeparator) + f.File.Name + ".dl")
		if err != nil {
			return err
		}
		f.touch(f.onFinish)
	}
	return nil
}

// 文件块下载器
// 根据线程ID获取下载块的起始位置
func (f *FileDl) downloadBlock(id int) error {
	request, err := http.NewRequest("GET", f.File.Url, nil)
	if err != nil {
		return err
	}
	begin := f.File.blockList[id].Begin
	end := f.File.blockList[id].End
	if end != -1 {
		request.Header.Set(
			"Range",
			"bytes="+strconv.FormatInt(begin, 10)+"-"+strconv.FormatInt(end, 10),
		)
	}

	resp, err := http.DefaultClient.Do(request)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var buf = make([]byte, CacheSize)
	for {
		switch {
		case f.paused == true:
			return nil
		default:
			n, e := resp.Body.Read(buf)

			func() {
				// 将缓冲数据写入硬盘
				f.File.f.WriteAt(buf[:n], f.File.blockList[id].Begin)
			}()

			// 保存块的下载信息。用于断点续传
			byt, err := json.Marshal(f.File.blockList)
			if err != nil {
				return err
			}
			err = ioutil.WriteFile(f.StoreDir+string(os.PathSeparator)+f.File.Name+".dl", byt, 0644)
			if err != nil {
				return err
			}

			f.status.Downloaded += int64(len(buf[:n]))
			f.File.blockList[id].Begin += int64(len(buf[:n]))

			if e != nil {
				if e == io.EOF {
					return nil
				}
				return e
			}
		}
	}

	return nil
}

func (f *FileDl) startGetSpeeds() {
	go func() {
		var old = f.status.Downloaded
		for {
			if f.paused {
				f.status.Speeds = 0
				return
			} else {
				time.Sleep(time.Second * 1)
				f.status.Speeds = f.status.Downloaded - old
				old = f.status.Downloaded
			}
		}
	}()
}

// 获取下载统计信息
func (f FileDl) GetStatus() Status {
	return f.status
}

// 暂停下载
func (f *FileDl) Pause() {
	f.paused = true
	f.touch(f.onPause)
}

// 继续下载
func (f *FileDl) Resume() {
	f.paused = false
	go func() {
		err := f.openFile(false)
		if err != nil {
			f.touchOnError(0, err.Error())
			return
		}

		if f.File.blockList == nil {
			byt, err := ioutil.ReadFile(f.StoreDir + string(os.PathSeparator) + f.File.Name + ".dl")
			if err != nil {
				f.touchOnError(0, err.Error())
				return
			}

			err = json.Unmarshal(byt, f.File.blockList)
			if err != nil {
				f.touchOnError(0, err.Error())
				return
			}
		}

		err = f.download()
		if err != nil {
			f.touchOnError(0, err.Error())
			return
		}
	}()
	f.touch(f.onResume)
}

// 任务开始时触发的事件
func (f *FileDl) OnStart(fn func()) {
	f.onStart = fn
}

// 任务暂停时触发的事件
func (f *FileDl) OnPause(fn func()) {
	f.onPause = fn
}

// 任务继续时触发的事件
func (f *FileDl) OnResume(fn func()) {
	f.onResume = fn
}

// 任务完成时触发的事件
func (f *FileDl) OnFinish(fn func()) {
	f.onFinish = fn
}

// 任务出错时触发的事件
//
// errCode为错误码，errStr为错误描述
func (f *FileDl) OnError(fn func(errCode int, errStr string)) {
	f.onError = fn
}

// 用于触发事件
func (f FileDl) touch(fn func()) {
	if fn != nil {
		go fn()
	}
}

// 触发Error事件
func (f FileDl) touchOnError(errCode int, errStr string) {
	if f.onError != nil {
		go f.onError(errCode, errStr)
	}
}

type FileInfo struct {
	Name string // 想要保存的文件名
	Url  string // 下载地址
	Size int64  // 文件大小

	f         *os.File
	blockList []block // 用于记录未下载的文件块起始位置
}

type Status struct {
	Downloaded int64
	Speeds     int64
}

type block struct {
	Begin int64 `json:"begin"`
	End   int64 `json:"end"`
}
<|endoftext|>


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

package net

import (
	"io"
	"io/ioutil"
	"os"
	"runtime"
	"testing"
	"time"
)

func TestShutdown(t *testing.T) {
	if runtime.GOOS == "plan9" {
		t.Skipf("skipping test on %q", runtime.GOOS)
	}
	ln, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		if ln, err = Listen("tcp6", "[::1]:0"); err != nil {
			t.Fatalf("ListenTCP on :0: %v", err)
		}
	}

	go func() {
		defer ln.Close()
		c, err := ln.Accept()
		if err != nil {
			t.Fatalf("Accept: %v", err)
		}
		var buf [10]byte
		n, err := c.Read(buf[:])
		if n != 0 || err != io.EOF {
			t.Fatalf("server Read = %d, %v; want 0, io.EOF", n, err)
		}
		c.Write([]byte("response"))
		c.Close()
	}()

	c, err := Dial("tcp", ln.Addr().String())
	if err != nil {
		t.Fatalf("Dial: %v", err)
	}
	defer c.Close()

	err = c.(*TCPConn).CloseWrite()
	if err != nil {
		t.Fatalf("CloseWrite: %v", err)
	}
	var buf [10]byte
	n, err := c.Read(buf[:])
	if err != nil {
		t.Fatalf("client Read: %d, %v", n, err)
	}
	got := string(buf[:n])
	if got != "response" {
		t.Errorf("read = %q, want \"response\"", got)
	}
}

func TestShutdownUnix(t *testing.T) {
	switch runtime.GOOS {
	case "windows", "plan9":
		t.Skipf("skipping test on %q", runtime.GOOS)
	}
	f, err := ioutil.TempFile("", "go_net_unixtest")
	if err != nil {
		t.Fatalf("TempFile: %s", err)
	}
	f.Close()
	tmpname := f.Name()
	os.Remove(tmpname)
	ln, err := Listen("unix", tmpname)
	if err != nil {
		t.Fatalf("ListenUnix on %s: %s", tmpname, err)
	}
	defer func() {
		ln.Close()
		os.Remove(tmpname)
	}()

	go func() {
		c, err := ln.Accept()
		if err != nil {
			t.Fatalf("Accept: %v", err)
		}
		var buf [10]byte
		n, err := c.Read(buf[:])
		if n != 0 || err != io.EOF {
			t.Fatalf("server Read = %d, %v; want 0, io.EOF", n, err)
		}
		c.Write([]byte("response"))
		c.Close()
	}()

	c, err := Dial("unix", tmpname)
	if err != nil {
		t.Fatalf("Dial: %v", err)
	}
	defer c.Close()

	err = c.(*UnixConn).CloseWrite()
	if err != nil {
		t.Fatalf("CloseWrite: %v", err)
	}
	var buf [10]byte
	n, err := c.Read(buf[:])
	if err != nil {
		t.Fatalf("client Read: %d, %v", n, err)
	}
	got := string(buf[:n])
	if got != "response" {
		t.Errorf("read = %q, want \"response\"", got)
	}
}

func TestTCPListenClose(t *testing.T) {
	ln, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("Listen failed: %v", err)
	}

	done := make(chan bool, 1)
	go func() {
		time.Sleep(100 * time.Millisecond)
		ln.Close()
	}()
	go func() {
		c, err := ln.Accept()
		if err == nil {
			c.Close()
			t.Error("Accept succeeded")
		} else {
			t.Logf("Accept timeout error: %s (any error is fine)", err)
		}
		done <- true
	}()
	select {
	case <-done:
	case <-time.After(2 * time.Second):
		t.Fatal("timeout waiting for TCP close")
	}
}

func TestUDPListenClose(t *testing.T) {
	switch runtime.GOOS {
	case "plan9":
		t.Skipf("skipping test on %q", runtime.GOOS)
	}
	ln, err := ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("Listen failed: %v", err)
	}

	buf := make([]byte, 1000)
	done := make(chan bool, 1)
	go func() {
		time.Sleep(100 * time.Millisecond)
		ln.Close()
	}()
	go func() {
		_, _, err = ln.ReadFrom(buf)
		if err == nil {
			t.Error("ReadFrom succeeded")
		} else {
			t.Logf("ReadFrom timeout error: %s (any error is fine)", err)
		}
		done <- true
	}()
	select {
	case <-done:
	case <-time.After(2 * time.Second):
		t.Fatal("timeout waiting for UDP close")
	}
}

func TestTCPClose(t *testing.T) {
	switch runtime.GOOS {
	case "plan9":
		t.Skipf("skipping test on %q", runtime.GOOS)
	}
	l, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()

	read := func(r io.Reader) error {
		var m [1]byte
		_, err := r.Read(m[:])
		return err
	}

	go func() {
		c, err := Dial("tcp", l.Addr().String())
		if err != nil {
			t.Fatal(err)
		}

		go read(c)

		time.Sleep(10 * time.Millisecond)
		c.Close()
	}()

	c, err := l.Accept()
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	for err == nil {
		err = read(c)
	}
	if err != nil && err != io.EOF {
		t.Fatal(err)
	}
}

func TestErrorNil(t *testing.T) {
	c, err := Dial("tcp", "127.0.0.1:65535")
	if err == nil {
		t.Fatal("Dial 127.0.0.1:65535 succeeded")
	}
	if c != nil {
		t.Fatalf("Dial returned non-nil interface %T(%v) with err != nil", c, c)
	}

	// Make Listen fail by relistening on the same address.
	l, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal("Listen 127.0.0.1:0: %v", err)
	}
	defer l.Close()
	l1, err := Listen("tcp", l.Addr().String())
	if err == nil {
		t.Fatal("second Listen %v: %v", l.Addr(), err)
	}
	if l1 != nil {
		t.Fatalf("Listen returned non-nil interface %T(%v) with err != nil", l1, l1)
	}

	// Make ListenPacket fail by relistening on the same address.
	lp, err := ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		t.Fatal("Listen 127.0.0.1:0: %v", err)
	}
	defer lp.Close()
	lp1, err := ListenPacket("udp", lp.LocalAddr().String())
	if err == nil {
		t.Fatal("second Listen %v: %v", lp.LocalAddr(), err)
	}
	if lp1 != nil {
		t.Fatalf("ListenPacket returned non-nil interface %T(%v) with err != nil", lp1, lp1)
	}
}
<commit_msg>net: fix nits found by go tool vet<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net

import (
	"io"
	"io/ioutil"
	"os"
	"runtime"
	"testing"
	"time"
)

func TestShutdown(t *testing.T) {
	if runtime.GOOS == "plan9" {
		t.Skipf("skipping test on %q", runtime.GOOS)
	}
	ln, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		if ln, err = Listen("tcp6", "[::1]:0"); err != nil {
			t.Fatalf("ListenTCP on :0: %v", err)
		}
	}

	go func() {
		defer ln.Close()
		c, err := ln.Accept()
		if err != nil {
			t.Fatalf("Accept: %v", err)
		}
		var buf [10]byte
		n, err := c.Read(buf[:])
		if n != 0 || err != io.EOF {
			t.Fatalf("server Read = %d, %v; want 0, io.EOF", n, err)
		}
		c.Write([]byte("response"))
		c.Close()
	}()

	c, err := Dial("tcp", ln.Addr().String())
	if err != nil {
		t.Fatalf("Dial: %v", err)
	}
	defer c.Close()

	err = c.(*TCPConn).CloseWrite()
	if err != nil {
		t.Fatalf("CloseWrite: %v", err)
	}
	var buf [10]byte
	n, err := c.Read(buf[:])
	if err != nil {
		t.Fatalf("client Read: %d, %v", n, err)
	}
	got := string(buf[:n])
	if got != "response" {
		t.Errorf("read = %q, want \"response\"", got)
	}
}

func TestShutdownUnix(t *testing.T) {
	switch runtime.GOOS {
	case "windows", "plan9":
		t.Skipf("skipping test on %q", runtime.GOOS)
	}
	f, err := ioutil.TempFile("", "go_net_unixtest")
	if err != nil {
		t.Fatalf("TempFile: %s", err)
	}
	f.Close()
	tmpname := f.Name()
	os.Remove(tmpname)
	ln, err := Listen("unix", tmpname)
	if err != nil {
		t.Fatalf("ListenUnix on %s: %s", tmpname, err)
	}
	defer func() {
		ln.Close()
		os.Remove(tmpname)
	}()

	go func() {
		c, err := ln.Accept()
		if err != nil {
			t.Fatalf("Accept: %v", err)
		}
		var buf [10]byte
		n, err := c.Read(buf[:])
		if n != 0 || err != io.EOF {
			t.Fatalf("server Read = %d, %v; want 0, io.EOF", n, err)
		}
		c.Write([]byte("response"))
		c.Close()
	}()

	c, err := Dial("unix", tmpname)
	if err != nil {
		t.Fatalf("Dial: %v", err)
	}
	defer c.Close()

	err = c.(*UnixConn).CloseWrite()
	if err != nil {
		t.Fatalf("CloseWrite: %v", err)
	}
	var buf [10]byte
	n, err := c.Read(buf[:])
	if err != nil {
		t.Fatalf("client Read: %d, %v", n, err)
	}
	got := string(buf[:n])
	if got != "response" {
		t.Errorf("read = %q, want \"response\"", got)
	}
}

func TestTCPListenClose(t *testing.T) {
	ln, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("Listen failed: %v", err)
	}

	done := make(chan bool, 1)
	go func() {
		time.Sleep(100 * time.Millisecond)
		ln.Close()
	}()
	go func() {
		c, err := ln.Accept()
		if err == nil {
			c.Close()
			t.Error("Accept succeeded")
		} else {
			t.Logf("Accept timeout error: %s (any error is fine)", err)
		}
		done <- true
	}()
	select {
	case <-done:
	case <-time.After(2 * time.Second):
		t.Fatal("timeout waiting for TCP close")
	}
}

func TestUDPListenClose(t *testing.T) {
	switch runtime.GOOS {
	case "plan9":
		t.Skipf("skipping test on %q", runtime.GOOS)
	}
	ln, err := ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("Listen failed: %v", err)
	}

	buf := make([]byte, 1000)
	done := make(chan bool, 1)
	go func() {
		time.Sleep(100 * time.Millisecond)
		ln.Close()
	}()
	go func() {
		_, _, err = ln.ReadFrom(buf)
		if err == nil {
			t.Error("ReadFrom succeeded")
		} else {
			t.Logf("ReadFrom timeout error: %s (any error is fine)", err)
		}
		done <- true
	}()
	select {
	case <-done:
	case <-time.After(2 * time.Second):
		t.Fatal("timeout waiting for UDP close")
	}
}

func TestTCPClose(t *testing.T) {
	switch runtime.GOOS {
	case "plan9":
		t.Skipf("skipping test on %q", runtime.GOOS)
	}
	l, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()

	read := func(r io.Reader) error {
		var m [1]byte
		_, err := r.Read(m[:])
		return err
	}

	go func() {
		c, err := Dial("tcp", l.Addr().String())
		if err != nil {
			t.Fatal(err)
		}

		go read(c)

		time.Sleep(10 * time.Millisecond)
		c.Close()
	}()

	c, err := l.Accept()
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	for err == nil {
		err = read(c)
	}
	if err != nil && err != io.EOF {
		t.Fatal(err)
	}
}

func TestErrorNil(t *testing.T) {
	c, err := Dial("tcp", "127.0.0.1:65535")
	if err == nil {
		t.Fatal("Dial 127.0.0.1:65535 succeeded")
	}
	if c != nil {
		t.Fatalf("Dial returned non-nil interface %T(%v) with err != nil", c, c)
	}

	// Make Listen fail by relistening on the same address.
	l, err := Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("Listen 127.0.0.1:0: %v", err)
	}
	defer l.Close()
	l1, err := Listen("tcp", l.Addr().String())
	if err == nil {
		t.Fatalf("second Listen %v: %v", l.Addr(), err)
	}
	if l1 != nil {
		t.Fatalf("Listen returned non-nil interface %T(%v) with err != nil", l1, l1)
	}

	// Make ListenPacket fail by relistening on the same address.
	lp, err := ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("Listen 127.0.0.1:0: %v", err)
	}
	defer lp.Close()
	lp1, err := ListenPacket("udp", lp.LocalAddr().String())
	if err == nil {
		t.Fatalf("second Listen %v: %v", lp.LocalAddr(), err)
	}
	if lp1 != nil {
		t.Fatalf("ListenPacket returned non-nil interface %T(%v) with err != nil", lp1, lp1)
	}
}
<|endoftext|>


<commit_before>//////////////////////////////////////////
// archivex.go
// Jhonathan Paulo Banczek - 2014
// jpbanczek@gmail.com - jhoonb.com
//////////////////////////////////////////

package archivex

import (
	"archive/tar"
	"archive/zip"
	"io"
	"io/ioutil"
	// "log"
	"os"
	"path"
	"strings"
)

// interface
type Archivex interface {
	Create(name string) error
	Add(name string, file []byte) error
	AddFile(name string) error
	AddAll(dir string, includeCurrentFolder bool) error
	Close() error
}

// ArchiveWriteFunc is the closure used by an archive's AddAll method to actually put a file into an archive
// Note that for directory entries, this func will be called with a nil 'file' param
type ArchiveWriteFunc func(info os.FileInfo, file io.Reader, entryName string) (err error)

// ZipFile implement *zip.Writer
type ZipFile struct {
	Writer *zip.Writer
	Name   string
}

// TarFile implement *tar.Writer
type TarFile struct {
	Writer *tar.Writer
	Name   string
}

// Create new file zip
func (z *ZipFile) Create(name string) error {
	// check extension .zip
	if strings.HasSuffix(name, ".zip") != true {
		if strings.HasSuffix(name, ".tar.gz") == true {
			name = strings.Replace(name, ".tar.gz", ".zip", -1)
		} else {
			name = name + ".zip"
		}
	}
	z.Name = name
	file, err := os.Create(z.Name)
	if err != nil {
		return err
	}
	z.Writer = zip.NewWriter(file)
	return nil
}

// Add add byte in archive zip
func (z *ZipFile) Add(name string, file []byte) error {

	iow, err := z.Writer.Create(name)
	if err != nil {
		return err
	}
	_, err = iow.Write(file)
	return err
}

// AddFile add file from dir in archive
func (z *ZipFile) AddFile(name string) error {
	bytearq, err := ioutil.ReadFile(name)
	if err != nil {
		return err
	}
	filep, err := z.Writer.Create(name)
	if err != nil {
		return err
	}
	_, err = filep.Write(bytearq)
	if err != nil {
		return err
	}
	return nil
}

// AddAll adds all files from dir in archive, recursively.
// Directories receive a zero-size entry in the archive, with a trailing slash in the header name, and no compression
func (z *ZipFile) AddAll(dir string, includeCurrentFolder bool) error {
	dir = path.Clean(dir)
	return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) {

		// Create a header based off of the fileinfo
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}

		// If it's a file, set the compression method to deflate (leave directories uncompressed)
		if !info.IsDir() {
			header.Method = zip.Deflate
		}

		// Set the header's name to what we want--it may not include the top folder
		header.Name = entryName

		// Add a trailing slash if the entry is a directory
		if info.IsDir() {
			header.Name += string(os.PathSeparator)
		}

		// Get a writer in the archive based on our header
		writer, err := z.Writer.CreateHeader(header)
		if err != nil {
			return err
		}

		// If we have a file to write (i.e., not a directory) then pipe the file into the archive writer
		if file != nil {
			if _, err := io.Copy(writer, file); err != nil {
				return err
			}
		}

		return nil
	})
}

func (z *ZipFile) Close() error {
	err := z.Writer.Close()
	return err
}

// Create new Tar file
func (t *TarFile) Create(name string) error {
	// check extension .zip
	if strings.HasSuffix(name, ".tar.gz") != true {
		if strings.HasSuffix(name, ".zip") == true {
			name = strings.Replace(name, ".zip", ".tar.gz", -1)
		} else {
			name = name + ".tar.gz"
		}
	}
	t.Name = name
	file, err := os.Create(t.Name)
	if err != nil {
		return err
	}
	t.Writer = tar.NewWriter(file)
	return nil
}

// Add add byte in archive tar
func (t *TarFile) Add(name string, file []byte) error {

	hdr := &tar.Header{Name: name, Size: int64(len(file))}
	if err := t.Writer.WriteHeader(hdr); err != nil {
		return err
	}
	_, err := t.Writer.Write(file)
	return err
}

// AddFile add file from dir in archive tar
func (t *TarFile) AddFile(name string) error {
	bytearq, err := ioutil.ReadFile(name)
	if err != nil {
		return err
	}

	info, err := os.Stat(name)
	if err != nil {
		return err
	}

	header, err := tar.FileInfoHeader(info, "")
	if err != nil {
		return err
	}

	err = t.Writer.WriteHeader(header)
	if err != nil {
		return err
	}
	_, err = t.Writer.Write(bytearq)
	if err != nil {
		return err
	}
	return nil

}

// AddAll adds all files from dir in archive
// Tar does not support directories
func (t *TarFile) AddAll(dir string, includeCurrentFolder bool) error {
	dir = path.Clean(dir)
	return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) {

		// Skip directory entries
		if file == nil {
			return nil
		}

		// Create a header based off of the fileinfo
		header, err := tar.FileInfoHeader(info, "")
		if err != nil {
			return err
		}

		// Set the header's name to what we want--it may not include the top folder
		header.Name = entryName

		// Write the header into the tar file
		if err := t.Writer.WriteHeader(header); err != nil {
			return err
		}

		// Pipe the file into the tar
		if _, err := io.Copy(t.Writer, file); err != nil {
			return err
		}

		return nil
	})
}

// Close the file Tar
func (t *TarFile) Close() error {
	err := t.Writer.Close()
	return err
}

func getSubDir(dir string, rootDir string, includeCurrentFolder bool) (subDir string) {

	subDir = strings.Replace(dir, rootDir, "", 1)

	if includeCurrentFolder {
		parts := strings.Split(rootDir, string(os.PathSeparator))
		subDir = path.Join(parts[len(parts)-1], subDir)
	}

	return
}

// addAll is used to recursively go down through directories and add each file and directory to an archive, based on an ArchiveWriteFunc given to it
func addAll(dir string, rootDir string, includeCurrentFolder bool, writerFunc ArchiveWriteFunc) error {

	// Get a list of all entries in the directory, as []os.FileInfo
	fileInfos, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}

	// Loop through all entries
	for _, info := range fileInfos {

		full := path.Join(dir, info.Name())

		// If the entry is a file, get an io.Reader for it
		var file io.Reader
		if !info.IsDir() {
			file, err = os.Open(full)
			if err != nil {
				return err
			}
		}

		// Write the entry into the archive
		subDir := getSubDir(dir, rootDir, includeCurrentFolder)
		entryName := path.Join(subDir, info.Name())
		if err := writerFunc(info, file, entryName); err != nil {
			return err
		}

		// If the entry is a directory, recurse into it
		if info.IsDir() {
			addAll(full, rootDir, includeCurrentFolder, writerFunc)
		}
	}

	return nil
}
<commit_msg>fixes Add for tarfiles<commit_after>//////////////////////////////////////////
// archivex.go
// Jhonathan Paulo Banczek - 2014
// jpbanczek@gmail.com - jhoonb.com
//////////////////////////////////////////

package archivex

import (
	"archive/tar"
	"archive/zip"
	"io"
	"io/ioutil"
	// "log"
	"os"
	"path"
	"strings"
)

// interface
type Archivex interface {
	Create(name string) error
	Add(name string, file []byte) error
	AddFile(name string) error
	AddAll(dir string, includeCurrentFolder bool) error
	Close() error
}

// ArchiveWriteFunc is the closure used by an archive's AddAll method to actually put a file into an archive
// Note that for directory entries, this func will be called with a nil 'file' param
type ArchiveWriteFunc func(info os.FileInfo, file io.Reader, entryName string) (err error)

// ZipFile implement *zip.Writer
type ZipFile struct {
	Writer *zip.Writer
	Name   string
}

// TarFile implement *tar.Writer
type TarFile struct {
	Writer *tar.Writer
	Name   string
}

// Create new file zip
func (z *ZipFile) Create(name string) error {
	// check extension .zip
	if strings.HasSuffix(name, ".zip") != true {
		if strings.HasSuffix(name, ".tar.gz") == true {
			name = strings.Replace(name, ".tar.gz", ".zip", -1)
		} else {
			name = name + ".zip"
		}
	}
	z.Name = name
	file, err := os.Create(z.Name)
	if err != nil {
		return err
	}
	z.Writer = zip.NewWriter(file)
	return nil
}

// Add add byte in archive zip
func (z *ZipFile) Add(name string, file []byte) error {

	iow, err := z.Writer.Create(name)
	if err != nil {
		return err
	}
	_, err = iow.Write(file)
	return err
}

// AddFile add file from dir in archive
func (z *ZipFile) AddFile(name string) error {
	bytearq, err := ioutil.ReadFile(name)
	if err != nil {
		return err
	}
	filep, err := z.Writer.Create(name)
	if err != nil {
		return err
	}
	_, err = filep.Write(bytearq)
	if err != nil {
		return err
	}
	return nil
}

// AddAll adds all files from dir in archive, recursively.
// Directories receive a zero-size entry in the archive, with a trailing slash in the header name, and no compression
func (z *ZipFile) AddAll(dir string, includeCurrentFolder bool) error {
	dir = path.Clean(dir)
	return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) {

		// Create a header based off of the fileinfo
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}

		// If it's a file, set the compression method to deflate (leave directories uncompressed)
		if !info.IsDir() {
			header.Method = zip.Deflate
		}

		// Set the header's name to what we want--it may not include the top folder
		header.Name = entryName

		// Add a trailing slash if the entry is a directory
		if info.IsDir() {
			header.Name += string(os.PathSeparator)
		}

		// Get a writer in the archive based on our header
		writer, err := z.Writer.CreateHeader(header)
		if err != nil {
			return err
		}

		// If we have a file to write (i.e., not a directory) then pipe the file into the archive writer
		if file != nil {
			if _, err := io.Copy(writer, file); err != nil {
				return err
			}
		}

		return nil
	})
}

func (z *ZipFile) Close() error {
	err := z.Writer.Close()
	return err
}

// Create new Tar file
func (t *TarFile) Create(name string) error {
	// check extension .zip
	if strings.HasSuffix(name, ".tar.gz") != true {
		if strings.HasSuffix(name, ".zip") == true {
			name = strings.Replace(name, ".zip", ".tar.gz", -1)
		} else {
			name = name + ".tar.gz"
		}
	}
	t.Name = name
	file, err := os.Create(t.Name)
	if err != nil {
		return err
	}
	t.Writer = tar.NewWriter(file)
	return nil
}

// Add add byte in archive tar
func (t *TarFile) Add(name string, file []byte) error {

	hdr := &tar.Header{Name: name, Size: int64(len(file)), Mode: 0777}
	if err := t.Writer.WriteHeader(hdr); err != nil {
		return err
	}
	_, err := t.Writer.Write(file)
	return err
}

// AddFile add file from dir in archive tar
func (t *TarFile) AddFile(name string) error {
	bytearq, err := ioutil.ReadFile(name)
	if err != nil {
		return err
	}

	info, err := os.Stat(name)
	if err != nil {
		return err
	}

	header, err := tar.FileInfoHeader(info, "")
	if err != nil {
		return err
	}

	err = t.Writer.WriteHeader(header)
	if err != nil {
		return err
	}
	_, err = t.Writer.Write(bytearq)
	if err != nil {
		return err
	}
	return nil

}

// AddAll adds all files from dir in archive
// Tar does not support directories
func (t *TarFile) AddAll(dir string, includeCurrentFolder bool) error {
	dir = path.Clean(dir)
	return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) {

		// Skip directory entries
		if file == nil {
			return nil
		}

		// Create a header based off of the fileinfo
		header, err := tar.FileInfoHeader(info, "")
		if err != nil {
			return err
		}

		// Set the header's name to what we want--it may not include the top folder
		header.Name = entryName

		// Write the header into the tar file
		if err := t.Writer.WriteHeader(header); err != nil {
			return err
		}

		// Pipe the file into the tar
		if _, err := io.Copy(t.Writer, file); err != nil {
			return err
		}

		return nil
	})
}

// Close the file Tar
func (t *TarFile) Close() error {
	err := t.Writer.Close()
	return err
}

func getSubDir(dir string, rootDir string, includeCurrentFolder bool) (subDir string) {

	subDir = strings.Replace(dir, rootDir, "", 1)

	if includeCurrentFolder {
		parts := strings.Split(rootDir, string(os.PathSeparator))
		subDir = path.Join(parts[len(parts)-1], subDir)
	}

	return
}

// addAll is used to recursively go down through directories and add each file and directory to an archive, based on an ArchiveWriteFunc given to it
func addAll(dir string, rootDir string, includeCurrentFolder bool, writerFunc ArchiveWriteFunc) error {

	// Get a list of all entries in the directory, as []os.FileInfo
	fileInfos, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}

	// Loop through all entries
	for _, info := range fileInfos {

		full := path.Join(dir, info.Name())

		// If the entry is a file, get an io.Reader for it
		var file io.Reader
		if !info.IsDir() {
			file, err = os.Open(full)
			if err != nil {
				return err
			}
		}

		// Write the entry into the archive
		subDir := getSubDir(dir, rootDir, includeCurrentFolder)
		entryName := path.Join(subDir, info.Name())
		if err := writerFunc(info, file, entryName); err != nil {
			return err
		}

		// If the entry is a directory, recurse into it
		if info.IsDir() {
			addAll(full, rootDir, includeCurrentFolder, writerFunc)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// go-bot-labirint
package main

import (
	"fmt"
	"os"
	"strings"
//	"strconv"
  	"io/ioutil"
	"io"
	"net/http"
	"golang.org/x/net/html"
)

// структура книги
type dataBook struct {
	name  string // название книги
	autor string // автор
	year  int    // год издания
	kolpages int // кол-во стрниц
	ves  int   // вес книги
	price float32 // цена для всех (обычная)
	pricediscount float32 // цена со скидкой которая видна

}

// чтение файла с именем namefи возвращение содержимое файла, иначе текст ошибки
func readfiletxt(namef string) string {
	file, err := os.Open(namef)
	if err != nil {
		return "handle the error here"
	}
	defer file.Close()
	// get the file size
	stat, err := file.Stat()
	if err != nil {
		return "error here"
	}
	// read the file
	bs := make([]byte, stat.Size())
	_, err = file.Read(bs)
	if err != nil {
		return "error here"
	}
	return string(bs)
}

// чтение из текстового конфиг файла и возращает массив строк
func readcfgs(namef string) []string {
	str := readfiletxt(namef)
	vv := strings.Split(str, "\n")
	return vv
}

//сохранение строки str в файл с именем namef
func savestrtofile(namef string, str string) error {
	file, err := os.Create(namef)
	if err != nil {
		// handle the error here
		return err
	}
	defer file.Close()

	file.WriteString(str)
	return err
}

//----------------
// возвращает содержимое html страницы по урлу suri и ошибку
func gethtmlfromurl0(suri string) (string,error){
	resp, err := http.Get(suri)
  	body, err := ioutil.ReadAll(resp.Body)
	return string(body),err
}

// сохраняет содержимое html страницы по урлу suri в файл с именем namef и ошибку
func savehtmlfromurl0(suri string,namef string) (string,error) {
	// Create a new browser and open url.
    shtml,err:=gethtmlfromurl0(suri)
    if err != nil {
        panic(err)
    }else{
			err=savestrtofile(namef,shtml)
		}
	return shtml,err
}
//----------------

func gethtmlfromurl(suri string) (io.ReadCloser,error){
	resp, err := http.Get(suri)
  	body:=resp.Body
	//defer b.Close() // close Body when the function returns
	return body,err
}

//функция парсинга страницы shtml
func parsehtmlbookean(shtml io.Reader) dataBook {
	var resdata dataBook
	z := html.NewTokenizer(shtml)
	fmt.Println(z)
	return resdata
}

func main() {
	namestore:="bookean"
	namefurls:=namestore+"-url.cfg"
//	namefhtml:=namestore+"-page.html"

	fmt.Println("Start programm....!")
	// сохраняем урлы в файлы
	list_urls:=readcfgs(namefurls)
	for i:=0;i<len(list_urls)-1;i++{
		//s, _:=savehtmlfromurl(list_urls[i],strconv.Itoa(i)+namefhtml)
		s, _:=gethtmlfromurl(list_urls[i])
		fmt.Println(parsehtmlbookean(s))
	}
	fmt.Println("The end....!")
}
<commit_msg>add save dannih dliya knig<commit_after>// go-bot-labirint
package main

import (
	"fmt"
	"os"
	"strings"
	"strconv"
  	"io/ioutil"
	"io"
	"net/http"
	"time"
	"golang.org/x/net/html"
	"github.com/ddo/pick"
	"golang.org/x/net/html/charset"
)

// структура книги
type dataBook struct {
	name  string // название книги
	autor string // автор
	year  int    // год издания
	kolpages int // кол-во стрниц
	ves  int   // вес книги
	price int // цена для всех (обычная)
	pricediscount int // цена со скидкой которая видна
}

func namebook(httpBody io.Reader) []string {
  links := make([]string, 0)
  page := html.NewTokenizer(httpBody)
  for {
    tokenType := page.Next()
    if tokenType == html.ErrorToken {
      return links
    }
    token := page.Token()
    if tokenType == html.StartTagToken && token.DataAtom.String() == "meta" {
      for _, attr := range token.Attr {
        if attr.Key == "content" {
          links = append(links, attr.Val)
        }
      }
    }
  }
}

//парсинг Автора, массы и кол-во страниц в книге
func parsedescribebook(s []string) dataBook {
	var b dataBook
	for i:=0;i<len(s);i++ {
		switch s[i] {
			case "Автор(ы)": b.autor=s[i+1]
			case "Масса": b.ves,_=strconv.Atoi(s[i+1])
			case "Количество страниц": b.kolpages,_=strconv.Atoi(s[i+1])
		}
	}
	return b
}

//получение страницы из урла url
func gethtmlpage(url string) []byte {
	resp, err := http.Get(url)
    if err != nil {
        fmt.Println("HTTP error:", err)
		panic("HTTP error")
    }

    defer resp.Body.Close()
    // вот здесь и начинается самое интересное
    utf8, err := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
    if err != nil {
        fmt.Println("Encoding error:", err)
        panic("Encoding error")
    }
    // оп-па-ча, готово

//	fmt.Println(namebook(utf8))

    body, err := ioutil.ReadAll(utf8)
    if err != nil {
        fmt.Println("IO error:", err)
		panic("IO error")
    }
	return body
}

//----- разбор html страницы сайта Лабиринт
func parselabirintbook (shtml string) dataBook {
	var book dataBook

	scena, _ := pick.PickText(&pick.Option{   // текст цены книги
		&shtml,
		"span",
		&pick.Attr{
			"itemprop",
			"price",
		},
	})

	scenaskidka, _ := pick.PickText(&pick.Option{   // текст цены книги
		&shtml,
		"span",
		&pick.Attr{
			"class",
			"buying-pricenew-val-number",
		},
	})

	sauthor, _ := pick.PickText(&pick.Option{   // текст описания книги
		&shtml,
		"span",
		&pick.Attr{
			"itemtype",
			"http://schema.org/ItemList",
		},
	})

	stitle, _ := pick.PickText(&pick.Option{&shtml,"span",&pick.Attr{"itemprop","name"}})
	book=parsedescribebook(sauthor)
	book.name=stitle[1]
	if len(scenaskidka)>0 {
		book.pricediscount,_=strconv.Atoi(scenaskidka[0])
	}
	vv := strings.Split(scena[0], " ")
	book.price,_ =strconv.Atoi(vv[1])
	return book
}

func printbook (book dataBook) {
	fmt.Println("Автор: ",book.autor)
	fmt.Println("Название книги: ",book.name)
	fmt.Println("Вес: ",book.ves)
	fmt.Println("Кол-во страниц: ",book.kolpages)
	fmt.Println("Цена: ",book.price)
	fmt.Println("Цена со скидкой: ",book.pricediscount)
	return
}

// чтение файла с именем namefи возвращение содержимое файла, иначе текст ошибки
func readfiletxt(namef string) string {
	file, err := os.Open(namef)
	if err != nil {
		return "handle the error here"
	}
	defer file.Close()
	// get the file size
	stat, err := file.Stat()
	if err != nil {
		return "error here"
	}
	// read the file
	bs := make([]byte, stat.Size())
	_, err = file.Read(bs)
	if err != nil {
		return "error here"
	}
	return string(bs)
}

// чтение из текстового конфиг файла и возращает массив строк
func readcfgs(namef string) []string {
	str := readfiletxt(namef)
	vv := strings.Split(str, "\n")
	return vv
}

//сохранение строки str в файл с именем namef
func savestrtofile(namef string, str string) error {
	file, err := os.Create(namef)
	if err != nil {
		// handle the error here
		return err
	}
	defer file.Close()

	file.WriteString(str)
	return err
}

func getbooklabirint(url string) dataBook {
	body:=gethtmlpage(url)

	shtml := string(body)

	book:=parselabirintbook(shtml)

	return book
}

//сохранить данные dataBook в файл
// stitle:="Дата выгрузки;Автор;Название книги;Год издания;Кол-во стр.;Вес;Цена;Цена со скидкой"
func (db *dataBook) savetocsvfile(namef string) error {
	file, err := os.OpenFile(namef, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
		if err != nil {
		// handle the error here
		return err
	}
	defer file.Close()
    curdate := time.Now().String()
	str:=curdate+";"+db.autor+";"+db.name+";"+strconv.Itoa(db.year)+";"+strconv.Itoa(db.kolpages)+";"+strconv.Itoa(db.ves)+";"+strconv.Itoa(db.price)+";"+strconv.Itoa(db.pricediscount)+"\n"
	file.WriteString(str)
	return err
	return err
}

func main() {
	namestore:="labirint"
	namefurls:=namestore+"-url.cfg"
	//namecsv:=namestore+
////	namefhtml:=namestore+"-page.html"

	fmt.Println("Start programm....!")
//	// получаем урлы из файлы
    list_urls:=readcfgs(namefurls)

	for i:=0;i<len(list_urls);i++{
		book:=getbooklabirint(list_urls[i])
		book.savetocsvfile(book.name)
		printbook(book)
	}

	fmt.Println("The end....!")
}
<|endoftext|>