// Copyright (c) 2013 Perforce Software. All rights reserved. // // Common utilities around the p4 API to make coding a bit easier package p4go import ( "bytes" "errors" "fmt" "regexp" "strings" ) var reNl *regexp.Regexp // Newline Regex var reKV *regexp.Regexp // Key-Value Regex func init() { reNl = regexp.MustCompile("\\n") reKV = regexp.MustCompile("(\\S+)\\s+(.*)") } // Converts tagged output to a sequence of string maps // // Uses the 'startKey' to indicate that we need to map to a new value. This // is needed otherwise you'll just get one item in your output map // // Warning: we can't easily differentiate between multiline values and key // values in return output. It's strongly recommended that your // multiline output not use newlines when possible. func mapListTagOutput(output string, startKey string) ([]map[string]string, error) { // Split into lines first lines := reNl.Split(output, -1) maps := make([]map[string]string, 0) bufKey := "" var buf bytes.Buffer var cur *map[string]string = nil for _, line := range lines { if reKV.MatchString(line) { submatches := reKV.FindAllStringSubmatch(line, -1) key := submatches[0][1] value := submatches[0][2] // note: no newline // If the key is new, we append the current value into the buffer if key == startKey && cur != nil { maps = append(maps, *cur) newMap := map[string]string{} cur = &newMap } else { // Make sure we have a current map that we'll use if cur == nil { newMap := map[string]string{} cur = &newMap } // Append any existing buffered key first then reset if bufKey != "" { (*cur)[bufKey] = buf.String() } } bufKey = key buf.Reset() buf.WriteString(value) } else { // Treat as multiline value for next string buf.WriteString("\n") buf.WriteString(line) } } if bufKey != "" { (*cur)[bufKey] = buf.String() } if cur != nil { maps = append(maps, *cur) } return maps, nil } func mapTagOutput(output string) (map[string]string, error) { // Split into lines first lines := reNl.Split(output, -1) mapped := map[string]string{} bufKey := "" var buf bytes.Buffer for _, line := range lines { if reKV.MatchString(line) { submatches := reKV.FindAllStringSubmatch(line, -1) key := submatches[0][1] value := submatches[0][2] // note: no newline // Append any existing buffered key first then reset if bufKey != "" { mapped[bufKey] = buf.String() } bufKey = key buf.Reset() buf.WriteString(value) } else { // Treat as multiline value for next string buf.WriteString("\n") buf.WriteString(line) } } if bufKey != "" { mapped[bufKey] = buf.String() } return mapped, nil } // Splits "//depot/... //client/..." into two array strings. // This will handle strings with quotes. // // If we can't map two strings, well, you're going to get an error. func splitMapping(mapStr string) ([]string, error) { left := bytes.Buffer{} right := bytes.Buffer{} var cur *bytes.Buffer = &left inQuote := false for _, ch := range mapStr { if ch == '"' { inQuote = !inQuote } else if ch == ' ' && !inQuote { cur = &right } else { cur.WriteRune(ch) } } if left.Len() == 0 || right.Len() == 0 { return nil, errors.New(fmt.Sprintf("Illegal mapping: %s", mapStr)) } return []string{left.String(), right.String()}, nil } // Appends values to a buffer in our common "input spec" format func appendSpecValues(buf *bytes.Buffer, values map[string]string) { for key, value := range values { if strings.ContainsRune(value, '\n') { // multi line mode buf.WriteString(key) buf.WriteString(":\n") lines := reNl.Split(value, -1) for _, line := range lines { buf.WriteRune('\t') buf.WriteString(line) buf.WriteRune('\n') } buf.WriteRune('\n') } else { // single line mode buf.WriteString(key) buf.WriteString(": ") buf.WriteString(value) buf.WriteString("\n\n") } } } func enquoteIfNeeded(pathStr string) string { buf := bytes.Buffer{} needsQuote := strings.ContainsAny(pathStr, " \t") && !strings.HasPrefix(pathStr, "\"") if needsQuote { buf.WriteRune('"') buf.WriteString(escapeQuotes(pathStr)) buf.WriteRune('"') return buf.String() } else { return pathStr } } func escapeQuotes(str string) string { buf := bytes.Buffer{} for _, ch := range str { if ch == '"' { buf.WriteString("\\\"") } else { buf.WriteRune(ch) } } return buf.String() }