<commit_before>// +build windows

package windows

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"
	"unsafe"

	ansiterm "github.com/Azure/go-ansiterm"
	"github.com/Azure/go-ansiterm/winterm"
)

const (
	escapeSequence = ansiterm.KEY_ESC_CSI
)

// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation.
type ansiReader struct {
	file     *os.File
	fd       uintptr
	buffer   []byte
	cbBuffer int
	command  []byte
}

// NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a
// Windows console input handle.
func NewAnsiReader(nFile int) io.ReadCloser {
	initLogger()
	file, fd := winterm.GetStdFile(nFile)
	return &ansiReader{
		file:    file,
		fd:      fd,
		command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH),
		buffer:  make([]byte, 0),
	}
}

// Close closes the wrapped file.
func (ar *ansiReader) Close() (err error) {
	return ar.file.Close()
}

// Fd returns the file descriptor of the wrapped file.
func (ar *ansiReader) Fd() uintptr {
	return ar.fd
}

// Read reads up to len(p) bytes of translated input events into p.
func (ar *ansiReader) Read(p []byte) (int, error) {
	if len(p) == 0 {
		return 0, nil
	}

	// Previously read bytes exist, read as much as we can and return
	if len(ar.buffer) > 0 {
		logger.Debugf("Reading previously cached bytes")

		originalLength := len(ar.buffer)
		copiedLength := copy(p, ar.buffer)

		if copiedLength == originalLength {
			ar.buffer = make([]byte, 0, len(p))
		} else {
			ar.buffer = ar.buffer[copiedLength:]
		}

		logger.Debugf("Read from cache p[%d]: % x", copiedLength, p)
		return copiedLength, nil
	}

	// Read and translate key events
	events, err := readInputEvents(ar.fd, len(p))
	if err != nil {
		return 0, err
	} else if len(events) == 0 {
		logger.Debug("No input events detected")
		return 0, nil
	}

	keyBytes := translateKeyEvents(events, []byte(escapeSequence))

	// Save excess bytes and right-size keyBytes
	if len(keyBytes) > len(p) {
		logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p))
		ar.buffer = keyBytes[len(p):]
		keyBytes = keyBytes[:len(p)]
	} else if len(keyBytes) == 0 {
		logger.Debug("No key bytes returned from the translator")
		return 0, nil
	}

	copiedLength := copy(p, keyBytes)
	if copiedLength != len(keyBytes) {
		return 0, errors.New("unexpected copy length encountered")
	}

	logger.Debugf("Read        p[%d]: % x", copiedLength, p)
	logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes)
	return copiedLength, nil
}

// readInputEvents polls until at least one event is available.
func readInputEvents(fd uintptr, maxBytes int) ([]winterm.INPUT_RECORD, error) {
	// Determine the maximum number of records to retrieve
	// -- Cast around the type system to obtain the size of a single INPUT_RECORD.
	//    unsafe.Sizeof requires an expression vs. a type-reference; the casting
	//    tricks the type system into believing it has such an expression.
	recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes)))))
	countRecords := maxBytes / recordSize
	if countRecords > ansiterm.MAX_INPUT_EVENTS {
		countRecords = ansiterm.MAX_INPUT_EVENTS
	}
	logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize)

	// Wait for and read input events
	events := make([]winterm.INPUT_RECORD, countRecords)
	nEvents := uint32(0)
	eventsExist, err := winterm.WaitForSingleObject(fd, winterm.WAIT_INFINITE)
	if err != nil {
		return nil, err
	}

	if eventsExist {
		err = winterm.ReadConsoleInput(fd, events, &nEvents)
		if err != nil {
			return nil, err
		}
	}

	// Return a slice restricted to the number of returned records
	logger.Debugf("[windows] readInputEvents: Read %v events", nEvents)
	return events[:nEvents], nil
}

// KeyEvent Translation Helpers

var arrowKeyMapPrefix = map[uint16]string{
	winterm.VK_UP:    "%s%sA",
	winterm.VK_DOWN:  "%s%sB",
	winterm.VK_RIGHT: "%s%sC",
	winterm.VK_LEFT:  "%s%sD",
}

var keyMapPrefix = map[uint16]string{
	winterm.VK_UP:     "\x1B[%sA",
	winterm.VK_DOWN:   "\x1B[%sB",
	winterm.VK_RIGHT:  "\x1B[%sC",
	winterm.VK_LEFT:   "\x1B[%sD",
	winterm.VK_HOME:   "\x1B[1%s~", // showkey shows ^[[1
	winterm.VK_END:    "\x1B[4%s~", // showkey shows ^[[4
	winterm.VK_INSERT: "\x1B[2%s~",
	winterm.VK_DELETE: "\x1B[3%s~",
	winterm.VK_PRIOR:  "\x1B[5%s~",
	winterm.VK_NEXT:   "\x1B[6%s~",
	winterm.VK_F1:     "",
	winterm.VK_F2:     "",
	winterm.VK_F3:     "\x1B[13%s~",
	winterm.VK_F4:     "\x1B[14%s~",
	winterm.VK_F5:     "\x1B[15%s~",
	winterm.VK_F6:     "\x1B[17%s~",
	winterm.VK_F7:     "\x1B[18%s~",
	winterm.VK_F8:     "\x1B[19%s~",
	winterm.VK_F9:     "\x1B[20%s~",
	winterm.VK_F10:    "\x1B[21%s~",
	winterm.VK_F11:    "\x1B[23%s~",
	winterm.VK_F12:    "\x1B[24%s~",
}

// translateKeyEvents converts the input events into the appropriate ANSI string.
func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte {
	var buffer bytes.Buffer
	for _, event := range events {
		if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 {
			buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence))
		}
	}

	return buffer.Bytes()
}

// keyToString maps the given input event record to the corresponding string.
func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) string {
	if keyEvent.UnicodeChar == 0 {
		return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
	}

	_, alt, control := getControlKeys(keyEvent.ControlKeyState)
	if control {
		// TODO(azlinux): Implement following control sequences
		// <Ctrl>-D  Signals the end of input from the keyboard; also exits current shell.
		// <Ctrl>-H  Deletes the first character to the left of the cursor. Also called the ERASE key.
		// <Ctrl>-Q  Restarts printing after it has been stopped with <Ctrl>-s.
		// <Ctrl>-S  Suspends printing on the screen (does not stop the program).
		// <Ctrl>-U  Deletes all characters on the current line. Also called the KILL key.
		// <Ctrl>-E  Quits current command and creates a core

	}

	// <Alt>+Key generates ESC N Key
	if !control && alt {
		return ansiterm.KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar))
	}

	return string(keyEvent.UnicodeChar)
}

// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string.
func formatVirtualKey(key uint16, controlState uint32, escapeSequence []byte) string {
	shift, alt, control := getControlKeys(controlState)
	modifier := getControlKeysModifier(shift, alt, control)

	if format, ok := arrowKeyMapPrefix[key]; ok {
		return fmt.Sprintf(format, escapeSequence, modifier)
	}

	if format, ok := keyMapPrefix[key]; ok {
		return fmt.Sprintf(format, modifier)
	}

	return ""
}

// getControlKeys extracts the shift, alt, and ctrl key states.
func getControlKeys(controlState uint32) (shift, alt, control bool) {
	shift = 0 != (controlState & winterm.SHIFT_PRESSED)
	alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED))
	control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED))
	return shift, alt, control
}

// getControlKeysModifier returns the ANSI modifier for the given combination of control keys.
func getControlKeysModifier(shift, alt, control bool) string {
	if shift && alt && control {
		return ansiterm.KEY_CONTROL_PARAM_8
	}
	if alt && control {
		return ansiterm.KEY_CONTROL_PARAM_7
	}
	if shift && control {
		return ansiterm.KEY_CONTROL_PARAM_6
	}
	if control {
		return ansiterm.KEY_CONTROL_PARAM_5
	}
	if shift && alt {
		return ansiterm.KEY_CONTROL_PARAM_4
	}
	if alt {
		return ansiterm.KEY_CONTROL_PARAM_3
	}
	if shift {
		return ansiterm.KEY_CONTROL_PARAM_2
	}
	return ""
}
<commit_msg>Windows: Fix crash in docker system prune<commit_after>// +build windows

package windows

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"
	"unsafe"

	ansiterm "github.com/Azure/go-ansiterm"
	"github.com/Azure/go-ansiterm/winterm"
)

const (
	escapeSequence = ansiterm.KEY_ESC_CSI
)

// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation.
type ansiReader struct {
	file     *os.File
	fd       uintptr
	buffer   []byte
	cbBuffer int
	command  []byte
}

// NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a
// Windows console input handle.
func NewAnsiReader(nFile int) io.ReadCloser {
	initLogger()
	file, fd := winterm.GetStdFile(nFile)
	return &ansiReader{
		file:    file,
		fd:      fd,
		command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH),
		buffer:  make([]byte, 0),
	}
}

// Close closes the wrapped file.
func (ar *ansiReader) Close() (err error) {
	return ar.file.Close()
}

// Fd returns the file descriptor of the wrapped file.
func (ar *ansiReader) Fd() uintptr {
	return ar.fd
}

// Read reads up to len(p) bytes of translated input events into p.
func (ar *ansiReader) Read(p []byte) (int, error) {
	if len(p) == 0 {
		return 0, nil
	}

	// Previously read bytes exist, read as much as we can and return
	if len(ar.buffer) > 0 {
		logger.Debugf("Reading previously cached bytes")

		originalLength := len(ar.buffer)
		copiedLength := copy(p, ar.buffer)

		if copiedLength == originalLength {
			ar.buffer = make([]byte, 0, len(p))
		} else {
			ar.buffer = ar.buffer[copiedLength:]
		}

		logger.Debugf("Read from cache p[%d]: % x", copiedLength, p)
		return copiedLength, nil
	}

	// Read and translate key events
	events, err := readInputEvents(ar.fd, len(p))
	if err != nil {
		return 0, err
	} else if len(events) == 0 {
		logger.Debug("No input events detected")
		return 0, nil
	}

	keyBytes := translateKeyEvents(events, []byte(escapeSequence))

	// Save excess bytes and right-size keyBytes
	if len(keyBytes) > len(p) {
		logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p))
		ar.buffer = keyBytes[len(p):]
		keyBytes = keyBytes[:len(p)]
	} else if len(keyBytes) == 0 {
		logger.Debug("No key bytes returned from the translator")
		return 0, nil
	}

	copiedLength := copy(p, keyBytes)
	if copiedLength != len(keyBytes) {
		return 0, errors.New("unexpected copy length encountered")
	}

	logger.Debugf("Read        p[%d]: % x", copiedLength, p)
	logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes)
	return copiedLength, nil
}

// readInputEvents polls until at least one event is available.
func readInputEvents(fd uintptr, maxBytes int) ([]winterm.INPUT_RECORD, error) {
	// Determine the maximum number of records to retrieve
	// -- Cast around the type system to obtain the size of a single INPUT_RECORD.
	//    unsafe.Sizeof requires an expression vs. a type-reference; the casting
	//    tricks the type system into believing it has such an expression.
	recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes)))))
	countRecords := maxBytes / recordSize
	if countRecords > ansiterm.MAX_INPUT_EVENTS {
		countRecords = ansiterm.MAX_INPUT_EVENTS
	} else if countRecords == 0 {
		countRecords = 1
	}
	logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize)

	// Wait for and read input events
	events := make([]winterm.INPUT_RECORD, countRecords)
	nEvents := uint32(0)
	eventsExist, err := winterm.WaitForSingleObject(fd, winterm.WAIT_INFINITE)
	if err != nil {
		return nil, err
	}

	if eventsExist {
		err = winterm.ReadConsoleInput(fd, events, &nEvents)
		if err != nil {
			return nil, err
		}
	}

	// Return a slice restricted to the number of returned records
	logger.Debugf("[windows] readInputEvents: Read %v events", nEvents)
	return events[:nEvents], nil
}

// KeyEvent Translation Helpers

var arrowKeyMapPrefix = map[uint16]string{
	winterm.VK_UP:    "%s%sA",
	winterm.VK_DOWN:  "%s%sB",
	winterm.VK_RIGHT: "%s%sC",
	winterm.VK_LEFT:  "%s%sD",
}

var keyMapPrefix = map[uint16]string{
	winterm.VK_UP:     "\x1B[%sA",
	winterm.VK_DOWN:   "\x1B[%sB",
	winterm.VK_RIGHT:  "\x1B[%sC",
	winterm.VK_LEFT:   "\x1B[%sD",
	winterm.VK_HOME:   "\x1B[1%s~", // showkey shows ^[[1
	winterm.VK_END:    "\x1B[4%s~", // showkey shows ^[[4
	winterm.VK_INSERT: "\x1B[2%s~",
	winterm.VK_DELETE: "\x1B[3%s~",
	winterm.VK_PRIOR:  "\x1B[5%s~",
	winterm.VK_NEXT:   "\x1B[6%s~",
	winterm.VK_F1:     "",
	winterm.VK_F2:     "",
	winterm.VK_F3:     "\x1B[13%s~",
	winterm.VK_F4:     "\x1B[14%s~",
	winterm.VK_F5:     "\x1B[15%s~",
	winterm.VK_F6:     "\x1B[17%s~",
	winterm.VK_F7:     "\x1B[18%s~",
	winterm.VK_F8:     "\x1B[19%s~",
	winterm.VK_F9:     "\x1B[20%s~",
	winterm.VK_F10:    "\x1B[21%s~",
	winterm.VK_F11:    "\x1B[23%s~",
	winterm.VK_F12:    "\x1B[24%s~",
}

// translateKeyEvents converts the input events into the appropriate ANSI string.
func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte {
	var buffer bytes.Buffer
	for _, event := range events {
		if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 {
			buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence))
		}
	}

	return buffer.Bytes()
}

// keyToString maps the given input event record to the corresponding string.
func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) string {
	if keyEvent.UnicodeChar == 0 {
		return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
	}

	_, alt, control := getControlKeys(keyEvent.ControlKeyState)
	if control {
		// TODO(azlinux): Implement following control sequences
		// <Ctrl>-D  Signals the end of input from the keyboard; also exits current shell.
		// <Ctrl>-H  Deletes the first character to the left of the cursor. Also called the ERASE key.
		// <Ctrl>-Q  Restarts printing after it has been stopped with <Ctrl>-s.
		// <Ctrl>-S  Suspends printing on the screen (does not stop the program).
		// <Ctrl>-U  Deletes all characters on the current line. Also called the KILL key.
		// <Ctrl>-E  Quits current command and creates a core

	}

	// <Alt>+Key generates ESC N Key
	if !control && alt {
		return ansiterm.KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar))
	}

	return string(keyEvent.UnicodeChar)
}

// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string.
func formatVirtualKey(key uint16, controlState uint32, escapeSequence []byte) string {
	shift, alt, control := getControlKeys(controlState)
	modifier := getControlKeysModifier(shift, alt, control)

	if format, ok := arrowKeyMapPrefix[key]; ok {
		return fmt.Sprintf(format, escapeSequence, modifier)
	}

	if format, ok := keyMapPrefix[key]; ok {
		return fmt.Sprintf(format, modifier)
	}

	return ""
}

// getControlKeys extracts the shift, alt, and ctrl key states.
func getControlKeys(controlState uint32) (shift, alt, control bool) {
	shift = 0 != (controlState & winterm.SHIFT_PRESSED)
	alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED))
	control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED))
	return shift, alt, control
}

// getControlKeysModifier returns the ANSI modifier for the given combination of control keys.
func getControlKeysModifier(shift, alt, control bool) string {
	if shift && alt && control {
		return ansiterm.KEY_CONTROL_PARAM_8
	}
	if alt && control {
		return ansiterm.KEY_CONTROL_PARAM_7
	}
	if shift && control {
		return ansiterm.KEY_CONTROL_PARAM_6
	}
	if control {
		return ansiterm.KEY_CONTROL_PARAM_5
	}
	if shift && alt {
		return ansiterm.KEY_CONTROL_PARAM_4
	}
	if alt {
		return ansiterm.KEY_CONTROL_PARAM_3
	}
	if shift {
		return ansiterm.KEY_CONTROL_PARAM_2
	}
	return ""
}
<|endoftext|>


<commit_before>package main

import "os/exec"
import "strings"
import "runtime"
import "color"
import "os"

func main() {

  Green := color.New(color.FgGreen)
  BoldGreen := Green.Add(color.Bold)
  Yellow := color.New(color.FgYellow)
  BoldYellow := Yellow.Add(color.Bold)
  Red := color.New(color.FgRed)
  BoldRed := Red.Add(color.Bold)
  White := color.New(color.FgWhite)
  BoldWhite := White.Add(color.Bold)

  color.Red(" ██░ ██ ▓█████  ██▀███   ▄████▄   █    ██  ██▓    ▓█████   ██████ ")
  color.Red("▓██░ ██▒▓█   ▀ ▓██ ▒ ██▒▒██▀ ▀█   ██  ▓██▒▓██▒    ▓█   ▀ ▒██    ▒ ")
  color.Red("▒██▀▀██░▒███   ▓██ ░▄█ ▒▒▓█    ▄ ▓██  ▒██░▒██░    ▒███   ░ ▓██▄   ")
  color.Red("░▓█ ░██ ▒▓█  ▄ ▒██▀▀█▄  ▒▓▓▄ ▄██▒▓▓█  ░██░▒██░    ▒▓█  ▄   ▒   ██▒")
  color.Red("░▓█▒░██▓░▒████▒░██▓ ▒██▒▒ ▓███▀ ░▒▒█████▓ ░██████▒░▒████▒▒██████▒▒")
  color.Red(" ▒ ░░▒░▒░░ ▒░ ░░ ▒▓ ░▒▓░░ ░▒ ▒  ░░▒▓▒ ▒ ▒ ░ ▒░▓  ░░░ ▒░ ░▒ ▒▓▒ ▒ ░")
  color.Red(" ▒ ░▒░ ░ ░ ░  ░  ░▒ ░ ▒░  ░  ▒   ░░▒░ ░ ░ ░ ░ ▒  ░ ░ ░  ░░ ░▒  ░ ░")
  color.Red(" ░  ░░ ░   ░     ░░   ░ ░         ░░░ ░ ░   ░ ░      ░   ░  ░  ░  ")
  color.Red(" ░  ░  ░   ░  ░   ░     ░ ░         ░         ░  ░   ░  ░      ░  ")
  color.Red("                        ░                                         ")

  color.Green("\n+ -- --=[      HERCULES FRAMEWORK           ]")
  BoldGreen.Println("+ -- --=[            Ege Balcı              ]")

  BoldWhite.Println("\n\n[*] STARTING HERCULES SETUP \n")

  BoldYellow.Println("[*] Detecting OS...")

  if runtime.GOOS == "linux" {
    OsVersion, _ := exec.Command("sh", "-c", "uname -a").Output()
    BoldYellow.Println("[*] OS Detected : " + string(OsVersion))
    BoldYellow.Println("[*] Installing golang...")
    Go := exec.Command("sh", "-c", "apt-get install golang")
    Go.Stdout = os.Stdout
    Go.Stdin = os.Stdin
    Go.Run()
    BoldYellow.Println("[*] Installing upx...")
    UPX := exec.Command("sh", "-c", "apt-get install upx")
    UPX.Stdout = os.Stdout
    UPX.Stdin = os.Stdin
    UPX.Run()
    BoldYellow.Println("[*] Installing openssl...")
    OSSL := exec.Command("sh", "-c", "apt-get install openssl")
    OSSL.Stdout = os.Stdout
    OSSL.Stdin = os.Stdin
    OSSL.Run()
    BoldYellow.Println("[*] Installing git...")
    Git := exec.Command("sh", "-c", "apt-get install git")
    Git.Stdout = os.Stdout
    Git.Stdin = os.Stdin
    Git.Run()

    BoldYellow.Println("[*] Cloning EGESPLOIT Library...")
    exec.Command("sh", "-c", "git clone https://github.com/EgeBalci/EGESPLOIT.git").Run()
    exec.Command("sh", "-c", "mv EGESPLOIT /usr/lib/go/src/").Run()

    BoldYellow.Println("[*] Cloning color Library...")
    exec.Command("sh", "-c", "git clone https://github.com/fatih/color.git").Run()
    exec.Command("sh", "-c", "mv color /usr/lib/go/src/").Run()

    Stat, Err := CheckValid()

    if Stat == false {
      BoldYellow.Println("\n")
      BoldRed.Println(Err)
    }else{
      BoldGreen.Println("\n\n[+] Setup completed successfully")
    }

  }else if runtime.GOOS != "linux" {
    BoldRed.Println("[!] ERROR : HERCULES+ only supports linux distributions")
  }

}

func CheckValid()  (bool, string){
  OutESP, _ := exec.Command("sh", "-c", "cd /usr/lib/go/src/ && ls").Output()
  if (!strings.Contains(string(OutESP), "EGESPLOIT")) {
    return false, "[!] ERROR : EGESPLOIT library is not installed"
  }

  OutCL, _ := exec.Command("sh", "-c", "cd /usr/lib/go/src/ && ls").Output()
  if (!strings.Contains(string(OutCL), "color")) {
    return false, "[!] ERROR : color library is not installed"
  }

  OutUPX, _ := exec.Command("sh", "-c", "upx").Output()
  if (!strings.Contains(string(OutUPX), "Copyright")) {
    return false, "[!] ERROR : upx is not installed"
  }

  OutGO, _ := exec.Command("sh", "-c", "go").Output()
  if (!strings.Contains(string(OutGO), "command")) {
    return false, "[!] ERROR : golang is not installed"
  }

  return true, ""

}
<commit_msg>Update Setup.go<commit_after>package main

import "os/exec"
import "strings"
import "runtime"
import "color"
import "os"

func main() {

  Green := color.New(color.FgGreen)
  BoldGreen := Green.Add(color.Bold)
  Yellow := color.New(color.FgYellow)
  BoldYellow := Yellow.Add(color.Bold)
  Red := color.New(color.FgRed)
  BoldRed := Red.Add(color.Bold)
  White := color.New(color.FgWhite)
  BoldWhite := White.Add(color.Bold)

  color.Red(" ██░ ██ ▓█████  ██▀███   ▄████▄   █    ██  ██▓    ▓█████   ██████ ")
  color.Red("▓██░ ██▒▓█   ▀ ▓██ ▒ ██▒▒██▀ ▀█   ██  ▓██▒▓██▒    ▓█   ▀ ▒██    ▒ ")
  color.Red("▒██▀▀██░▒███   ▓██ ░▄█ ▒▒▓█    ▄ ▓██  ▒██░▒██░    ▒███   ░ ▓██▄   ")
  color.Red("░▓█ ░██ ▒▓█  ▄ ▒██▀▀█▄  ▒▓▓▄ ▄██▒▓▓█  ░██░▒██░    ▒▓█  ▄   ▒   ██▒")
  color.Red("░▓█▒░██▓░▒████▒░██▓ ▒██▒▒ ▓███▀ ░▒▒█████▓ ░██████▒░▒████▒▒██████▒▒")
  color.Red(" ▒ ░░▒░▒░░ ▒░ ░░ ▒▓ ░▒▓░░ ░▒ ▒  ░░▒▓▒ ▒ ▒ ░ ▒░▓  ░░░ ▒░ ░▒ ▒▓▒ ▒ ░")
  color.Red(" ▒ ░▒░ ░ ░ ░  ░  ░▒ ░ ▒░  ░  ▒   ░░▒░ ░ ░ ░ ░ ▒  ░ ░ ░  ░░ ░▒  ░ ░")
  color.Red(" ░  ░░ ░   ░     ░░   ░ ░         ░░░ ░ ░   ░ ░      ░   ░  ░  ░  ")
  color.Red(" ░  ░  ░   ░  ░   ░     ░ ░         ░         ░  ░   ░  ░      ░  ")
  color.Red("                        ░                                         ")

  color.Green("\n+ -- --=[      HERCULES FRAMEWORK           ]")
  BoldGreen.Println("+ -- --=[            Ege Balcı              ]")

  BoldWhite.Println("\n\n[*] STARTING HERCULES SETUP \n")

  BoldYellow.Println("[*] Detecting OS...")

  if runtime.GOOS == "linux" {
    OsVersion, _ := exec.Command("sh", "-c", "uname -a").Output()
    BoldYellow.Println("[*] OS Detected : " + string(OsVersion))
    BoldYellow.Println("[*] Installing golang...")
    Go := exec.Command("sh", "-c", "apt-get install golang")
    Go.Stdout = os.Stdout
    Go.Stdin = os.Stdin
    Go.Run()
    BoldYellow.Println("[*] Installing upx...")
    UPX := exec.Command("sh", "-c", "apt-get install upx")
    UPX.Stdout = os.Stdout
    UPX.Stdin = os.Stdin
    UPX.Run()
    BoldYellow.Println("[*] Installing openssl...")
    OSSL := exec.Command("sh", "-c", "apt-get install openssl")
    OSSL.Stdout = os.Stdout
    OSSL.Stdin = os.Stdin
    OSSL.Run()
    BoldYellow.Println("[*] Installing git...")
    Git := exec.Command("sh", "-c", "apt-get install git")
    Git.Stdout = os.Stdout
    Git.Stdin = os.Stdin
    Git.Run()

    BoldYellow.Println("[*] Cloning EGESPLOIT Library...")
    exec.Command("sh", "-c", "git clone https://github.com/EgeBalci/EGESPLOIT.git").Run()
    exec.Command("sh", "-c", "mv EGESPLOIT /usr/lib/go/src/").Run()

    BoldYellow.Println("[*] Cloning color Library...")
    exec.Command("sh", "-c", "git clone https://github.com/fatih/color.git").Run()
    exec.Command("sh", "-c", "mv color /usr/lib/go/src/").Run()

    Stat, Err := CheckValid()

    if Stat == false {
      BoldYellow.Println("\n")
      BoldRed.Println(Err)
    }else{
      BoldGreen.Println("\n\n[+] Setup completed successfully")
    }

  }else if runtime.GOOS != "linux" {
    BoldRed.Println("[!] ERROR : HERCULES+ only supports linux distributions")
  }

}

func CheckValid()  (bool, string){
  OutESP, _ := exec.Command("sh", "-c", "cd /usr/lib/go/src/ && ls").Output()
  if (!strings.Contains(string(OutESP), "EGESPLOIT")) {
    return false, "[!] ERROR : EGESPLOIT library is not installed"
  }

  OutCL, _ := exec.Command("sh", "-c", "cd /usr/lib/go/src/ && ls").Output()
  if (!strings.Contains(string(OutCL), "color")) {
    return false, "[!] ERROR : color library is not installed"
  }

  OutUPX, _ := exec.Command("sh", "-c", "upx").Output()
  if (!strings.Contains(string(OutUPX), "Copyright")) {
    return false, "[!] ERROR : upx is not installed"
  }

  OutGO, _ := exec.Command("sh", "-c", "go version").Output()
  if (!strings.Contains(string(OutGO), "version")) {
    return false, "[!] ERROR : golang is not installed"
  }

  return true, ""

}
<|endoftext|>


<commit_before>package pipoint

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestLinPred(t *testing.T) {
	l := &LinPred{}

	// Returns initial value.
	l.SetEx(5, 1)
	assert.InDelta(t, l.GetEx(1), 5, 0.001)
	// Initial value continues.
	assert.InDelta(t, l.GetEx(2), 5, 0.001)

	l.SetEx(5.5, 2)
	// Initially returns the same value.
	assert.InDelta(t, l.GetEx(2), 5.5, 0.001)
	// Velocity is 0.5/s
	assert.InDelta(t, l.GetEx(2.5), 5.75, 0.001)
	assert.InDelta(t, l.GetEx(3.0), 6.0, 0.001)

	// Negative velocity also works.
	l.SetEx(4, 3)
	assert.InDelta(t, l.GetEx(3), 4, 0.001)
	assert.InDelta(t, l.GetEx(4), 2.5, 0.001)
}
<commit_msg>linpred: update tests to match.<commit_after>package pipoint

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestLinPred(t *testing.T) {
	l := &LinPred{}

	// Returns initial value.
	l.SetEx(5, 1, 1)
	assert.InDelta(t, l.GetEx(1), 5, 0.001)
	// Initial value continues.
	assert.InDelta(t, l.GetEx(2), 5, 0.001)

	l.SetEx(5.5, 2, 2)
	// Initially returns the same value.
	assert.InDelta(t, l.GetEx(2), 5.5, 0.001)
	// Velocity is 0.5/s
	assert.InDelta(t, l.GetEx(2.5), 5.75, 0.001)
	assert.InDelta(t, l.GetEx(3.0), 6.0, 0.001)

	// Negative velocity also works.
	l.SetEx(4, 3, 3)
	assert.InDelta(t, l.GetEx(3), 4, 0.001)
	assert.InDelta(t, l.GetEx(4), 2.5, 0.001)
}
<|endoftext|>


<commit_before>// goncurses - ncurses library for Go.
// Copyright 2011 Rob Thornton. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/* ncurses library

   1. No functions which operate only on stdscr have been implemented because
   it makes little sense to do so in a Go implementation. Stdscr is treated the
   same as any other window.

   2. Whenever possible, versions of ncurses functions which could potentially
   have a buffer overflow, like the getstr() family of functions, have not been
   implemented. Instead, only the mvwgetnstr() and wgetnstr() can be used. */
package goncurses

/*
#cgo LDFLAGS: -lncurses
#include <ncurses.h>
#include "goncurses.h"
*/
import "C"

import (
	"errors"
	"fmt"
	"unsafe"
)

type Pad Window

// BaudRate returns the speed of the terminal in bits per second
func BaudRate() int {
	return int(C.baudrate())
}

// Beep requests the terminal make an audible bell or, if not available,
// flashes the screen. Note that screen flashing doesn't work on all
// terminals
func Beep() {
	C.beep()
}

// Turn on/off buffering; raw user signals are passed to the program for
// handling. Overrides raw mode
func CBreak(on bool) {
	if on {
		C.cbreak()
		return
	}
	C.nocbreak()
}

// Test whether colour values can be changed
func CanChangeColor() bool {
	return bool(C.bool(C.can_change_color()))
}

// Get RGB values for specified colour
func ColorContent(col int) (int, int, int) {
	var r, g, b C.short
	C.color_content(C.short(col), (*C.short)(&r), (*C.short)(&g),
		(*C.short)(&b))
	return int(r), int(g), int(b)
}

// Return the value of a color pair which can be passed to functions which
// accept attributes like AddChar or AttrOn/Off.
func ColorPair(pair int) int {
	return int(C.COLOR_PAIR(C.int(pair)))
}

// CursesVersion returns the version of the ncurses library currently linked to
func CursesVersion() string {
	return C.GoString(C.curses_version())
}

// Set the cursor visibility. Options are: 0 (invisible/hidden), 1 (normal)
// and 2 (extra-visible)
func Cursor(vis byte) error {
	if C.curs_set(C.int(vis)) == C.ERR {
		return errors.New("Failed to enable ")
	}
	return nil
}

// Echo turns on/off the printing of typed characters
func Echo(on bool) {
	if on {
		C.echo()
		return
	}
	C.noecho()
}

// Must be called prior to exiting the program in order to make sure the
// terminal returns to normal operation
func End() {
	C.endwin()
}

// Flash requests the terminal flashes the screen or, if not available,
// make an audible bell. Note that screen flashing doesn't work on all
// terminals
func Flash() {
	C.flash()
}

// Returns an array of integers representing the following, in order:
// x, y and z coordinates, id of the device, and a bit masked state of
// the devices buttons
func GetMouse() ([]int, error) {
	if bool(C.ncurses_has_mouse()) != true {
		return nil, errors.New("Mouse support not enabled")
	}
	var event C.MEVENT
	if C.getmouse(&event) != C.OK {
		return nil, errors.New("Failed to get mouse event")
	}
	return []int{int(event.x), int(event.y), int(event.z), int(event.id),
		int(event.bstate)}, nil
}

// Behaves like cbreak() but also adds a timeout for input. If timeout is
// exceeded after a call to Getch() has been made then GetChar will return
// with an error.
func HalfDelay(delay int) error {
	var cerr C.int
	if delay > 0 {
		cerr = C.halfdelay(C.int(delay))
	}
	if cerr == C.ERR {
		return errors.New("Unable to set delay mode")
	}
	return nil
}

// HasColors returns true if terminal can display colors
func HasColors() bool {
	return bool(C.has_colors())
}

// HasKey returns true if terminal recognized the given character
func HasKey(ch Key) bool {
	if C.has_key(C.int(ch)) == 1 {
		return true
	}
	return false
}

// InitColor is used to set 'color' to the specified RGB values. Values may
// be between 0 and 1000.
func InitColor(col int, r, g, b int) error {
	if C.init_color(C.short(col), C.short(r), C.short(g),
		C.short(b)) == C.ERR {
		return errors.New("Failed to set new color definition")
	}
	return nil
}

// InitPair sets a colour pair designated by 'pair' to fg and bg colors
func InitPair(pair byte, fg, bg int) error {
	if pair == 0 || C.int(pair) > (C.COLOR_PAIRS-1) {
		return errors.New("Invalid color pair selected")
	}
	if C.init_pair(C.short(pair), C.short(fg), C.short(bg)) == C.ERR {
		return errors.New("Failed to init color pair")
	}
	return nil
}

// Initialize the ncurses library. You must run this function prior to any
// other goncurses function in order for the library to work
func Init() (stdscr Window, err error) {
	stdscr = Window{C.initscr()}
	if unsafe.Pointer(stdscr.win) == nil {
		err = errors.New("An error occurred initializing ncurses")
	}
	return
}

// IsEnd returns true if End() has been called, otherwise false
func IsEnd() bool {
	return bool(C.isendwin())
}

// IsTermResized returns true if ResizeTerm would modify any current Windows
// if called with the given parameters
func IsTermResized(nlines, ncols int) bool {
	return bool(C.is_term_resized(C.int(nlines), C.int(ncols)))
}

// Returns a string representing the value of input returned by Getch
func KeyString(k Key) string {
	key, ok := keyList[k]
	if !ok {
		key = fmt.Sprintf("%c", int(k))
	}
	return key
}

func Mouse() bool {
	return bool(C.ncurses_has_mouse())
}

func MouseInterval() {
}

// MouseMask accepts a single int of OR'd mouse events. If a mouse event
// is triggered, GetChar() will return KEY_MOUSE. To retrieve the actual
// event use GetMouse() to pop it off the queue. Pass a pointer as the
// second argument to store the prior events being monitored or nil.
func MouseMask(mask int, old *int) (m int) {
	if bool(C.ncurses_has_mouse()) {
		m = int(C.mousemask((C.mmask_t)(mask),
			(*C.mmask_t)(unsafe.Pointer(old))))
	}
	return
}

// NewWindow creates a window of size h(eight) and w(idth) at y, x
func NewWindow(h, w, y, x int) (window Window, err error) {
	window = Window{C.newwin(C.int(h), C.int(w), C.int(y), C.int(x))}
	if window.win == nil {
		err = errors.New("Failed to create a new window")
	}
	return
}

// NL turns newline translation on/off.
func NL(on bool) {
	if on {
		C.nl()
		return
	}
	C.nonl()
}

// Raw turns on input buffering; user signals are disabled and the key strokes
// are passed directly to input. Set to false if you wish to turn this mode
// off
func Raw(on bool) {
	if on {
		C.raw()
		return
	}
	C.noraw()
}

// ResizeTerm will attempt to resize the terminal. This only has an effect if
// the terminal is in an XWindows (GUI) environment.
func ResizeTerm(nlines, ncols int) error {
	if C.resizeterm(C.int(nlines), C.int(ncols)) == C.ERR {
		return errors.New("Failed to resize terminal")
	}
	return nil
}

// Enables colors to be displayed. Will return an error if terminal is not
// capable of displaying colors
func StartColor() error {
	if C.has_colors() == C.bool(false) {
		return errors.New("Terminal does not support colors")
	}
	if C.start_color() == C.ERR {
		return errors.New("Failed to enable color mode")
	}
	return nil
}

// Update the screen, refreshing all windows
func Update() error {
	if C.doupdate() == C.ERR {
		return errors.New("Failed to update")
	}
	return nil
}

// NewPad creates a window which is not restricted by the terminal's
// dimentions (unlike a Window)
func NewPad(lines, cols int) Pad {
	return Pad{C.newpad(C.int(lines), C.int(cols))}
}

// Echo prints a single character to the pad immediately. This has the
// same effect of calling AddChar() + Refresh() but has a significant
// speed advantage
func (p *Pad) Echo(ch int) {
	C.pechochar(p.win, C.chtype(ch))
}

func (p *Pad) NoutRefresh(py, px, ty, tx, by, bx int) {
	C.pnoutrefresh(p.win, C.int(py), C.int(px), C.int(ty),
		C.int(tx), C.int(by), C.int(bx))
}

// Refresh the pad at location py, px using the rectangle specified by
// ty, tx, by, bx (bottom/top y/x)
func (p *Pad) Refresh(py, px, ty, tx, by, bx int) {
	C.prefresh(p.win, C.int(py), C.int(px), C.int(ty), C.int(tx),
		C.int(by), C.int(bx))
}

// Sub creates a sub-pad lines by columns in size
func (p *Pad) Sub(y, x, h, w int) Pad {
	return Pad{C.subpad(p.win, C.int(h), C.int(w), C.int(y),
		C.int(x))}
}

// Window is a helper function for calling Window functions on a pad like
// Print(). Convention would be to use Pad.Window().Print().
func (p *Pad) Window() *Window {
	return (*Window)(p)
}
<commit_msg>Implement several more functions<commit_after>// goncurses - ncurses library for Go.
// Copyright 2011 Rob Thornton. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/* ncurses library

   1. No functions which operate only on stdscr have been implemented because
   it makes little sense to do so in a Go implementation. Stdscr is treated the
   same as any other window.

   2. Whenever possible, versions of ncurses functions which could potentially
   have a buffer overflow, like the getstr() family of functions, have not been
   implemented. Instead, only the mvwgetnstr() and wgetnstr() can be used. */
package goncurses

/*
#cgo LDFLAGS: -lncurses
#include <ncurses.h>
#include "goncurses.h"
*/
import "C"

import (
	"errors"
	"fmt"
	"unsafe"
)

type Pad Window

// BaudRate returns the speed of the terminal in bits per second
func BaudRate() int {
	return int(C.baudrate())
}

// Beep requests the terminal make an audible bell or, if not available,
// flashes the screen. Note that screen flashing doesn't work on all
// terminals
func Beep() {
	C.beep()
}

// Turn on/off buffering; raw user signals are passed to the program for
// handling. Overrides raw mode
func CBreak(on bool) {
	if on {
		C.cbreak()
		return
	}
	C.nocbreak()
}

// Test whether colour values can be changed
func CanChangeColor() bool {
	return bool(C.bool(C.can_change_color()))
}

// Get RGB values for specified colour
func ColorContent(col int) (int, int, int) {
	var r, g, b C.short
	C.color_content(C.short(col), (*C.short)(&r), (*C.short)(&g),
		(*C.short)(&b))
	return int(r), int(g), int(b)
}

// Return the value of a color pair which can be passed to functions which
// accept attributes like AddChar or AttrOn/Off.
func ColorPair(pair int) int {
	return int(C.COLOR_PAIR(C.int(pair)))
}

// CursesVersion returns the version of the ncurses library currently linked to
func CursesVersion() string {
	return C.GoString(C.curses_version())
}

// Set the cursor visibility. Options are: 0 (invisible/hidden), 1 (normal)
// and 2 (extra-visible)
func Cursor(vis byte) error {
	if C.curs_set(C.int(vis)) == C.ERR {
		return errors.New("Failed to enable ")
	}
	return nil
}

// Echo turns on/off the printing of typed characters
func Echo(on bool) {
	if on {
		C.echo()
		return
	}
	C.noecho()
}

// Must be called prior to exiting the program in order to make sure the
// terminal returns to normal operation
func End() {
	C.endwin()
}

// Flash requests the terminal flashes the screen or, if not available,
// make an audible bell. Note that screen flashing doesn't work on all
// terminals
func Flash() {
	C.flash()
}

// FlushInput flushes all input
func FlushInput() error {
	if C.flushinp() == C.ERR {
		return errors.New("Flush input failed")
	}
	return nil
}

// Returns an array of integers representing the following, in order:
// x, y and z coordinates, id of the device, and a bit masked state of
// the devices buttons
func GetMouse() ([]int, error) {
	if bool(C.ncurses_has_mouse()) != true {
		return nil, errors.New("Mouse support not enabled")
	}
	var event C.MEVENT
	if C.getmouse(&event) != C.OK {
		return nil, errors.New("Failed to get mouse event")
	}
	return []int{int(event.x), int(event.y), int(event.z), int(event.id),
		int(event.bstate)}, nil
}

// Behaves like cbreak() but also adds a timeout for input. If timeout is
// exceeded after a call to Getch() has been made then GetChar will return
// with an error.
func HalfDelay(delay int) error {
	var cerr C.int
	if delay > 0 {
		cerr = C.halfdelay(C.int(delay))
	}
	if cerr == C.ERR {
		return errors.New("Unable to set delay mode")
	}
	return nil
}

// HasColors returns true if terminal can display colors
func HasColors() bool {
	return bool(C.has_colors())
}

// HasInsertCharacter return true if the terminal has insert and delete
// character capabilities
func HasInsertCharacter() bool {
	return bool(C.has_ic())
}

// HasInsertLine returns true if the terminal has insert and delete line
// capabilities. See ncurses documentation for more details
func HasInsertLine() bool {
	return bool(C.has_il())
}

// HasKey returns true if terminal recognized the given character
func HasKey(ch Key) bool {
	if C.has_key(C.int(ch)) == 1 {
		return true
	}
	return false
}

// InitColor is used to set 'color' to the specified RGB values. Values may
// be between 0 and 1000.
func InitColor(col int, r, g, b int) error {
	if C.init_color(C.short(col), C.short(r), C.short(g),
		C.short(b)) == C.ERR {
		return errors.New("Failed to set new color definition")
	}
	return nil
}

// InitPair sets a colour pair designated by 'pair' to fg and bg colors
func InitPair(pair byte, fg, bg int) error {
	if pair == 0 || C.int(pair) > (C.COLOR_PAIRS-1) {
		return errors.New("Invalid color pair selected")
	}
	if C.init_pair(C.short(pair), C.short(fg), C.short(bg)) == C.ERR {
		return errors.New("Failed to init color pair")
	}
	return nil
}

// Initialize the ncurses library. You must run this function prior to any
// other goncurses function in order for the library to work
func Init() (stdscr Window, err error) {
	stdscr = Window{C.initscr()}
	if unsafe.Pointer(stdscr.win) == nil {
		err = errors.New("An error occurred initializing ncurses")
	}
	return
}

// IsEnd returns true if End() has been called, otherwise false
func IsEnd() bool {
	return bool(C.isendwin())
}

// IsTermResized returns true if ResizeTerm would modify any current Windows
// if called with the given parameters
func IsTermResized(nlines, ncols int) bool {
	return bool(C.is_term_resized(C.int(nlines), C.int(ncols)))
}

// Returns a string representing the value of input returned by Getch
func KeyString(k Key) string {
	key, ok := keyList[k]
	if !ok {
		key = fmt.Sprintf("%c", int(k))
	}
	return key
}

func Mouse() bool {
	return bool(C.ncurses_has_mouse())
}

func MouseInterval() {
}

// MouseMask accepts a single int of OR'd mouse events. If a mouse event
// is triggered, GetChar() will return KEY_MOUSE. To retrieve the actual
// event use GetMouse() to pop it off the queue. Pass a pointer as the
// second argument to store the prior events being monitored or nil.
func MouseMask(mask int, old *int) (m int) {
	if bool(C.ncurses_has_mouse()) {
		m = int(C.mousemask((C.mmask_t)(mask),
			(*C.mmask_t)(unsafe.Pointer(old))))
	}
	return
}

// NapMilliseconds is used to sleep for ms milliseconds
func NapMilliseconds(ms int) {
	C.napms(C.int(ms))
}

// NewWindow creates a window of size h(eight) and w(idth) at y, x
func NewWindow(h, w, y, x int) (window Window, err error) {
	window = Window{C.newwin(C.int(h), C.int(w), C.int(y), C.int(x))}
	if window.win == nil {
		err = errors.New("Failed to create a new window")
	}
	return
}

// NL turns newline translation on/off.
func NL(on bool) {
	if on {
		C.nl()
		return
	}
	C.nonl()
}

// Raw turns on input buffering; user signals are disabled and the key strokes
// are passed directly to input. Set to false if you wish to turn this mode
// off
func Raw(on bool) {
	if on {
		C.raw()
		return
	}
	C.noraw()
}

// ResizeTerm will attempt to resize the terminal. This only has an effect if
// the terminal is in an XWindows (GUI) environment.
func ResizeTerm(nlines, ncols int) error {
	if C.resizeterm(C.int(nlines), C.int(ncols)) == C.ERR {
		return errors.New("Failed to resize terminal")
	}
	return nil
}

// Enables colors to be displayed. Will return an error if terminal is not
// capable of displaying colors
func StartColor() error {
	if C.has_colors() == C.bool(false) {
		return errors.New("Terminal does not support colors")
	}
	if C.start_color() == C.ERR {
		return errors.New("Failed to enable color mode")
	}
	return nil
}

// Update the screen, refreshing all windows
func Update() error {
	if C.doupdate() == C.ERR {
		return errors.New("Failed to update")
	}
	return nil
}

// UseEnvironment specifies whether the LINES and COLUMNS environmental
// variables should be used or not
func UseEnvironment(use bool) {
	C.use_env(C.bool(use))
}

// NewPad creates a window which is not restricted by the terminal's
// dimentions (unlike a Window)
func NewPad(lines, cols int) Pad {
	return Pad{C.newpad(C.int(lines), C.int(cols))}
}

// Echo prints a single character to the pad immediately. This has the
// same effect of calling AddChar() + Refresh() but has a significant
// speed advantage
func (p *Pad) Echo(ch int) {
	C.pechochar(p.win, C.chtype(ch))
}

func (p *Pad) NoutRefresh(py, px, ty, tx, by, bx int) {
	C.pnoutrefresh(p.win, C.int(py), C.int(px), C.int(ty),
		C.int(tx), C.int(by), C.int(bx))
}

// Refresh the pad at location py, px using the rectangle specified by
// ty, tx, by, bx (bottom/top y/x)
func (p *Pad) Refresh(py, px, ty, tx, by, bx int) {
	C.prefresh(p.win, C.int(py), C.int(px), C.int(ty), C.int(tx),
		C.int(by), C.int(bx))
}

// Sub creates a sub-pad lines by columns in size
func (p *Pad) Sub(y, x, h, w int) Pad {
	return Pad{C.subpad(p.win, C.int(h), C.int(w), C.int(y),
		C.int(x))}
}

// Window is a helper function for calling Window functions on a pad like
// Print(). Convention would be to use Pad.Window().Print().
func (p *Pad) Window() *Window {
	return (*Window)(p)
}
<|endoftext|>


<commit_before>package bothandlers

import (
	"time"
	"math/rand"
	"strings"
	"github.com/djosephsen/hal"
)

type ikr struct{
}

func (h *ikr) Method() string {
	return hal.HEAR
}

func (h *ikr) Usage() string {
	return `ikr - listens for enthusiasm; responds with validation`
}

func (h *ikr) Pattern() string {
	triggers:=[]string{
		"best.*ev(er|ar)",
		"so good",
		"they have the best",
		"awesome",
		"I love",
		"fantastic|wonderful|outstanding|magnificent|brilliant|genius|amazing",
		"ZOMG|OMG|OMFG",
		"(so|pretty) great",
		"off the hook",
	}
	pat := "(?i)"+strings.Join(triggers,"|")
	return pat
}

func (h *ikr) Run(res *hal.Response) error {
   now:=time.Now()
	rand.Seed(int64(now.Unix()))
	replies := []string{
		"*I know right?!*",
		"*OMG* couldn't agree more",
		":+1:",
		"+1",
		":arrow_up: THAT",
		":arrow_up: you complete me :arrow_up:",
		"so true",
		"agreed.",
		"that's the fact jack",
		"YUUUUUUP",
		"that's what I'm talkin bout",
		"*IKR?!*",
		"singit",
		"^droppin the truth bombs :boom: :boom: :boom:",
		"#legit",
		"/me nodds emphatically in agreement",
		"for REALZ though",
		"FOR REALSIES",
		"it's like you *literally* just read my mind right now",
	}
	reply := replies[rand.Intn(len(replies)-1)]
	hal.Logger.Debug(" *** ikr:Sending response: %s", reply)
	res.Send(reply)
	return nil
}

// Ping exports
var IKR = &ikr{}
<commit_msg>a few new triggers<commit_after>package bothandlers

import (
	"time"
	"math/rand"
	"strings"
	"github.com/djosephsen/hal"
)

type ikr struct{
}

func (h *ikr) Method() string {
	return hal.HEAR
}

func (h *ikr) Usage() string {
	return `ikr - listens for enthusiasm; responds with validation`
}

func (h *ikr) Pattern() string {
	triggers:=[]string{
		"best.*ev(er|ar)",
		"so good",
		"they have the best",
		"awesome",
		"I love",
		"fantastic|wonderful|outstanding|magnificent|brilliant|genius|amazing|epic|nice!",
		"ZOMG|OMG|OMFG",
		"(so|pretty) great",
		"off the hook",
	}
	pat := "(?i)"+strings.Join(triggers,"|")
	return pat
}

func (h *ikr) Run(res *hal.Response) error {
   now:=time.Now()
	rand.Seed(int64(now.Unix()))
	replies := []string{
		"*I know right?!*",
		"*OMG* couldn't agree more",
		":+1:",
		"+1",
		":arrow_up: THAT",
		":arrow_up: you complete me :arrow_up:",
		"so true",
		"agreed.",
		"that's the fact jack",
		"YUUUUUUP",
		"that's what I'm talkin bout",
		"*IKR?!*",
		"singit",
		"^droppin the truth bombs :boom: :boom: :boom:",
		"#legit",
		"/me nodds emphatically in agreement",
		"for REALZ though",
		"FOR REALSIES",
		"it's like you *literally* just read my mind right now",
	}
	reply := replies[rand.Intn(len(replies)-1)]
	hal.Logger.Debug(" *** ikr:Sending response: %s", reply)
	res.Send(reply)
	return nil
}

// Ping exports
var IKR = &ikr{}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rsa"
	"encoding/gob"
	"errors"
	"io"
	"net"
	"sync"
)

type MsgType int32

const (
	PeerListRequest  MsgType = iota
	PeerListResponse MsgType = iota
	PeerBroadcast    MsgType = iota

	BlockChainRequest  MsgType = iota
	BlockChainResponse MsgType = iota
	BlockBroadcast     MsgType = iota

	TransactionRequest   MsgType = iota
	TransactionResponse  MsgType = iota
	TransactionBroadcast MsgType = iota

	Error MsgType = iota
)

type NetworkMessage struct {
	Type  MsgType
	Value interface{}

	addr string // filled on the receiving side
}

type PeerConn struct {
	base    net.Conn
	encoder *gob.Encoder
	decoder *gob.Decoder
}

func NewPeerConn(conn net.Conn) *PeerConn {
	return &PeerConn{conn, gob.NewEncoder(conn), gob.NewDecoder(conn)}
}

func (peer *PeerConn) Send(msg *NetworkMessage) error {
	err := peer.encoder.Encode(msg)

	if err == nil {
		return nil
	}

	switch err.(type) {
	case net.Error:
		// caller may choose to ignore this
		return err
	default:
		// probably a gob error which we want to know about
		panic(err)
	}
}

func (peer *PeerConn) Receive() (*NetworkMessage, error) {
	msg := new(NetworkMessage)
	err := peer.decoder.Decode(msg)

	if err == nil {
		return msg, nil
	}

	switch err.(type) {
	case net.Error:
		// caller may choose to ignore this
		return nil, err
	default:
		// probably a gob error which we want to know about
		panic(err)
	}
}

type PeerNetwork struct {
	peers      map[string]*PeerConn
	server     net.Listener
	events     chan *NetworkMessage
	payExpects map[string]chan *rsa.PublicKey
	closing    bool
	lock       sync.RWMutex
}

func NewPeerNetwork(address, startPeer string) (network *PeerNetwork, err error) {
	var peerAddrs []string

	if startPeer != "" {
		conn, err := net.Dial("tcp", startPeer)
		if err != nil {
			return nil, err
		}
		defer conn.Close()

		peer := NewPeerConn(conn)

		err = peer.Send(&NetworkMessage{Type: PeerListRequest})
		if err != nil {
			return nil, err
		}

		msg, err := peer.Receive()
		if err != nil {
			return nil, err
		}

		if msg.Type != PeerListResponse {
			return nil, errors.New("Received message not a PeerListResponse")
		}

		switch v := msg.Value.(type) {
		case []string:
			peerAddrs = v
		default:
			return nil, errors.New("Unknown value in PeerListResponse")
		}
	}

	network = &PeerNetwork{
		peers:      make(map[string]*PeerConn, len(peerAddrs)),
		payExpects: make(map[string]chan *rsa.PublicKey),
		events:     make(chan *NetworkMessage),
	}
	network.server, err = net.Listen("tcp", address)
	if err != nil {
		return nil, err
	}

	go network.AcceptNewConns()
	go network.HandleEvents()

	msg := NetworkMessage{Type: PeerBroadcast, Value: network.server.Addr().String()}
	for _, addr := range peerAddrs {
		conn, err := net.Dial("tcp", addr)
		if err != nil {
			continue
		}

		peer := NewPeerConn(conn)

		err = peer.Send(&msg)
		if err != nil {
			conn.Close()
			continue
		}

		network.peers[addr] = peer
		go network.ReceiveFromConn(addr)
	}

	return network, nil
}

func (network *PeerNetwork) AcceptNewConns() {
	for {
		conn, err := network.server.Accept()

		if err != nil {
			network.events <- &NetworkMessage{Error, err, ""}
			return
		}

		peer := NewPeerConn(conn)

		msg, err := peer.Receive()
		if err != nil {
			conn.Close()
			continue
		}

		switch msg.Type {
		case PeerListRequest:
			response := NetworkMessage{Type: PeerListResponse, Value: append(network.PeerAddrList(), network.server.Addr().String())}
			peer.Send(&response)
			conn.Close()
		case PeerBroadcast:
			switch addr := msg.Value.(type) {
			case string:
				network.lock.Lock()
				if !network.closing && network.peers[addr] == nil {
					network.peers[addr] = peer
					go network.ReceiveFromConn(addr)
					logger.Println("New peer:", addr)
				} else {
					conn.Close()
				}
				network.lock.Unlock()
			default:
				conn.Close()
			}
		default:
			conn.Close()
		}
	}
}

func (network *PeerNetwork) ReceiveFromConn(addr string) {
	peer := network.peers[addr]

	var err error
	var msg NetworkMessage

	for {
		err = peer.decoder.Decode(&msg)
		if err != nil {
			network.events <- &NetworkMessage{Error, err, addr}
			return
		}

		msg.addr = addr

		network.events <- &msg
	}
}

func (network *PeerNetwork) HandleEvents() {
	for msg := range network.events {
		switch msg.Type {
		case BlockChainRequest:
			hash := msg.Value.([]byte)
			chain := state.ChainFromHash(hash)
			message := NetworkMessage{Type: BlockChainResponse, Value: chain}
			peer := network.Peer(msg.addr)
			peer.Send(&message)
		case BlockChainResponse:
			chain := msg.Value.(BlockChain)
			logger.Println("Received blockchain from", msg.addr)
			state.AddBlockChain(&chain)
		case BlockBroadcast:
			logger.Println("Received block from", msg.addr)
			block := msg.Value.(Block)
			valid, haveChain := state.AddBlock(&block)
			if valid && !haveChain {
				network.RequestBlockChain(block.Hash())
			}
		case TransactionRequest:
			key := genKey()
			message := NetworkMessage{Type: TransactionResponse, Value: key.PublicKey}
			peer := network.Peer(msg.addr)
			peer.Send(&message)
			state.AddToWallet(key)
		case TransactionResponse:
			network.lock.Lock()
			expect := network.payExpects[msg.addr]
			if expect != nil {
				key := msg.Value.(rsa.PublicKey)
				expect <- &key
				close(expect)
				delete(network.payExpects, msg.addr)
			}
			network.lock.Unlock()
		case TransactionBroadcast:
			logger.Println("Received txn from", msg.addr)
			txn := msg.Value.(Transaction)
			state.AddTxn(&txn)
		case Error:
			if msg.addr == "" {
				if network.closing {
					if len(network.peers) == 0 {
						close(network.events)
						return
					}
				} else {
					panic(msg.Value)
				}
			} else {
				network.lock.Lock()
				delete(network.peers, msg.addr)
				network.lock.Unlock()
				logger.Println("Lost peer:", msg.addr)
				if len(network.peers) == 0 {
					if network.closing {
						close(network.events)
						return
					} else if msg.Value != io.EOF {
						panic(msg.Value)
					}
				}
			}
		default:
			panic(msg.Type)
		}
	}
}

func (network *PeerNetwork) Close() {
	network.lock.Lock()
	defer network.lock.Unlock()

	network.closing = true
	network.server.Close()
	for _, peer := range network.peers {
		peer.base.Close()
	}
}

func (network *PeerNetwork) PeerAddrList() []string {
	network.lock.RLock()
	defer network.lock.RUnlock()

	list := make([]string, 0, len(network.peers))
	for addr, _ := range network.peers {
		list = append(list, addr)
	}
	return list
}

func (network *PeerNetwork) Peer(addr string) *PeerConn {
	network.lock.RLock()
	defer network.lock.RUnlock()

	return network.peers[addr]
}

func (network *PeerNetwork) CancelPayExpectation(addr string) {
	network.lock.Lock()
	defer network.lock.Unlock()

	close(network.payExpects[addr])
	delete(network.payExpects, addr)
}

func (network *PeerNetwork) genPayExpectation(addr string) chan *rsa.PublicKey {
	network.lock.Lock()
	defer network.lock.Unlock()

	c := make(chan *rsa.PublicKey)
	network.payExpects[addr] = c
	return c
}

func (network *PeerNetwork) RequestPayableAddress(addr string) (chan *rsa.PublicKey, error) {
	peer := network.Peer(addr)

	if peer == nil {
		return nil, errors.New("Peer no longer connected")
	}

	expect := network.genPayExpectation(addr)

	message := NetworkMessage{Type: TransactionRequest}
	err := peer.Send(&message)

	if err != nil {
		network.CancelPayExpectation(addr)
		return nil, err
	}

	return expect, nil
}

func (network *PeerNetwork) RequestBlockChain(hash []byte) {
	network.lock.RLock()
	defer network.lock.RUnlock()

	// pick a random peer
	message := NetworkMessage{Type: BlockChainRequest, Value: hash}
	for _, peer := range network.peers {
		peer.Send(&message)
		return
	}
}

func (network *PeerNetwork) BroadcastBlock(b *Block) {
	network.lock.RLock()
	defer network.lock.RUnlock()

	// send to all peers
	message := NetworkMessage{Type: BlockBroadcast, Value: b}
	for _, peer := range network.peers {
		peer.Send(&message)
	}
}

func (network *PeerNetwork) BroadcastTxn(txn *Transaction) {
	network.lock.RLock()
	defer network.lock.RUnlock()

	// send to all peers
	message := NetworkMessage{Type: TransactionBroadcast, Value: txn}
	for _, peer := range network.peers {
		peer.Send(&message)
	}
}
<commit_msg>minor refactor<commit_after>package main

import (
	"crypto/rsa"
	"encoding/gob"
	"errors"
	"io"
	"net"
	"sync"
)

type MsgType int32

const (
	PeerListRequest  MsgType = iota
	PeerListResponse MsgType = iota
	PeerBroadcast    MsgType = iota

	BlockChainRequest  MsgType = iota
	BlockChainResponse MsgType = iota
	BlockBroadcast     MsgType = iota

	TransactionRequest   MsgType = iota
	TransactionResponse  MsgType = iota
	TransactionBroadcast MsgType = iota

	Error MsgType = iota
)

type NetworkMessage struct {
	Type  MsgType
	Value interface{}

	addr string // filled on the receiving side
}

type PeerConn struct {
	base    net.Conn
	encoder *gob.Encoder
	decoder *gob.Decoder
}

func NewPeerConn(conn net.Conn) *PeerConn {
	return &PeerConn{conn, gob.NewEncoder(conn), gob.NewDecoder(conn)}
}

func (peer *PeerConn) Send(msg *NetworkMessage) error {
	err := peer.encoder.Encode(msg)

	if err == nil {
		return nil
	}

	switch err.(type) {
	case net.Error:
		// caller may choose to ignore this
		return err
	default:
		// probably a gob error which we want to know about
		panic(err)
	}
}

func (peer *PeerConn) Receive() (*NetworkMessage, error) {
	msg := new(NetworkMessage)
	err := peer.decoder.Decode(msg)

	if err == nil {
		return msg, nil
	}

	switch err.(type) {
	case net.Error:
		// caller may choose to ignore this
		return nil, err
	default:
		// probably a gob error which we want to know about
		panic(err)
	}
}

type PeerNetwork struct {
	peers      map[string]*PeerConn
	server     net.Listener
	events     chan *NetworkMessage
	payExpects map[string]chan *rsa.PublicKey
	closing    bool
	lock       sync.RWMutex
}

func NewPeerNetwork(address, startPeer string) (network *PeerNetwork, err error) {
	var peerAddrs []string

	if startPeer != "" {
		conn, err := net.Dial("tcp", startPeer)
		if err != nil {
			return nil, err
		}
		defer conn.Close()

		peer := NewPeerConn(conn)

		err = peer.Send(&NetworkMessage{Type: PeerListRequest})
		if err != nil {
			return nil, err
		}

		msg, err := peer.Receive()
		if err != nil {
			return nil, err
		}

		if msg.Type != PeerListResponse {
			return nil, errors.New("Received message not a PeerListResponse")
		}

		switch v := msg.Value.(type) {
		case []string:
			peerAddrs = v
		default:
			return nil, errors.New("Unknown value in PeerListResponse")
		}
	}

	network = &PeerNetwork{
		peers:      make(map[string]*PeerConn, len(peerAddrs)),
		payExpects: make(map[string]chan *rsa.PublicKey),
		events:     make(chan *NetworkMessage),
	}
	network.server, err = net.Listen("tcp", address)
	if err != nil {
		return nil, err
	}

	go network.AcceptNewConns()
	go network.HandleEvents()

	msg := NetworkMessage{Type: PeerBroadcast, Value: network.server.Addr().String()}
	for _, addr := range peerAddrs {
		conn, err := net.Dial("tcp", addr)
		if err != nil {
			continue
		}

		peer := NewPeerConn(conn)

		err = peer.Send(&msg)
		if err != nil {
			conn.Close()
			continue
		}

		network.peers[addr] = peer
		go network.ReceiveFromConn(addr)
	}

	return network, nil
}

func (network *PeerNetwork) AcceptNewConns() {
	for {
		conn, err := network.server.Accept()

		if err != nil {
			network.events <- &NetworkMessage{Error, err, ""}
			return
		}

		peer := NewPeerConn(conn)

		msg, err := peer.Receive()
		if err != nil {
			conn.Close()
			continue
		}

		switch msg.Type {
		case PeerListRequest:
			response := NetworkMessage{Type: PeerListResponse, Value: append(network.PeerAddrList(), network.server.Addr().String())}
			peer.Send(&response)
			conn.Close()
		case PeerBroadcast:
			switch addr := msg.Value.(type) {
			case string:
				network.lock.Lock()
				if !network.closing && network.peers[addr] == nil {
					network.peers[addr] = peer
					go network.ReceiveFromConn(addr)
					logger.Println("New peer:", addr)
				} else {
					conn.Close()
				}
				network.lock.Unlock()
			default:
				conn.Close()
			}
		default:
			conn.Close()
		}
	}
}

func (network *PeerNetwork) ReceiveFromConn(addr string) {
	peer := network.peers[addr]

	var err error
	var msg NetworkMessage

	for {
		err = peer.decoder.Decode(&msg)
		if err != nil {
			network.events <- &NetworkMessage{Error, err, addr}
			return
		}

		msg.addr = addr

		network.events <- &msg
	}
}

func (network *PeerNetwork) HandleEvents() {
	for msg := range network.events {
		switch msg.Type {
		case BlockChainRequest:
			hash := msg.Value.([]byte)
			chain := state.ChainFromHash(hash)
			message := NetworkMessage{Type: BlockChainResponse, Value: chain}
			peer := network.Peer(msg.addr)
			peer.Send(&message)
		case BlockChainResponse:
			chain := msg.Value.(BlockChain)
			logger.Println("Received blockchain from", msg.addr)
			state.AddBlockChain(&chain)
		case BlockBroadcast:
			logger.Println("Received block from", msg.addr)
			block := msg.Value.(Block)
			valid, haveChain := state.AddBlock(&block)
			if valid && !haveChain {
				network.RequestBlockChain(block.Hash())
			}
		case TransactionRequest:
			key := genKey()
			message := NetworkMessage{Type: TransactionResponse, Value: key.PublicKey}
			peer := network.Peer(msg.addr)
			peer.Send(&message)
			state.AddToWallet(key)
		case TransactionResponse:
			network.lock.Lock()
			expect := network.payExpects[msg.addr]
			if expect != nil {
				key := msg.Value.(rsa.PublicKey)
				expect <- &key
				close(expect)
				delete(network.payExpects, msg.addr)
			}
			network.lock.Unlock()
		case TransactionBroadcast:
			logger.Println("Received txn from", msg.addr)
			txn := msg.Value.(Transaction)
			state.AddTxn(&txn)
		case Error:
			if msg.addr == "" {
				if network.closing {
					if len(network.peers) == 0 {
						close(network.events)
						return
					}
				} else {
					panic(msg.Value)
				}
			} else {
				network.lock.Lock()
				delete(network.peers, msg.addr)
				network.lock.Unlock()
				logger.Println("Lost peer:", msg.addr)
				if len(network.peers) == 0 {
					if network.closing {
						close(network.events)
						return
					} else if msg.Value != io.EOF {
						panic(msg.Value)
					}
				}
			}
		default:
			panic(msg.Type)
		}
	}
}

func (network *PeerNetwork) Close() {
	network.lock.Lock()
	defer network.lock.Unlock()

	network.closing = true
	network.server.Close()
	for _, peer := range network.peers {
		peer.base.Close()
	}
}

func (network *PeerNetwork) PeerAddrList() []string {
	network.lock.RLock()
	defer network.lock.RUnlock()

	list := make([]string, 0, len(network.peers))
	for addr, _ := range network.peers {
		list = append(list, addr)
	}
	return list
}

func (network *PeerNetwork) Peer(addr string) *PeerConn {
	network.lock.RLock()
	defer network.lock.RUnlock()

	return network.peers[addr]
}

func (network *PeerNetwork) CancelPayExpectation(addr string) {
	network.lock.Lock()
	defer network.lock.Unlock()

	close(network.payExpects[addr])
	delete(network.payExpects, addr)
}

func (network *PeerNetwork) genPayExpectation(addr string) chan *rsa.PublicKey {
	network.lock.Lock()
	defer network.lock.Unlock()

	c := make(chan *rsa.PublicKey)
	network.payExpects[addr] = c
	return c
}

func (network *PeerNetwork) RequestPayableAddress(addr string) (chan *rsa.PublicKey, error) {
	peer := network.Peer(addr)

	if peer == nil {
		return nil, errors.New("Peer no longer connected")
	}

	expect := network.genPayExpectation(addr)

	message := NetworkMessage{Type: TransactionRequest}
	err := peer.Send(&message)

	if err != nil {
		network.CancelPayExpectation(addr)
		return nil, err
	}

	return expect, nil
}

func (network *PeerNetwork) RequestBlockChain(hash []byte) {
	network.lock.RLock()
	defer network.lock.RUnlock()

	// pick a random peer
	message := NetworkMessage{Type: BlockChainRequest, Value: hash}
	for _, peer := range network.peers {
		peer.Send(&message)
		return
	}
}

func (network *PeerNetwork) BroadcastBlock(b *Block) {
	message := NetworkMessage{Type: BlockBroadcast, Value: b}
	network.broadcast(&message)
}

func (network *PeerNetwork) BroadcastTxn(txn *Transaction) {
	message := NetworkMessage{Type: TransactionBroadcast, Value: txn}
	network.broadcast(&message)
}

func (network *PeerNetwork) broadcast(msg *NetworkMessage) {
	network.lock.RLock()
	defer network.lock.RUnlock()

	// send to all peers
	for _, peer := range network.peers {
		peer.Send(msg)
	}
}
<|endoftext|>


<commit_before>package gitlab_test

import (
	"github.com/concourse/atc"
	"github.com/concourse/atc/auth/gitlab"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("GitLab Provider", func() {
	Describe("AuthMethod", func() {
		var (
			authMethod atc.AuthMethod
			authConfig *gitlab.GitLabAuthConfig
		)
		BeforeEach(func() {
			authConfig = &gitlab.GitLabAuthConfig{}
			authMethod = authConfig.AuthMethod("http://bum-bum-bum.com", "dudududum")
		})

		It("creates path for route", func() {
			Expect(authMethod).To(Equal(atc.AuthMethod{
				Type:        atc.AuthTypeOAuth,
				DisplayName: "GitLab",
				AuthURL:     "http://bum-bum-bum.com/auth/gitlab?team_name=dudududum",
			}))
		})
	})

})
<commit_msg>Add GitLab auth provider validate unit tests<commit_after>package gitlab_test

import (
	"github.com/concourse/atc"
	"github.com/concourse/atc/auth/gitlab"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("GitLab Provider", func() {
	var (
		authMethod atc.AuthMethod
		authConfig *gitlab.GitLabAuthConfig
	)

	BeforeEach(func() {
		authConfig = &gitlab.GitLabAuthConfig{}
	})

	Describe("AuthMethod", func() {
		BeforeEach(func() {
			authMethod = authConfig.AuthMethod("http://bum-bum-bum.com", "dudududum")
		})

		It("creates path for route", func() {
			Expect(authMethod).To(Equal(atc.AuthMethod{
				Type:        atc.AuthTypeOAuth,
				DisplayName: "GitLab",
				AuthURL:     "http://bum-bum-bum.com/auth/gitlab?team_name=dudududum",
			}))
		})
	})

	Describe("Validate", func() {
		BeforeEach(func() {
			authConfig.ClientID = "foo"
			authConfig.ClientSecret = "bar"
			authConfig.Groups = []string{"group1", "group2"}
		})

		Context("when client id/secret and groups are specified", func() {
			It("succeeds", func() {
				err := authConfig.Validate()
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when client id is not specified", func() {
			It("returns an error", func() {
				authConfig.ClientID = ""
				err := authConfig.Validate()
				Expect(err).To(HaveOccurred())
				Expect(err).To(MatchError(ContainSubstring("must specify --gitlab-auth-client-id and --gitlab-auth-client-secret to use GitLab OAuth.")))
			})
		})

		Context("when client secret is not specified", func() {
			It("returns an error", func() {
				authConfig.ClientSecret = ""
				err := authConfig.Validate()
				Expect(err).To(HaveOccurred())
				Expect(err).To(MatchError(ContainSubstring("must specify --gitlab-auth-client-id and --gitlab-auth-client-secret to use GitLab OAuth.")))
			})
		})

		Context("when group is not specified", func() {
			It("returns an error", func() {
				authConfig.Groups = nil
				err := authConfig.Validate()
				Expect(err).To(HaveOccurred())
				Expect(err).To(MatchError(ContainSubstring("the following is required for gitlab-auth: groups")))
			})
		})

		Context("when client id/secret and groups specified", func() {
			It("succeeds", func() {
				err := authConfig.Validate()
				Expect(err).NotTo(HaveOccurred())
			})
		})
	})

})
<|endoftext|>


<commit_before>package transcription

import (
	"log"
	"os/exec"
)

func convertAudioIntoRequiredFormat(fn string) {
	// http://cmusphinx.sourceforge.net/wiki/faq
	// -ar 16000 sets frequency to required 16khz
	// -ac 1 sets the number of audio channels to 1
	cmd := exec.Command("ffmpeg", "-i", fn, "-ar", "16000", "-ac", "1", "file.wav")
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Deleted file.go which existed in origin but was no longer needed<commit_after><|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"golang.org/x/net/context"
)

type (
	CommandConfig struct {
		Template []string            `json:"-"`
		Options  map[string][]string `json:"options,omitempty"`
		Dryrun   bool                `json:"dryrun,omitempty"`
	}

	Job struct {
		config *CommandConfig
		// https://godoc.org/google.golang.org/genproto/googleapis/pubsub/v1#ReceivedMessage
		message      *JobMessage
		notification *ProgressNotification
		storage      Storage

		// These are set at at setupWorkspace
		workspace     string
		downloads_dir string
		uploads_dir   string

		// These are set at setupDownloadFiles
		downloadFileMap     map[string]string
		remoteDownloadFiles interface{}
		localDownloadFiles  interface{}

		cmd *exec.Cmd
	}
)

func (job *Job) run(ctx context.Context) error {
	err := job.runImpl(ctx)
	switch err.(type) {
	case InvalidJobError:
		err := job.withNotify(CANCELLING, job.message.Ack)()
		if err != nil {
			return err
		}
	}
	return err
}

func (job *Job) runImpl(ctx context.Context) error {
	job.notification.notify(PROCESSING, job.message.MessageId(), "info")

	verr := job.message.Validate()
	if verr != nil {
		log.Printf("Invalid Message: MessageId: %v, Message: %v, error: %v\n", job.message.MessageId(), job.message.raw.Message, verr)
		return nil
	}

	err := job.setupWorkspace()
	if err != nil {
		return err
	}

	defer job.clearWorkspace()

	err = job.withNotify(PREPARING, job.setupDownloadFiles)()
	if err != nil {
		return err
	}

	err = job.build()
	if err != nil {
		log.Fatalf("Command build Error template: %v msg: %v cause of %v\n", job.config.Template, job.message, err)
		return err
	}

	go job.message.sendMADPeriodically()
	defer job.message.Done()

	err = job.withNotify(DOWNLOADING, job.downloadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(EXECUTING, job.execute)()
	if err != nil {
		return err
	}

	err = job.withNotify(UPLOADING, job.uploadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(ACKSENDING, job.message.Ack)()
	if err != nil {
		return err
	}

	job.notification.notify(CLEANUP, job.message.MessageId(), "info")
	return err
}

func (job *Job) withNotify(progress int, f func() error) func() error {
	msg_id := job.message.MessageId()
	return func() error {
		job.notification.notify(progress, msg_id, "info")
		err := f()
		if err != nil {
			job.notification.notify(progress+2, msg_id, "error")
			return err
		}
		job.notification.notify(progress+1, msg_id, "info")
		return nil
	}
}

func (job *Job) setupWorkspace() error {
	dir, err := ioutil.TempDir("", "workspace")
	if err != nil {
		log.Fatal(err)
		return err
	}

	subdirs := []string{
		filepath.Join(dir, "downloads"),
		filepath.Join(dir, "uploads"),
	}
	for _, subdir := range subdirs {
		err := os.MkdirAll(subdir, 0700)
		if err != nil {
			return err
		}
	}
	job.workspace = dir
	job.downloads_dir = subdirs[0]
	job.uploads_dir = subdirs[1]
	return nil
}

func (job *Job) clearWorkspace() error {
	return os.RemoveAll(job.workspace)
}

func (job *Job) setupDownloadFiles() error {
	job.downloadFileMap = map[string]string{}
	job.remoteDownloadFiles = job.message.DownloadFiles()
	objects := job.flatten(job.remoteDownloadFiles)
	remoteUrls := []string{}
	for _, obj := range objects {
		switch obj.(type) {
		case string:
			remoteUrls = append(remoteUrls, obj.(string))
		default:
			log.Printf("Invalid download file URL: %v [%T]", obj, obj)
		}
	}
	for _, remote_url := range remoteUrls {
		url, err := url.Parse(remote_url)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remote_url, err)
			return err
		}
		urlstr := fmt.Sprintf("gs://%v%v", url.Host, url.Path)
		destPath := filepath.Join(job.downloads_dir, url.Host, url.Path)
		job.downloadFileMap[urlstr] = destPath
	}
	job.localDownloadFiles = job.copyWithFileMap(job.remoteDownloadFiles)
	return nil
}

func (job *Job) copyWithFileMap(obj interface{}) interface{} {
	switch obj.(type) {
	case map[string]interface{}:
		result := map[string]interface{}{}
		for k, v := range obj.(map[string]interface{}) {
			result[k] = job.copyWithFileMap(v)
		}
		return result
	case []interface{}:
		result := []interface{}{}
		for _, v := range obj.([]interface{}) {
			result = append(result, job.copyWithFileMap(v))
		}
		return result
	case string:
		return job.downloadFileMap[obj.(string)]
	default:
		return obj
	}
}

func (job *Job) buildVariable() *Variable {
	return &Variable{
		data: map[string]interface{}{
			"workspace":             job.workspace,
			"downloads_dir":         job.downloads_dir,
			"uploads_dir":           job.uploads_dir,
			"download_files":        job.localDownloadFiles,
			"local_download_files":  job.localDownloadFiles,
			"remote_download_files": job.remoteDownloadFiles,
			"attrs":                 job.message.raw.Message.Attributes,
			"attributes":            job.message.raw.Message.Attributes,
			"data":                  job.message.raw.Message.Data,
		},
	}
}

func (job *Job) build() error {
	v := job.buildVariable()

	values, err := job.extract(v, job.config.Template)
	if err != nil {
		return err
	}
	if len(job.config.Options) > 0 {
		key := strings.Join(values, " ")
		t := job.config.Options[key]
		if t == nil {
			t = job.config.Options["default"]
		}
		if t != nil {
			values, err = job.extract(v, t)
			if err != nil {
				return err
			}
		}
	}
	job.cmd = exec.Command(values[0], values[1:]...)
	return nil
}

func (job *Job) extract(v *Variable, values []string) ([]string, error) {
	result := []string{}
	for _, src := range values {
		extracted, err := v.expand(src)
		if err != nil {
			return nil, &InvalidJobError{err.Error()}
		}
		vals := strings.Split(extracted, v.separator)
		for _, val := range vals {
			result = append(result, val)
		}
	}
	return result, nil
}

func (job *Job) downloadFiles() error {
	for remoteURL, destPath := range job.downloadFileMap {
		url, err := url.Parse(remoteURL)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remoteURL, err)
			return err
		}

		dir := path.Dir(destPath)
		err = os.MkdirAll(dir, 0700)
		if err != nil {
			return err
		}

		err = job.storage.Download(url.Host, url.Path[1:], destPath)
		if err != nil {
			return err
		}
	}
	return nil
}

func (job *Job) execute() error {
	var out bytes.Buffer
	job.cmd.Stdout = &out
	job.cmd.Stderr = &out
	log.Printf("EXECUTE running: %v\n", job.cmd)
	err := job.cmd.Run()
	if err != nil {
		log.Printf("Command Error: cmd: %v cause of %v\n%v\n", job.cmd, err, out.String())
		return err
	}
	return nil
}

func (job *Job) uploadFiles() error {
	localPaths, err := job.listFiles(job.uploads_dir)
	if err != nil {
		return err
	}
	for _, localPath := range localPaths {
		relPath, err := filepath.Rel(job.uploads_dir, localPath)
		if err != nil {
			log.Fatalf("Error getting relative path of %v: %v\n", localPath, err)
			return err
		}
		sep := string([]rune{os.PathSeparator})
		parts := strings.Split(relPath, sep)
		bucket := parts[0]
		object := strings.Join(parts[1:], sep)
		err = job.storage.Upload(bucket, object, localPath)
		if err != nil {
			log.Fatalf("Error uploading %v to gs://%v/%v: %v\n", localPath, bucket, object, err)
			return err
		}
	}
	return nil
}

func (job *Job) listFiles(dir string) ([]string, error) {
	result := []string{}
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			result = append(result, path)
		}
		return nil
	})
	if err != nil {
		log.Fatalf("Error listing upload files: %v\n", err)
		return nil, err
	}
	return result, nil
}

func (job *Job) flatten(obj interface{}) []interface{} {
	// Support only unmarshalled object from JSON
	// See https://golang.org/pkg/encoding/json/#Unmarshal also
	switch obj.(type) {
	case []interface{}:
		res := []interface{}{}
		for _, i := range obj.([]interface{}) {
			switch i.(type) {
			case bool, float64, string, nil:
				res = append(res, i)
			default:
				for _, j := range job.flatten(i) {
					res = append(res, j)
				}
			}
		}
		return res
	case map[string]interface{}:
		values := []interface{}{}
		for _, val := range obj.(map[string]interface{}) {
			values = append(values, val)
		}
		return job.flatten(values)
	default:
		return []interface{}{obj}
	}
}
<commit_msg>:recycle: Rename verr to err<commit_after>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"golang.org/x/net/context"
)

type (
	CommandConfig struct {
		Template []string            `json:"-"`
		Options  map[string][]string `json:"options,omitempty"`
		Dryrun   bool                `json:"dryrun,omitempty"`
	}

	Job struct {
		config *CommandConfig
		// https://godoc.org/google.golang.org/genproto/googleapis/pubsub/v1#ReceivedMessage
		message      *JobMessage
		notification *ProgressNotification
		storage      Storage

		// These are set at at setupWorkspace
		workspace     string
		downloads_dir string
		uploads_dir   string

		// These are set at setupDownloadFiles
		downloadFileMap     map[string]string
		remoteDownloadFiles interface{}
		localDownloadFiles  interface{}

		cmd *exec.Cmd
	}
)

func (job *Job) run(ctx context.Context) error {
	err := job.runImpl(ctx)
	switch err.(type) {
	case InvalidJobError:
		err := job.withNotify(CANCELLING, job.message.Ack)()
		if err != nil {
			return err
		}
	}
	return err
}

func (job *Job) runImpl(ctx context.Context) error {
	job.notification.notify(PROCESSING, job.message.MessageId(), "info")

	err := job.message.Validate()
	if err != nil {
		log.Printf("Invalid Message: MessageId: %v, Message: %v, error: %v\n", job.message.MessageId(), job.message.raw.Message, err)
		return nil
	}

	err = job.setupWorkspace()
	if err != nil {
		return err
	}

	defer job.clearWorkspace()

	err = job.withNotify(PREPARING, job.setupDownloadFiles)()
	if err != nil {
		return err
	}

	err = job.build()
	if err != nil {
		log.Fatalf("Command build Error template: %v msg: %v cause of %v\n", job.config.Template, job.message, err)
		return err
	}

	go job.message.sendMADPeriodically()
	defer job.message.Done()

	err = job.withNotify(DOWNLOADING, job.downloadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(EXECUTING, job.execute)()
	if err != nil {
		return err
	}

	err = job.withNotify(UPLOADING, job.uploadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(ACKSENDING, job.message.Ack)()
	if err != nil {
		return err
	}

	job.notification.notify(CLEANUP, job.message.MessageId(), "info")
	return err
}

func (job *Job) withNotify(progress int, f func() error) func() error {
	msg_id := job.message.MessageId()
	return func() error {
		job.notification.notify(progress, msg_id, "info")
		err := f()
		if err != nil {
			job.notification.notify(progress+2, msg_id, "error")
			return err
		}
		job.notification.notify(progress+1, msg_id, "info")
		return nil
	}
}

func (job *Job) setupWorkspace() error {
	dir, err := ioutil.TempDir("", "workspace")
	if err != nil {
		log.Fatal(err)
		return err
	}

	subdirs := []string{
		filepath.Join(dir, "downloads"),
		filepath.Join(dir, "uploads"),
	}
	for _, subdir := range subdirs {
		err := os.MkdirAll(subdir, 0700)
		if err != nil {
			return err
		}
	}
	job.workspace = dir
	job.downloads_dir = subdirs[0]
	job.uploads_dir = subdirs[1]
	return nil
}

func (job *Job) clearWorkspace() error {
	return os.RemoveAll(job.workspace)
}

func (job *Job) setupDownloadFiles() error {
	job.downloadFileMap = map[string]string{}
	job.remoteDownloadFiles = job.message.DownloadFiles()
	objects := job.flatten(job.remoteDownloadFiles)
	remoteUrls := []string{}
	for _, obj := range objects {
		switch obj.(type) {
		case string:
			remoteUrls = append(remoteUrls, obj.(string))
		default:
			log.Printf("Invalid download file URL: %v [%T]", obj, obj)
		}
	}
	for _, remote_url := range remoteUrls {
		url, err := url.Parse(remote_url)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remote_url, err)
			return err
		}
		urlstr := fmt.Sprintf("gs://%v%v", url.Host, url.Path)
		destPath := filepath.Join(job.downloads_dir, url.Host, url.Path)
		job.downloadFileMap[urlstr] = destPath
	}
	job.localDownloadFiles = job.copyWithFileMap(job.remoteDownloadFiles)
	return nil
}

func (job *Job) copyWithFileMap(obj interface{}) interface{} {
	switch obj.(type) {
	case map[string]interface{}:
		result := map[string]interface{}{}
		for k, v := range obj.(map[string]interface{}) {
			result[k] = job.copyWithFileMap(v)
		}
		return result
	case []interface{}:
		result := []interface{}{}
		for _, v := range obj.([]interface{}) {
			result = append(result, job.copyWithFileMap(v))
		}
		return result
	case string:
		return job.downloadFileMap[obj.(string)]
	default:
		return obj
	}
}

func (job *Job) buildVariable() *Variable {
	return &Variable{
		data: map[string]interface{}{
			"workspace":             job.workspace,
			"downloads_dir":         job.downloads_dir,
			"uploads_dir":           job.uploads_dir,
			"download_files":        job.localDownloadFiles,
			"local_download_files":  job.localDownloadFiles,
			"remote_download_files": job.remoteDownloadFiles,
			"attrs":                 job.message.raw.Message.Attributes,
			"attributes":            job.message.raw.Message.Attributes,
			"data":                  job.message.raw.Message.Data,
		},
	}
}

func (job *Job) build() error {
	v := job.buildVariable()

	values, err := job.extract(v, job.config.Template)
	if err != nil {
		return err
	}
	if len(job.config.Options) > 0 {
		key := strings.Join(values, " ")
		t := job.config.Options[key]
		if t == nil {
			t = job.config.Options["default"]
		}
		if t != nil {
			values, err = job.extract(v, t)
			if err != nil {
				return err
			}
		}
	}
	job.cmd = exec.Command(values[0], values[1:]...)
	return nil
}

func (job *Job) extract(v *Variable, values []string) ([]string, error) {
	result := []string{}
	for _, src := range values {
		extracted, err := v.expand(src)
		if err != nil {
			return nil, &InvalidJobError{err.Error()}
		}
		vals := strings.Split(extracted, v.separator)
		for _, val := range vals {
			result = append(result, val)
		}
	}
	return result, nil
}

func (job *Job) downloadFiles() error {
	for remoteURL, destPath := range job.downloadFileMap {
		url, err := url.Parse(remoteURL)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remoteURL, err)
			return err
		}

		dir := path.Dir(destPath)
		err = os.MkdirAll(dir, 0700)
		if err != nil {
			return err
		}

		err = job.storage.Download(url.Host, url.Path[1:], destPath)
		if err != nil {
			return err
		}
	}
	return nil
}

func (job *Job) execute() error {
	var out bytes.Buffer
	job.cmd.Stdout = &out
	job.cmd.Stderr = &out
	log.Printf("EXECUTE running: %v\n", job.cmd)
	err := job.cmd.Run()
	if err != nil {
		log.Printf("Command Error: cmd: %v cause of %v\n%v\n", job.cmd, err, out.String())
		return err
	}
	return nil
}

func (job *Job) uploadFiles() error {
	localPaths, err := job.listFiles(job.uploads_dir)
	if err != nil {
		return err
	}
	for _, localPath := range localPaths {
		relPath, err := filepath.Rel(job.uploads_dir, localPath)
		if err != nil {
			log.Fatalf("Error getting relative path of %v: %v\n", localPath, err)
			return err
		}
		sep := string([]rune{os.PathSeparator})
		parts := strings.Split(relPath, sep)
		bucket := parts[0]
		object := strings.Join(parts[1:], sep)
		err = job.storage.Upload(bucket, object, localPath)
		if err != nil {
			log.Fatalf("Error uploading %v to gs://%v/%v: %v\n", localPath, bucket, object, err)
			return err
		}
	}
	return nil
}

func (job *Job) listFiles(dir string) ([]string, error) {
	result := []string{}
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			result = append(result, path)
		}
		return nil
	})
	if err != nil {
		log.Fatalf("Error listing upload files: %v\n", err)
		return nil, err
	}
	return result, nil
}

func (job *Job) flatten(obj interface{}) []interface{} {
	// Support only unmarshalled object from JSON
	// See https://golang.org/pkg/encoding/json/#Unmarshal also
	switch obj.(type) {
	case []interface{}:
		res := []interface{}{}
		for _, i := range obj.([]interface{}) {
			switch i.(type) {
			case bool, float64, string, nil:
				res = append(res, i)
			default:
				for _, j := range job.flatten(i) {
					res = append(res, j)
				}
			}
		}
		return res
	case map[string]interface{}:
		values := []interface{}{}
		for _, val := range obj.(map[string]interface{}) {
			values = append(values, val)
		}
		return job.flatten(values)
	default:
		return []interface{}{obj}
	}
}
<|endoftext|>


<commit_before>package sisparse

import (
	"bufio"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/yhat/scrape"
	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"

	"github.com/iamwave/samorozvrh/backend/cache"
)

// The year in which semester 1 begins
const schoolYear = 2019

// 1 for winter, 2 for summer
const semester = 1

const sisUrl = "https://is.cuni.cz/studium/predmety/index.php?do=predmet&kod=%s&skr=%d&sem=%d"
const scheduleBaseUrl = "https://is.cuni.cz/studium/rozvrhng/"

const DEBUG = false

// Returns a two-dimensional array containing groups of events.
// Each group is a slice of events which must be enrolled together,
// the groups represent different times/teachers of the same course.
// Also, lectures and seminars/practicals are in separate groups.
func GetCourseEvents(courseCode string) ([][]Event, error) {
	subjectUrl := fmt.Sprintf(sisUrl, courseCode, schoolYear, semester)

	subjectHtmlRoot, err := getHtml(subjectUrl)
	if err != nil {
		return nil, err
	}
	// It is difficult to directly convert an event code to a schedule link,
	// because SIS requires the faculty number. Therefore we first open the course
	// in the "Subjects" SIS module and then go to a link which takes
	// us to the schedule.
	scheduleUrl, err := getScheduleUrl(subjectHtmlRoot)
	if err != nil {
		return nil, err
	}

	// For some subjects (ASZLJ3010), the link has the wrong semester for whatever reason
	// (even though the correct semester is specified in the original URL).
	// Let's fix this manually.
	scheduleUrl = strings.Replace(
		scheduleUrl,
		fmt.Sprintf("sem=%d", 3-semester),
		fmt.Sprintf("sem=%d", semester),
		-1,
	)

	scheduleHtmlRoot, err := getHtml(scheduleUrl)
	if err != nil {
		return nil, err
	}
	return parseCourseEvents(scheduleHtmlRoot)
}

func getScheduleUrl(root *html.Node) (string, error) {
	const scheduleLinkText = "Rozvrh"

	matcher := func(n *html.Node) bool {
		if n.DataAtom == atom.A {
			return scrape.Text(n) == scheduleLinkText
		}
		return false
	}

	scheduleLink, ok := scrape.Find(root, matcher)
	if !ok {
		return "", errors.New("Couldn't find schedule URL")
	}
	relativeUrl := scrape.Attr(scheduleLink, "href")
	return getAbsoluteUrl(sisUrl, relativeUrl)
}

func parseCourseEvents(root *html.Node) ([][]Event, error) {
	if DEBUG {
		f, err := os.Create("/tmp/samorozvrh_debug.html")
		if err != nil {
			return nil, err
		}
		w := bufio.NewWriter(f)
		html.Render(w, root)
		w.Flush()
	}

	matcher := func(n *html.Node) bool {
		if n.DataAtom == atom.Tr && n.Parent != nil && n.Parent.Parent != nil {
			return (scrape.Attr(n.Parent.Parent, "id") == "table1" ||
				scrape.Attr(n.Parent.Parent, "id") == "roz_predmet_macro1") &&
				scrape.Attr(n, "class") != "head1" // ignore table header
		}
		return false
	}

	eventsTable := scrape.FindAll(root, matcher)
	if len(eventsTable) == 0 {
		// The event table is not present at all (possibly SIS returned an error message)
		return nil, errors.New("Couldn't find the event table")
	}

	res := [][]Event{}
	group := []Event{}
	for _, row := range eventsTable {
		event, err := parseEvent(row)
		if err != nil {
			return nil, err
		}
		if event == nil {
			// This could happen if an event is not scheduled, see:
			// https://is.cuni.cz/studium/rozvrhng/roz_predmet_macro.php?fak=11320&skr=2018&sem=1&predmet=NAIL062
			continue
		}
		// A non-empty name means the start of a new group;
		// names are omitted in all but the first event of a group.
		if event.Name != "" {
			if len(group) > 0 {
				res = append(res, group)
			}
			group = []Event{}
		} else {
			// Add the missing fields based on the group's first event
			event.Name = group[0].Name
			event.Teacher = group[0].Teacher
		}
		group = append(group, *event)
	}
	if len(group) > 0 {
		res = append(res, group)
	}

	if len(res) == 0 {
		return nil, errors.New("The course has no scheduled events")
	}

	return res, nil
}

func parseEvent(event *html.Node) (*Event, error) {
	var cols []string
	for col := event.FirstChild; col != nil; col = col.NextSibling {
		// For some reason we also get siblings with no tag and no data?
		if len(strings.TrimSpace(col.Data)) > 0 {
			cols = append(cols, scrape.Text(col))
		}
	}

	e := Event{
		Type:     cols[1],
		Name:     cols[2],
		Teacher:  cols[3],
		Room:     cols[5],
		Language: cols[7],
	}

	err := addEventScheduling(&e, cols[4], cols[6])
	if err != nil {
		// The event is not scheduled - this is ok
		return nil, nil
	}

	firstCol := event.FirstChild.NextSibling.FirstChild
	eventCode := scrape.Text(firstCol)
	relativeEventUrl := scrape.Attr(firstCol, "href")

	eventUrl, err := getAbsoluteUrl(scheduleBaseUrl, relativeEventUrl)
	if err != nil {
		return nil, err
	}

	err = addEventBuilding(&e, eventUrl, eventCode)
	if err != nil {
		return nil, err
	}

	return &e, err
}

func addEventBuilding(e *Event, eventUrl string, eventCode string) error {
	cacheName := []string{"rooms", e.Room}

	if cache.Has(cacheName) {
		building, err := cache.Get(cacheName)
		if err == nil {
			e.Building = building
		}
		return err
	} else {
		root, err := getHtml(eventUrl)
		if err != nil {
			return err
		}

		matcher := func(n *html.Node) bool {
			// Ex: https://is.cuni.cz/studium/rozvrhng/roz_predmet_gl.php?skr=2018&sem=1&gl=18aMB150P31p1&fak=11310
			// Must be in the correct row of the correct table
			if n.DataAtom == atom.Td &&
				hasNthParent(n, 4) &&
				n.Parent.FirstChild != n &&
				scrape.Text(n.Parent.FirstChild) == "Místo výuky:" {

				p := n.Parent.Parent.Parent.Parent
				if p.FirstChild != nil && p.FirstChild.NextSibling != nil &&
					strings.HasSuffix(scrape.Text(p.FirstChild.NextSibling), eventCode) {
					return true
				}
			}
			return false
		}

		rooms := scrape.FindAll(root, matcher)
		if len(rooms) != 1 {
			return errors.New(fmt.Sprintf("Matched %d rooms, expected 1", len(rooms)))
		}
		building, err := roomToBuilding(scrape.Text(rooms[0]))
		if err != nil {
			return err
		}
		cache.Set(cacheName, building)
		e.Building = building
	}
	return nil
}

func addEventScheduling(e *Event, daytime string, dur string) error {
	// For strings such as "Út 12:20"
	if len(daytime) == 0 {
		return errors.New("The daytime field is empty")
	}

	daytimeRunes := []rune(daytime)
	var err error
	e.Day, err = parseDay(string(daytimeRunes[:2]))
	if err != nil {
		return err
	}

	timeFrom, err := time.Parse("15:04", string(daytimeRunes[3:]))
	if err != nil {
		return err
	}

	d, parity, err := parseDurationAndWeekParity(dur)
	if err != nil {
		return err
	}

	e.TimeFrom = timeFrom
	e.TimeTo = timeFrom.Add(time.Minute * time.Duration(d))
	e.WeekParity = parity
	return nil
}

func parseDurationAndWeekParity(dur string) (int, int, error) {
	// Strings like "90" or "240 Sudé týdny (liché kalendářní)"
	w := strings.Fields(dur)
	d, err := strconv.Atoi(w[0])
	if err != nil {
		return -1, -1, errors.New(fmt.Sprintf("Unable to parse duration: %s", err))
	}
	parity := 0
	if len(w) > 1 {
		if w[1] == "Liché" {
			parity = 1
		} else {
			parity = 2
		}
	}
	return d, parity, nil
}

func parseDay(day string) (int, error) {
	days := []string{"Po", "Út", "St", "Čt", "Pá"}
	for i, d := range days {
		if d == day {
			return i, nil
		}
	}
	return -1, errors.New(fmt.Sprintf("Unknown day \"%s\"", day))
}

func getAbsoluteUrl(base, relative string) (string, error) {
	baseUrl, err := url.Parse(base)
	if err != nil {
		return "", err
	}
	relativeUrl, err := url.Parse(relative)
	if err != nil {
		return "", err
	}
	return baseUrl.ResolveReference(relativeUrl).String(), nil
}

func roomToBuilding(room string) (string, error) {
	p := strings.LastIndex(room, ") ")
	if p == -1 {
		return "", errors.New(fmt.Sprintf("Could not parse room: %s", room))
	}
	return room[p+2:], nil
}

func getHtml(url string) (*html.Node, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	return html.Parse(resp.Body)
}

func hasNthParent(node *html.Node, n int) bool {
	if n <= 0 {
		return true
	}
	for i := 0; i <= n; i++ {
		if node == nil {
			return false
		}
		node = node.Parent
	}
	return true
}
<commit_msg>Bump semester to 2<commit_after>package sisparse

import (
	"bufio"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/yhat/scrape"
	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"

	"github.com/iamwave/samorozvrh/backend/cache"
)

// The year in which semester 1 begins
const schoolYear = 2019

// 1 for winter, 2 for summer
const semester = 2

const sisUrl = "https://is.cuni.cz/studium/predmety/index.php?do=predmet&kod=%s&skr=%d&sem=%d"
const scheduleBaseUrl = "https://is.cuni.cz/studium/rozvrhng/"

const DEBUG = false

// Returns a two-dimensional array containing groups of events.
// Each group is a slice of events which must be enrolled together,
// the groups represent different times/teachers of the same course.
// Also, lectures and seminars/practicals are in separate groups.
func GetCourseEvents(courseCode string) ([][]Event, error) {
	subjectUrl := fmt.Sprintf(sisUrl, courseCode, schoolYear, semester)

	subjectHtmlRoot, err := getHtml(subjectUrl)
	if err != nil {
		return nil, err
	}
	// It is difficult to directly convert an event code to a schedule link,
	// because SIS requires the faculty number. Therefore we first open the course
	// in the "Subjects" SIS module and then go to a link which takes
	// us to the schedule.
	scheduleUrl, err := getScheduleUrl(subjectHtmlRoot)
	if err != nil {
		return nil, err
	}

	// For some subjects (ASZLJ3010), the link has the wrong semester for whatever reason
	// (even though the correct semester is specified in the original URL).
	// Let's fix this manually.
	scheduleUrl = strings.Replace(
		scheduleUrl,
		fmt.Sprintf("sem=%d", 3-semester),
		fmt.Sprintf("sem=%d", semester),
		-1,
	)

	scheduleHtmlRoot, err := getHtml(scheduleUrl)
	if err != nil {
		return nil, err
	}
	return parseCourseEvents(scheduleHtmlRoot)
}

func getScheduleUrl(root *html.Node) (string, error) {
	const scheduleLinkText = "Rozvrh"

	matcher := func(n *html.Node) bool {
		if n.DataAtom == atom.A {
			return scrape.Text(n) == scheduleLinkText
		}
		return false
	}

	scheduleLink, ok := scrape.Find(root, matcher)
	if !ok {
		return "", errors.New("Couldn't find schedule URL")
	}
	relativeUrl := scrape.Attr(scheduleLink, "href")
	return getAbsoluteUrl(sisUrl, relativeUrl)
}

func parseCourseEvents(root *html.Node) ([][]Event, error) {
	if DEBUG {
		f, err := os.Create("/tmp/samorozvrh_debug.html")
		if err != nil {
			return nil, err
		}
		w := bufio.NewWriter(f)
		html.Render(w, root)
		w.Flush()
	}

	matcher := func(n *html.Node) bool {
		if n.DataAtom == atom.Tr && n.Parent != nil && n.Parent.Parent != nil {
			return (scrape.Attr(n.Parent.Parent, "id") == "table1" ||
				scrape.Attr(n.Parent.Parent, "id") == "roz_predmet_macro1") &&
				scrape.Attr(n, "class") != "head1" // ignore table header
		}
		return false
	}

	eventsTable := scrape.FindAll(root, matcher)
	if len(eventsTable) == 0 {
		// The event table is not present at all (possibly SIS returned an error message)
		return nil, errors.New("Couldn't find the event table")
	}

	res := [][]Event{}
	group := []Event{}
	for _, row := range eventsTable {
		event, err := parseEvent(row)
		if err != nil {
			return nil, err
		}
		if event == nil {
			// This could happen if an event is not scheduled, see:
			// https://is.cuni.cz/studium/rozvrhng/roz_predmet_macro.php?fak=11320&skr=2018&sem=1&predmet=NAIL062
			continue
		}
		// A non-empty name means the start of a new group;
		// names are omitted in all but the first event of a group.
		if event.Name != "" {
			if len(group) > 0 {
				res = append(res, group)
			}
			group = []Event{}
		} else {
			// Add the missing fields based on the group's first event
			event.Name = group[0].Name
			event.Teacher = group[0].Teacher
		}
		group = append(group, *event)
	}
	if len(group) > 0 {
		res = append(res, group)
	}

	if len(res) == 0 {
		return nil, errors.New("The course has no scheduled events")
	}

	return res, nil
}

func parseEvent(event *html.Node) (*Event, error) {
	var cols []string
	for col := event.FirstChild; col != nil; col = col.NextSibling {
		// For some reason we also get siblings with no tag and no data?
		if len(strings.TrimSpace(col.Data)) > 0 {
			cols = append(cols, scrape.Text(col))
		}
	}

	e := Event{
		Type:     cols[1],
		Name:     cols[2],
		Teacher:  cols[3],
		Room:     cols[5],
		Language: cols[7],
	}

	err := addEventScheduling(&e, cols[4], cols[6])
	if err != nil {
		// The event is not scheduled - this is ok
		return nil, nil
	}

	firstCol := event.FirstChild.NextSibling.FirstChild
	eventCode := scrape.Text(firstCol)
	relativeEventUrl := scrape.Attr(firstCol, "href")

	eventUrl, err := getAbsoluteUrl(scheduleBaseUrl, relativeEventUrl)
	if err != nil {
		return nil, err
	}

	err = addEventBuilding(&e, eventUrl, eventCode)
	if err != nil {
		return nil, err
	}

	return &e, err
}

func addEventBuilding(e *Event, eventUrl string, eventCode string) error {
	cacheName := []string{"rooms", e.Room}

	if cache.Has(cacheName) {
		building, err := cache.Get(cacheName)
		if err == nil {
			e.Building = building
		}
		return err
	} else {
		root, err := getHtml(eventUrl)
		if err != nil {
			return err
		}

		matcher := func(n *html.Node) bool {
			// Ex: https://is.cuni.cz/studium/rozvrhng/roz_predmet_gl.php?skr=2018&sem=1&gl=18aMB150P31p1&fak=11310
			// Must be in the correct row of the correct table
			if n.DataAtom == atom.Td &&
				hasNthParent(n, 4) &&
				n.Parent.FirstChild != n &&
				scrape.Text(n.Parent.FirstChild) == "Místo výuky:" {

				p := n.Parent.Parent.Parent.Parent
				if p.FirstChild != nil && p.FirstChild.NextSibling != nil &&
					strings.HasSuffix(scrape.Text(p.FirstChild.NextSibling), eventCode) {
					return true
				}
			}
			return false
		}

		rooms := scrape.FindAll(root, matcher)
		if len(rooms) != 1 {
			return errors.New(fmt.Sprintf("Matched %d rooms, expected 1", len(rooms)))
		}
		building, err := roomToBuilding(scrape.Text(rooms[0]))
		if err != nil {
			return err
		}
		cache.Set(cacheName, building)
		e.Building = building
	}
	return nil
}

func addEventScheduling(e *Event, daytime string, dur string) error {
	// For strings such as "Út 12:20"
	if len(daytime) == 0 {
		return errors.New("The daytime field is empty")
	}

	daytimeRunes := []rune(daytime)
	var err error
	e.Day, err = parseDay(string(daytimeRunes[:2]))
	if err != nil {
		return err
	}

	timeFrom, err := time.Parse("15:04", string(daytimeRunes[3:]))
	if err != nil {
		return err
	}

	d, parity, err := parseDurationAndWeekParity(dur)
	if err != nil {
		return err
	}

	e.TimeFrom = timeFrom
	e.TimeTo = timeFrom.Add(time.Minute * time.Duration(d))
	e.WeekParity = parity
	return nil
}

func parseDurationAndWeekParity(dur string) (int, int, error) {
	// Strings like "90" or "240 Sudé týdny (liché kalendářní)"
	w := strings.Fields(dur)
	d, err := strconv.Atoi(w[0])
	if err != nil {
		return -1, -1, errors.New(fmt.Sprintf("Unable to parse duration: %s", err))
	}
	parity := 0
	if len(w) > 1 {
		if w[1] == "Liché" {
			parity = 1
		} else {
			parity = 2
		}
	}
	return d, parity, nil
}

func parseDay(day string) (int, error) {
	days := []string{"Po", "Út", "St", "Čt", "Pá"}
	for i, d := range days {
		if d == day {
			return i, nil
		}
	}
	return -1, errors.New(fmt.Sprintf("Unknown day \"%s\"", day))
}

func getAbsoluteUrl(base, relative string) (string, error) {
	baseUrl, err := url.Parse(base)
	if err != nil {
		return "", err
	}
	relativeUrl, err := url.Parse(relative)
	if err != nil {
		return "", err
	}
	return baseUrl.ResolveReference(relativeUrl).String(), nil
}

func roomToBuilding(room string) (string, error) {
	p := strings.LastIndex(room, ") ")
	if p == -1 {
		return "", errors.New(fmt.Sprintf("Could not parse room: %s", room))
	}
	return room[p+2:], nil
}

func getHtml(url string) (*html.Node, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	return html.Parse(resp.Body)
}

func hasNthParent(node *html.Node, n int) bool {
	if n <= 0 {
		return true
	}
	for i := 0; i <= n; i++ {
		if node == nil {
			return false
		}
		node = node.Parent
	}
	return true
}
<|endoftext|>