<commit_before>package rtime

import (
	"strings"
	"time"
)

// Returns the minute offset within the hour specified by t, in the range [0, 59].
func (t Time) Minute() int {
	return time.Time(t).Minute()
}

// Returns the second offset within the minute specified by t, in the range [0, 59].
func (t Time) Second() int {
	return time.Time(t).Second()
}

// Returns the hour within the day specified by t, in the range [0, 23].
func (t Time) Hour() int {
	return time.Time(t).Hour()
}

// Returns string representation of Hour
func (t Time) HourString() (result string) {
	result = numberInString(t.Hour(), false)
	switch t.Hour() {
	case 1:
		result += " час"
	case 2, 3, 4:
		result += " часа"
	default:
		result += " часов"
	}
	return
}

// Returns string representation of Minute
func (t Time) MinuteString() (result string) {
	result = numberInString(t.Minute(), true)
	minutes := t.Minute()
	if minutes > 20 {
		minutes = minutes % 10
	}
	switch minutes {
	case 1:
		result += " минута"
	case 2, 3, 4:
		result += " минуты"
	default:
		result += " минут"
	}
	return
}

// Returns string representation of Second
func (t Time) SecondString() (result string) {
	result = numberInString(t.Second(), true)
	seconds := t.Second()
	if seconds > 20 {
		seconds = seconds % 10
	}
	switch seconds {
	case 1:
		result += " секунда"
	case 2, 3, 4:
		result += " секунды"
	default:
		result += " секунд"
	}
	return
}

func numberInString(num int, female bool) (result string) {
	switch num {
	case 0:
		result = "Ноль"
	case 1:
		if female {
			result = "Одна"
		} else {
			result = "Один"
		}
	case 2:
		if female {
			result = "Две"
		} else {
			result = "Две"
		}
	case 3:
		result = "Три"
	case 4:
		result = "Четыре"
	case 5:
		result = "Пять"
	case 6:
		result = "Шесть"
	case 7:
		result = "Семь"
	case 8:
		result = "Восемь"
	case 9:
		result = "Девять"
	case 10:
		result = "Десять"
	case 11:
		result = "Одиннадцать"
	case 12:
		result = "Двенадцать"
	case 13:
		result = "Тринадцать"
	case 14:
		result = "Четырнадцать"
	case 15:
		result = "Пятнадцать"
	case 16:
		result = "Шестнадцать"
	case 17:
		result = "Семнадцать"
	case 18:
		result = "Восемнадцать"
	case 19:
		result = "Девятнадцать"
	case 20:
		result = "Двадцать"
	case 30:
		result = "Тридцать"
	case 40:
		result = "Сорок"
	case 50:
		result = "Пятьдесят"
	case 60:
		result = "Шестьдесят"
	case 70:
		result = "Семьдесят"
	case 80:
		result = "Восемьдесят"
	case 90:
		result = "Девяносто"
	default:
		lastDigit := num % 10
		result = numberInString(num-lastDigit, female)
		result += " " + strings.ToLower(numberInString(lastDigit, female))
	}
	return
}
<commit_msg>Some refactoring<commit_after>package rtime

import (
	"strings"
	"time"
)

// Returns the minute offset within the hour specified by t, in the range [0, 59].
func (t Time) Minute() int {
	return time.Time(t).Minute()
}

// Returns the second offset within the minute specified by t, in the range [0, 59].
func (t Time) Second() int {
	return time.Time(t).Second()
}

// Returns the hour within the day specified by t, in the range [0, 23].
func (t Time) Hour() int {
	return time.Time(t).Hour()
}

// Returns string representation of Hour
func (t Time) HourString() (result string) {
	result = numberInString(t.Hour(), false)
	result += " " + hoursTail(t.Hour())
	return
}

// Returns string representation of Minute
func (t Time) MinuteString() (result string) {
	result = numberInString(t.Minute(), true)
	result += " " + minutesTail(t.Minute())
	return
}

// Returns string representation of Second
func (t Time) SecondString() (result string) {
	result = numberInString(t.Second(), true)
	result += " " + secondsTail(t.Second())
	return
}

func hoursTail(hours int) (result string) {
	switch {
	case hours > 20 && hours < 100:
		hours = hours % 10
	case hours > 100:
		hours = hours % 100 % 10
	}
	switch hours {
	case 1:
		result = "час"
	case 2, 3, 4:
		result = "часа"
	default:
		result = "часов"
	}
	return
}

func minutesTail(minutes int) (result string) {
	switch {
	case minutes > 20 && minutes < 60:
		minutes = minutes % 10
	case minutes > 60:
		minutes = minutes % 60 % 10
	}
	switch minutes {
	case 1:
		result = "минута"
	case 2, 3, 4:
		result = "минуты"
	default:
		result = "минут"
	}
	return
}

func secondsTail(seconds int) (result string) {
	switch {
	case seconds > 20 && seconds < 60:
		seconds = seconds % 10
	case seconds > 60:
		seconds = seconds % 60 % 10
	}
	switch seconds {
	case 1:
		result = "секунда"
	case 2, 3, 4:
		result = "секунды"
	default:
		result = "секунд"
	}
	return
}

func numberInString(num int, female bool) (result string) {
	switch num {
	case 0:
		result = "Ноль"
	case 1:
		if female {
			result = "Одна"
		} else {
			result = "Один"
		}
	case 2:
		if female {
			result = "Две"
		} else {
			result = "Два"
		}
	case 3:
		result = "Три"
	case 4:
		result = "Четыре"
	case 5:
		result = "Пять"
	case 6:
		result = "Шесть"
	case 7:
		result = "Семь"
	case 8:
		result = "Восемь"
	case 9:
		result = "Девять"
	case 10:
		result = "Десять"
	case 11:
		result = "Одиннадцать"
	case 12:
		result = "Двенадцать"
	case 13:
		result = "Тринадцать"
	case 14:
		result = "Четырнадцать"
	case 15:
		result = "Пятнадцать"
	case 16:
		result = "Шестнадцать"
	case 17:
		result = "Семнадцать"
	case 18:
		result = "Восемнадцать"
	case 19:
		result = "Девятнадцать"
	case 20:
		result = "Двадцать"
	case 30:
		result = "Тридцать"
	case 40:
		result = "Сорок"
	case 50:
		result = "Пятьдесят"
	case 60:
		result = "Шестьдесят"
	case 70:
		result = "Семьдесят"
	case 80:
		result = "Восемьдесят"
	case 90:
		result = "Девяносто"
	default:
		lastDigit := num % 10
		result = numberInString(num-lastDigit, female)
		result += " " + strings.ToLower(numberInString(lastDigit, female))
	}
	return
}
<|endoftext|>


<commit_before>/* Mozilla InvestiGator Console

Version: MPL 1.1/GPL 2.0/LGPL 2.1

The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the
License.

The Initial Developer of the Original Code is
Mozilla Corporation
Portions created by the Initial Developer are Copyright (C) 2014
the Initial Developer. All Rights Reserved.

Contributor(s):
Julien Vehent jvehent@mozilla.com [:ulfr]

Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
*/

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"mig"
	"strings"

	"github.com/bobappleyard/readline"
)

// actionReader retrieves an action from the API using its numerical ID
// and enters prompt mode to analyze it
func actionReader(input string, ctx Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("actionReader() -> %v", e)
		}
	}()
	inputArr := strings.Split(input, " ")
	if len(inputArr) < 2 {
		panic("wrong order format. must be 'action <actionid>'")
	}
	aid := inputArr[1]
	a, err := getAction(aid, ctx)
	if err != nil {
		panic(err)
	}
	investigators := investigatorsStringFromAction(a.Investigators, 80)

	// completion
	var symbols = []string{"command", "copy", "counters", "details", "exit", "foundsomething",
		"foundnothing", "help", "investigators", "json", "pretty", "r", "times"}
	readline.Completer = func(query, ctx string) []string {
		var res []string
		for _, sym := range symbols {
			if strings.HasPrefix(sym, query) {
				res = append(res, sym)
			}
		}
		return res
	}

	fmt.Println("Entering action reader mode. Type \x1b[32;1mexit\x1b[0m or press \x1b[32;1mctrl+d\x1b[0m to leave. \x1b[32;1mhelp\x1b[0m may help.")
	fmt.Printf("Action: '%s'.\nLaunched by '%s' on '%s'.\nStatus '%s'.\n",
		a.Name, investigators, a.StartTime, a.Status)
	prompt := "\x1b[31;1maction " + aid[len(aid)-3:len(aid)] + ">\x1b[0m "
	for {
		input, err := readline.String(prompt)
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("error: ", err)
			break
		}
		orders := strings.Split(input, " ")
		switch orders[0] {
		case "command":
			err = commandReader(input, ctx)
			if err != nil {
				panic(err)
			}
		case "copy":
			err = actionLauncher(a, ctx)
			if err != nil {
				panic(err)
			}
			goto exit
		case "counters":
			fmt.Printf("Sent:\t\t%d\nReturned:\t%d\nDone:\t\t%d\n"+
				"Cancelled:\t%d\nFailed:\t\t%d\nTimeout:\t%d\n",
				a.Counters.Sent, a.Counters.Returned, a.Counters.Done,
				a.Counters.Cancelled, a.Counters.Failed, a.Counters.TimeOut)
		case "exit":
			fmt.Printf("exit\n")
			goto exit
		case "foundsomething":
			err = searchFoundAnything(a, true, ctx)
			if err != nil {
				panic(err)
			}
		case "foundnothing":
			err = searchFoundAnything(a, false, ctx)
			if err != nil {
				panic(err)
			}
		case "help":
			fmt.Printf(`The following orders are available:
command <id>	jump to command reader mode for command <id>
copy		enter action launcher mode using current action as template
counters	display the counters of the action
exit		exit this mode
foundsomething	list commands and agents that have found something
foundnothing	list commands and agents that have found nothing
help		show this help
investigators   print the list of investigators that signed the action
json <pretty>	show the json of the action
details		display the details of the action, including status & times
r		refresh the action (get latest version from upstream)
times		show the various timestamps of the action
`)
		case "investigators":
			for _, i := range a.Investigators {
				fmt.Println(i.Name, "- Key ID:", i.PGPFingerprint)
			}
		case "json":
			var ajson []byte
			if len(orders) > 1 {
				if orders[1] == "pretty" {
					ajson, err = json.MarshalIndent(a, "", "  ")
				} else {
					fmt.Printf("Unknown option '%s'\n", orders[1])
				}
			} else {
				ajson, err = json.Marshal(a)
			}
			if err != nil {
				panic(err)
			}
			fmt.Printf("%s\n", ajson)
		case "details":
			fmt.Printf(`
ID             %.0f
Name           %s
Target         %s
Desc           author '%s <%s>'; revision '%.0f';
               url '%s'
Threat         type '%s'; level '%s'; family '%s'; reference '%s'
Status         %s
Times          valid from %s until %s
               started %s; last updated %s; finished %s
               duration: %s
`, a.ID, a.Name, a.Target, a.Description.Author, a.Description.Email, a.Description.Revision,
				a.Description.URL, a.Threat.Type, a.Threat.Level, a.Threat.Family, a.Threat.Ref, a.Status,
				a.ValidFrom, a.ExpireAfter, a.StartTime, a.LastUpdateTime, a.FinishTime, a.LastUpdateTime.Sub(a.StartTime).String())
			fmt.Printf("Investigators  ")
			for _, i := range a.Investigators {
				fmt.Println(i.Name, "- keyid:", i.PGPFingerprint)
			}
			fmt.Printf("Operations     count=%d => ", len(a.Operations))
			for _, op := range a.Operations {
				fmt.Printf("%s; ", op.Module)
			}
			fmt.Printf("\n")
			fmt.Printf("Counters       sent=%d; returned=%d; done=%d\n"+
				"               cancelled=%d; failed=%d; timeout=%d\n",
				a.Counters.Sent, a.Counters.Returned, a.Counters.Done,
				a.Counters.Cancelled, a.Counters.Failed, a.Counters.TimeOut)
		case "r":
			a, err = getAction(aid, ctx)
			if err != nil {
				panic(err)
			}
			fmt.Println("Reload succeeded")
		case "times":
			fmt.Printf("Valid from   '%s' until '%s'\nStarted on   '%s'\n"+
				"Last updated '%s'\nFinished on  '%s'\n",
				a.ValidFrom, a.ExpireAfter, a.StartTime, a.LastUpdateTime, a.FinishTime)
		case "":
			break
		default:
			fmt.Printf("Unknown order '%s'. You are in action reader mode. Try `help`.\n", orders[0])
		}
		readline.AddHistory(input)
	}
exit:
	fmt.Printf("\n")
	return
}

func getAction(aid string, ctx Context) (a mig.Action, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("getAction() -> %v", e)
		}
	}()
	targetURL := ctx.API.URL + "action?actionid=" + aid
	resource, err := getAPIResource(targetURL, ctx)
	if err != nil {
		panic(err)
	}
	if resource.Collection.Items[0].Data[0].Name != "action" {
		panic("API returned something that is not an action... something's wrong.")
	}
	a, err = valueToAction(resource.Collection.Items[0].Data[0].Value)
	if err != nil {
		panic(err)
	}
	return
}

func valueToAction(v interface{}) (a mig.Action, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("valueToAction() -> %v", e)
		}
	}()
	bData, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	err = json.Unmarshal(bData, &a)
	if err != nil {
		panic(err)
	}
	return
}

func searchFoundAnything(a mig.Action, wantFound bool, ctx Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("foundAnything() -> %v", e)
		}
	}()
	foundanything := "false"
	if wantFound {
		foundanything = "true"
	}
	targetURL := ctx.API.URL + "search?type=command&limit=1000000&actionid=" + fmt.Sprintf("%.0f", a.ID) + "&foundanything=" + foundanything
	resource, err := getAPIResource(targetURL, ctx)
	if resource.Collection.Items[0].Data[0].Name != "search results" {
		fmt.Println(targetURL)
		panic("API returned something that is not search results.")
	}
	var results []mig.Command
	bData, err := json.Marshal(resource.Collection.Items[0].Data[0].Value)
	if err != nil {
		panic(err)
	}
	err = json.Unmarshal(bData, &results)
	if err != nil {
		panic(err)
	}
	agents := make(map[float64]mig.Command)
	for _, cmd := range results {
		agents[cmd.Agent.ID] = cmd
	}
	if wantFound {
		fmt.Printf("%d agents have found things\n", len(agents))
	} else {
		fmt.Printf("%d agents have not found anything\n", len(agents))
	}
	if len(agents) > 0 {
		fmt.Println("---- Command ID ----\t---- Agent Name & ID----")
		for agtid, cmd := range agents {
			fmt.Printf("%.0f\t%s [%.0f]\n", cmd.ID, cmd.Agent.Name, agtid)
		}
	}
	return
}

func investigatorsStringFromAction(invlist []mig.Investigator, strlen int) (investigators string) {
	for ctr, i := range invlist {
		if ctr > 0 {
			investigators += "; "
		}
		investigators += i.Name
	}
	if len(investigators) > strlen {
		investigators = investigators[0:(strlen-3)] + "..."
	}
	return
}
<commit_msg>[minor] manual indent in console action reader<commit_after>/* Mozilla InvestiGator Console

Version: MPL 1.1/GPL 2.0/LGPL 2.1

The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the
License.

The Initial Developer of the Original Code is
Mozilla Corporation
Portions created by the Initial Developer are Copyright (C) 2014
the Initial Developer. All Rights Reserved.

Contributor(s):
Julien Vehent jvehent@mozilla.com [:ulfr]

Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
*/

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"mig"
	"strings"

	"github.com/bobappleyard/readline"
)

// actionReader retrieves an action from the API using its numerical ID
// and enters prompt mode to analyze it
func actionReader(input string, ctx Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("actionReader() -> %v", e)
		}
	}()
	inputArr := strings.Split(input, " ")
	if len(inputArr) < 2 {
		panic("wrong order format. must be 'action <actionid>'")
	}
	aid := inputArr[1]
	a, err := getAction(aid, ctx)
	if err != nil {
		panic(err)
	}
	investigators := investigatorsStringFromAction(a.Investigators, 80)

	// completion
	var symbols = []string{"command", "copy", "counters", "details", "exit", "foundsomething",
		"foundnothing", "help", "investigators", "json", "pretty", "r", "times"}
	readline.Completer = func(query, ctx string) []string {
		var res []string
		for _, sym := range symbols {
			if strings.HasPrefix(sym, query) {
				res = append(res, sym)
			}
		}
		return res
	}

	fmt.Println("Entering action reader mode. Type \x1b[32;1mexit\x1b[0m or press \x1b[32;1mctrl+d\x1b[0m to leave. \x1b[32;1mhelp\x1b[0m may help.")
	fmt.Printf("Action: '%s'.\nLaunched by '%s' on '%s'.\nStatus '%s'.\n",
		a.Name, investigators, a.StartTime, a.Status)
	prompt := "\x1b[31;1maction " + aid[len(aid)-3:len(aid)] + ">\x1b[0m "
	for {
		input, err := readline.String(prompt)
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("error: ", err)
			break
		}
		orders := strings.Split(input, " ")
		switch orders[0] {
		case "command":
			err = commandReader(input, ctx)
			if err != nil {
				panic(err)
			}
		case "copy":
			err = actionLauncher(a, ctx)
			if err != nil {
				panic(err)
			}
			goto exit
		case "counters":
			fmt.Printf("Sent:\t\t%d\nReturned:\t%d\nDone:\t\t%d\n"+
				"Cancelled:\t%d\nFailed:\t\t%d\nTimeout:\t%d\n",
				a.Counters.Sent, a.Counters.Returned, a.Counters.Done,
				a.Counters.Cancelled, a.Counters.Failed, a.Counters.TimeOut)
		case "exit":
			fmt.Printf("exit\n")
			goto exit
		case "foundsomething":
			err = searchFoundAnything(a, true, ctx)
			if err != nil {
				panic(err)
			}
		case "foundnothing":
			err = searchFoundAnything(a, false, ctx)
			if err != nil {
				panic(err)
			}
		case "help":
			fmt.Printf(`The following orders are available:
command <id>	jump to command reader mode for command <id>
copy		enter action launcher mode using current action as template
counters	display the counters of the action
exit		exit this mode
foundsomething	list commands and agents that have found something
foundnothing	list commands and agents that have found nothing
help		show this help
investigators   print the list of investigators that signed the action
json <pretty>	show the json of the action
details		display the details of the action, including status & times
r		refresh the action (get latest version from upstream)
times		show the various timestamps of the action
`)
		case "investigators":
			for _, i := range a.Investigators {
				fmt.Println(i.Name, "- Key ID:", i.PGPFingerprint)
			}
		case "json":
			var ajson []byte
			if len(orders) > 1 {
				if orders[1] == "pretty" {
					ajson, err = json.MarshalIndent(a, "", "  ")
				} else {
					fmt.Printf("Unknown option '%s'\n", orders[1])
				}
			} else {
				ajson, err = json.Marshal(a)
			}
			if err != nil {
				panic(err)
			}
			fmt.Printf("%s\n", ajson)
		case "details":
			fmt.Printf(`
ID             %.0f
Name           %s
Target         %s
Desc           author '%s <%s>'; revision '%.0f';
               url '%s'
Threat         type '%s'; level '%s'; family '%s'; reference '%s'
Status         %s
Times          valid from %s until %s
               started %s; last updated %s; finished %s
               duration: %s
`, a.ID, a.Name, a.Target, a.Description.Author, a.Description.Email, a.Description.Revision,
				a.Description.URL, a.Threat.Type, a.Threat.Level, a.Threat.Family, a.Threat.Ref, a.Status,
				a.ValidFrom, a.ExpireAfter, a.StartTime, a.LastUpdateTime, a.FinishTime, a.LastUpdateTime.Sub(a.StartTime).String())
			fmt.Printf("Investigators  ")
			for _, i := range a.Investigators {
				fmt.Println(i.Name, "- keyid:", i.PGPFingerprint)
			}
			fmt.Printf("Operations     count=%d => ", len(a.Operations))
			for _, op := range a.Operations {
				fmt.Printf("%s; ", op.Module)
			}
			fmt.Printf("\n")
			fmt.Printf("Counters       sent=%d; returned=%d; done=%d\n"+
				"               cancelled=%d; failed=%d; timeout=%d\n",
				a.Counters.Sent, a.Counters.Returned, a.Counters.Done,
				a.Counters.Cancelled, a.Counters.Failed, a.Counters.TimeOut)
		case "r":
			a, err = getAction(aid, ctx)
			if err != nil {
				panic(err)
			}
			fmt.Println("Reload succeeded")
		case "times":
			fmt.Printf("Valid from   '%s' until '%s'\nStarted on   '%s'\n"+
				"Last updated '%s'\nFinished on  '%s'\n",
				a.ValidFrom, a.ExpireAfter, a.StartTime, a.LastUpdateTime, a.FinishTime)
		case "":
			break
		default:
			fmt.Printf("Unknown order '%s'. You are in action reader mode. Try `help`.\n", orders[0])
		}
		readline.AddHistory(input)
	}
exit:
	fmt.Printf("\n")
	return
}

func getAction(aid string, ctx Context) (a mig.Action, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("getAction() -> %v", e)
		}
	}()
	targetURL := ctx.API.URL + "action?actionid=" + aid
	resource, err := getAPIResource(targetURL, ctx)
	if err != nil {
		panic(err)
	}
	if resource.Collection.Items[0].Data[0].Name != "action" {
		panic("API returned something that is not an action... something's wrong.")
	}
	a, err = valueToAction(resource.Collection.Items[0].Data[0].Value)
	if err != nil {
		panic(err)
	}
	return
}

func valueToAction(v interface{}) (a mig.Action, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("valueToAction() -> %v", e)
		}
	}()
	bData, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	err = json.Unmarshal(bData, &a)
	if err != nil {
		panic(err)
	}
	return
}

func searchFoundAnything(a mig.Action, wantFound bool, ctx Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("foundAnything() -> %v", e)
		}
	}()
	foundanything := "false"
	if wantFound {
		foundanything = "true"
	}
	targetURL := ctx.API.URL + "search?type=command&limit=1000000&actionid=" + fmt.Sprintf("%.0f", a.ID) + "&foundanything=" + foundanything
	resource, err := getAPIResource(targetURL, ctx)
	if resource.Collection.Items[0].Data[0].Name != "search results" {
		fmt.Println(targetURL)
		panic("API returned something that is not search results.")
	}
	var results []mig.Command
	bData, err := json.Marshal(resource.Collection.Items[0].Data[0].Value)
	if err != nil {
		panic(err)
	}
	err = json.Unmarshal(bData, &results)
	if err != nil {
		panic(err)
	}
	agents := make(map[float64]mig.Command)
	for _, cmd := range results {
		agents[cmd.Agent.ID] = cmd
	}
	if wantFound {
		fmt.Printf("%d agents have found things\n", len(agents))
	} else {
		fmt.Printf("%d agents have not found anything\n", len(agents))
	}
	if len(agents) > 0 {
		fmt.Println("---- Command ID ----    ---- Agent Name & ID----")
		for agtid, cmd := range agents {
			fmt.Printf("%20.0f    %s [%.0f]\n", cmd.ID, cmd.Agent.Name, agtid)
		}
	}
	return
}

func investigatorsStringFromAction(invlist []mig.Investigator, strlen int) (investigators string) {
	for ctr, i := range invlist {
		if ctr > 0 {
			investigators += "; "
		}
		investigators += i.Name
	}
	if len(investigators) > strlen {
		investigators = investigators[0:(strlen-3)] + "..."
	}
	return
}
<|endoftext|>


<commit_before>// Copyright (c) 2014, Rob Thornton
// All rights reserved.
// This source code is governed by a Simplied BSD-License. Please see the
// LICENSE included in this distribution for a copy of the full license
// or, if one is not included, you may also find a copy at
// http://opensource.org/licenses/BSD-2-Clause

package parse

import (
	"os"

	"github.com/rthornton128/calc/ast"
	"github.com/rthornton128/calc/scan"
	"github.com/rthornton128/calc/token"
)

func ParseFile(filename, src string) *ast.File {
	var p parser
	p.init(filename, src)
	f := p.parseFile()
	if p.errors.Count() > 0 {
		p.errors.Print()
		return nil
	}
	return f
}

type parser struct {
	file    *token.File
	errors  scan.ErrorList
	scanner scan.Scanner

	curScope *ast.Scope
	topScope *ast.Scope

	pos token.Pos
	tok token.Token
	lit string
}

func (p *parser) addError(msg string) {
	p.errors.Add(p.file.Position(p.pos), msg)
	if p.errors.Count() >= 10 {
		p.errors.Print()
		os.Exit(1)
	}
}

func (p *parser) expect(tok token.Token) token.Pos {
	pos := p.pos
	if p.tok != tok {
		p.addError("Expected '" + tok.String() + "' got '" + p.lit + "'")
	}
	p.next()
	return pos
}

func (p *parser) init(fname, src string) {
	p.file = token.NewFile(fname, 1, len(src))
	p.scanner.Init(p.file, src)
	p.curScope = ast.NewScope(nil)
	p.topScope = p.curScope
	p.next()
}

func (p *parser) next() {
	p.lit, p.tok, p.pos = p.scanner.Scan()
}

func (p *parser) openScope() {
	p.curScope = ast.NewScope(p.curScope)
}

func (p *parser) closeScope() {
	p.curScope = p.curScope.Parent()
}

func (p *parser) parseAssignExpr(open token.Pos) *ast.AssignExpr {
	pos := p.expect(token.ASSIGN)
	nam := p.parseIdent()
	val := p.parseGenExpr()

	ob := &ast.Object{
		NamePos: nam.NamePos,
		Name:    nam.Name,
		Type:    nil,
		Value:   val,
	}
	old := p.curScope.Lookup(nam.Name)
	if old == nil {
		p.addError("Cannot assign to undeclared identifier " + nam.Name)
	}
	if ob.Type.Name != old.Type.Name {
		p.addError("Cannot assign " + ob.Name + " of type (" + ob.Type.Name +
			") to " + old.Name + " of type (" + ob.Type.Name + ")")
	}
	end := p.expect(token.RPAREN)
	return &ast.AssignExpr{
		Expression: ast.Expression{Opening: open, Closing: end},
		Equal:      pos,
		Name:       nam,
		Object:     ob,
	}
}

func (p *parser) parseBasicLit() *ast.BasicLit {
	pos, tok, lit := p.pos, p.tok, p.lit
	p.next()
	return &ast.BasicLit{LitPos: pos, Kind: tok, Lit: lit}
}

func (p *parser) parseBinaryExpr(open token.Pos) *ast.BinaryExpr {
	pos := p.pos
	op := p.tok
	p.next()

	var list []ast.Expr
	for p.tok != token.RPAREN && p.tok != token.EOF {
		list = append(list, p.parseGenExpr())
	}
	if len(list) < 2 {
		p.addError("binary expression must have at least two operands")
	}
	end := p.expect(token.RPAREN)
	return &ast.BinaryExpr{
		Expression: ast.Expression{
			Opening: open,
			Closing: end,
		},
		Op:    op,
		OpPos: pos,
		List:  list,
	}
}

func (p *parser) parseGenExpr() ast.Expr {
	var expr ast.Expr

	switch p.tok {
	case token.LPAREN:
		expr = p.parseExpr()
	case token.IDENT:
		expr = p.parseIdent()
	case token.INTEGER:
		expr = p.parseBasicLit()
	default:
		p.addError("Expected '" + token.LPAREN.String() + "' or '" +
			token.INTEGER.String() + "' got '" + p.lit + "'")
		p.next()
	}

	return expr
}

func (p *parser) parseExpr() ast.Expr {
	var expr ast.Expr

	pos := p.expect(token.LPAREN)
	switch p.tok {
	case token.ADD, token.SUB, token.MUL, token.QUO, token.REM,
		token.EQL, token.GTE, token.GTT, token.NEQ, token.LST, token.LTE:
		expr = p.parseBinaryExpr(pos)
	case token.VAR:
		expr = p.parseVarExpr(pos)
	default:
		p.addError("Expected binary operator but got '" + p.lit + "'")
	}

	return expr
}

func (p *parser) parseFile() *ast.File {
	var expr ast.Expr
	expr = p.parseGenExpr()
	if p.tok != token.EOF {
		p.addError("Expected EOF, got '" + p.lit + "'")
	}
	scope := ast.NewScope(nil)
	ob := &ast.Object{
		NamePos: token.NoPos,
		Name:    "main",
		Kind:    ast.Decl,
		Type:    &ast.Ident{NamePos: token.NoPos, Name: "int"},
		Value:   expr,
	}
	scope.Insert(ob)
	return &ast.File{Scope: scope}
}

func (p *parser) parseIdent() *ast.Ident {
	name := p.lit
	pos := p.expect(token.IDENT)
	return &ast.Ident{NamePos: pos, Name: name}
}

func (p *parser) parseVarExpr(lparen token.Pos) *ast.VarExpr {
	var (
		typ *ast.Ident
		val ast.Expr
	)
	varpos := p.expect(token.VAR)
	if p.tok != token.IDENT {
		p.expect(token.IDENT)
	}
	nam := p.parseIdent()

	// TODO: Needs improvement; maybe a p.tryTypeOrExpression?
	if p.tok == token.RPAREN {
		p.addError("Expected type, expression or literal, got: )")
	}

	if p.tok == token.IDENT {
		typ = p.parseIdent()
	}

	if p.tok != token.RPAREN {
		val = p.parseGenExpr()
	}

	if p.tok != token.RPAREN {
		typ = p.parseIdent()
	}
	// TODO: end
	rparen := p.expect(token.RPAREN)

	ob := &ast.Object{
		NamePos: nam.NamePos,
		Name:    nam.Name,
		Kind:    ast.Var,
		Type:    typ,
		Value:   val,
	}

	if old := p.curScope.Insert(ob); old != nil {
		p.addError("Identifier " + nam.Name + " redeclared; original " +
			"declaration at " + p.file.Position(old.NamePos).String())
	}

	return &ast.VarExpr{
		Expression: ast.Expression{Opening: lparen, Closing: rparen},
		Var:        varpos,
		Name:       nam,
		Object:     ob,
	}
}
<commit_msg>implement parsing three more object<commit_after>// Copyright (c) 2014, Rob Thornton
// All rights reserved.
// This source code is governed by a Simplied BSD-License. Please see the
// LICENSE included in this distribution for a copy of the full license
// or, if one is not included, you may also find a copy at
// http://opensource.org/licenses/BSD-2-Clause

package parse

import (
	"os"

	"github.com/rthornton128/calc/ast"
	"github.com/rthornton128/calc/scan"
	"github.com/rthornton128/calc/token"
)

func ParseFile(filename, src string) *ast.File {
	var p parser
	p.init(filename, src)
	f := p.parseFile()
	if p.errors.Count() > 0 {
		p.errors.Print()
		return nil
	}
	return f
}

type parser struct {
	file    *token.File
	errors  scan.ErrorList
	scanner scan.Scanner

	curScope *ast.Scope
	topScope *ast.Scope

	pos token.Pos
	tok token.Token
	lit string
}

func (p *parser) addError(msg string) {
	p.errors.Add(p.file.Position(p.pos), msg)
	if p.errors.Count() >= 10 {
		p.errors.Print()
		os.Exit(1)
	}
}

func (p *parser) expect(tok token.Token) token.Pos {
	pos := p.pos
	if p.tok != tok {
		p.addError("Expected '" + tok.String() + "' got '" + p.lit + "'")
	}
	p.next()
	return pos
}

func (p *parser) init(fname, src string) {
	p.file = token.NewFile(fname, 1, len(src))
	p.scanner.Init(p.file, src)
	p.curScope = ast.NewScope(nil)
	p.topScope = p.curScope
	p.next()
}

func (p *parser) next() {
	p.lit, p.tok, p.pos = p.scanner.Scan()
}

func (p *parser) openScope() {
	p.curScope = ast.NewScope(p.curScope)
}

func (p *parser) closeScope() {
	p.curScope = p.curScope.Parent()
}

func (p *parser) parseAssignExpr(open token.Pos) *ast.AssignExpr {
	pos := p.expect(token.ASSIGN)
	nam := p.parseIdent()
	val := p.parseGenExpr()

	ob := &ast.Object{
		NamePos: nam.NamePos,
		Name:    nam.Name,
		Type:    nil,
		Value:   val,
	}
	old := p.curScope.Lookup(nam.Name)
	if old == nil {
		p.addError("Cannot assign to undeclared identifier " + nam.Name)
	}
	/*
		if ob.Type.Name != old.Type.Name {
			p.addError("Cannot assign " + ob.Name + " of type (" + ob.Type.Name +
				") to " + old.Name + " of type (" + ob.Type.Name + ")")
		}*/
	end := p.expect(token.RPAREN)
	return &ast.AssignExpr{
		Expression: ast.Expression{Opening: open, Closing: end},
		Equal:      pos,
		Name:       nam,
		Object:     ob,
	}
}

func (p *parser) parseBasicLit() *ast.BasicLit {
	pos, tok, lit := p.pos, p.tok, p.lit
	p.next()
	return &ast.BasicLit{LitPos: pos, Kind: tok, Lit: lit}
}

func (p *parser) parseBinaryExpr(open token.Pos) *ast.BinaryExpr {
	pos := p.pos
	op := p.tok
	p.next()

	var list []ast.Expr
	for p.tok != token.RPAREN && p.tok != token.EOF {
		list = append(list, p.parseGenExpr())
	}
	if len(list) < 2 {
		p.addError("binary expression must have at least two operands")
	}
	end := p.expect(token.RPAREN)
	return &ast.BinaryExpr{
		Expression: ast.Expression{
			Opening: open,
			Closing: end,
		},
		Op:    op,
		OpPos: pos,
		List:  list,
	}
}

func (p *parser) parseCallExpr(open token.Pos) *ast.CallExpr {
	pos := p.pos
	nam := p.parseIdent()

	var list []ast.Expr
	for p.tok != token.RPAREN && p.tok != token.EOF {
		list = append(list, p.parseGenExpr())
	}
	ob := p.curScope.Lookup(nam.Name)
	if ob.Kind != ast.Decl {
		p.addError("call to undeclared expression")
	}
	end := p.expect(token.RPAREN)
	return &ast.CallExpr{
		Expression: ast.Expression{
			Opening: open,
			Closing: end,
		},
		Call: pos,
		Name: nam,
		Args: list,
	}
}

func (p *parser) parseDeclExpr(open token.Pos) *ast.DeclExpr {

	pos := p.pos
	nam := p.parseIdent()

	p.openScope()

	var list []*ast.Ident
	if p.tok == token.LPAREN {
		p.next()
		list = p.parseParamList()
	}

	typ := p.parseIdent()
	bod := p.parseExprList()

	p.closeScope()

	decl := &ast.DeclExpr{
		Decl:   pos,
		Name:   nam,
		Type:   typ,
		Params: list,
		Body:   bod,
	}
	ob := &ast.Object{
		NamePos: nam.NamePos,
		Name:    nam.Name,
		Kind:    ast.Decl,
		Type:    typ,
		Value:   decl,
	}

	if old := p.curScope.Insert(ob); old != nil {
		p.addError("identifier already exists")
	}

	return decl
}

func (p *parser) parseExprList() ast.Expr {
	open := p.expect(token.LPAREN)
	if p.tok == token.LPAREN {
		var list []ast.Expr
		for p.tok != token.RPAREN {
			list = append(list, p.parseGenExpr())
		}
		if len(list) < 1 {
			p.addError("empty expression list not allowed")
		}
		end := p.expect(token.RPAREN)
		return &ast.ExprList{
			Expression: ast.Expression{
				Opening: open,
				Closing: end,
			},
			List: list,
		}
	}
	return p.parseGenExpr()
}

func (p *parser) parseGenExpr() ast.Expr {
	var expr ast.Expr

	switch p.tok {
	case token.LPAREN:
		expr = p.parseExpr()
	case token.IDENT:
		expr = p.parseIdent()
	case token.INTEGER:
		expr = p.parseBasicLit()
	default:
		p.addError("Expected '" + token.LPAREN.String() + "' or '" +
			token.INTEGER.String() + "' got '" + p.lit + "'")
		p.next()
	}

	return expr
}

func (p *parser) parseExpr() ast.Expr {
	var expr ast.Expr

	pos := p.expect(token.LPAREN)
	switch p.tok {
	case token.ADD, token.SUB, token.MUL, token.QUO, token.REM,
		token.EQL, token.GTE, token.GTT, token.NEQ, token.LST, token.LTE:
		expr = p.parseBinaryExpr(pos)
	case token.VAR:
		expr = p.parseVarExpr(pos)
	default:
		p.addError("Expected binary operator but got '" + p.lit + "'")
	}

	return expr
}

func (p *parser) parseFile() *ast.File {
	var expr ast.Expr
	expr = p.parseGenExpr()
	if p.tok != token.EOF {
		p.addError("Expected EOF, got '" + p.lit + "'")
	}
	scope := ast.NewScope(nil)
	ob := &ast.Object{
		NamePos: token.NoPos,
		Name:    "main",
		Kind:    ast.Decl,
		Type:    &ast.Ident{NamePos: token.NoPos, Name: "int"},
		Value:   expr,
	}
	scope.Insert(ob)
	return &ast.File{Scope: scope}
}

func (p *parser) parseIdent() *ast.Ident {
	name := p.lit
	pos := p.expect(token.IDENT)
	return &ast.Ident{NamePos: pos, Name: name}
}

func (p *parser) parseParamList() []*ast.Ident {
	// TODO: finish
	var list []*ast.Ident
	for p.tok != token.RPAREN {
		list = append(list, p.parseIdent())
	}
	return list
}

func (p *parser) parseVarExpr(lparen token.Pos) *ast.VarExpr {
	var (
		typ *ast.Ident
		val ast.Expr
	)
	varpos := p.expect(token.VAR)
	if p.tok != token.IDENT {
		p.expect(token.IDENT)
	}
	nam := p.parseIdent()

	// TODO: Needs improvement; maybe a p.tryTypeOrExpression?
	if p.tok == token.RPAREN {
		p.addError("Expected type, expression or literal, got: )")
	}

	if p.tok == token.IDENT {
		typ = p.parseIdent()
	}

	if p.tok != token.RPAREN {
		val = p.parseGenExpr()
	}

	if p.tok != token.RPAREN {
		typ = p.parseIdent()
	}
	// TODO: end
	rparen := p.expect(token.RPAREN)

	ob := &ast.Object{
		NamePos: nam.NamePos,
		Name:    nam.Name,
		Kind:    ast.Var,
		Type:    typ,
		Value:   val,
	}

	if old := p.curScope.Insert(ob); old != nil {
		p.addError("Identifier " + nam.Name + " redeclared; original " +
			"declaration at " + p.file.Position(old.NamePos).String())
	}

	return &ast.VarExpr{
		Expression: ast.Expression{Opening: lparen, Closing: rparen},
		Var:        varpos,
		Name:       nam,
		Object:     ob,
	}
}
<|endoftext|>


<commit_before>package lib

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/jh-bate/fantail-bot/Godeps/_workspace/src/github.com/tucnak/telebot"
)

const (
	//being a good citizen
	start_cmd, help_cmd = "/start", "/help"

	chat_cmd, remind_cmd, say_cmd, reminders_cmd, said_cmd = "/chat", "/remind", "/say", "/reminders", "/said"

	remind_cmd_hint, say_cmd_hint = "/remind me in <days> to <message>", "/say [what you want to say]"

	default_script = "default"
)

type (
	Info struct {
		App       []string `json:"appInfo"`
		Reminders []string `json:"remindersInfo"`
		Chat      []string `json:"chatInfo"`
		Said      []string `json:"saidInfo"`
	}

	QProcess struct {
		s    *session
		lang struct {
			questions `json:"QandA"`
		}
		info     *Info
		next     *Question
		lastTime Notes
	}
)

func NewQProcess(b *telebot.Bot, s *Storage) *QProcess {
	q := &QProcess{s: newSession(b, s)}
	q.loadInfo()
	return q
}

func (this *QProcess) Run(input <-chan telebot.Message) {
	for msg := range input {
		this.s.User = msg.Sender
		this.
			quickWinFirst(msg).
			determineScript(msg).
			findNextQuestion(msg).
			andChat()
	}
}

func (this *QProcess) quickWinFirst(msg telebot.Message) *QProcess {

	if isCmd(msg.Text, start_cmd, help_cmd) {
		this.s.send(this.info.App...)

		availCmds := fmt.Sprintf("```%s %s %s %s %s```",
			chat_cmd+" - to have a quick chat about what your upto",
			say_cmd_hint+" - to say anything thats on your mind",
			said_cmd+" - to show all the things you have said",
			remind_cmd_hint+" - to keep track of the things you need to be reminded about",
			reminders_cmd+" - to show all the reminders you have made",
		)

		this.s.send(availCmds)
	} else if hasSubmisson(msg.Text, say_cmd) {
		log.Println("save something said ", msg.Text)
		this.s.save(msg, say_cmd)
	} else if hasSubmisson(msg.Text, remind_cmd) {
		log.Println("save a reminder ", msg.Text)
		this.s.saveAsReminder(msg)
	} else if hasSubmisson(msg.Text, reminders_cmd) || isCmd(msg.Text, reminders_cmd) {
		log.Println("showing reminders ", msg.Text)
		r := this.s.getReminders(msg)
		this.s.send(this.info.Reminders...)
		for i := range r {
			this.s.send(r[i].ToString())

		}
	} else if hasSubmisson(msg.Text, said_cmd) || isCmd(msg.Text, said_cmd) {
		log.Println("showing things said ", msg.Text)
		r := this.s.getNotes(msg)
		this.s.send(this.info.Said...)
		for i := range r {
			this.s.send(r[i].ToString())
		}
	}
	return this
}

func (this *QProcess) loadInfo() {
	file, err := os.Open("./config/fantail.json")
	if err != nil {
		log.Panic("could not load App info", err.Error())
	}
	err = json.NewDecoder(file).Decode(&this.info)
	if err != nil {
		log.Panic("could not decode App info ", err.Error())
	}
}

func (this *QProcess) loadScript(scriptName string) {
	file, err := os.Open(fmt.Sprintf("./config/%s.json", scriptName))
	if err != nil {
		log.Panic("could not load QandA language file ", err.Error())
	}
	err = json.NewDecoder(file).Decode(&this.lang)
	if err != nil {
		log.Panic("could not decode QandA ", err.Error())
	}
}

func (this *QProcess) determineScript(msg telebot.Message) *QProcess {

	if hasSubmisson(msg.Text, remind_cmd, say_cmd) {
		log.Println("load default script after submisson")
		this.loadScript(default_script)
	} else if isCmd(msg.Text, chat_cmd, said_cmd, say_cmd, remind_cmd, reminders_cmd) {
		words := strings.Fields(msg.Text)
		scriptName := strings.SplitAfter(words[0], "/")[1]
		log.Println("load command script", scriptName)
		this.loadScript(scriptName)
	}
	return this
}

func (this *QProcess) findNextQuestion(msg telebot.Message) *QProcess {
	this.next = nil

	if isCmd(msg.Text, chat_cmd, said_cmd, say_cmd, remind_cmd, reminders_cmd) {
		//start at the beginning - covers submissons also
		this.next = this.lang.questions[0]
		return this
	} else {
		//find the next question
		for i := range this.lang.questions {
			for a := range this.lang.questions[i].RelatesTo.Answers {
				if this.lang.questions[i].RelatesTo.Answers[a] == msg.Text {
					//was the answer a remainder to save?
					if this.lang.questions[i].RelatesTo.Save {
						this.s.save(msg, chat_cmd, this.lang.questions[i].RelatesTo.SaveTag)
						this.lastTime = append(this.lastTime, this.s.getLastChat(this.lang.questions[i].RelatesTo.SaveTag))
					}
					this.next = this.lang.questions[i]
					return this
				}
			}
		}
	}
	log.Println("looks like we are all done!")
	return this
}

func (this *QProcess) makeKeyboard() Keyboard {
	keyboard := Keyboard{}
	for i := range this.next.PossibleAnswers {
		keyboard = append(keyboard, []string{this.next.PossibleAnswers[i]})
	}
	return keyboard
}

func (this *QProcess) andChat() {
	if this.next != nil {
		this.s.send(this.next.Context...)
		this.s.sendWithKeyboard(this.next.QuestionText, this.makeKeyboard())
		return
	}

	if len(this.lastTime) > 0 {
		this.s.send(this.info.Chat...)
		for i := range this.lastTime {
			this.s.send(this.lastTime[i].ToString())
		}
		this.lastTime = nil
	}
	return
}
<commit_msg>markdown<commit_after>package lib

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/jh-bate/fantail-bot/Godeps/_workspace/src/github.com/tucnak/telebot"
)

const (
	//being a good citizen
	start_cmd, help_cmd = "/start", "/help"

	chat_cmd, remind_cmd, say_cmd, reminders_cmd, said_cmd = "/chat", "/remind", "/say", "/reminders", "/said"

	remind_cmd_hint, say_cmd_hint = "/remind me in <days> to <message>", "/say [what you want to say]"

	default_script = "default"
)

type (
	Info struct {
		App       []string `json:"appInfo"`
		Reminders []string `json:"remindersInfo"`
		Chat      []string `json:"chatInfo"`
		Said      []string `json:"saidInfo"`
	}

	QProcess struct {
		s    *session
		lang struct {
			questions `json:"QandA"`
		}
		info     *Info
		next     *Question
		lastTime Notes
	}
)

func NewQProcess(b *telebot.Bot, s *Storage) *QProcess {
	q := &QProcess{s: newSession(b, s)}
	q.loadInfo()
	return q
}

func (this *QProcess) Run(input <-chan telebot.Message) {
	for msg := range input {
		this.s.User = msg.Sender
		this.
			quickWinFirst(msg).
			determineScript(msg).
			findNextQuestion(msg).
			andChat()
	}
}

func (this *QProcess) quickWinFirst(msg telebot.Message) *QProcess {

	if isCmd(msg.Text, start_cmd, help_cmd) {
		this.s.send(this.info.App...)

		availCmds := fmt.Sprintf("%s %s %s %s %s",
			chat_cmd+" - to have a *quick* chat about what your upto \n\n",
			say_cmd_hint+" - to say _anything_ thats on your mind \n\n",
			said_cmd+" - to show all the things you have said \n\n",
			remind_cmd_hint+" - to keep track of the things you need to be reminded about \n\n",
			reminders_cmd+" - to show all the reminders you have made",
		)

		this.s.send(availCmds)
	} else if hasSubmisson(msg.Text, say_cmd) {
		log.Println("save something said ", msg.Text)
		this.s.save(msg, say_cmd)
	} else if hasSubmisson(msg.Text, remind_cmd) {
		log.Println("save a reminder ", msg.Text)
		this.s.saveAsReminder(msg)
	} else if hasSubmisson(msg.Text, reminders_cmd) || isCmd(msg.Text, reminders_cmd) {
		log.Println("showing reminders ", msg.Text)
		r := this.s.getReminders(msg)
		this.s.send(this.info.Reminders...)
		for i := range r {
			this.s.send(r[i].ToString())

		}
	} else if hasSubmisson(msg.Text, said_cmd) || isCmd(msg.Text, said_cmd) {
		log.Println("showing things said ", msg.Text)
		r := this.s.getNotes(msg)
		this.s.send(this.info.Said...)
		for i := range r {
			this.s.send(r[i].ToString())
		}
	}
	return this
}

func (this *QProcess) loadInfo() {
	file, err := os.Open("./config/fantail.json")
	if err != nil {
		log.Panic("could not load App info", err.Error())
	}
	err = json.NewDecoder(file).Decode(&this.info)
	if err != nil {
		log.Panic("could not decode App info ", err.Error())
	}
}

func (this *QProcess) loadScript(scriptName string) {
	file, err := os.Open(fmt.Sprintf("./config/%s.json", scriptName))
	if err != nil {
		log.Panic("could not load QandA language file ", err.Error())
	}
	err = json.NewDecoder(file).Decode(&this.lang)
	if err != nil {
		log.Panic("could not decode QandA ", err.Error())
	}
}

func (this *QProcess) determineScript(msg telebot.Message) *QProcess {

	if hasSubmisson(msg.Text, remind_cmd, say_cmd) {
		log.Println("load default script after submisson")
		this.loadScript(default_script)
	} else if isCmd(msg.Text, chat_cmd, said_cmd, say_cmd, remind_cmd, reminders_cmd) {
		words := strings.Fields(msg.Text)
		scriptName := strings.SplitAfter(words[0], "/")[1]
		log.Println("load command script", scriptName)
		this.loadScript(scriptName)
	}
	return this
}

func (this *QProcess) findNextQuestion(msg telebot.Message) *QProcess {
	this.next = nil

	if isCmd(msg.Text, chat_cmd, said_cmd, say_cmd, remind_cmd, reminders_cmd) {
		//start at the beginning - covers submissons also
		this.next = this.lang.questions[0]
		return this
	} else {
		//find the next question
		for i := range this.lang.questions {
			for a := range this.lang.questions[i].RelatesTo.Answers {
				if this.lang.questions[i].RelatesTo.Answers[a] == msg.Text {
					//was the answer a remainder to save?
					if this.lang.questions[i].RelatesTo.Save {
						this.s.save(msg, chat_cmd, this.lang.questions[i].RelatesTo.SaveTag)
						this.lastTime = append(this.lastTime, this.s.getLastChat(this.lang.questions[i].RelatesTo.SaveTag))
					}
					this.next = this.lang.questions[i]
					return this
				}
			}
		}
	}
	log.Println("looks like we are all done!")
	return this
}

func (this *QProcess) makeKeyboard() Keyboard {
	keyboard := Keyboard{}
	for i := range this.next.PossibleAnswers {
		keyboard = append(keyboard, []string{this.next.PossibleAnswers[i]})
	}
	return keyboard
}

func (this *QProcess) andChat() {
	if this.next != nil {
		this.s.send(this.next.Context...)
		this.s.sendWithKeyboard(this.next.QuestionText, this.makeKeyboard())
		return
	}

	if len(this.lastTime) > 0 {
		this.s.send(this.info.Chat...)
		for i := range this.lastTime {
			this.s.send(this.lastTime[i].ToString())
		}
		this.lastTime = nil
	}
	return
}
<|endoftext|>


<commit_before>package main

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

type fixturePair struct {
	json     []byte
	triplets []serveRecord
}

func (f *fixturePair) check(t *testing.T, sdb *serveDb) {
	for _, triplet := range f.triplets {
		rec, ok := sdb.identToServe[sKey{I: triplet.I, P: triplet.P}]
		if !ok {
			t.Fatalf("Expected to find identifier %q", triplet.I)
		}

		if triplet.T != rec.T {
			t.Fatalf("Expected to resolve to %v, "+
				"but got %v instead", triplet.T, rec.T)
		}
	}

}

var fixtures = []fixturePair{
	{
		json: []byte(`{"serves": ` +
			`[{"i": "apple", "t": "chocolate", ` +
			`"p": "/p1/log.sock"}, ` +
			`{"i": "banana", "t": "vanilla", ` +
			`"p": "/p2/log.sock"}]}`),
		triplets: []serveRecord{
			{sKey{I: "apple", P: "/p1/log.sock"}, "chocolate"},
			{sKey{I: "banana", P: "/p2/log.sock"}, "vanilla"},
		},
	},
	{
		json: []byte(`{"serves": ` +
			`[{"i": "bed", "t": "pillow", ` +
			`"p": "/p1/log.sock"}, ` +
			`{"i": "nightstand", "t": "alarm clock", ` +
			`"p": "/p2/log.sock"}]}`),
		triplets: []serveRecord{
			{sKey{I: "bed", P: "/p1/log.sock"},
				"pillow"},
			{sKey{I: "nightstand", P: "/p2/log.sock"},
				"alarm clock"},
		},
	},
}

func newTmpDb(t *testing.T) string {
	name, err := ioutil.TempDir("", "test_")
	if err != nil {
		t.Fatalf("Could not create temporary directory for test: %v",
			err)
	}

	return name
}

func TestEmptyDB(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)
	updates, err := sdb.Poll()

	if err != nil {
		t.Fatalf("Poll on an empty directory should succeed, "+
			"instead failed: %v", err)
	}

	if !updates {
		t.Fatal("Expect updates for first poll in an empty database")
	}

	updates, err = sdb.Poll()
	if err != nil {
		t.Fatalf("Poll on an empty directory should succeed, "+
			"instead failed: %v", err)
	}

	if updates {
		t.Fatal("Expect no updates for second poll")
	}

}

func TestMultipleLoad(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)
	for i := range fixtures {
		fixture := &fixtures[i]
		ioutil.WriteFile(sdb.newPath(), fixture.json, 0400)

		if _, err := sdb.Poll(); err != nil {
			t.Fatalf("Poll should succeed with valid input, "+
				"instead: %v", err)
		}

		_, err := os.Stat(sdb.loadedPath())
		if err != nil {
			t.Fatalf("Input should be successfully loaded to %v, "+
				"but the file could not be stat()ed for some "+
				"reason: %v", sdb.loadedPath(), err)
		}

		fixture.check(t, sdb)
	}
}

// Write out some valid input to serves.new.
func writeLoadFixture(t *testing.T, sdb *serveDb, fixture *fixturePair) {
	ioutil.WriteFile(sdb.newPath(), fixture.json, 0400)
	if _, err := sdb.Poll(); err != nil {
		t.Fatalf("Poll should succeed with valid input, "+
			"instead: %v", err)
	}
}

func TestIntermixedGoodBadInput(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)

	fixture := &fixtures[0]
	writeLoadFixture(t, sdb, fixture)

	// Write a bad serves.new file.
	ioutil.WriteFile(sdb.newPath(), []byte(`{}`), 0400)
	if _, err := sdb.Poll(); err != nil {
		t.Fatalf("Poll should succeed with invalid input, "+
			"instead: %v", err)
	}

	// Confirm that the original, good fixture's data is still in
	// place.
	fixture.check(t, sdb)

	// Confirm that the serves.rej and last_error file have been
	// made.
	_, err := os.Stat(sdb.errPath())
	if err != nil {
		t.Fatalf("last_error file should exist: %v", err)
	}

	_, err = os.Stat(sdb.rejPath())
	if err != nil {
		t.Fatalf("serves.rej should exist: %v", err)
	}

	// Submit a new set of good input, to see if the last_error
	// and serves.rej are unlinked.
	secondFixture := &fixtures[1]
	writeLoadFixture(t, sdb, secondFixture)

	// Make sure new data was loaded properly.
	secondFixture.check(t, sdb)

	// Check that the old reject file and error file are gone.
	_, err = os.Stat(sdb.errPath())
	if err == nil || !os.IsNotExist(err) {
		t.Fatalf("last_error file shouldn't exist: %v", err)
	}

	_, err = os.Stat(sdb.rejPath())
	if err == nil || !os.IsNotExist(err) {
		t.Fatalf("serves.rej shouldn't exist: %v", err)
	}
}

func TestFirstTimeLoadPoll(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)

	// Write directly to the serves.loaded file, which is not the
	// normal way thing are done; Poll() should move things around
	// outside a test environment.
	fixture := &fixtures[0]
	ioutil.WriteFile(sdb.loadedPath(), fixture.json, 0400)

	if _, err := sdb.Poll(); err != nil {
		t.Fatalf("Poll should succeed with valid input, "+
			"instead: %v", err)
	}

	fixture.check(t, sdb)
}

func TestEmptyPoll(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)
	_, err := sdb.Poll()
	if err != nil {
		t.Fatalf("An empty database should not cause an error, "+
			"but got: %v", err)
	}

	if sdb.identToServe == nil {
		t.Fatal("An empty database should yield an " +
			"empty routing table.")
	}
}

func TestFirstLoadBad(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)

	// Write a bad serves.new file.
	ioutil.WriteFile(sdb.newPath(), []byte(`{}`), 0400)
	if _, err := sdb.Poll(); err != nil {
		t.Fatalf("Poll should succeed with invalid input, "+
			"instead: %v", err)
	}

	_, err := sdb.Poll()
	if err != nil {
		t.Fatalf("Rejected input should not cause an error, "+
			"but got: %v", err)
	}

	// Confirm that the serves.rej and last_error file have been
	// made.
	_, err = os.Stat(sdb.errPath())
	if err != nil {
		t.Fatalf("last_error file should exist: %v", err)
	}

	_, err = os.Stat(sdb.rejPath())
	if err != nil {
		t.Fatalf("serves.rej should exist: %v", err)
	}
}

func TestSnapshot(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)
	snap := sdb.Snapshot()
	if len(snap) != 0 {
		t.Fatalf("Expect snapshot to have be empty")
	}

	fix := &fixtures[0]
	writeLoadFixture(t, sdb, fix)

	snap = sdb.Snapshot()
	if len(snap) != len(fix.triplets) {
		t.Fatalf("Expect snapshot to be filled, got %v", snap)
	}
}

func TestSnapReload(t *testing.T) {
	// Sketch how to use Poll() and Snapshot() together with
	// goroutines.
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)
	writeLoadFixture(t, sdb, &fixtures[0])

	die := make(chan struct{})
	deaths := make(chan bool)

	justWait := func(die <-chan struct{}, r serveRecord) {
		t.Logf("justWait started with %v", r)

		for {
			t.Logf("justWait 'running' with %v", r)
			select {
			case <-die:
				t.Logf("justWait 'dies' with %v", r)
				deaths <- true
				return
			default:
				break
			}
		}
	}

	snapLoad := func(fix *fixturePair) {
		writeLoadFixture(t, sdb, fix)

		snap := sdb.Snapshot()
		for i := range snap {
			go justWait(die, snap[i])
		}

		close(die)

		// Confirm everyone dies
		for _ = range snap {
			<-deaths
		}
	}

	// Simulates a case where one calls Poll() repeatedly -- in
	// fact, writeLoadFixture used above does do this and checks
	// the results.
	for i := range fixtures {
		snapLoad(&fixtures[i])

		// Have to make a new 'die' channel for each
		// generation of goroutines.
		die = make(chan struct{})
	}

	// If anyone gorooutines are left and trying to write to the
	// deaths channel, (due to a programming error), try to force
	// a panic by closing the death channel.
	close(deaths)
}
<commit_msg>Un-bitrot serve_db test<commit_after>package main

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

type fixturePair struct {
	json     []byte
	triplets []serveRecord
}

func (f *fixturePair) check(t *testing.T, sdb *serveDb) {
	for _, triplet := range f.triplets {
		rec, ok := sdb.identToServe[sKey{I: triplet.I, P: triplet.P}]
		if !ok {
			t.Fatalf("Expected to find identifier %q", triplet.I)
		}

		if triplet.T != rec.T {
			t.Fatalf("Expected to resolve to %v, "+
				"but got %v instead", triplet.T, rec.T)
		}
	}

}

var fixtures = []fixturePair{
	{
		json: []byte(`{"serves": ` +
			`[{"i": "apple", "t": "chocolate", ` +
			`"p": "/p1/log.sock"}, ` +
			`{"i": "banana", "t": "vanilla", ` +
			`"p": "/p2/log.sock"}]}`),
		triplets: []serveRecord{
			{sKey{I: "apple", P: "/p1/log.sock"}, "chocolate",
				"brown"},
			{sKey{I: "banana", P: "/p2/log.sock"}, "vanilla",
				"white"},
		},
	},
	{
		json: []byte(`{"serves": ` +
			`[{"i": "bed", "t": "pillow", ` +
			`"p": "/p1/log.sock"}, ` +
			`{"i": "nightstand", "t": "alarm clock", ` +
			`"p": "/p2/log.sock"}]}`),
		triplets: []serveRecord{
			{sKey{I: "bed", P: "/p1/log.sock"},
				"pillow", "white"},
			{sKey{I: "nightstand", P: "/p2/log.sock"},
				"alarm clock", "black"},
		},
	},
}

func newTmpDb(t *testing.T) string {
	name, err := ioutil.TempDir("", "test_")
	if err != nil {
		t.Fatalf("Could not create temporary directory for test: %v",
			err)
	}

	return name
}

func TestEmptyDB(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)
	updates, err := sdb.Poll()

	if err != nil {
		t.Fatalf("Poll on an empty directory should succeed, "+
			"instead failed: %v", err)
	}

	if !updates {
		t.Fatal("Expect updates for first poll in an empty database")
	}

	updates, err = sdb.Poll()
	if err != nil {
		t.Fatalf("Poll on an empty directory should succeed, "+
			"instead failed: %v", err)
	}

	if updates {
		t.Fatal("Expect no updates for second poll")
	}

}

func TestMultipleLoad(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)
	for i := range fixtures {
		fixture := &fixtures[i]
		ioutil.WriteFile(sdb.newPath(), fixture.json, 0400)

		if _, err := sdb.Poll(); err != nil {
			t.Fatalf("Poll should succeed with valid input, "+
				"instead: %v", err)
		}

		_, err := os.Stat(sdb.loadedPath())
		if err != nil {
			t.Fatalf("Input should be successfully loaded to %v, "+
				"but the file could not be stat()ed for some "+
				"reason: %v", sdb.loadedPath(), err)
		}

		fixture.check(t, sdb)
	}
}

// Write out some valid input to serves.new.
func writeLoadFixture(t *testing.T, sdb *serveDb, fixture *fixturePair) {
	ioutil.WriteFile(sdb.newPath(), fixture.json, 0400)
	if _, err := sdb.Poll(); err != nil {
		t.Fatalf("Poll should succeed with valid input, "+
			"instead: %v", err)
	}
}

func TestIntermixedGoodBadInput(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)

	fixture := &fixtures[0]
	writeLoadFixture(t, sdb, fixture)

	// Write a bad serves.new file.
	ioutil.WriteFile(sdb.newPath(), []byte(`{}`), 0400)
	if _, err := sdb.Poll(); err != nil {
		t.Fatalf("Poll should succeed with invalid input, "+
			"instead: %v", err)
	}

	// Confirm that the original, good fixture's data is still in
	// place.
	fixture.check(t, sdb)

	// Confirm that the serves.rej and last_error file have been
	// made.
	_, err := os.Stat(sdb.errPath())
	if err != nil {
		t.Fatalf("last_error file should exist: %v", err)
	}

	_, err = os.Stat(sdb.rejPath())
	if err != nil {
		t.Fatalf("serves.rej should exist: %v", err)
	}

	// Submit a new set of good input, to see if the last_error
	// and serves.rej are unlinked.
	secondFixture := &fixtures[1]
	writeLoadFixture(t, sdb, secondFixture)

	// Make sure new data was loaded properly.
	secondFixture.check(t, sdb)

	// Check that the old reject file and error file are gone.
	_, err = os.Stat(sdb.errPath())
	if err == nil || !os.IsNotExist(err) {
		t.Fatalf("last_error file shouldn't exist: %v", err)
	}

	_, err = os.Stat(sdb.rejPath())
	if err == nil || !os.IsNotExist(err) {
		t.Fatalf("serves.rej shouldn't exist: %v", err)
	}
}

func TestFirstTimeLoadPoll(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)

	// Write directly to the serves.loaded file, which is not the
	// normal way thing are done; Poll() should move things around
	// outside a test environment.
	fixture := &fixtures[0]
	ioutil.WriteFile(sdb.loadedPath(), fixture.json, 0400)

	if _, err := sdb.Poll(); err != nil {
		t.Fatalf("Poll should succeed with valid input, "+
			"instead: %v", err)
	}

	fixture.check(t, sdb)
}

func TestEmptyPoll(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)
	_, err := sdb.Poll()
	if err != nil {
		t.Fatalf("An empty database should not cause an error, "+
			"but got: %v", err)
	}

	if sdb.identToServe == nil {
		t.Fatal("An empty database should yield an " +
			"empty routing table.")
	}
}

func TestFirstLoadBad(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)

	// Write a bad serves.new file.
	ioutil.WriteFile(sdb.newPath(), []byte(`{}`), 0400)
	if _, err := sdb.Poll(); err != nil {
		t.Fatalf("Poll should succeed with invalid input, "+
			"instead: %v", err)
	}

	_, err := sdb.Poll()
	if err != nil {
		t.Fatalf("Rejected input should not cause an error, "+
			"but got: %v", err)
	}

	// Confirm that the serves.rej and last_error file have been
	// made.
	_, err = os.Stat(sdb.errPath())
	if err != nil {
		t.Fatalf("last_error file should exist: %v", err)
	}

	_, err = os.Stat(sdb.rejPath())
	if err != nil {
		t.Fatalf("serves.rej should exist: %v", err)
	}
}

func TestSnapshot(t *testing.T) {
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)
	snap := sdb.Snapshot()
	if len(snap) != 0 {
		t.Fatalf("Expect snapshot to have be empty")
	}

	fix := &fixtures[0]
	writeLoadFixture(t, sdb, fix)

	snap = sdb.Snapshot()
	if len(snap) != len(fix.triplets) {
		t.Fatalf("Expect snapshot to be filled, got %v", snap)
	}
}

func TestSnapReload(t *testing.T) {
	// Sketch how to use Poll() and Snapshot() together with
	// goroutines.
	name := newTmpDb(t)
	defer os.RemoveAll(name)

	sdb := newServeDb(name)
	writeLoadFixture(t, sdb, &fixtures[0])

	die := make(chan struct{})
	deaths := make(chan bool)

	justWait := func(die <-chan struct{}, r serveRecord) {
		t.Logf("justWait started with %v", r)

		for {
			t.Logf("justWait 'running' with %v", r)
			select {
			case <-die:
				t.Logf("justWait 'dies' with %v", r)
				deaths <- true
				return
			default:
				break
			}
		}
	}

	snapLoad := func(fix *fixturePair) {
		writeLoadFixture(t, sdb, fix)

		snap := sdb.Snapshot()
		for i := range snap {
			go justWait(die, snap[i])
		}

		close(die)

		// Confirm everyone dies
		for _ = range snap {
			<-deaths
		}
	}

	// Simulates a case where one calls Poll() repeatedly -- in
	// fact, writeLoadFixture used above does do this and checks
	// the results.
	for i := range fixtures {
		snapLoad(&fixtures[i])

		// Have to make a new 'die' channel for each
		// generation of goroutines.
		die = make(chan struct{})
	}

	// If anyone gorooutines are left and trying to write to the
	// deaths channel, (due to a programming error), try to force
	// a panic by closing the death channel.
	close(deaths)
}
<|endoftext|>


<commit_before>package tmsh

import (
	"bufio"
	"bytes"
	"io"
	"strings"

	"golang.org/x/crypto/ssh"
)

type BigIP struct {
	host    string
	user    string
	sshconn *SSHConn
}

type SSHConn struct {
	session *ssh.Session
	stdin   io.WriteCloser
	stdout  io.Reader
	stderr  io.Reader
}

func NewSession(host, port, user, password string) (*BigIP, error) {
	sshconn, err := NewSSHConnection(host+":"+port, user, password)
	if err != nil {
		return nil, err
	}

	return &BigIP{
		host:    host,
		user:    user,
		sshconn: sshconn,
	}, nil
}

func (bigip *BigIP) ExecuteCommand(cmd string) (string, error) {
	promptSuffix := "# "
	promptPrefix := bigip.user + "@"

	_, err := bigip.sshconn.Send(cmd)
	if err != nil {
		return "", err
	}

	results, err := bigip.sshconn.Recv(promptSuffix)
	if err != nil {
		return "", err
	}

	// Remove carriage return
	removedResults := make([]byte, 0)
	for _, c := range results {
		if c != 13 {
			removedResults = append(removedResults, c)
		}
	}

	reader := bytes.NewReader(removedResults)
	scanner := bufio.NewScanner(reader)

	returnStr := ""

	for scanner.Scan() {
		text := scanner.Text()

		// Remove space + back space
		line := strings.Replace(text, " \b", "", -1)

		if strings.HasPrefix(line, "Last login:") ||
			strings.HasPrefix(line, promptPrefix) ||
			strings.HasPrefix(line, cmd) {
			continue
		}

		returnStr += text + "\n"
	}

	return returnStr, nil
}

func (bigip *BigIP) Save() {
	bigip.sshconn.Close()
}
<commit_msg>Add Close()<commit_after>package tmsh

import (
	"bufio"
	"bytes"
	"io"
	"strings"

	"golang.org/x/crypto/ssh"
)

type BigIP struct {
	host    string
	user    string
	sshconn *SSHConn
}

type SSHConn struct {
	session *ssh.Session
	stdin   io.WriteCloser
	stdout  io.Reader
	stderr  io.Reader
}

func NewSession(host, port, user, password string) (*BigIP, error) {
	sshconn, err := NewSSHConnection(host+":"+port, user, password)
	if err != nil {
		return nil, err
	}

	return &BigIP{
		host:    host,
		user:    user,
		sshconn: sshconn,
	}, nil
}

func (bigip *BigIP) ExecuteCommand(cmd string) (string, error) {
	promptSuffix := "# "
	promptPrefix := bigip.user + "@"

	_, err := bigip.sshconn.Send(cmd)
	if err != nil {
		return "", err
	}

	results, err := bigip.sshconn.Recv(promptSuffix)
	if err != nil {
		return "", err
	}

	// Remove carriage return
	removedResults := make([]byte, 0)
	for _, c := range results {
		if c != 13 {
			removedResults = append(removedResults, c)
		}
	}

	reader := bytes.NewReader(removedResults)
	scanner := bufio.NewScanner(reader)

	returnStr := ""

	for scanner.Scan() {
		text := scanner.Text()

		// Remove space + back space
		line := strings.Replace(text, " \b", "", -1)

		if strings.HasPrefix(line, "Last login:") ||
			strings.HasPrefix(line, promptPrefix) ||
			strings.HasPrefix(line, cmd) {
			continue
		}

		returnStr += text + "\n"
	}

	return returnStr, nil
}

func (bigip *BigIP) Close() {
	bigip.sshconn.Close()
}

func (bigip *BigIP) Save() {
	bigip.Close()
}
<|endoftext|>


<commit_before><commit_msg>Pass a pointer to Validate() so that default values can be applied<commit_after><|endoftext|>


<commit_before><commit_msg>Fix a bug<commit_after><|endoftext|>


<commit_before><commit_msg>delete residuary bucket after test<commit_after><|endoftext|>


<commit_before>// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package gtcp

import (
	"encoding/binary"
	"fmt"
	"time"

	"github.com/gogf/gf/g/errors/gerror"
)

const (
	// 默认允许最大的简单协议包大小(byte), 65535 byte
	gPKG_MAX_DATA_SIZE = 65535
	// 简单协议包头大小
	gPKG_HEADER_SIZE = 3
)

// 数据读取选项
type PkgOption struct {
	MaxSize int   // (byte)数据读取的最大包大小，最大不能超过3字节(0xFFFFFF,15MB)，默认为65535byte
	Retry   Retry // 失败重试
}

// getPkgOption wraps and returns the PkgOption.
// If no option given, it returns a new option with default value.
func getPkgOption(option ...PkgOption) (*PkgOption, error) {
	pkgOption := PkgOption{}
	if len(option) > 0 {
		pkgOption = option[0]
	}
	if pkgOption.MaxSize == 0 {
		pkgOption.MaxSize = gPKG_MAX_DATA_SIZE
	} else if pkgOption.MaxSize > 0xFFFFFF {
		return nil, fmt.Errorf(`package size %d exceeds allowed max size %d`, pkgOption.MaxSize, 0xFFFFFF)
	}
	return &pkgOption, nil
}

// 根据简单协议发送数据包。
//
// 简单协议数据格式：数据长度(24bit)|数据字段(变长)。
//
// 注意：
// 1. "数据长度"仅为"数据字段"的长度，不包含头信息的长度字段3字节。
// 2. 由于"数据长度"为3字节，并且使用的BigEndian字节序，因此这里最后返回的buffer使用了buffer[1:]。
func (c *Conn) SendPkg(data []byte, option ...PkgOption) error {
	pkgOption, err := getPkgOption(option...)
	if err != nil {
		return err
	}
	length := len(data)
	if length > pkgOption.MaxSize {
		return fmt.Errorf(`data size %d exceeds max pkg size %d`, length, gPKG_MAX_DATA_SIZE)
	}
	buffer := make([]byte, gPKG_HEADER_SIZE+1+len(data))
	binary.BigEndian.PutUint32(buffer[0:], uint32(length))
	copy(buffer[gPKG_HEADER_SIZE+1:], data)
	if pkgOption.Retry.Count > 0 {
		return c.Send(buffer[1:], pkgOption.Retry)
	}
	//fmt.Println("SendPkg:", buffer[1:])
	return c.Send(buffer[1:])
}

// 简单协议: 带超时时间的数据发送
func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) (err error) {
	if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil {
		return err
	}
	defer func() {
		err = gerror.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error")
	}()
	err = c.SendPkg(data, option...)
	return
}

// 简单协议: 发送数据并等待接收返回数据
func (c *Conn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error) {
	if err := c.SendPkg(data, option...); err == nil {
		return c.RecvPkg(option...)
	} else {
		return nil, err
	}
}

// 简单协议: 发送数据并等待接收返回数据(带返回超时等待时间)
func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error) {
	if err := c.SendPkg(data, option...); err == nil {
		return c.RecvPkgWithTimeout(timeout, option...)
	} else {
		return nil, err
	}
}

// 简单协议: 获取一个数据包。
func (c *Conn) RecvPkg(option ...PkgOption) (result []byte, err error) {
	var temp []byte
	var length int
	pkgOption, err := getPkgOption(option...)
	if err != nil {
		return nil, err
	}
	for {
		// 先根据对象的缓冲区数据进行计算
		for {
			if len(c.buffer) >= gPKG_HEADER_SIZE {
				// 注意"数据长度"为3个字节，不满足4个字节的uint32类型，因此这里"低位"补0
				length = int(binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]}))
				// 解析的大小是否符合规范，清空从该连接接收到的所有数据包
				if length < 0 || length > pkgOption.MaxSize {
					c.buffer = c.buffer[:0]
					return nil, fmt.Errorf(`invalid package size %d`, length)
				}
				// 不满足包大小，需要继续读取
				if len(c.buffer) < length+gPKG_HEADER_SIZE {
					break
				}
				result = c.buffer[gPKG_HEADER_SIZE : gPKG_HEADER_SIZE+length]
				c.buffer = c.buffer[gPKG_HEADER_SIZE+length:]
				return
			} else {
				break
			}
		}
		// 读取系统socket缓冲区的完整数据
		temp, err = c.Recv(0, pkgOption.Retry)
		if err != nil {
			break
		}
		if len(temp) > 0 {
			c.buffer = append(c.buffer, temp...)
		}
		//fmt.Println("RecvPkg:", c.buffer)
	}
	return
}

// 简单协议: 带超时时间的消息包获取
func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) (data []byte, err error) {
	if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil {
		return nil, err
	}
	defer func() {
		err = gerror.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error")
	}()
	data, err = c.RecvPkg(option...)
	return
}
<commit_msg>improve package feature for gtcp<commit_after>// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package gtcp

import (
	"encoding/binary"
	"fmt"
	"time"

	"github.com/gogf/gf/g/errors/gerror"
)

const (
	// 默认允许最大的简单协议包大小(byte), 65535 byte
	gPKG_DEFAULT_MAX_DATA_SIZE = 65535
	// 默认简单协议包头大小
	gPKG_DEFAULT_HEADER_SIZE = 2
	// 协议头最大大小
	gPKG_MAX_HEADER_SIZE = 4
)

// 数据读取选项
type PkgOption struct {
	HeaderSize  int   // 自定义头大小(默认为2字节，最大不能超过4字节)
	MaxDataSize int   // (byte)数据读取的最大包大小，默认最大不能超过2字节(65535 byte)
	Retry       Retry // 失败重试
}

// getPkgOption wraps and returns the PkgOption.
// If no option given, it returns a new option with default value.
func getPkgOption(option ...PkgOption) (*PkgOption, error) {
	pkgOption := PkgOption{}
	if len(option) > 0 {
		pkgOption = option[0]
	}
	if pkgOption.HeaderSize == 0 {
		pkgOption.HeaderSize = gPKG_DEFAULT_HEADER_SIZE
	}
	if pkgOption.MaxDataSize == 0 {
		pkgOption.MaxDataSize = gPKG_DEFAULT_MAX_DATA_SIZE
	} else if pkgOption.MaxDataSize > 0xFFFFFF {
		return nil, fmt.Errorf(`package size %d exceeds allowed max size %d`, pkgOption.MaxDataSize, 0xFFFFFF)
	}
	return &pkgOption, nil
}

// 根据简单协议发送数据包。
//
// 简单协议数据格式：数据长度(24bit)|数据字段(变长)。
//
// 注意：
// 1. "数据长度"仅为"数据字段"的长度，不包含头信息的长度字段3字节。
// 2. 由于"数据长度"为3字节，并且使用的BigEndian字节序，因此这里最后返回的buffer使用了buffer[1:]。
func (c *Conn) SendPkg(data []byte, option ...PkgOption) error {
	pkgOption, err := getPkgOption(option...)
	if err != nil {
		return err
	}
	length := len(data)
	if length > pkgOption.MaxDataSize {
		return fmt.Errorf(`data size %d exceeds max pkg size %d`, length, pkgOption.MaxDataSize)
	}
	offset := gPKG_MAX_HEADER_SIZE - pkgOption.HeaderSize
	buffer := make([]byte, gPKG_MAX_HEADER_SIZE+len(data))
	binary.BigEndian.PutUint32(buffer[0:], uint32(length))
	copy(buffer[gPKG_MAX_HEADER_SIZE:], data)
	if pkgOption.Retry.Count > 0 {
		return c.Send(buffer[offset:], pkgOption.Retry)
	}
	//fmt.Println("SendPkg:", buffer[offset:])
	return c.Send(buffer[offset:])
}

// 简单协议: 带超时时间的数据发送
func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) (err error) {
	if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil {
		return err
	}
	defer func() {
		err = gerror.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error")
	}()
	err = c.SendPkg(data, option...)
	return
}

// 简单协议: 发送数据并等待接收返回数据
func (c *Conn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error) {
	if err := c.SendPkg(data, option...); err == nil {
		return c.RecvPkg(option...)
	} else {
		return nil, err
	}
}

// 简单协议: 发送数据并等待接收返回数据(带返回超时等待时间)
func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error) {
	if err := c.SendPkg(data, option...); err == nil {
		return c.RecvPkgWithTimeout(timeout, option...)
	} else {
		return nil, err
	}
}

// 简单协议: 获取一个数据包。
func (c *Conn) RecvPkg(option ...PkgOption) (result []byte, err error) {
	var temp []byte
	var length int
	pkgOption, err := getPkgOption(option...)
	if err != nil {
		return nil, err
	}
	for {
		// 先根据对象的缓冲区数据进行计算
		for {
			if len(c.buffer) >= pkgOption.HeaderSize {
				// 不满足4个字节的uint32类型，因此这里"低位"补0
				if length <= 0 {
					switch pkgOption.HeaderSize {
					case 1:
						length = int(binary.BigEndian.Uint32([]byte{0, 0, 0, c.buffer[0]}))
					case 2:
						length = int(binary.BigEndian.Uint32([]byte{0, 0, c.buffer[0], c.buffer[1]}))
					case 3:
						length = int(binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]}))
					default:
						length = int(binary.BigEndian.Uint32([]byte{c.buffer[0], c.buffer[1], c.buffer[2], c.buffer[3]}))
					}
				}
				// 解析的大小是否符合规范，清空从该连接接收到的所有数据包
				if length < 0 || length > pkgOption.MaxDataSize {
					c.buffer = c.buffer[:0]
					return nil, fmt.Errorf(`invalid package size %d`, length)
				}
				// 不满足包大小，需要继续读取
				if len(c.buffer) < length+pkgOption.HeaderSize {
					break
				}
				result = c.buffer[pkgOption.HeaderSize : pkgOption.HeaderSize+length]
				c.buffer = c.buffer[pkgOption.HeaderSize+length:]
				length = 0
				return
			} else {
				break
			}
		}
		// 读取系统socket当前缓冲区的数据
		temp, err = c.Recv(0, pkgOption.Retry)
		if err != nil {
			break
		}
		if len(temp) > 0 {
			c.buffer = append(c.buffer, temp...)
		}
		//fmt.Println("RecvPkg:", c.buffer)
	}
	return
}

// 简单协议: 带超时时间的消息包获取
func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) (data []byte, err error) {
	if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil {
		return nil, err
	}
	defer func() {
		err = gerror.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error")
	}()
	data, err = c.RecvPkg(option...)
	return
}
<|endoftext|>


<commit_before>package jobsupervisor

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
	"text/template"

	boshdirs "github.com/cloudfoundry/bosh-agent/settings/directories"
	bosherr "github.com/cloudfoundry/bosh-utils/errors"
	boshlog "github.com/cloudfoundry/bosh-utils/logger"
	boshsys "github.com/cloudfoundry/bosh-utils/system"
)

const (
	serviceDescription = "vcap"

	serviceWrapperExeFileName    = "job-service-wrapper.exe"
	serviceWrapperConfigFileName = "job-service-wrapper.xml"

	addJobScript = `
New-Service -Name "%s" -Description "` + serviceDescription + `" -binaryPathName "%s" -StartupType Automatic
`
	startJobScript = `
(get-wmiobject win32_service -filter "description='` + serviceDescription + `'") | ForEach{ Start-Service $_.Name }
`
	stopJobScript = `
(get-wmiobject win32_service -filter "description='` + serviceDescription + `'") | ForEach{ Stop-Service $_.Name }
`
	deleteAllJobsScript = `
(get-wmiobject win32_service -filter "description='` + serviceDescription + `'") | ForEach{ $_.delete() }
`
	getStatusScript = `
(get-wmiobject win32_service -filter "description='` + serviceDescription + `'") | ForEach{ $_.State }
`
	serviceWrapperTemplate = `
<service>
  <id>{{ .ID }}</id>
  <name>{{ .Name }}</name>
  <description>` + serviceDescription + `</description>
  <executable>{{ .Executable }}</executable>
  <arguments>{{ .Arguments }}</arguments>
  <logpath>{{ .LogPath }}</logpath>
  <log mode="append"/>
  <onfailure action="restart" delay="5 sec"/>
</service>
`
)

type WindowsServiceWrapperConfig struct {
	ID         string
	Name       string
	Executable string
	Arguments  string
	LogPath    string
}

type WindowsProcessConfig struct {
	Processes []WindowsProcess `json:"processes"`
}

type WindowsProcess struct {
	Name       string   `json:"name"`
	Executable string   `json:"executable"`
	Args       []string `json:"args"`
}

type windowsJobSupervisor struct {
	cmdRunner   boshsys.CmdRunner
	dirProvider boshdirs.Provider
	fs          boshsys.FileSystem
	logger      boshlog.Logger
	logTag      string
}

func NewWindowsJobSupervisor(
	cmdRunner boshsys.CmdRunner,
	dirProvider boshdirs.Provider,
	fs boshsys.FileSystem,
	logger boshlog.Logger,
) JobSupervisor {
	return &windowsJobSupervisor{
		cmdRunner:   cmdRunner,
		dirProvider: dirProvider,
		fs:          fs,
		logger:      logger,
		logTag:      "windowsJobSupervisor",
	}
}

func (s *windowsJobSupervisor) Reload() error {
	return nil
}

func (s *windowsJobSupervisor) Start() error {
	_, _, _, err := s.cmdRunner.RunCommand("powershell", "-noprofile", "-noninteractive", "/C", startJobScript)
	if err != nil {
		return bosherr.WrapError(err, "Starting windows job process")
	}

	err = s.fs.RemoveAll(s.stoppedFilePath())
	if err != nil {
		return bosherr.WrapError(err, "Removing stopped file")
	}

	return nil
}

func (s *windowsJobSupervisor) Stop() error {
	_, _, _, err := s.cmdRunner.RunCommand("powershell", "-noprofile", "-noninteractive", "/C", stopJobScript)
	if err != nil {
		return bosherr.WrapError(err, "Stopping services")
	}

	err = s.fs.WriteFileString(s.stoppedFilePath(), "")
	if err != nil {
		return bosherr.WrapError(err, "Creating stopped file")
	}

	return nil
}

func (s *windowsJobSupervisor) Unmonitor() error {
	return nil
}

func (s *windowsJobSupervisor) Status() (status string) {
	if s.fs.FileExists(s.stoppedFilePath()) {
		return "stopped"
	}

	stdout, _, _, err := s.cmdRunner.RunCommand("powershell", "-noprofile", "-noninteractive", "/C", getStatusScript)
	if err != nil {
		return "failing"
	}

	stdout = strings.TrimSpace(stdout)
	if len(stdout) == 0 {
		s.logger.Debug(s.logTag, "No statuses reported for job processes")
		return "running"
	}

	statuses := strings.Split(stdout, "\r\n")
	s.logger.Debug(s.logTag, "Got statuses %#v", statuses)

	for _, status := range statuses {
		if status != "Running" {
			return "failing"
		}
	}

	return "running"
}

func (s *windowsJobSupervisor) Processes() ([]Process, error) {
	return []Process{}, nil
}

func (s *windowsJobSupervisor) AddJob(jobName string, jobIndex int, configPath string) error {
	configFileContents, err := s.fs.ReadFile(configPath)
	if err != nil {
		return err
	}

	if len(configFileContents) == 0 {
		s.logger.Debug(s.logTag, "Skipping job configuration for %q, empty monit config file %q", jobName, configPath)
		return nil
	}

	var processConfig WindowsProcessConfig
	err = json.Unmarshal(configFileContents, &processConfig)
	if err != nil {
		return err
	}

	for _, process := range processConfig.Processes {
		logPath := path.Join(s.dirProvider.LogsDir(), jobName, process.Name)
		err := s.fs.MkdirAll(logPath, os.FileMode(0750))
		if err != nil {
			return bosherr.WrapErrorf(err, "Creating log directory for service '%s'", process.Name)
		}

		serviceConfig := WindowsServiceWrapperConfig{
			ID:         jobName,
			Name:       process.Name,
			Executable: process.Executable,
			Arguments:  strings.Join(process.Args, " "),
			LogPath:    logPath,
		}

		buffer := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("service-wrapper-config").Parse(serviceWrapperTemplate))
		err = t.Execute(buffer, serviceConfig)
		if err != nil {
			return bosherr.WrapErrorf(err, "Rendering service config template for service '%s'", process.Name)
		}

		s.logger.Debug(s.logTag, "Configuring service wrapper for job %q with configPath %q", jobName, configPath)

		jobDir := filepath.Dir(configPath)
		serviceWrapperConfigFile := filepath.Join(jobDir, serviceWrapperConfigFileName)
		err = s.fs.WriteFile(serviceWrapperConfigFile, buffer.Bytes())
		if err != nil {
			return bosherr.WrapErrorf(err, "Saving service config file for service '%s'", process.Name)
		}

		serviceWrapperExePath := filepath.Join(s.dirProvider.BoshBinDir(), serviceWrapperExeFileName)
		err = s.fs.CopyFile(serviceWrapperExePath, filepath.Join(jobDir, serviceWrapperExeFileName))
		if err != nil {
			return bosherr.WrapErrorf(err, "Copying service wrapper in job directory '%s'", jobDir)
		}

		cmdToRun := filepath.Join(jobDir, serviceWrapperExeFileName)

		psScript := fmt.Sprintf(addJobScript, jobName, cmdToRun)
		_, _, _, err = s.cmdRunner.RunCommand("powershell", "-noprofile", "-noninteractive", "/C", psScript)
		if err != nil {
			return bosherr.WrapErrorf(err, "Creating service '%s'", process.Name)
		}
	}

	return nil
}

func (s *windowsJobSupervisor) RemoveAllJobs() error {
	_, _, _, err := s.cmdRunner.RunCommand("powershell", "-noprofile", "-noninteractive", "/C", deleteAllJobsScript)
	return err
}

func (s *windowsJobSupervisor) MonitorJobFailures(handler JobFailureHandler) error {
	return nil
}

func (s *windowsJobSupervisor) stoppedFilePath() string {
	return filepath.Join(s.dirProvider.MonitDir(), "stopped")
}
<commit_msg>Make sure that we are restarting processes upon failure<commit_after>package jobsupervisor

import (
	"bytes"
	"encoding/json"
	"os"
	"path"
	"path/filepath"
	"strings"
	"text/template"

	boshdirs "github.com/cloudfoundry/bosh-agent/settings/directories"
	bosherr "github.com/cloudfoundry/bosh-utils/errors"
	boshlog "github.com/cloudfoundry/bosh-utils/logger"
	boshsys "github.com/cloudfoundry/bosh-utils/system"
)

const (
	serviceDescription = "vcap"

	serviceWrapperExeFileName    = "job-service-wrapper.exe"
	serviceWrapperConfigFileName = "job-service-wrapper.xml"

	startJobScript = `
(get-wmiobject win32_service -filter "description='` + serviceDescription + `'") | ForEach{ Start-Service $_.Name }
`
	stopJobScript = `
(get-wmiobject win32_service -filter "description='` + serviceDescription + `'") | ForEach{ Stop-Service $_.Name }
`
	deleteAllJobsScript = `
(get-wmiobject win32_service -filter "description='` + serviceDescription + `'") | ForEach{ $_.delete() }
`
	getStatusScript = `
(get-wmiobject win32_service -filter "description='` + serviceDescription + `'") | ForEach{ $_.State }
`
	serviceWrapperTemplate = `
<service>
  <id>{{ .ID }}</id>
  <name>{{ .Name }}</name>
  <description>` + serviceDescription + `</description>
  <executable>{{ .Executable }}</executable>
  <arguments>{{ .Arguments }}</arguments>
  <logpath>{{ .LogPath }}</logpath>
  <log mode="append"/>
  <onfailure action="restart" delay="5 sec"/>
</service>
`
)

type WindowsServiceWrapperConfig struct {
	ID         string
	Name       string
	Executable string
	Arguments  string
	LogPath    string
}

type WindowsProcessConfig struct {
	Processes []WindowsProcess `json:"processes"`
}

type WindowsProcess struct {
	Name       string   `json:"name"`
	Executable string   `json:"executable"`
	Args       []string `json:"args"`
}

type windowsJobSupervisor struct {
	cmdRunner   boshsys.CmdRunner
	dirProvider boshdirs.Provider
	fs          boshsys.FileSystem
	logger      boshlog.Logger
	logTag      string
}

func NewWindowsJobSupervisor(
	cmdRunner boshsys.CmdRunner,
	dirProvider boshdirs.Provider,
	fs boshsys.FileSystem,
	logger boshlog.Logger,
) JobSupervisor {
	return &windowsJobSupervisor{
		cmdRunner:   cmdRunner,
		dirProvider: dirProvider,
		fs:          fs,
		logger:      logger,
		logTag:      "windowsJobSupervisor",
	}
}

func (s *windowsJobSupervisor) Reload() error {
	return nil
}

func (s *windowsJobSupervisor) Start() error {
	_, _, _, err := s.cmdRunner.RunCommand("powershell", "-noprofile", "-noninteractive", "/C", startJobScript)
	if err != nil {
		return bosherr.WrapError(err, "Starting windows job process")
	}

	err = s.fs.RemoveAll(s.stoppedFilePath())
	if err != nil {
		return bosherr.WrapError(err, "Removing stopped file")
	}

	return nil
}

func (s *windowsJobSupervisor) Stop() error {
	_, _, _, err := s.cmdRunner.RunCommand("powershell", "-noprofile", "-noninteractive", "/C", stopJobScript)
	if err != nil {
		return bosherr.WrapError(err, "Stopping services")
	}

	err = s.fs.WriteFileString(s.stoppedFilePath(), "")
	if err != nil {
		return bosherr.WrapError(err, "Creating stopped file")
	}

	return nil
}

func (s *windowsJobSupervisor) Unmonitor() error {
	return nil
}

func (s *windowsJobSupervisor) Status() (status string) {
	if s.fs.FileExists(s.stoppedFilePath()) {
		return "stopped"
	}

	stdout, _, _, err := s.cmdRunner.RunCommand("powershell", "-noprofile", "-noninteractive", "/C", getStatusScript)
	if err != nil {
		return "failing"
	}

	stdout = strings.TrimSpace(stdout)
	if len(stdout) == 0 {
		s.logger.Debug(s.logTag, "No statuses reported for job processes")
		return "running"
	}

	statuses := strings.Split(stdout, "\r\n")
	s.logger.Debug(s.logTag, "Got statuses %#v", statuses)

	for _, status := range statuses {
		if status != "Running" {
			return "failing"
		}
	}

	return "running"
}

func (s *windowsJobSupervisor) Processes() ([]Process, error) {
	return []Process{}, nil
}

func (s *windowsJobSupervisor) AddJob(jobName string, jobIndex int, configPath string) error {
	configFileContents, err := s.fs.ReadFile(configPath)
	if err != nil {
		return err
	}

	if len(configFileContents) == 0 {
		s.logger.Debug(s.logTag, "Skipping job configuration for %q, empty monit config file %q", jobName, configPath)
		return nil
	}

	var processConfig WindowsProcessConfig
	err = json.Unmarshal(configFileContents, &processConfig)
	if err != nil {
		return err
	}

	for _, process := range processConfig.Processes {
		logPath := path.Join(s.dirProvider.LogsDir(), jobName, process.Name)
		err := s.fs.MkdirAll(logPath, os.FileMode(0750))
		if err != nil {
			return bosherr.WrapErrorf(err, "Creating log directory for service '%s'", process.Name)
		}

		serviceConfig := WindowsServiceWrapperConfig{
			ID:         jobName,
			Name:       process.Name,
			Executable: process.Executable,
			Arguments:  strings.Join(process.Args, " "),
			LogPath:    logPath,
		}

		buffer := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("service-wrapper-config").Parse(serviceWrapperTemplate))
		err = t.Execute(buffer, serviceConfig)
		if err != nil {
			return bosherr.WrapErrorf(err, "Rendering service config template for service '%s'", process.Name)
		}

		s.logger.Debug(s.logTag, "Configuring service wrapper for job %q with configPath %q", jobName, configPath)

		jobDir := filepath.Dir(configPath)
		serviceWrapperConfigFile := filepath.Join(jobDir, serviceWrapperConfigFileName)
		err = s.fs.WriteFile(serviceWrapperConfigFile, buffer.Bytes())
		if err != nil {
			return bosherr.WrapErrorf(err, "Saving service config file for service '%s'", process.Name)
		}

		serviceWrapperExePath := filepath.Join(s.dirProvider.BoshBinDir(), serviceWrapperExeFileName)
		err = s.fs.CopyFile(serviceWrapperExePath, filepath.Join(jobDir, serviceWrapperExeFileName))
		if err != nil {
			return bosherr.WrapErrorf(err, "Copying service wrapper in job directory '%s'", jobDir)
		}

		cmdToRun := filepath.Join(jobDir, serviceWrapperExeFileName)

		_, _, _, err = s.cmdRunner.RunCommand(cmdToRun, "install")
		if err != nil {
			return bosherr.WrapErrorf(err, "Creating service '%s'", process.Name)
		}
	}

	return nil
}

func (s *windowsJobSupervisor) RemoveAllJobs() error {
	_, _, _, err := s.cmdRunner.RunCommand("powershell", "-noprofile", "-noninteractive", "/C", deleteAllJobsScript)
	return err
}

func (s *windowsJobSupervisor) MonitorJobFailures(handler JobFailureHandler) error {
	return nil
}

func (s *windowsJobSupervisor) stoppedFilePath() string {
	return filepath.Join(s.dirProvider.MonitDir(), "stopped")
}
<|endoftext|>