<commit_before>package hive

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"github.com/eaciit/cast"
	"github.com/eaciit/errorlib"
	"github.com/eaciit/toolkit"
	// "log"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

type HiveResult struct {
	Header     []string
	Result     []string
	ResultObj  interface{}
	DateFormat string
	//Dup        *DuplexTerm
	/*OutputType string
	DateFormat string
	JsonPart   string*/
}

var JsonPart string

func (hr *HiveResult) constructHeader(header string, delimiter string) {
	var tmpHeader []string
	for _, header := range strings.Split(header, delimiter) {
		split := strings.Split(header, ".")
		if len(split) > 1 {
			tmpHeader = append(tmpHeader, strings.Trim(split[1], " '"))
		} else {
			tmpHeader = append(tmpHeader, strings.Trim(header, " '"))
		}
	}
	hr.Header = tmpHeader
}

func Parse(header []string, in interface{}, m interface{}, outputType string, dateFormat string) (e error) {
	// log.Printf("start parse:\n")
	if !toolkit.IsPointer(m) {
		// log.Printf("not pointer\n")
		return errorlib.Error("", "", "Fetch", "Model object should be pointer")
	}
	// log.Printf("pointer\n")
	slice := false
	var ins []string
	if reflect.ValueOf(m).Elem().Kind() == reflect.Slice || toolkit.TypeName(in) == "[]string" {
		slice = true
		ins = in.([]string)
	} else {
		ins = append(ins, in.(string))
	}

	// log.Printf("outputType: %v\n", outputType)

	if outputType == CSV {
		var v reflect.Type

		if slice {
			v = reflect.TypeOf(m).Elem().Elem()
		} else {
			v = reflect.TypeOf(m).Elem()
		}

		ivs := reflect.MakeSlice(reflect.SliceOf(v), 0, 0)
		for _, data := range ins {
			appendData := toolkit.M{}
			iv := reflect.New(v).Interface()
			reader := csv.NewReader(strings.NewReader(""))
			if strings.Contains(data, "','") {
				reader = csv.NewReader(strings.NewReader("\"" + strings.Trim(strings.Replace(data, "','", "\",\"", -1), "'") + "\""))
			} else {
				reader = csv.NewReader(strings.NewReader(data))
			}
			record, e := reader.Read()

			if e != nil {
				return e
			}

			if v.Kind() == reflect.Struct {
				for i := 0; i < v.NumField(); i++ {
					appendData[v.Field(i).Name] = strings.TrimSpace(record[i])
				}

				for i := 0; i < v.NumField(); i++ {
					tag := v.Field(i).Tag

					if appendData.Has(v.Field(i).Name) || appendData.Has(tag.Get("tag_name")) {
						valthis := appendData[v.Field(i).Name]
						if valthis == nil {
							valthis = appendData[tag.Get("tag_name")]
						}

						switch v.Field(i).Type.Kind() {
						case reflect.Int:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int16:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int32:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int64:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Float32:
							valf, _ := strconv.ParseFloat(valthis.(string), 32)
							appendData.Set(v.Field(i).Name, valf)
						case reflect.Float64:
							valf, _ := strconv.ParseFloat(valthis.(string), 64)
							appendData.Set(v.Field(i).Name, valf)
						}

						dtype := DetectFormat(valthis.(string), dateFormat)
						if dtype == "date" {
							valf := cast.String2Date(valthis.(string), dateFormat)
							appendData.Set(v.Field(i).Name, valf)
						} else if dtype == "bool" {
							valf, _ := strconv.ParseBool(valthis.(string))
							appendData.Set(v.Field(i).Name, valf)
						}
					}
				}
			} else {
				for i, val := range header {
					appendData[val] = strings.TrimSpace(record[i])
				}

				if len(header) == 0 {
					e = errorlib.Error("", "", "Parse Out", "Header cant be null because object is not struct")
					return e
				}

				for _, val := range header {
					valthis := appendData[val]
					dtype := DetectFormat(valthis.(string), dateFormat)
					if dtype == "int" {
						appendData.Set(val, cast.ToInt(valthis, cast.RoundingAuto))
					} else if dtype == "float" {
						valf, _ := strconv.ParseFloat(valthis.(string), 64)
						appendData.Set(val, valf)
					} else if dtype == "date" {
						valf := cast.String2Date(valthis.(string), dateFormat)
						appendData.Set(val, valf)
					} else if dtype == "bool" {
						valf, _ := strconv.ParseBool(valthis.(string))
						appendData.Set(val, valf)
					}
				}
			}

			toolkit.Serde(appendData, iv, JSON)
			ivs = reflect.Append(ivs, reflect.ValueOf(iv).Elem())
		}
		if slice {
			reflect.ValueOf(m).Elem().Set(ivs)
		} else {
			reflect.ValueOf(m).Elem().Set(ivs.Index(0))
		}
	} else if outputType == JSON {
		var temp interface{}
		ins = InspectJson(ins)

		//for catch multi json in one line
		if JsonPart != "" && slice {
			for {
				tempjsonpart := JsonPart
				JsonPart = ""
				tempIn := InspectJson([]string{tempjsonpart})
				if len(tempIn) == 0 {
					break
				} else {
					for _, tin := range tempIn {
						ins = append(ins, tin)
					}
				}
			}
		}

		forSerde := strings.Join(ins, ",")
		if slice {
			forSerde = fmt.Sprintf("[%s]", strings.Join(ins, ","))
		}
		if len(ins) > 0 {
			e := json.Unmarshal([]byte(forSerde), &temp)
			if e != nil {
				return e
			}
			e = toolkit.Serde(temp, m, "json")
			if e != nil {
				return e
			}
		}
	} else {
		var v reflect.Type

		if slice {
			v = reflect.TypeOf(m).Elem().Elem()
		} else {
			v = reflect.TypeOf(m).Elem()
		}

		// log.Printf("v: %v\n", v)

		ivs := reflect.MakeSlice(reflect.SliceOf(v), 0, 0)

		// log.Printf("ivs: %v\n", ivs)

		for _, data := range ins {
			appendData := toolkit.M{}
			iv := reflect.New(v).Interface()

			/*log.Printf("data: %v\n", data)
			log.Printf("iv: %v\n", iv)*/

			splitted := strings.Split(data, "\t")

			/*log.Printf("appendData: %v\n", appendData)
			log.Printf("kind: %v\n", v.Kind())
			log.Printf("test: %v", fmt.Sprintf("%v", v))
			//log.Printf("v.Name: %T\n", v)

			if fmt.Sprintf("%v", v) == "reflect.Value" {
				log.Printf("else: %v\n", "reflect.Value")
				for _, val := range header {
					log.Printf("val: %v\n", val)
					valthis := appendData[val]
					dtype := DetectFormat(valthis.(string), dateFormat)
					if dtype == "int" {
						appendData.Set(val, cast.ToInt(valthis, cast.RoundingAuto))
					} else if dtype == "float" {
						valf, _ := strconv.ParseFloat(valthis.(string), 64)
						appendData.Set(val, valf)
					} else if dtype == "date" {
						valf := cast.String2Date(valthis.(string), dateFormat)
						appendData.Set(val, valf)
					} else if dtype == "bool" {
						valf, _ := strconv.ParseBool(valthis.(string))
						appendData.Set(val, valf)
					}
				}
				log.Printf("appendData: %v\n", appendData)
			} else */
			if v.Kind() == reflect.Struct {
				for i := 0; i < v.NumField(); i++ {
					appendData[v.Field(i).Name] = strings.TrimSpace(strings.Trim(splitted[i], " '"))
				}

				// log.Printf("struct: %v\n", v.Kind())
				for i := 0; i < v.NumField(); i++ {
					tag := v.Field(i).Tag
					// log.Printf("i: %v\n", i)

					// log.Printf("name: (%v) tag: (%v)\n", appendData.Has(v.Field(i).Name), appendData.Has(tag.Get("tag_name")))

					if appendData.Has(v.Field(i).Name) || appendData.Has(tag.Get("tag_name")) {
						valthis := appendData[v.Field(i).Name]
						if valthis == nil {
							valthis = appendData[tag.Get("tag_name")]
						}
						// log.Printf("valthis: %v\n", valthis)
						switch v.Field(i).Type.Kind() {
						case reflect.Int:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int16:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int32:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int64:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Float32:
							valf, _ := strconv.ParseFloat(valthis.(string), 32)
							appendData.Set(v.Field(i).Name, valf)
						case reflect.Float64:
							valf, _ := strconv.ParseFloat(valthis.(string), 64)
							appendData.Set(v.Field(i).Name, valf)
						}
						dtype := DetectFormat(valthis.(string), dateFormat)
						if dtype == "date" {
							valf := cast.String2Date(valthis.(string), dateFormat)
							appendData.Set(v.Field(i).Name, valf)
						} else if dtype == "bool" {
							valf, _ := strconv.ParseBool(valthis.(string))
							appendData.Set(v.Field(i).Name, valf)
						}
					}
				}

			} else {
				for i, val := range header {
					appendData[val] = strings.TrimSpace(strings.Trim(splitted[i], " '"))
				}

				if len(header) == 0 {
					e = errorlib.Error("", "", "Parse Out", "Header cant be null because object is not struct")
					return e
				}

				// log.Printf("else: %v\n", v.Kind())
				for _, val := range header {
					// log.Printf("val: %v\n", val)
					valthis := appendData[val]
					dtype := DetectFormat(valthis.(string), dateFormat)
					if dtype == "int" {
						appendData.Set(val, cast.ToInt(valthis, cast.RoundingAuto))
					} else if dtype == "float" {
						valf, _ := strconv.ParseFloat(valthis.(string), 64)
						appendData.Set(val, valf)
					} else if dtype == "date" {
						valf := cast.String2Date(valthis.(string), dateFormat)
						appendData.Set(val, valf)
					} else if dtype == "bool" {
						valf, _ := strconv.ParseBool(valthis.(string))
						appendData.Set(val, valf)
					}
				}
			}

			toolkit.Serde(appendData, iv, JSON)
			// log.Printf("iv result: %v\n", iv)
			ivs = reflect.Append(ivs, reflect.ValueOf(iv).Elem())
			// log.Printf("ivs result: %v\n", ivs)
		}

		if slice {
			reflect.ValueOf(m).Elem().Set(ivs)
		} else {
			reflect.ValueOf(m).Elem().Set(ivs.Index(0))
		}
		// log.Printf("result: %v\n", m)
	}
	return nil
}

func InspectJson(ins []string) (out []string) {
	var re []string

	for _, in := range ins {
		if JsonPart != "" {
			in = JsonPart + in
		}
		in = strings.Trim(strings.TrimSpace(in), " ,")
		charopen := 0
		charclose := 0
		for i, r := range in {
			c := string(r)
			if c == "{" {
				charopen += 1
			} else if c == "}" {
				charclose += 1
			}

			if charopen == charclose && (charclose != 0 && charopen != 0) {
				if len(in) == i+1 {
					JsonPart = ""
				} else {
					JsonPart = in[i+1:]
				}
				re = append(re, strings.Trim(strings.TrimSpace(in[:i+1]), " ,"))
				break
			}
			if charopen != charclose || (charclose == 0 && charopen == 0) {
				JsonPart = in
			}
		}

	}
	return re
}

func DetectFormat(in string, dateFormat string) (res string) {
	if in != "" {
		matchNumber := false
		matchFloat := false
		matchDate := false

		formatDate := "((^(0[0-9]|[0-9]|(1|2)[0-9]|3[0-1])(\\.|\\/|-)(0[0-9]|[0-9]|1[0-2])(\\.|\\/|-)[\\d]{4}$)|(^[\\d]{4}(\\.|\\/|-)(0[0-9]|[0-9]|1[0-2])(\\.|\\/|-)(0[0-9]|[0-9]|(1|2)[0-9]|3[0-1])$))"
		matchDate, _ = regexp.MatchString(formatDate, in)

		if !matchDate && dateFormat != "" {
			d := cast.String2Date(in, dateFormat)
			if d.Year() > 1 {
				matchDate = true
			}
		}

		x := strings.Index(in, ".")

		if x > 0 {
			matchFloat = true
			in = strings.Replace(in, ".", "", 1)
		}

		matchNumber, _ = regexp.MatchString("^\\d+$", in)

		if strings.TrimSpace(in) == "true" || strings.TrimSpace(in) == "false" {
			res = "bool"
		} else {
			res = "string"
			if matchNumber {
				res = "int"
				if matchFloat {
					res = "float"
				}
			}

			if matchDate {
				res = "date"
			}
		}
	}

	return res
}

type FieldMismatch struct {
	expected, found int
}

func (e *FieldMismatch) Error() string {
	return "CSV line fields mismatch. Expected " + strconv.Itoa(e.expected) + " found " + strconv.Itoa(e.found)
}

type UnsupportedType struct {
	Type string
}

func (e *UnsupportedType) Error() string {
	return "Unsupported type: " + e.Type
}
<commit_msg>remove remarked code<commit_after>package hive

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"github.com/eaciit/cast"
	"github.com/eaciit/errorlib"
	"github.com/eaciit/toolkit"
	// "log"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

type HiveResult struct {
	Header     []string
	Result     []string
	ResultObj  interface{}
	DateFormat string
	//Dup        *DuplexTerm
	/*OutputType string
	DateFormat string
	JsonPart   string*/
}

var JsonPart string

func (hr *HiveResult) constructHeader(header string, delimiter string) {
	var tmpHeader []string
	for _, header := range strings.Split(header, delimiter) {
		split := strings.Split(header, ".")
		if len(split) > 1 {
			tmpHeader = append(tmpHeader, strings.Trim(split[1], " '"))
		} else {
			tmpHeader = append(tmpHeader, strings.Trim(header, " '"))
		}
	}
	hr.Header = tmpHeader
}

func Parse(header []string, in interface{}, m interface{}, outputType string, dateFormat string) (e error) {
	if !toolkit.IsPointer(m) {
		return errorlib.Error("", "", "Fetch", "Model object should be pointer")
	}
	slice := false
	var ins []string
	if reflect.ValueOf(m).Elem().Kind() == reflect.Slice || toolkit.TypeName(in) == "[]string" {
		slice = true
		ins = in.([]string)
	} else {
		ins = append(ins, in.(string))
	}

	if outputType == CSV {
		var v reflect.Type

		if slice {
			v = reflect.TypeOf(m).Elem().Elem()
		} else {
			v = reflect.TypeOf(m).Elem()
		}

		ivs := reflect.MakeSlice(reflect.SliceOf(v), 0, 0)
		for _, data := range ins {
			appendData := toolkit.M{}
			iv := reflect.New(v).Interface()
			reader := csv.NewReader(strings.NewReader(""))
			if strings.Contains(data, "','") {
				reader = csv.NewReader(strings.NewReader("\"" + strings.Trim(strings.Replace(data, "','", "\",\"", -1), "'") + "\""))
			} else {
				reader = csv.NewReader(strings.NewReader(data))
			}
			record, e := reader.Read()

			if e != nil {
				return e
			}

			if v.Kind() == reflect.Struct {
				for i := 0; i < v.NumField(); i++ {
					appendData[v.Field(i).Name] = strings.TrimSpace(record[i])
				}

				for i := 0; i < v.NumField(); i++ {
					tag := v.Field(i).Tag

					if appendData.Has(v.Field(i).Name) || appendData.Has(tag.Get("tag_name")) {
						valthis := appendData[v.Field(i).Name]
						if valthis == nil {
							valthis = appendData[tag.Get("tag_name")]
						}

						switch v.Field(i).Type.Kind() {
						case reflect.Int:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int16:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int32:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int64:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Float32:
							valf, _ := strconv.ParseFloat(valthis.(string), 32)
							appendData.Set(v.Field(i).Name, valf)
						case reflect.Float64:
							valf, _ := strconv.ParseFloat(valthis.(string), 64)
							appendData.Set(v.Field(i).Name, valf)
						}

						dtype := DetectFormat(valthis.(string), dateFormat)
						if dtype == "date" {
							valf := cast.String2Date(valthis.(string), dateFormat)
							appendData.Set(v.Field(i).Name, valf)
						} else if dtype == "bool" {
							valf, _ := strconv.ParseBool(valthis.(string))
							appendData.Set(v.Field(i).Name, valf)
						}
					}
				}
			} else {
				for i, val := range header {
					appendData[val] = strings.TrimSpace(record[i])
				}

				if len(header) == 0 {
					e = errorlib.Error("", "", "Parse Out", "Header cant be null because object is not struct")
					return e
				}

				for _, val := range header {
					valthis := appendData[val]
					dtype := DetectFormat(valthis.(string), dateFormat)
					if dtype == "int" {
						appendData.Set(val, cast.ToInt(valthis, cast.RoundingAuto))
					} else if dtype == "float" {
						valf, _ := strconv.ParseFloat(valthis.(string), 64)
						appendData.Set(val, valf)
					} else if dtype == "date" {
						valf := cast.String2Date(valthis.(string), dateFormat)
						appendData.Set(val, valf)
					} else if dtype == "bool" {
						valf, _ := strconv.ParseBool(valthis.(string))
						appendData.Set(val, valf)
					}
				}
			}

			toolkit.Serde(appendData, iv, JSON)
			ivs = reflect.Append(ivs, reflect.ValueOf(iv).Elem())
		}
		if slice {
			reflect.ValueOf(m).Elem().Set(ivs)
		} else {
			reflect.ValueOf(m).Elem().Set(ivs.Index(0))
		}
	} else if outputType == JSON {
		var temp interface{}
		ins = InspectJson(ins)

		//for catch multi json in one line
		if JsonPart != "" && slice {
			for {
				tempjsonpart := JsonPart
				JsonPart = ""
				tempIn := InspectJson([]string{tempjsonpart})
				if len(tempIn) == 0 {
					break
				} else {
					for _, tin := range tempIn {
						ins = append(ins, tin)
					}
				}
			}
		}

		forSerde := strings.Join(ins, ",")
		if slice {
			forSerde = fmt.Sprintf("[%s]", strings.Join(ins, ","))
		}
		if len(ins) > 0 {
			e := json.Unmarshal([]byte(forSerde), &temp)
			if e != nil {
				return e
			}
			e = toolkit.Serde(temp, m, "json")
			if e != nil {
				return e
			}
		}
	} else {
		var v reflect.Type

		if slice {
			v = reflect.TypeOf(m).Elem().Elem()
		} else {
			v = reflect.TypeOf(m).Elem()
		}

		ivs := reflect.MakeSlice(reflect.SliceOf(v), 0, 0)

		for _, data := range ins {
			appendData := toolkit.M{}
			iv := reflect.New(v).Interface()

			splitted := strings.Split(data, "\t")

			if v.Kind() == reflect.Struct {
				for i := 0; i < v.NumField(); i++ {
					appendData[v.Field(i).Name] = strings.TrimSpace(strings.Trim(splitted[i], " '"))
				}

				for i := 0; i < v.NumField(); i++ {
					tag := v.Field(i).Tag

					if appendData.Has(v.Field(i).Name) || appendData.Has(tag.Get("tag_name")) {
						valthis := appendData[v.Field(i).Name]
						if valthis == nil {
							valthis = appendData[tag.Get("tag_name")]
						}
						switch v.Field(i).Type.Kind() {
						case reflect.Int:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int16:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int32:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Int64:
							appendData.Set(v.Field(i).Name, cast.ToInt(valthis, cast.RoundingAuto))
						case reflect.Float32:
							valf, _ := strconv.ParseFloat(valthis.(string), 32)
							appendData.Set(v.Field(i).Name, valf)
						case reflect.Float64:
							valf, _ := strconv.ParseFloat(valthis.(string), 64)
							appendData.Set(v.Field(i).Name, valf)
						}
						dtype := DetectFormat(valthis.(string), dateFormat)
						if dtype == "date" {
							valf := cast.String2Date(valthis.(string), dateFormat)
							appendData.Set(v.Field(i).Name, valf)
						} else if dtype == "bool" {
							valf, _ := strconv.ParseBool(valthis.(string))
							appendData.Set(v.Field(i).Name, valf)
						}
					}
				}

			} else {
				for i, val := range header {
					appendData[val] = strings.TrimSpace(strings.Trim(splitted[i], " '"))
				}

				if len(header) == 0 {
					e = errorlib.Error("", "", "Parse Out", "Header cant be null because object is not struct")
					return e
				}

				for _, val := range header {
					valthis := appendData[val]
					dtype := DetectFormat(valthis.(string), dateFormat)
					if dtype == "int" {
						appendData.Set(val, cast.ToInt(valthis, cast.RoundingAuto))
					} else if dtype == "float" {
						valf, _ := strconv.ParseFloat(valthis.(string), 64)
						appendData.Set(val, valf)
					} else if dtype == "date" {
						valf := cast.String2Date(valthis.(string), dateFormat)
						appendData.Set(val, valf)
					} else if dtype == "bool" {
						valf, _ := strconv.ParseBool(valthis.(string))
						appendData.Set(val, valf)
					}
				}
			}

			toolkit.Serde(appendData, iv, JSON)
			ivs = reflect.Append(ivs, reflect.ValueOf(iv).Elem())
		}

		if slice {
			reflect.ValueOf(m).Elem().Set(ivs)
		} else {
			reflect.ValueOf(m).Elem().Set(ivs.Index(0))
		}
	}
	return nil
}

func InspectJson(ins []string) (out []string) {
	var re []string

	for _, in := range ins {
		if JsonPart != "" {
			in = JsonPart + in
		}
		in = strings.Trim(strings.TrimSpace(in), " ,")
		charopen := 0
		charclose := 0
		for i, r := range in {
			c := string(r)
			if c == "{" {
				charopen += 1
			} else if c == "}" {
				charclose += 1
			}

			if charopen == charclose && (charclose != 0 && charopen != 0) {
				if len(in) == i+1 {
					JsonPart = ""
				} else {
					JsonPart = in[i+1:]
				}
				re = append(re, strings.Trim(strings.TrimSpace(in[:i+1]), " ,"))
				break
			}
			if charopen != charclose || (charclose == 0 && charopen == 0) {
				JsonPart = in
			}
		}

	}
	return re
}

func DetectFormat(in string, dateFormat string) (res string) {
	if in != "" {
		matchNumber := false
		matchFloat := false
		matchDate := false

		formatDate := "((^(0[0-9]|[0-9]|(1|2)[0-9]|3[0-1])(\\.|\\/|-)(0[0-9]|[0-9]|1[0-2])(\\.|\\/|-)[\\d]{4}$)|(^[\\d]{4}(\\.|\\/|-)(0[0-9]|[0-9]|1[0-2])(\\.|\\/|-)(0[0-9]|[0-9]|(1|2)[0-9]|3[0-1])$))"
		matchDate, _ = regexp.MatchString(formatDate, in)

		if !matchDate && dateFormat != "" {
			d := cast.String2Date(in, dateFormat)
			if d.Year() > 1 {
				matchDate = true
			}
		}

		x := strings.Index(in, ".")

		if x > 0 {
			matchFloat = true
			in = strings.Replace(in, ".", "", 1)
		}

		matchNumber, _ = regexp.MatchString("^\\d+$", in)

		if strings.TrimSpace(in) == "true" || strings.TrimSpace(in) == "false" {
			res = "bool"
		} else {
			res = "string"
			if matchNumber {
				res = "int"
				if matchFloat {
					res = "float"
				}
			}

			if matchDate {
				res = "date"
			}
		}
	}

	return res
}

type FieldMismatch struct {
	expected, found int
}

func (e *FieldMismatch) Error() string {
	return "CSV line fields mismatch. Expected " + strconv.Itoa(e.expected) + " found " + strconv.Itoa(e.found)
}

type UnsupportedType struct {
	Type string
}

func (e *UnsupportedType) Error() string {
	return "Unsupported type: " + e.Type
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/spf13/cobra"
	"github.com/yungsang/tablewriter"
	"github.com/yungsang/talk2docker/client"
)

func ListHosts(ctx *cobra.Command, args []string) {
	path := os.ExpandEnv(GetStringFlag(ctx, "config"))

	config, err := client.LoadConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	if GetBoolFlag(ctx, "quiet") {
		for _, host := range config.Hosts {
			fmt.Println(host.Name)
		}
		return
	}

	var items [][]string
	for _, host := range config.Hosts {
		out := []string{
			FormatBool(host.Name == config.Default, "*"),
			host.Name,
			host.URL,
			FormatNonBreakingString(host.Description),
			FormatBool(host.TLS, "YES"),
		}
		items = append(items, out)
	}

	var header = []string{
		"",
		"Name",
		"URL",
		"Description",
		"TLS",
	}

	table := tablewriter.NewWriter(os.Stdout)
	if !GetBoolFlag(ctx, "no-header") {
		table.SetHeader(header)
	}
	table.SetBorder(false)
	table.AppendBulk(items)
	table.Render()
}

func SwitchHost(ctx *cobra.Command, args []string) {
	if len(args) == 0 {
		fmt.Println("Needs an argument <NAME> to switch")
		ctx.Usage()
		return
	}

	path := os.ExpandEnv(GetStringFlag(ctx, "config"))

	config, err := client.LoadConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	var name = args[0]

	host, err := config.GetHost(name)
	if err != nil {
		log.Fatal(err)
	}

	config.Default = host.Name

	err = config.SaveConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	ListHosts(ctx, args)
}

func AddHost(ctx *cobra.Command, args []string) {
	if len(args) < 2 {
		fmt.Println("Needs two arguments <NAME> and <URL> at least")
		ctx.Usage()
		return
	}

	name := args[0]
	url := args[1]
	desc := ""
	if len(args) > 2 {
		desc = strings.Join(args[2:], " ")
	}

	path := os.ExpandEnv(GetStringFlag(ctx, "config"))

	config, err := client.LoadConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	_, err = config.GetHost(name)
	if err == nil {
		log.Fatal(fmt.Sprintf("\"%s\" already exists", name))
	}

	newHost := client.Host{
		Name:        name,
		URL:         url,
		Description: desc,
	}

	config.Default = newHost.Name
	config.Hosts = append(config.Hosts, newHost)

	err = config.SaveConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	ListHosts(ctx, args)
}

func RmHost(ctx *cobra.Command, args []string) {
	if len(args) < 1 {
		fmt.Println("Needs an argument <NAME> to remove")
		ctx.Usage()
		return
	}

	name := args[0]

	path := os.ExpandEnv(GetStringFlag(ctx, "config"))

	config, err := client.LoadConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	_, err = config.GetHost(name)
	if err != nil {
		log.Fatal(fmt.Sprintf("\"%s\" doesn't exist", name))
	}

	hosts := []client.Host{}

	for _, host := range config.Hosts {
		if host.Name != name {
			hosts = append(hosts, host)
		}
	}

	config.Hosts = hosts
	if config.Default == name {
		config.Default = hosts[0].Name
	}

	err = config.SaveConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	ListHosts(ctx, args)
}

func EditHosts(ctx *cobra.Command, args []string) {
	path := os.ExpandEnv(GetStringFlag(ctx, "config"))

	editor := os.Getenv("EDITOR")
	if editor == "" {
		editor = "vi"
	}

	cmd := exec.Command(editor, path)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}

	ListHosts(ctx, args)
}
<commit_msg>Use an error from downstream<commit_after>package commands

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/spf13/cobra"
	"github.com/yungsang/tablewriter"
	"github.com/yungsang/talk2docker/client"
)

func ListHosts(ctx *cobra.Command, args []string) {
	path := os.ExpandEnv(GetStringFlag(ctx, "config"))

	config, err := client.LoadConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	if GetBoolFlag(ctx, "quiet") {
		for _, host := range config.Hosts {
			fmt.Println(host.Name)
		}
		return
	}

	var items [][]string
	for _, host := range config.Hosts {
		out := []string{
			FormatBool(host.Name == config.Default, "*"),
			host.Name,
			host.URL,
			FormatNonBreakingString(host.Description),
			FormatBool(host.TLS, "YES"),
		}
		items = append(items, out)
	}

	var header = []string{
		"",
		"Name",
		"URL",
		"Description",
		"TLS",
	}

	table := tablewriter.NewWriter(os.Stdout)
	if !GetBoolFlag(ctx, "no-header") {
		table.SetHeader(header)
	}
	table.SetBorder(false)
	table.AppendBulk(items)
	table.Render()
}

func SwitchHost(ctx *cobra.Command, args []string) {
	if len(args) == 0 {
		fmt.Println("Needs an argument <NAME> to switch")
		ctx.Usage()
		return
	}

	path := os.ExpandEnv(GetStringFlag(ctx, "config"))

	config, err := client.LoadConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	var name = args[0]

	host, err := config.GetHost(name)
	if err != nil {
		log.Fatal(err)
	}

	config.Default = host.Name

	err = config.SaveConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	ListHosts(ctx, args)
}

func AddHost(ctx *cobra.Command, args []string) {
	if len(args) < 2 {
		fmt.Println("Needs two arguments <NAME> and <URL> at least")
		ctx.Usage()
		return
	}

	name := args[0]
	url := args[1]
	desc := ""
	if len(args) > 2 {
		desc = strings.Join(args[2:], " ")
	}

	path := os.ExpandEnv(GetStringFlag(ctx, "config"))

	config, err := client.LoadConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	_, err = config.GetHost(name)
	if err == nil {
		log.Fatal(fmt.Sprintf("\"%s\" already exists", name))
	}

	newHost := client.Host{
		Name:        name,
		URL:         url,
		Description: desc,
	}

	config.Default = newHost.Name
	config.Hosts = append(config.Hosts, newHost)

	err = config.SaveConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	ListHosts(ctx, args)
}

func RmHost(ctx *cobra.Command, args []string) {
	if len(args) < 1 {
		fmt.Println("Needs an argument <NAME> to remove")
		ctx.Usage()
		return
	}

	name := args[0]

	path := os.ExpandEnv(GetStringFlag(ctx, "config"))

	config, err := client.LoadConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	_, err = config.GetHost(name)
	if err != nil {
		log.Fatal(err)
	}

	hosts := []client.Host{}

	for _, host := range config.Hosts {
		if host.Name != name {
			hosts = append(hosts, host)
		}
	}

	config.Hosts = hosts
	if config.Default == name {
		config.Default = hosts[0].Name
	}

	err = config.SaveConfig(path)
	if err != nil {
		log.Fatal(err)
	}

	ListHosts(ctx, args)
}

func EditHosts(ctx *cobra.Command, args []string) {
	path := os.ExpandEnv(GetStringFlag(ctx, "config"))

	editor := os.Getenv("EDITOR")
	if editor == "" {
		editor = "vi"
	}

	cmd := exec.Command(editor, path)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}

	ListHosts(ctx, args)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"log"

	"fmt"

	"github.com/kr/pretty"
	"github.com/pborman/uuid"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type mongoEngine struct {
	session    *mgo.Session
	dbName     string
	isBinaryId bool
}

func (eng mongoEngine) Close() {
	// TODO
}

// NewMongoEngine returns an Engine based on a mongodb database backend
func NewMongoEngine(dbName string, collections Collections, hostPorts string, isBinaryId bool) Engine {
	log.Printf("connecting to mongodb '%s'\n", hostPorts)
	s, err := mgo.Dial(hostPorts)
	if err != nil {
		panic(err)
	}
	s.SetMode(mgo.Monotonic, true)
	eng := &mongoEngine{
		session:    s,
		dbName:     dbName,
		isBinaryId: isBinaryId,
	}

	for _, coll := range collections {
		eng.EnsureIndexes(coll)
	}

	return eng
}

func (eng *mongoEngine) EnsureIndexes(collection Collection) {
	c := eng.session.DB(eng.dbName).C(collection.name)

	eng.session.ResetIndexCache()

	// create collection if it's not there
	c.Create(&mgo.CollectionInfo{})

	err := c.EnsureIndex(mgo.Index{
		Key:        []string{collection.idPropertyName},
		Unique:     true,
		DropDups:   true,
		Background: false,
		Sparse:     false,
	})
	if err != nil {
		log.Printf("failed to create index by %s on %s : %v\n", collection.name, collection.idPropertyName, err.Error())
	}

}

func (eng *mongoEngine) Drop(collection Collection) {
	err := eng.session.DB(eng.dbName).C(collection.name).DropCollection()
	if err != nil {
		log.Printf("failed to drop collection")
	}
	eng.EnsureIndexes(collection)
}

func (eng *mongoEngine) Write(collection Collection, id string, cont Document) error {
	coll := eng.session.DB(eng.dbName).C(collection.name)
	if id == "" {
		return errors.New("missing id")
	}
	_, err := coll.Upsert(bson.D{{collection.idPropertyName, id}}, cont)
	if err != nil {
		log.Printf("insert failed: %v\n", err)
	}
	return nil
}

func (eng *mongoEngine) Count(collection Collection) int {
	coll := eng.session.DB(eng.dbName).C(collection.name)
	count, err := coll.Count()
	if err != nil {
		panic(err)
	}
	return count
}

func (eng *mongoEngine) Load(collection Collection, id string) (bool, Document, error) {
	c := eng.session.DB(eng.dbName).C(collection.name)
	var content Document

	var err error
	if eng.isBinaryId {
		binaryId := bson.Binary{Kind: 0x04, Data: []byte(uuid.Parse(id))}
		err = c.Find(bson.M{collection.idPropertyName: binaryId}).One(&content)
	} else {
		err = c.Find(bson.M{collection.idPropertyName: id}).One(&content)
	}

	if err == mgo.ErrNotFound {
		return false, Document{}, nil
	}
	if err != nil {
		return false, Document{}, err
	}
	cleanup(content)
	return true, content, nil
}

func (eng *mongoEngine) Delete(collection Collection, id string) error {
	c := eng.session.DB(eng.dbName).C(collection.name)
	err := c.Remove(bson.M{collection.idPropertyName: id})
	if err != mgo.ErrNotFound {
		return err
	}
	return nil
}

func (eng mongoEngine) All(collection Collection, stopchan chan struct{}) (chan Document, error) {
	cont := make(chan Document)

	go func() {
		defer close(cont)
		coll := eng.session.DB(eng.dbName).C(collection.name)
		iter := coll.Find(nil).Iter()
		result := &Document{}
		for iter.Next(result) {
			cleanup(*result)
			select {
			case <-stopchan:
				break
			case cont <- *result:
				result = &Document{}
			}
		}
		if err := iter.Close(); err != nil {
			panic(err)
		}
	}()

	return cont, nil
}

func (eng mongoEngine) Ids(collection Collection, stopchan chan struct{}) (chan string, error) {
	ids := make(chan string)
	go func() {
		defer close(ids)
		coll := eng.session.DB(eng.dbName).C(collection.name)
		iter := coll.Find(nil).Select(bson.M{collection.idPropertyName: true}).Iter()
		var result map[string]interface{}
		for iter.Next(&result) {
			select {
			case <-stopchan:
				break
			case ids <- getUUIDString(result[collection.idPropertyName]):
			}
		}
		if err := iter.Close(); err != nil {
			panic(err)
		}
	}()
	return ids, nil
}

func getUUIDString(uuidValue interface{}) string {
	if uuidString, ok := uuidValue.(string); ok {
		return uuidString
	} else if binaryId, ok := uuidValue.(bson.Binary); ok {
		return uuid.UUID(binaryId.Data).String()
	} else {
		fmt.Printf("UUID field is in an unknown format!\n %# v", pretty.Formatter(uuidValue))
		return ""
	}
}

func cleanup(doc Document) {
	delete(doc, "_id")
}
<commit_msg>Return a string id even if the DB one is binary<commit_after>package main

import (
	"errors"
	"log"

	"fmt"

	"github.com/kr/pretty"
	"github.com/pborman/uuid"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type mongoEngine struct {
	session    *mgo.Session
	dbName     string
	isBinaryId bool
}

func (eng mongoEngine) Close() {
	// TODO
}

// NewMongoEngine returns an Engine based on a mongodb database backend
func NewMongoEngine(dbName string, collections Collections, hostPorts string, isBinaryId bool) Engine {
	log.Printf("connecting to mongodb '%s'\n", hostPorts)
	s, err := mgo.Dial(hostPorts)
	if err != nil {
		panic(err)
	}
	s.SetMode(mgo.Monotonic, true)
	eng := &mongoEngine{
		session:    s,
		dbName:     dbName,
		isBinaryId: isBinaryId,
	}

	for _, coll := range collections {
		eng.EnsureIndexes(coll)
	}

	return eng
}

func (eng *mongoEngine) EnsureIndexes(collection Collection) {
	c := eng.session.DB(eng.dbName).C(collection.name)

	eng.session.ResetIndexCache()

	// create collection if it's not there
	c.Create(&mgo.CollectionInfo{})

	err := c.EnsureIndex(mgo.Index{
		Key:        []string{collection.idPropertyName},
		Unique:     true,
		DropDups:   true,
		Background: false,
		Sparse:     false,
	})
	if err != nil {
		log.Printf("failed to create index by %s on %s : %v\n", collection.name, collection.idPropertyName, err.Error())
	}

}

func (eng *mongoEngine) Drop(collection Collection) {
	err := eng.session.DB(eng.dbName).C(collection.name).DropCollection()
	if err != nil {
		log.Printf("failed to drop collection")
	}
	eng.EnsureIndexes(collection)
}

func (eng *mongoEngine) Write(collection Collection, id string, cont Document) error {
	coll := eng.session.DB(eng.dbName).C(collection.name)
	if id == "" {
		return errors.New("missing id")
	}
	_, err := coll.Upsert(bson.D{{collection.idPropertyName, id}}, cont)
	if err != nil {
		log.Printf("insert failed: %v\n", err)
	}
	return nil
}

func (eng *mongoEngine) Count(collection Collection) int {
	coll := eng.session.DB(eng.dbName).C(collection.name)
	count, err := coll.Count()
	if err != nil {
		panic(err)
	}
	return count
}

func (eng *mongoEngine) Load(collection Collection, id string) (bool, Document, error) {
	c := eng.session.DB(eng.dbName).C(collection.name)
	var content Document

	var err error
	if eng.isBinaryId {
		binaryId := bson.Binary{Kind: 0x04, Data: []byte(uuid.Parse(id))}
		err = c.Find(bson.M{collection.idPropertyName: binaryId}).One(&content)
	} else {
		err = c.Find(bson.M{collection.idPropertyName: id}).One(&content)
	}

	if err == mgo.ErrNotFound {
		return false, Document{}, nil
	}
	if err != nil {
		return false, Document{}, err
	}
	cleanup(content)
	if eng.isBinaryId {
		content["uuid"] = id
	}
	return true, content, nil
}

func (eng *mongoEngine) Delete(collection Collection, id string) error {
	c := eng.session.DB(eng.dbName).C(collection.name)
	err := c.Remove(bson.M{collection.idPropertyName: id})
	if err != mgo.ErrNotFound {
		return err
	}
	return nil
}

func (eng mongoEngine) All(collection Collection, stopchan chan struct{}) (chan Document, error) {
	cont := make(chan Document)

	go func() {
		defer close(cont)
		coll := eng.session.DB(eng.dbName).C(collection.name)
		iter := coll.Find(nil).Iter()
		result := &Document{}
		for iter.Next(result) {
			cleanup(*result)
			select {
			case <-stopchan:
				break
			case cont <- *result:
				result = &Document{}
			}
		}
		if err := iter.Close(); err != nil {
			panic(err)
		}
	}()

	return cont, nil
}

func (eng mongoEngine) Ids(collection Collection, stopchan chan struct{}) (chan string, error) {
	ids := make(chan string)
	go func() {
		defer close(ids)
		coll := eng.session.DB(eng.dbName).C(collection.name)
		iter := coll.Find(nil).Select(bson.M{collection.idPropertyName: true}).Iter()
		var result map[string]interface{}
		for iter.Next(&result) {
			select {
			case <-stopchan:
				break
			case ids <- getUUIDString(result[collection.idPropertyName]):
			}
		}
		if err := iter.Close(); err != nil {
			panic(err)
		}
	}()
	return ids, nil
}

func getUUIDString(uuidValue interface{}) string {
	if uuidString, ok := uuidValue.(string); ok {
		return uuidString
	} else if binaryId, ok := uuidValue.(bson.Binary); ok {
		return uuid.UUID(binaryId.Data).String()
	} else {
		fmt.Printf("UUID field is in an unknown format!\n %# v", pretty.Formatter(uuidValue))
		return ""
	}
}

func cleanup(doc Document) {
	delete(doc, "_id")
}
<|endoftext|>


<commit_before>package config

import (
	"errors"
	"strings"

	"github.com/go-git/go-git/v5/plumbing"
	format "github.com/go-git/go-git/v5/plumbing/format/config"
)

var (
	errBranchEmptyName     = errors.New("branch config: empty name")
	errBranchInvalidMerge  = errors.New("branch config: invalid merge")
	errBranchInvalidRebase = errors.New("branch config: rebase must be one of 'true' or 'interactive'")
)

// Branch contains information on the
// local branches and which remote to track
type Branch struct {
	// Name of branch
	Name string
	// Remote name of remote to track
	Remote string
	// Merge is the local refspec for the branch
	Merge plumbing.ReferenceName
	// Rebase instead of merge when pulling. Valid values are
	// "true" and "interactive".  "false" is undocumented and
	// typically represented by the non-existence of this field
	Rebase string
	// Description explains what the branch is for.
	// Multi-line explanations may be used.
	//
	// Original git command to edit:
	//	git branch --edit-description
	Description string

	raw *format.Subsection
}

// Validate validates fields of branch
func (b *Branch) Validate() error {
	if b.Name == "" {
		return errBranchEmptyName
	}

	if b.Merge != "" && !b.Merge.IsBranch() {
		return errBranchInvalidMerge
	}

	if b.Rebase != "" &&
		b.Rebase != "true" &&
		b.Rebase != "interactive" &&
		b.Rebase != "false" {
		return errBranchInvalidRebase
	}

	return nil
}

func (b *Branch) marshal() *format.Subsection {
	if b.raw == nil {
		b.raw = &format.Subsection{}
	}

	b.raw.Name = b.Name

	if b.Remote == "" {
		b.raw.RemoveOption(remoteSection)
	} else {
		b.raw.SetOption(remoteSection, b.Remote)
	}

	if b.Merge == "" {
		b.raw.RemoveOption(mergeKey)
	} else {
		b.raw.SetOption(mergeKey, string(b.Merge))
	}

	if b.Rebase == "" {
		b.raw.RemoveOption(rebaseKey)
	} else {
		b.raw.SetOption(rebaseKey, b.Rebase)
	}

	if b.Description == "" {
		b.raw.RemoveOption(descriptionKey)
	} else {
		desc := quoteDescription(b.Description)
		b.raw.SetOption(descriptionKey, desc)
	}

	return b.raw
}

func quoteDescription(desc string) string {
	return strings.ReplaceAll(desc, "\n", `\n`)
}

func (b *Branch) unmarshal(s *format.Subsection) error {
	b.raw = s

	b.Name = b.raw.Name
	b.Remote = b.raw.Options.Get(remoteSection)
	b.Merge = plumbing.ReferenceName(b.raw.Options.Get(mergeKey))
	b.Rebase = b.raw.Options.Get(rebaseKey)
	b.Description = unquoteDescription(b.raw.Options.Get(descriptionKey))

	return b.Validate()
}

func unquoteDescription(desc string) string {
	return strings.ReplaceAll(desc, `\n`, "\n")
}
<commit_msg>config: describe reason for custom quote functions<commit_after>package config

import (
	"errors"
	"strings"

	"github.com/go-git/go-git/v5/plumbing"
	format "github.com/go-git/go-git/v5/plumbing/format/config"
)

var (
	errBranchEmptyName     = errors.New("branch config: empty name")
	errBranchInvalidMerge  = errors.New("branch config: invalid merge")
	errBranchInvalidRebase = errors.New("branch config: rebase must be one of 'true' or 'interactive'")
)

// Branch contains information on the
// local branches and which remote to track
type Branch struct {
	// Name of branch
	Name string
	// Remote name of remote to track
	Remote string
	// Merge is the local refspec for the branch
	Merge plumbing.ReferenceName
	// Rebase instead of merge when pulling. Valid values are
	// "true" and "interactive".  "false" is undocumented and
	// typically represented by the non-existence of this field
	Rebase string
	// Description explains what the branch is for.
	// Multi-line explanations may be used.
	//
	// Original git command to edit:
	//	git branch --edit-description
	Description string

	raw *format.Subsection
}

// Validate validates fields of branch
func (b *Branch) Validate() error {
	if b.Name == "" {
		return errBranchEmptyName
	}

	if b.Merge != "" && !b.Merge.IsBranch() {
		return errBranchInvalidMerge
	}

	if b.Rebase != "" &&
		b.Rebase != "true" &&
		b.Rebase != "interactive" &&
		b.Rebase != "false" {
		return errBranchInvalidRebase
	}

	return nil
}

func (b *Branch) marshal() *format.Subsection {
	if b.raw == nil {
		b.raw = &format.Subsection{}
	}

	b.raw.Name = b.Name

	if b.Remote == "" {
		b.raw.RemoveOption(remoteSection)
	} else {
		b.raw.SetOption(remoteSection, b.Remote)
	}

	if b.Merge == "" {
		b.raw.RemoveOption(mergeKey)
	} else {
		b.raw.SetOption(mergeKey, string(b.Merge))
	}

	if b.Rebase == "" {
		b.raw.RemoveOption(rebaseKey)
	} else {
		b.raw.SetOption(rebaseKey, b.Rebase)
	}

	if b.Description == "" {
		b.raw.RemoveOption(descriptionKey)
	} else {
		desc := quoteDescription(b.Description)
		b.raw.SetOption(descriptionKey, desc)
	}

	return b.raw
}

// hack to trigger conditional quoting in the
// plumbing/format/config/Encoder.encodeOptions
//
// Current Encoder implementation uses Go %q format if value contains a backslash character,
// which is not consistent with reference git implementation.
// git just replaces newline characters with \n, while Encoder prints them directly.
// Until value quoting fix, we should escape description value by replacing newline characters with \n.
func quoteDescription(desc string) string {
	return strings.ReplaceAll(desc, "\n", `\n`)
}

func (b *Branch) unmarshal(s *format.Subsection) error {
	b.raw = s

	b.Name = b.raw.Name
	b.Remote = b.raw.Options.Get(remoteSection)
	b.Merge = plumbing.ReferenceName(b.raw.Options.Get(mergeKey))
	b.Rebase = b.raw.Options.Get(rebaseKey)
	b.Description = unquoteDescription(b.raw.Options.Get(descriptionKey))

	return b.Validate()
}

// hack to enable conditional quoting in the
// plumbing/format/config/Encoder.encodeOptions
// goto quoteDescription for details.
func unquoteDescription(desc string) string {
	return strings.ReplaceAll(desc, `\n`, "\n")
}
<|endoftext|>


<commit_before>package config

import (
	"bytes"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"text/tabwriter"

	"github.com/wallix/awless/aws"
	awsconfig "github.com/wallix/awless/aws/config"
	"github.com/wallix/awless/cloud"
	"github.com/wallix/awless/database"
	"github.com/wallix/awless/logger"
	"github.com/wallix/awless/sync"
)

var (
	Config   = map[string]interface{}{}
	Defaults = map[string]interface{}{}
)

const (
	configDatabaseKey   = "userconfig"
	defaultsDatabaseKey = "defaults"

	//Config
	autosyncConfigKey              = "autosync"
	checkUpgradeFrequencyConfigKey = "upgrade.checkfrequency"
	RegionConfigKey                = "aws.region"
	ProfileConfigKey               = "aws.profile"

	//Config prefix
	awsCloudPrefix = "aws."

	//Defaults
	instanceTypeDefaultsKey    = "instance.type"
	instanceImageDefaultsKey   = "instance.image"
	instanceCountDefaultsKey   = "instance.count"
	instanceTimeoutDefaultsKey = "instance.timeout"
)

var configDefinitions = map[string]*Definition{
	autosyncConfigKey:              {help: "Automatically synchronize your cloud locally", defaultValue: "true", parseParamFn: parseBool},
	RegionConfigKey:                {help: "AWS region", defaultValue: "us-east-1", parseParamFn: awsconfig.ParseRegion, stdinParamProviderFn: awsconfig.StdinRegionSelector, onUpdateFns: []onUpdateFunc{awsconfig.WarningChangeRegion, runSyncWithUpdatedRegion}},
	ProfileConfigKey:               {help: "AWS profile", defaultValue: "default"},
	"aws.infra.sync":               {help: "Sync AWS EC2/ELBv2 service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	"aws.access.sync":              {help: "Sync AWS IAM service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	"aws.storage.sync":             {help: "Sync AWS S3 service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	"aws.storage.s3object.sync":    {help: "Sync AWS S3/s3object (when empty: true)", defaultValue: "false", parseParamFn: parseBool},
	"aws.notification.sync":        {help: "Sync AWS SNS service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	"aws.queue.sync":               {help: "Sync AWS SQS service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	"aws.dns.sync":                 {help: "Sync Route53 service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	checkUpgradeFrequencyConfigKey: {help: "Upgrade check frequency (hours); a negative value disables check", defaultValue: "8", parseParamFn: parseInt},
}

var defaultsDefinitions = map[string]*Definition{
	instanceTypeDefaultsKey:    {defaultValue: "t2.micro", help: "AWS EC2 instance type", stdinParamProviderFn: awsconfig.StdinInstanceTypeSelector, parseParamFn: awsconfig.ParseInstanceType},
	instanceImageDefaultsKey:   {help: "AWS EC2 AMI"},
	instanceCountDefaultsKey:   {defaultValue: "1", help: "Number of instances to create on AWS EC2", parseParamFn: parseInt},
	instanceTimeoutDefaultsKey: {defaultValue: "180", help: "Time to wait when checking instance states on AWS EC2", parseParamFn: parseInt},
}

var deprecated = map[string]string{
	"sync.auto": autosyncConfigKey,
	"region":    RegionConfigKey,
}

type onUpdateFunc func(interface{})

type Definition struct {
	help                 string
	parseParamFn         func(string) (interface{}, error)
	stdinParamProviderFn func() string
	onUpdateFns          []onUpdateFunc
	defaultValue         string
}

func LoadAll() error {
	db, err, dbclose := database.Current()
	if err != nil {
		return fmt.Errorf("load config: %s", err)
	}
	defer dbclose()

	Config, err = db.GetConfigs(configDatabaseKey)
	if err != nil {
		return fmt.Errorf("config: load config: %s", err)
	}

	Defaults, err = db.GetConfigs(defaultsDatabaseKey)
	if err != nil {
		return fmt.Errorf("config: load defaults: %s", err)
	}
	return nil
}

func Display() string {
	return fmt.Sprintf("%s\n%s", displayConfig(), displayDefaults())
}

func InitConfigAndDefaults() error {
	for k, v := range configDefinitions {
		if err := Set(k, v.defaultValue); err != nil {
			return err
		}
	}
	for k, v := range defaultsDefinitions {
		if err := Set(k, v.defaultValue); err != nil {
			return err
		}
	}
	return nil
}

func Set(key, value string) error {
	v, def, isConf, err := setVolatile(key, value)
	if err != nil {
		return err
	}
	var databaseKey string
	if isConf {
		databaseKey = configDatabaseKey
	} else {
		databaseKey = defaultsDatabaseKey
	}

	db, err, dbclose := database.Current()
	if err != nil {
		return fmt.Errorf("set config: %s", err)
	}
	defer dbclose()
	if err := db.SetConfig(databaseKey, key, v); err != nil {
		return err
	}
	if def != nil {
		for _, fn := range def.onUpdateFns {
			fn(v)
		}
	}

	return nil
}

func Unset(key string) error {
	var dbKey string
	if _, ok := Config[key]; ok {
		delete(Config, key)
		dbKey = configDatabaseKey
	}
	if _, ok := Defaults[key]; ok {
		delete(Defaults, key)
		dbKey = defaultsDatabaseKey
	}
	if dbKey != "" {
		db, err, dbclose := database.Current()
		if err != nil {
			return fmt.Errorf("unset config: %s", err)
		}
		err = db.UnsetConfig(dbKey, key)
		if err != nil {
			return fmt.Errorf("unset config: %s", err)
		}
		dbclose()
	}
	return nil
}

func Get(key string) (interface{}, bool) {
	if v, ok := Config[key]; ok {
		return v, ok
	}
	v, ok := Defaults[key]
	return v, ok
}

func SetVolatile(key, value string) error {
	_, _, _, err := setVolatile(key, value)
	return err
}

func InteractiveSet(key string) error {
	var val string
	if def, ok := configDefinitions[key]; ok && def.stdinParamProviderFn != nil {
		val = def.stdinParamProviderFn()
	} else if def, ok := defaultsDefinitions[key]; ok && def.stdinParamProviderFn != nil {
		val = def.stdinParamProviderFn()
	} else {
		val = defaultStdinParamProvider()
	}
	return Set(key, val)
}

func parseBool(i string) (interface{}, error) {
	b, err := strconv.ParseBool(i)
	if err != nil {
		return b, fmt.Errorf("invalid value, expected a boolean, got '%s'", i)
	}
	return b, nil
}

func parseInt(a string) (interface{}, error) {
	i, err := strconv.Atoi(a)
	if err != nil {
		return i, fmt.Errorf("invalid value, expected an int, got '%s'", a)
	}
	return i, nil
}

func defaultParser(value string) (interface{}, error) {
	if num, err := strconv.Atoi(value); err == nil {
		return num, nil
	}
	if b, err := strconv.ParseBool(value); err == nil {
		return b, nil
	}
	return value, nil
}

func defaultStdinParamProvider() string {
	var value string
	for value == "" {
		fmt.Print("Value ? > ")
		fmt.Scan(&value)
	}
	return value
}

func setVolatile(key, value string) (interface{}, *Definition, bool, error) {
	var isConf bool
	confDef, confOk := configDefinitions[key]
	defDef, defOk := defaultsDefinitions[key]
	var def *Definition
	switch {
	case confOk && defOk:
		return nil, def, isConf, fmt.Errorf("%s can not be in both config and defaults", key)
	case confOk:
		isConf = true
		def = confDef
	case defOk:
		def = defDef
	default:
		if strings.Contains(key, awsCloudPrefix) {
			isConf = true
		}
	}
	var v interface{}
	var err error
	if def != nil && def.parseParamFn != nil {
		if v, err = def.parseParamFn(value); err != nil {
			return nil, def, isConf, err
		}
	} else {
		if v, err = defaultParser(value); err != nil {
			return nil, def, isConf, err
		}
	}
	if isConf {
		Config[key] = v
	} else {
		Defaults[key] = v
	}
	return v, def, isConf, nil
}

func displayConfig() string {
	var b bytes.Buffer
	b.WriteString("# Config parameters\n")
	t := tabwriter.NewWriter(&b, 0, 0, 3, ' ', 0)
	var keys []string
	for k := range Config {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		fmt.Fprintf(t, "\t%s:\t%v\t(%[2]T)", k, Config[k])
		if def, ok := configDefinitions[k]; ok && def.help != "" {
			fmt.Fprintf(t, "\t# %s\n", def.help)
		} else {
			fmt.Fprintln(t)
		}
	}
	for k := range configDefinitions {
		if _, ok := Config[k]; !ok {
			fmt.Fprintf(t, "\t%s:\t\t", k)
			if def, ok := configDefinitions[k]; ok && def.help != "" {
				fmt.Fprintf(t, "\t# %s\n", def.help)
			} else {
				fmt.Fprintln(t)
			}
		}
	}
	t.Flush()
	return b.String()
}

func displayDefaults() string {
	var b bytes.Buffer
	b.WriteString("# Template defaults\n")
	b.WriteString("   ## Predefined\n")
	t := tabwriter.NewWriter(&b, 0, 0, 3, ' ', 0)
	var keys []string
	for k := range Defaults {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		if def, ok := defaultsDefinitions[k]; ok {
			if def.help != "" {
				fmt.Fprintf(t, "\t%s:\t%v\t(%[2]T)\t# %s\n", k, Defaults[k], def.help)
			} else {
				fmt.Fprintf(t, "\t%s:\t%v\t(%[2]T)\n", k, Defaults[k])
			}
		}
	}
	t.Flush()
	count := 0
	t = tabwriter.NewWriter(&b, 0, 0, 3, ' ', 0)
	for _, k := range keys {
		if _, ok := defaultsDefinitions[k]; !ok {
			count++
			fmt.Fprintf(t, "\t%s:\t%v\t(%[2]T)", k, Defaults[k])
			if newKey, ok := deprecated[k]; ok {
				fmt.Fprintf(t, "\t# DEPRECATED, update with `awless config set %s` `awless config unset %s`", newKey, k)
			}
			fmt.Fprintln(t)
		}
	}
	if count > 0 {
		b.WriteString("\n   ## User defined\n")
		t.Flush()
	}
	return b.String()
}

func runSyncWithUpdatedRegion(i interface{}) {
	if !GetAutosync() {
		return
	}

	fmt.Println("Syncing new region...")
	aws.InitServices(GetConfigWithPrefix("aws."), logger.DiscardLogger)

	var services []cloud.Service
	for _, srv := range cloud.ServiceRegistry {
		services = append(services, srv)
	}

	if sync.DefaultSyncer != nil {
		sync.DefaultSyncer.Sync(services...)
	}
}
<commit_msg>Add volume device in defaults config<commit_after>package config

import (
	"bytes"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"text/tabwriter"

	"github.com/wallix/awless/aws"
	awsconfig "github.com/wallix/awless/aws/config"
	"github.com/wallix/awless/cloud"
	"github.com/wallix/awless/database"
	"github.com/wallix/awless/logger"
	"github.com/wallix/awless/sync"
)

var (
	Config   = map[string]interface{}{}
	Defaults = map[string]interface{}{}
)

const (
	configDatabaseKey   = "userconfig"
	defaultsDatabaseKey = "defaults"

	//Config
	autosyncConfigKey              = "autosync"
	checkUpgradeFrequencyConfigKey = "upgrade.checkfrequency"
	RegionConfigKey                = "aws.region"
	ProfileConfigKey               = "aws.profile"

	//Config prefix
	awsCloudPrefix = "aws."

	//Defaults
	instanceTypeDefaultsKey    = "instance.type"
	instanceImageDefaultsKey   = "instance.image"
	instanceCountDefaultsKey   = "instance.count"
	instanceTimeoutDefaultsKey = "instance.timeout"
	volumeDeviceDefaultsKey    = "volume.device"
)

var configDefinitions = map[string]*Definition{
	autosyncConfigKey:              {help: "Automatically synchronize your cloud locally", defaultValue: "true", parseParamFn: parseBool},
	RegionConfigKey:                {help: "AWS region", defaultValue: "us-east-1", parseParamFn: awsconfig.ParseRegion, stdinParamProviderFn: awsconfig.StdinRegionSelector, onUpdateFns: []onUpdateFunc{awsconfig.WarningChangeRegion, runSyncWithUpdatedRegion}},
	ProfileConfigKey:               {help: "AWS profile", defaultValue: "default"},
	"aws.infra.sync":               {help: "Sync AWS EC2/ELBv2 service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	"aws.access.sync":              {help: "Sync AWS IAM service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	"aws.storage.sync":             {help: "Sync AWS S3 service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	"aws.storage.s3object.sync":    {help: "Sync AWS S3/s3object (when empty: true)", defaultValue: "false", parseParamFn: parseBool},
	"aws.notification.sync":        {help: "Sync AWS SNS service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	"aws.queue.sync":               {help: "Sync AWS SQS service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	"aws.dns.sync":                 {help: "Sync Route53 service (when empty: true)", defaultValue: "true", parseParamFn: parseBool},
	checkUpgradeFrequencyConfigKey: {help: "Upgrade check frequency (hours); a negative value disables check", defaultValue: "8", parseParamFn: parseInt},
}

var defaultsDefinitions = map[string]*Definition{
	instanceTypeDefaultsKey:    {defaultValue: "t2.micro", help: "AWS EC2 instance type", stdinParamProviderFn: awsconfig.StdinInstanceTypeSelector, parseParamFn: awsconfig.ParseInstanceType},
	instanceImageDefaultsKey:   {help: "AWS EC2 AMI"},
	instanceCountDefaultsKey:   {defaultValue: "1", help: "Number of instances to create on AWS EC2", parseParamFn: parseInt},
	instanceTimeoutDefaultsKey: {defaultValue: "180", help: "Time to wait when checking instance states on AWS EC2", parseParamFn: parseInt},
	volumeDeviceDefaultsKey:    {defaultValue: "/dev/sdh", help: "Device name to expose to an EC2 instance"},
}

var deprecated = map[string]string{
	"sync.auto": autosyncConfigKey,
	"region":    RegionConfigKey,
}

type onUpdateFunc func(interface{})

type Definition struct {
	help                 string
	parseParamFn         func(string) (interface{}, error)
	stdinParamProviderFn func() string
	onUpdateFns          []onUpdateFunc
	defaultValue         string
}

func LoadAll() error {
	db, err, dbclose := database.Current()
	if err != nil {
		return fmt.Errorf("load config: %s", err)
	}
	defer dbclose()

	Config, err = db.GetConfigs(configDatabaseKey)
	if err != nil {
		return fmt.Errorf("config: load config: %s", err)
	}

	Defaults, err = db.GetConfigs(defaultsDatabaseKey)
	if err != nil {
		return fmt.Errorf("config: load defaults: %s", err)
	}
	return nil
}

func Display() string {
	return fmt.Sprintf("%s\n%s", displayConfig(), displayDefaults())
}

func InitConfigAndDefaults() error {
	for k, v := range configDefinitions {
		if err := Set(k, v.defaultValue); err != nil {
			return err
		}
	}
	for k, v := range defaultsDefinitions {
		if err := Set(k, v.defaultValue); err != nil {
			return err
		}
	}
	return nil
}

func Set(key, value string) error {
	v, def, isConf, err := setVolatile(key, value)
	if err != nil {
		return err
	}
	var databaseKey string
	if isConf {
		databaseKey = configDatabaseKey
	} else {
		databaseKey = defaultsDatabaseKey
	}

	db, err, dbclose := database.Current()
	if err != nil {
		return fmt.Errorf("set config: %s", err)
	}
	defer dbclose()
	if err := db.SetConfig(databaseKey, key, v); err != nil {
		return err
	}
	if def != nil {
		for _, fn := range def.onUpdateFns {
			fn(v)
		}
	}

	return nil
}

func Unset(key string) error {
	var dbKey string
	if _, ok := Config[key]; ok {
		delete(Config, key)
		dbKey = configDatabaseKey
	}
	if _, ok := Defaults[key]; ok {
		delete(Defaults, key)
		dbKey = defaultsDatabaseKey
	}
	if dbKey != "" {
		db, err, dbclose := database.Current()
		if err != nil {
			return fmt.Errorf("unset config: %s", err)
		}
		err = db.UnsetConfig(dbKey, key)
		if err != nil {
			return fmt.Errorf("unset config: %s", err)
		}
		dbclose()
	}
	return nil
}

func Get(key string) (interface{}, bool) {
	if v, ok := Config[key]; ok {
		return v, ok
	}
	v, ok := Defaults[key]
	return v, ok
}

func SetVolatile(key, value string) error {
	_, _, _, err := setVolatile(key, value)
	return err
}

func InteractiveSet(key string) error {
	var val string
	if def, ok := configDefinitions[key]; ok && def.stdinParamProviderFn != nil {
		val = def.stdinParamProviderFn()
	} else if def, ok := defaultsDefinitions[key]; ok && def.stdinParamProviderFn != nil {
		val = def.stdinParamProviderFn()
	} else {
		val = defaultStdinParamProvider()
	}
	return Set(key, val)
}

func parseBool(i string) (interface{}, error) {
	b, err := strconv.ParseBool(i)
	if err != nil {
		return b, fmt.Errorf("invalid value, expected a boolean, got '%s'", i)
	}
	return b, nil
}

func parseInt(a string) (interface{}, error) {
	i, err := strconv.Atoi(a)
	if err != nil {
		return i, fmt.Errorf("invalid value, expected an int, got '%s'", a)
	}
	return i, nil
}

func defaultParser(value string) (interface{}, error) {
	if num, err := strconv.Atoi(value); err == nil {
		return num, nil
	}
	if b, err := strconv.ParseBool(value); err == nil {
		return b, nil
	}
	return value, nil
}

func defaultStdinParamProvider() string {
	var value string
	for value == "" {
		fmt.Print("Value ? > ")
		fmt.Scan(&value)
	}
	return value
}

func setVolatile(key, value string) (interface{}, *Definition, bool, error) {
	var isConf bool
	confDef, confOk := configDefinitions[key]
	defDef, defOk := defaultsDefinitions[key]
	var def *Definition
	switch {
	case confOk && defOk:
		return nil, def, isConf, fmt.Errorf("%s can not be in both config and defaults", key)
	case confOk:
		isConf = true
		def = confDef
	case defOk:
		def = defDef
	default:
		if strings.Contains(key, awsCloudPrefix) {
			isConf = true
		}
	}
	var v interface{}
	var err error
	if def != nil && def.parseParamFn != nil {
		if v, err = def.parseParamFn(value); err != nil {
			return nil, def, isConf, err
		}
	} else {
		if v, err = defaultParser(value); err != nil {
			return nil, def, isConf, err
		}
	}
	if isConf {
		Config[key] = v
	} else {
		Defaults[key] = v
	}
	return v, def, isConf, nil
}

func displayConfig() string {
	var b bytes.Buffer
	b.WriteString("# Config parameters\n")
	t := tabwriter.NewWriter(&b, 0, 0, 3, ' ', 0)
	var keys []string
	for k := range Config {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		fmt.Fprintf(t, "\t%s:\t%v\t(%[2]T)", k, Config[k])
		if def, ok := configDefinitions[k]; ok && def.help != "" {
			fmt.Fprintf(t, "\t# %s\n", def.help)
		} else {
			fmt.Fprintln(t)
		}
	}
	for k := range configDefinitions {
		if _, ok := Config[k]; !ok {
			fmt.Fprintf(t, "\t%s:\t\t", k)
			if def, ok := configDefinitions[k]; ok && def.help != "" {
				fmt.Fprintf(t, "\t# %s\n", def.help)
			} else {
				fmt.Fprintln(t)
			}
		}
	}
	t.Flush()
	return b.String()
}

func displayDefaults() string {
	var b bytes.Buffer
	b.WriteString("# Template defaults\n")
	b.WriteString("   ## Predefined\n")
	t := tabwriter.NewWriter(&b, 0, 0, 3, ' ', 0)
	var keys []string
	for k := range Defaults {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		if def, ok := defaultsDefinitions[k]; ok {
			if def.help != "" {
				fmt.Fprintf(t, "\t%s:\t%v\t(%[2]T)\t# %s\n", k, Defaults[k], def.help)
			} else {
				fmt.Fprintf(t, "\t%s:\t%v\t(%[2]T)\n", k, Defaults[k])
			}
		}
	}
	t.Flush()
	count := 0
	t = tabwriter.NewWriter(&b, 0, 0, 3, ' ', 0)
	for _, k := range keys {
		if _, ok := defaultsDefinitions[k]; !ok {
			count++
			fmt.Fprintf(t, "\t%s:\t%v\t(%[2]T)", k, Defaults[k])
			if newKey, ok := deprecated[k]; ok {
				fmt.Fprintf(t, "\t# DEPRECATED, update with `awless config set %s` `awless config unset %s`", newKey, k)
			}
			fmt.Fprintln(t)
		}
	}
	if count > 0 {
		b.WriteString("\n   ## User defined\n")
		t.Flush()
	}
	return b.String()
}

func runSyncWithUpdatedRegion(i interface{}) {
	if !GetAutosync() {
		return
	}

	fmt.Println("Syncing new region...")
	aws.InitServices(GetConfigWithPrefix("aws."), logger.DiscardLogger)

	var services []cloud.Service
	for _, srv := range cloud.ServiceRegistry {
		services = append(services, srv)
	}

	if sync.DefaultSyncer != nil {
		sync.DefaultSyncer.Sync(services...)
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright 2013 Nan Deng
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package config

import (
	"fmt"
	"github.com/uniqush/uniqush-conn/evthandler"
	"github.com/uniqush/uniqush-conn/msgcache"

	"github.com/uniqush/uniqush-conn/push"
	"github.com/uniqush/uniqush-conn/rpc"
	"net"
	"time"
)

type Config struct {
	HandshakeTimeout time.Duration
	HttpAddr         string
	Auth             evthandler.Authenticator
	ErrorHandler     evthandler.ErrorHandler
	filename         string
	srvConfig        map[string]*ServiceConfig
	defaultConfig    *ServiceConfig
}

func (self *Config) OnError(addr net.Addr, err error) {
	if self == nil || self.ErrorHandler == nil {
		return
	}
	go self.ErrorHandler.OnError("", "", "", addr.String(), err)
	return
}

func (self *Config) AllServices() []string {
	ret := make([]string, 0, len(self.srvConfig))
	for srv, _ := range self.srvConfig {
		ret = append(ret, srv)
	}
	return ret
}

func (self *Config) ReadConfig(srv string) *ServiceConfig {
	if ret, ok := self.srvConfig[srv]; ok {
		return ret
	}
	return self.defaultConfig
}

func (self *Config) Authenticate(srv, usr, token, addr string) (bool, error) {
	if self == nil || self.Auth == nil {
		return false, nil
	}
	return self.Auth.Authenticate(srv, usr, token, addr)
}

type ServiceConfig struct {
	ServiceName       string
	MaxNrConns        int
	MaxNrUsers        int
	MaxNrConnsPerUser int

	MsgCache msgcache.Cache

	LoginHandler          evthandler.LoginHandler
	LogoutHandler         evthandler.LogoutHandler
	MessageHandler        evthandler.MessageHandler
	ForwardRequestHandler evthandler.ForwardRequestHandler
	ErrorHandler          evthandler.ErrorHandler

	// Push related web hooks
	SubscribeHandler   evthandler.SubscribeHandler
	UnsubscribeHandler evthandler.UnsubscribeHandler

	PushService push.Push
}

func (self *ServiceConfig) clone(srv string, dst *ServiceConfig) *ServiceConfig {
	if self == nil {
		dst = new(ServiceConfig)
		return dst
	}
	if dst == nil {
		dst = new(ServiceConfig)
	}
	dst.ServiceName = srv
	dst.MaxNrConns = self.MaxNrConns
	dst.MaxNrUsers = self.MaxNrUsers
	dst.MaxNrConnsPerUser = self.MaxNrConnsPerUser

	dst.MsgCache = self.MsgCache

	dst.LoginHandler = self.LoginHandler
	dst.LogoutHandler = self.LogoutHandler
	dst.MessageHandler = self.MessageHandler
	dst.ForwardRequestHandler = self.ForwardRequestHandler
	dst.ErrorHandler = self.ErrorHandler

	// Push related web hooks
	dst.SubscribeHandler = self.SubscribeHandler
	dst.UnsubscribeHandler = self.UnsubscribeHandler

	dst.PushService = self.PushService
	return dst
}

func (self *ServiceConfig) Cache() msgcache.Cache {
	if self == nil {
		return nil
	}
	return self.MsgCache
}
func (self *ServiceConfig) Subscribe(req *rpc.SubscribeRequest) {
	if req.Subscribe {
		self.subscribe(req.Username, req.Params)
	} else {
		self.unsubscribe(req.Username, req.Params)
	}
}

func (self *ServiceConfig) subscribe(username string, info map[string]string) {
	if self == nil || self.PushService == nil {
		return
	}
	go func() {
		if self.shouldSubscribe(self.ServiceName, username, info) {
			self.PushService.Subscribe(self.ServiceName, username, info)
		}
	}()
	return
}

func (self *ServiceConfig) unsubscribe(username string, info map[string]string) {
	if self == nil || self.PushService == nil {
		return
	}
	go self.PushService.Unsubscribe(self.ServiceName, username, info)
	return
}

type connDescriptor interface {
	RemoteAddr() net.Addr
	Service() string
	Username() string
	UniqId() string
}

func (self *ServiceConfig) OnError(c connDescriptor, err error) {
	if self == nil || self.ErrorHandler == nil {
		return
	}
	go self.ErrorHandler.OnError(c.Service(), c.Username(), c.UniqId(), c.RemoteAddr().String(), err)
	return
}

func (self *ServiceConfig) OnLogin(c connDescriptor) {
	if self == nil || self.LoginHandler == nil {
		return
	}
	go self.LoginHandler.OnLogin(c.Service(), c.Username(), c.UniqId(), c.RemoteAddr().String())
	return
}

func (self *ServiceConfig) OnLogout(c connDescriptor, reason error) {
	if self == nil || self.LogoutHandler == nil {
		return
	}
	go self.LogoutHandler.OnLogout(c.Service(), c.Username(), c.UniqId(), c.RemoteAddr().String(), reason)
	return
}

func (self *ServiceConfig) OnMessage(c connDescriptor, msg *rpc.Message) {
	if self == nil || self.MessageHandler == nil {
		return
	}
	go self.MessageHandler.OnMessage(c.Service(), c.Username(), c.UniqId(), msg)
	return
}

func (self *ServiceConfig) CacheMessage(username string, mc *rpc.MessageContainer, ttl time.Duration) (string, error) {
	if self == nil || self.MsgCache == nil {
		return "", fmt.Errorf("no cache available")
	}
	return self.MsgCache.CacheMessage(self.ServiceName, username, mc, ttl)
}

func (self *ServiceConfig) Push(username, senderService, senderName string, info map[string]string, msgId string, size int) {
	if self == nil || self.PushService == nil {
		return
	}
	self.PushService.Push(self.ServiceName, username, senderService, senderName, info, msgId, size)
}

func (self *ServiceConfig) ShouldForward(fwdreq *rpc.ForwardRequest) (shouldForward, shouldPush bool, pushInfo map[string]string) {
	if self == nil || self.ForwardRequestHandler == nil {
		return false, false, nil
	}
	return self.ForwardRequestHandler.ShouldForward(fwdreq.SenderService, fwdreq.Sender, fwdreq.ReceiverService, fwdreq.Receivers, fwdreq.TTL, fwdreq.Message)
}

func (self *ServiceConfig) shouldSubscribe(srv, usr string, info map[string]string) bool {
	if self == nil || self.SubscribeHandler == nil {
		return false
	}
	return self.SubscribeHandler.ShouldSubscribe(srv, usr, info)
}
<commit_msg>fix #10<commit_after>/*
 * Copyright 2013 Nan Deng
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package config

import (
	"fmt"
	"github.com/uniqush/uniqush-conn/evthandler"
	"github.com/uniqush/uniqush-conn/msgcache"

	"github.com/uniqush/uniqush-conn/push"
	"github.com/uniqush/uniqush-conn/rpc"
	"net"
	"time"
)

type Config struct {
	HandshakeTimeout time.Duration
	HttpAddr         string
	Auth             evthandler.Authenticator
	ErrorHandler     evthandler.ErrorHandler
	filename         string
	srvConfig        map[string]*ServiceConfig
	defaultConfig    *ServiceConfig
}

func (self *Config) OnError(addr net.Addr, err error) {
	if self == nil || self.ErrorHandler == nil {
		return
	}
	go self.ErrorHandler.OnError("", "", "", addr.String(), err)
	return
}

func (self *Config) AllServices() []string {
	ret := make([]string, 0, len(self.srvConfig))
	for srv, _ := range self.srvConfig {
		ret = append(ret, srv)
	}
	return ret
}

func (self *Config) ReadConfig(srv string) *ServiceConfig {
	if ret, ok := self.srvConfig[srv]; ok {
		return ret
	}
	return self.defaultConfig
}

func (self *Config) Authenticate(srv, usr, connId, token, addr string) (bool, error) {
	if self == nil || self.Auth == nil {
		return false, nil
	}
	return self.Auth.Authenticate(srv, usr, connId, token, addr)
}

type ServiceConfig struct {
	ServiceName       string
	MaxNrConns        int
	MaxNrUsers        int
	MaxNrConnsPerUser int

	MsgCache msgcache.Cache

	LoginHandler          evthandler.LoginHandler
	LogoutHandler         evthandler.LogoutHandler
	MessageHandler        evthandler.MessageHandler
	ForwardRequestHandler evthandler.ForwardRequestHandler
	ErrorHandler          evthandler.ErrorHandler

	// Push related web hooks
	SubscribeHandler   evthandler.SubscribeHandler
	UnsubscribeHandler evthandler.UnsubscribeHandler

	PushService push.Push
}

func (self *ServiceConfig) clone(srv string, dst *ServiceConfig) *ServiceConfig {
	if self == nil {
		dst = new(ServiceConfig)
		return dst
	}
	if dst == nil {
		dst = new(ServiceConfig)
	}
	dst.ServiceName = srv
	dst.MaxNrConns = self.MaxNrConns
	dst.MaxNrUsers = self.MaxNrUsers
	dst.MaxNrConnsPerUser = self.MaxNrConnsPerUser

	dst.MsgCache = self.MsgCache

	dst.LoginHandler = self.LoginHandler
	dst.LogoutHandler = self.LogoutHandler
	dst.MessageHandler = self.MessageHandler
	dst.ForwardRequestHandler = self.ForwardRequestHandler
	dst.ErrorHandler = self.ErrorHandler

	// Push related web hooks
	dst.SubscribeHandler = self.SubscribeHandler
	dst.UnsubscribeHandler = self.UnsubscribeHandler

	dst.PushService = self.PushService
	return dst
}

func (self *ServiceConfig) Cache() msgcache.Cache {
	if self == nil {
		return nil
	}
	return self.MsgCache
}
func (self *ServiceConfig) Subscribe(req *rpc.SubscribeRequest) {
	if req.Subscribe {
		self.subscribe(req.Username, req.Params)
	} else {
		self.unsubscribe(req.Username, req.Params)
	}
}

func (self *ServiceConfig) subscribe(username string, info map[string]string) {
	if self == nil || self.PushService == nil {
		return
	}
	go func() {
		if self.shouldSubscribe(self.ServiceName, username, info) {
			self.PushService.Subscribe(self.ServiceName, username, info)
		}
	}()
	return
}

func (self *ServiceConfig) unsubscribe(username string, info map[string]string) {
	if self == nil || self.PushService == nil {
		return
	}
	go self.PushService.Unsubscribe(self.ServiceName, username, info)
	return
}

type connDescriptor interface {
	RemoteAddr() net.Addr
	Service() string
	Username() string
	UniqId() string
}

func (self *ServiceConfig) OnError(c connDescriptor, err error) {
	if self == nil || self.ErrorHandler == nil {
		return
	}
	go self.ErrorHandler.OnError(c.Service(), c.Username(), c.UniqId(), c.RemoteAddr().String(), err)
	return
}

func (self *ServiceConfig) OnLogin(c connDescriptor) {
	if self == nil || self.LoginHandler == nil {
		return
	}
	go self.LoginHandler.OnLogin(c.Service(), c.Username(), c.UniqId(), c.RemoteAddr().String())
	return
}

func (self *ServiceConfig) OnLogout(c connDescriptor, reason error) {
	if self == nil || self.LogoutHandler == nil {
		return
	}
	go self.LogoutHandler.OnLogout(c.Service(), c.Username(), c.UniqId(), c.RemoteAddr().String(), reason)
	return
}

func (self *ServiceConfig) OnMessage(c connDescriptor, msg *rpc.Message) {
	if self == nil || self.MessageHandler == nil {
		return
	}
	go self.MessageHandler.OnMessage(c.Service(), c.Username(), c.UniqId(), msg)
	return
}

func (self *ServiceConfig) CacheMessage(username string, mc *rpc.MessageContainer, ttl time.Duration) (string, error) {
	if self == nil || self.MsgCache == nil {
		return "", fmt.Errorf("no cache available")
	}
	return self.MsgCache.CacheMessage(self.ServiceName, username, mc, ttl)
}

func (self *ServiceConfig) Push(username, senderService, senderName string, info map[string]string, msgId string, size int) {
	if self == nil || self.PushService == nil {
		return
	}
	self.PushService.Push(self.ServiceName, username, senderService, senderName, info, msgId, size)
}

func (self *ServiceConfig) ShouldForward(fwdreq *rpc.ForwardRequest) (shouldForward, shouldPush bool, pushInfo map[string]string) {
	if self == nil || self.ForwardRequestHandler == nil {
		return false, false, nil
	}
	return self.ForwardRequestHandler.ShouldForward(fwdreq.SenderService, fwdreq.Sender, fwdreq.ReceiverService, fwdreq.Receivers, fwdreq.TTL, fwdreq.Message)
}

func (self *ServiceConfig) shouldSubscribe(srv, usr string, info map[string]string) bool {
	if self == nil || self.SubscribeHandler == nil {
		return false
	}
	return self.SubscribeHandler.ShouldSubscribe(srv, usr, info)
}
<|endoftext|>


<commit_before>// Package config contains utilities and types necessary for
// launching specially-configured server instances.
package config

import (
	"net"
	"os"

	"github.com/mholt/caddy/middleware"
)

const (
	defaultHost = "localhost"
	defaultPort = "8080"
	defaultRoot = "."

	// The default configuration file to load if none is specified
	DefaultConfigFile = "Caddyfile"
)

// config represents a server configuration. It
// is populated by parsing a config file (via the
// Load function).
type Config struct {
	// The hostname or IP on which to serve
	Host string

	// The port to listen on
	Port string

	// The directory from which to serve files
	Root string

	// HTTPS configuration
	TLS TLSConfig

	// Middleware stack
	Middleware map[string][]middleware.Middleware

	// Functions (or methods) to execute at server start; these
	// are executed before any parts of the server are configured,
	// and the functions are blocking
	Startup []func() error

	// Functions (or methods) to execute when the server quits;
	// these are executed in response to SIGINT and are blocking
	Shutdown []func() error

	// MaxCPU is the maximum number of cores for the whole process to use
	MaxCPU int

	// The path to the configuration file from which this was loaded
	ConfigFile string
}

// Address returns the host:port of c as a string.
func (c Config) Address() string {
	return net.JoinHostPort(c.Host, c.Port)
}

// TLSConfig describes how TLS should be configured and used,
// if at all. A certificate and key are both required.
type TLSConfig struct {
	Enabled     bool
	Certificate string
	Key         string
}

// Load loads a configuration file, parses it,
// and returns a slice of Config structs which
// can be used to create and configure server
// instances.
func Load(filename string) ([]Config, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	p, err := newParser(file)
	if err != nil {
		return nil, err
	}

	cfgs, err := p.parse()
	if err != nil {
		return []Config{}, err
	}

	for i := 0; i < len(cfgs); i++ {
		cfgs[i].ConfigFile = filename
	}

	return cfgs, nil
}

// IsNotFound returns whether or not the error is
// one which indicates that the configuration file
// was not found. (Useful for checking the error
// returned from Load).
func IsNotFound(err error) bool {
	return os.IsNotExist(err)
}

// Default makes a default configuration
// that's empty except for root, host, and port,
// which are essential for serving the cwd.
func Default() []Config {
	cfg := []Config{
		Config{
			Root: defaultRoot,
			Host: defaultHost,
			Port: defaultPort,
		},
	}
	return cfg
}
<commit_msg>Turn off log timestamp for parse errors (easier to read)<commit_after>// Package config contains utilities and types necessary for
// launching specially-configured server instances.
package config

import (
	"log"
	"net"
	"os"

	"github.com/mholt/caddy/middleware"
)

const (
	defaultHost = "localhost"
	defaultPort = "8080"
	defaultRoot = "."

	// The default configuration file to load if none is specified
	DefaultConfigFile = "Caddyfile"
)

// config represents a server configuration. It
// is populated by parsing a config file (via the
// Load function).
type Config struct {
	// The hostname or IP on which to serve
	Host string

	// The port to listen on
	Port string

	// The directory from which to serve files
	Root string

	// HTTPS configuration
	TLS TLSConfig

	// Middleware stack
	Middleware map[string][]middleware.Middleware

	// Functions (or methods) to execute at server start; these
	// are executed before any parts of the server are configured,
	// and the functions are blocking
	Startup []func() error

	// Functions (or methods) to execute when the server quits;
	// these are executed in response to SIGINT and are blocking
	Shutdown []func() error

	// MaxCPU is the maximum number of cores for the whole process to use
	MaxCPU int

	// The path to the configuration file from which this was loaded
	ConfigFile string
}

// Address returns the host:port of c as a string.
func (c Config) Address() string {
	return net.JoinHostPort(c.Host, c.Port)
}

// TLSConfig describes how TLS should be configured and used,
// if at all. A certificate and key are both required.
type TLSConfig struct {
	Enabled     bool
	Certificate string
	Key         string
}

// Load loads a configuration file, parses it,
// and returns a slice of Config structs which
// can be used to create and configure server
// instances.
func Load(filename string) ([]Config, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	// turn off timestamp for parsing
	flags := log.Flags()
	log.SetFlags(0)

	p, err := newParser(file)
	if err != nil {
		return nil, err
	}

	cfgs, err := p.parse()
	if err != nil {
		return []Config{}, err
	}

	for i := 0; i < len(cfgs); i++ {
		cfgs[i].ConfigFile = filename
	}

	// restore logging settings
	log.SetFlags(flags)

	return cfgs, nil
}

// IsNotFound returns whether or not the error is
// one which indicates that the configuration file
// was not found. (Useful for checking the error
// returned from Load).
func IsNotFound(err error) bool {
	return os.IsNotExist(err)
}

// Default makes a default configuration
// that's empty except for root, host, and port,
// which are essential for serving the cwd.
func Default() []Config {
	cfg := []Config{
		Config{
			Root: defaultRoot,
			Host: defaultHost,
			Port: defaultPort,
		},
	}
	return cfg
}
<|endoftext|>


<commit_before>package config

import (
	"github.com/freeformz/shh/utils"
	"time"
)

const (
	VERSION              = "0.1.0"
	DEFAULT_INTERVAL     = "10s"                                                              // Default tick interval for pollers
	DEFAULT_OUTPUTTER    = "stdoutl2metder"                                                   // Default outputter
	DEFAULT_POLLERS      = "conntrack,cpu,df,disk,listen,load,mem,nif,ntpdate,processes,self" // Default pollers
	DEFAULT_PROFILE_PORT = "0"
	DEFAULT_DF_TYPES     = "btrfs,ext3,ext4,tmpfs,xfs"
	DEFAULT_NIF_DEVICES  = "eth0,lo"
)

var (
	Interval            = utils.GetEnvWithDefaultDuration("SHH_INTERVAL", DEFAULT_INTERVAL)                      // Polling Interval
	Outputter           = utils.GetEnvWithDefault("SHH_OUTPUTTER", DEFAULT_OUTPUTTER)                            // Outputter
	Pollers             = utils.GetEnvWithDefaultStrings("SHH_POLLERS", DEFAULT_POLLERS)                         // Pollers to poll
	Source              = utils.GetEnvWithDefault("SHH_SOURCE", "")                                              // Source to emit
	Prefix              = utils.GetEnvWithDefault("SHH_PREFIX", "")                                              // Metric prefix to use
	ProfilePort         = utils.GetEnvWithDefault("SHH_PROFILE_PORT", DEFAULT_PROFILE_PORT)                      // Profile Port
	DfTypes             = utils.GetEnvWithDefaultStrings("SHH_DF_TYPES", DEFAULT_DF_TYPES)                       // Default DF types
	Listen              = utils.GetEnvWithDefault("SHH_LISTEN", "unix,#shh")                                     // Default network socket info for listen
	NifDevices          = utils.GetEnvWithDefaultStrings("SHH_NIF_DEVICES", DEFAULT_NIF_DEVICES)                 // Devices to poll
	NtpdateServers      = utils.GetEnvWithDefaultStrings("SHH_NTPDATE_SERVERS", "0.pool.ntp.org,1.pool.ntp.org") // NTP Servers
	LibratoUser         = utils.GetEnvWithDefault("SHH_LIBRATO_USER", "")                                        // The Librato API User
	LibratoToken        = utils.GetEnvWithDefault("SHH_LIBRATO_TOKEN", "")                                       // The Librato API TOken
	LibratoBatchSize    = utils.GetEnvWithDefaultInt("SHH_LIBRATO_BATCH_SIZE", 50)                               // The max number of metrics to submit in a single request
	LibratoBatchTimeout = utils.GetEnvWithDefaultDuration("SHH_LIBRATO_BATCH_TIMEOUT", "500ms")                  // The max time metrics will sit un-delivered

	Start = time.Now() // Start time
)
<commit_msg>bump version<commit_after>package config

import (
	"github.com/freeformz/shh/utils"
	"time"
)

const (
	VERSION              = "0.1.1"
	DEFAULT_INTERVAL     = "10s"                                                              // Default tick interval for pollers
	DEFAULT_OUTPUTTER    = "stdoutl2metder"                                                   // Default outputter
	DEFAULT_POLLERS      = "conntrack,cpu,df,disk,listen,load,mem,nif,ntpdate,processes,self" // Default pollers
	DEFAULT_PROFILE_PORT = "0"
	DEFAULT_DF_TYPES     = "btrfs,ext3,ext4,tmpfs,xfs"
	DEFAULT_NIF_DEVICES  = "eth0,lo"
)

var (
	Interval            = utils.GetEnvWithDefaultDuration("SHH_INTERVAL", DEFAULT_INTERVAL)                      // Polling Interval
	Outputter           = utils.GetEnvWithDefault("SHH_OUTPUTTER", DEFAULT_OUTPUTTER)                            // Outputter
	Pollers             = utils.GetEnvWithDefaultStrings("SHH_POLLERS", DEFAULT_POLLERS)                         // Pollers to poll
	Source              = utils.GetEnvWithDefault("SHH_SOURCE", "")                                              // Source to emit
	Prefix              = utils.GetEnvWithDefault("SHH_PREFIX", "")                                              // Metric prefix to use
	ProfilePort         = utils.GetEnvWithDefault("SHH_PROFILE_PORT", DEFAULT_PROFILE_PORT)                      // Profile Port
	DfTypes             = utils.GetEnvWithDefaultStrings("SHH_DF_TYPES", DEFAULT_DF_TYPES)                       // Default DF types
	Listen              = utils.GetEnvWithDefault("SHH_LISTEN", "unix,#shh")                                     // Default network socket info for listen
	NifDevices          = utils.GetEnvWithDefaultStrings("SHH_NIF_DEVICES", DEFAULT_NIF_DEVICES)                 // Devices to poll
	NtpdateServers      = utils.GetEnvWithDefaultStrings("SHH_NTPDATE_SERVERS", "0.pool.ntp.org,1.pool.ntp.org") // NTP Servers
	LibratoUser         = utils.GetEnvWithDefault("SHH_LIBRATO_USER", "")                                        // The Librato API User
	LibratoToken        = utils.GetEnvWithDefault("SHH_LIBRATO_TOKEN", "")                                       // The Librato API TOken
	LibratoBatchSize    = utils.GetEnvWithDefaultInt("SHH_LIBRATO_BATCH_SIZE", 50)                               // The max number of metrics to submit in a single request
	LibratoBatchTimeout = utils.GetEnvWithDefaultDuration("SHH_LIBRATO_BATCH_TIMEOUT", "500ms")                  // The max time metrics will sit un-delivered

	Start = time.Now() // Start time
)
<|endoftext|>


<commit_before>package config

import (
	"io/ioutil"
	"log"
	"os"
	"regexp"

	"gopkg.in/yaml.v2"

	"github.com/tsiemens/gmail-tools/util"
)

const (
	ConfigYamlFileName = "config.yaml"

	caseIgnore = "(?i)"
)

type Config struct {
	InterestingMessageQuery          string            `yaml:"InterestingMessageQuery"`
	AlwaysUninterestingLabelPatterns []string          `yaml:"AlwaysUninterestingLabelPatterns"`
	UninterestingLabelPatterns       []string          `yaml:"UninterestingLabelPatterns"`
	InterestingLabelPatterns         []string          `yaml:"InterestingLabelPatterns"`
	ApplyLabelOnTouch                string            `yaml:"ApplyLabelOnTouch"`
	LabelColors                      map[string]string `yaml:"LabelColors"`
	Aliases                          map[string]string `yaml:"Aliases"`

	AlwaysUninterLabelRegexps []*regexp.Regexp
	UninterLabelRegexps       []*regexp.Regexp
	InterLabelRegexps         []*regexp.Regexp
	ConfigFile                string
}

// May be called at any time (all dependencies are static)
func loadConfig() *Config {
	confFname := util.RequiredHomeDirAndFile(util.UserAppDirName, ConfigYamlFileName)

	var confData []byte

	if _, err := os.Stat(confFname); err != nil {
		if os.IsNotExist(err) {
			// File does *not* exist
			_, err := os.Create(confFname)
			if err != nil {
				log.Fatalf("Failed to create config file: %v", err)
			}
			confData = make([]byte, 0)
		} else {
			// Schrodinger: file may or may not exist. See err for details.
			log.Fatalf("Failed to stat config file: %v", err)
		}
	}

	confData, err := ioutil.ReadFile(confFname)
	if err != nil {
		log.Fatalf("Failed to read file: %v", err)
	}

	conf := &Config{}
	conf.ConfigFile = confFname
	err = yaml.Unmarshal(confData, conf)
	if err != nil {
		log.Fatalf("Could not unmarshal: %v", err)
	}
	util.Debugf("config: %+v\n", conf)

	checkLoadErr := func(e error) {
		if err != nil {
			log.Fatalf("Failed to load config: \"%s\"", err)
		}
	}

	for _, pat := range conf.AlwaysUninterestingLabelPatterns {
		re, err := regexp.Compile(caseIgnore + pat)
		checkLoadErr(err)
		conf.AlwaysUninterLabelRegexps = append(conf.AlwaysUninterLabelRegexps, re)
	}

	for _, pat := range conf.UninterestingLabelPatterns {
		re, err := regexp.Compile(caseIgnore + pat)
		checkLoadErr(err)
		conf.UninterLabelRegexps = append(conf.UninterLabelRegexps, re)
	}

	for _, pat := range conf.InterestingLabelPatterns {
		re, err := regexp.Compile(caseIgnore + pat)
		checkLoadErr(err)
		conf.InterLabelRegexps = append(conf.InterLabelRegexps, re)
	}
	return conf
}

var appConfig *Config

func AppConfig() *Config {
	if appConfig == nil {
		appConfig = loadConfig()
	}
	return appConfig
}
<commit_msg>Add a public API for plugins to unmarshal their own parts from the config<commit_after>package config

import (
	"io/ioutil"
	"log"
	"os"
	"regexp"

	"gopkg.in/yaml.v2"

	"github.com/tsiemens/gmail-tools/prnt"
	"github.com/tsiemens/gmail-tools/util"
)

const (
	ConfigYamlFileName = "config.yaml"

	caseIgnore = "(?i)"
)

type Config struct {
	InterestingMessageQuery          string            `yaml:"InterestingMessageQuery"`
	AlwaysUninterestingLabelPatterns []string          `yaml:"AlwaysUninterestingLabelPatterns"`
	UninterestingLabelPatterns       []string          `yaml:"UninterestingLabelPatterns"`
	InterestingLabelPatterns         []string          `yaml:"InterestingLabelPatterns"`
	ApplyLabelOnTouch                string            `yaml:"ApplyLabelOnTouch"`
	LabelColors                      map[string]string `yaml:"LabelColors"`
	Aliases                          map[string]string `yaml:"Aliases"`

	AlwaysUninterLabelRegexps []*regexp.Regexp
	UninterLabelRegexps       []*regexp.Regexp
	InterLabelRegexps         []*regexp.Regexp
	ConfigFile                string
}

// LoadConfigInto loads the central config file contents into confOut object.
// If the file does not exist, it is created. If it cannot be created or read,
// the process will be terminated.
// Returns the name of the loaded config file.
//
// confOut : A config object marked up for yaml unmarshaling.
//           This may be any subset of the config file, allowing for use by
//           plugins to add extra fields into the config file for their own use.
func LoadConfigInto(confOut interface{}) string {
	confFname := util.RequiredHomeDirAndFile(util.UserAppDirName, ConfigYamlFileName)

	var confData []byte

	if _, err := os.Stat(confFname); err != nil {
		if os.IsNotExist(err) {
			// File does *not* exist
			_, err := os.Create(confFname)
			if err != nil {
				log.Fatalf("Failed to create config file: %v", err)
			}
			confData = make([]byte, 0)
		} else {
			// Schrodinger: file may or may not exist. See err for details.
			log.Fatalf("Failed to stat config file: %v", err)
		}
	}

	confData, err := ioutil.ReadFile(confFname)
	if err != nil {
		log.Fatalf("Failed to read file: %v", err)
	}

	err = yaml.Unmarshal(confData, confOut)
	if err != nil {
		log.Fatalf("Could not unmarshal: %v", err)
	}
	util.Debugf("config: %+v\n", confOut)

	return confFname
}

func UserFriendlyMustCompile(pattern string, attrName string, configComponent string) *regexp.Regexp {
	re, err := regexp.Compile(pattern)
	if err != nil {
		prnt.StderrLog.Fatalf("Error loading %s config attribute %s '%s': %v\n",
			configComponent, attrName, pattern, err)
	}
	return re
}

// May be called at any time (all dependencies are static)
func loadConfig() *Config {
	conf := &Config{}
	confFname := LoadConfigInto(conf)
	conf.ConfigFile = confFname

	comp := "main"
	for _, pat := range conf.AlwaysUninterestingLabelPatterns {
		re := UserFriendlyMustCompile(
			caseIgnore+pat, "AlwaysUninterestingLabelPatterns", comp)
		conf.AlwaysUninterLabelRegexps = append(conf.AlwaysUninterLabelRegexps, re)
	}

	for _, pat := range conf.UninterestingLabelPatterns {
		re := UserFriendlyMustCompile(caseIgnore+pat, "UninterestingLabelPatterns", comp)
		conf.UninterLabelRegexps = append(conf.UninterLabelRegexps, re)
	}

	for _, pat := range conf.InterestingLabelPatterns {
		re := UserFriendlyMustCompile(caseIgnore+pat, "InterestingLabelPatterns", comp)
		conf.InterLabelRegexps = append(conf.InterLabelRegexps, re)
	}
	return conf
}

var appConfig *Config

func AppConfig() *Config {
	if appConfig == nil {
		appConfig = loadConfig()
	}
	return appConfig
}
<|endoftext|>