Example #1
0
func main() {
	fmt.Println(strings.Index("Hello, world!", "He"))  // 0: He가 맨 처음에 있으므로 0
	fmt.Println(strings.Index("Hello, world!", "wor")) // 7: wor가 8번째에 있으므로 7
	fmt.Println(strings.Index("Hello, world!", "ow"))  // -1: ow는 없으므로 -1

	fmt.Println(strings.IndexAny("Hello, world!", "eo")) // 1: e가 2번째에 있으므로 1
	fmt.Println(strings.IndexAny("Hello, world!", "f"))  // -1: f는 없으므로 -1

	var c byte
	c = 'd'
	fmt.Println(strings.IndexByte("Hello, world!", c)) // 11: d가 12번째에 있으므로 11
	c = 'f'
	fmt.Println(strings.IndexByte("Hello, world!", c)) // -1: f는 없으므로 -1

	var r rune
	r = '언'
	fmt.Println(strings.IndexRune("고 언어", r)) // 4: "언"이 시작되는 인덱스가 4

	f := func(r rune) bool {
		return unicode.Is(unicode.Hangul, r) // r이 한글 유니코드이면 true를 리턴
	}
	fmt.Println(strings.IndexFunc("Go 언어", f))       // 3: 한글이 4번째부터 시작하므로 3
	fmt.Println(strings.IndexFunc("Go Language", f)) // -1: 한글이 없으므로 -1

	fmt.Println(strings.LastIndex("Hello Hello Hello, world!", "Hello"))
	// 12: 마지막 Hello가 13번째에 있으므로 12

	fmt.Println(strings.LastIndexAny("Hello, world", "ol")) // 10: 마지막 l이 11번째에 있으므로 10

	fmt.Println(strings.LastIndexFunc("Go 언어 안녕", f)) // 13: 마지막 한글인 '녕'이 시작되는 인덱스가 13
}
Example #2
0
func RemoveDateline(text string) string {
	found := false
	for {
		brk := strings.IndexFunc(text, unicode.IsSpace)
		if brk < 0 {
			return text
		}

		word := text[0:brk]
		if word != strings.ToUpper(word) {
			if !found {
				return removeDatelineAlternative(text)
			} else {
				return text
			}
		}

		text = text[brk:]
		found = true

		nxt := strings.IndexFunc(text, isNotSkipped)
		if nxt < 0 {
			return ""
		}
		text = text[nxt:]
	}
}
Example #3
0
func isSpell(str string) bool {
	//fmt.Printf("\tisSpell: %s\n", str)
	var pos = strings.IndexFunc(str, isVowel)
	if pos == -1 {
		return false
	}
	var next = strings.IndexFunc(str[pos+1:len(str)], isVowel)
	if next == -1 {
		return false
	}

	var end = pos + next + 2
	var first = str[pos:end]

	//fmt.Printf("\tfirst: %s\n", first)

	var posSecond = strings.LastIndex(str[end:len(str)], first)
	if posSecond == -1 {
		return isSpell(str[end-1 : len(str)])
	}

	var middle = str[end : end+posSecond]
	//fmt.Printf("\tmiddle: %s\n", middle)

	if len(middle) == 0 || strings.IndexFunc(middle, isVowel) == -1 {
		return isSpell(str[end-1 : len(str)])
	}

	return true
}
Example #4
0
// IndexFunc returns the index into s of the first Unicode code point satisfying f(c)
// or -1 if none do
func IndexFunc(s string, f func(rune) bool) int {
	function := func(c rune) bool {
		return unicode.Is(unicode.Han, c)
	}

	fmt.Println(strings.IndexFunc("Hello, 世界", function))    // 7
	fmt.Println(strings.IndexFunc("Hello, world", function)) // -1
	return strings.IndexFunc(s, f)
}
Example #5
0
func ExampleIndexFunc() {
	f := func(c rune) bool {
		return unicode.Is(unicode.Han, c)
	}
	fmt.Println(strings.IndexFunc("Hello, 世界", f))
	fmt.Println(strings.IndexFunc("Hello, world", f))
	// Output:
	// 7
	// -1
}
Example #6
0
func (s *Server) sweep() {
	log.Printf("Performing sweep for old files")
	now := time.Now()

	accountsPath := filepath.Join(s.baseDirectory, "accounts")
	accountsDir, err := os.Open(accountsPath)
	if err != nil {
		log.Printf("Failed to open %s: %s", accountsPath, err)
		return
	}
	defer accountsDir.Close()

	ents, err := accountsDir.Readdir(0)
	if err != nil {
		log.Printf("Failed to read %s: %s", accountsPath, err)
		return
	}

	for _, ent := range ents {
		name := ent.Name()
		if len(name) != 64 || strings.IndexFunc(name, notLowercaseHex) != -1 {
			continue
		}

		filesPath := filepath.Join(accountsPath, name, "files")
		filesDir, err := os.Open(filesPath)
		if os.IsNotExist(err) {
			continue
		} else if err != nil {
			log.Printf("Failed to open %s: %s", filesPath, err)
			continue
		}

		filesEnts, err := filesDir.Readdir(0)
		if err == nil {
			for _, fileEnt := range filesEnts {
				name := fileEnt.Name()
				if len(name) > 0 && strings.IndexFunc(name, notLowercaseHex) == -1 {
					mtime := fileEnt.ModTime()
					if now.After(mtime) && now.Sub(mtime) > fileLifetime {
						if err := os.Remove(filepath.Join(filesPath, name)); err != nil {
							log.Printf("Failed to delete file: %s", err)
						}
					}
				}
			}
		} else {
			log.Printf("Failed to read %s: %s", filesPath, err)
		}

		filesDir.Close()
	}
}
Example #7
0
func checkName(s string) error {
	if len(s) == 0 {
		return &ErrInvalidName{"not be empty"}
	}
	if strings.IndexFunc(s[:1], isRuneInvalidForFirstCharacter) != -1 {
		return &ErrInvalidName{"start with [A-Za-z_]"}
	}
	if strings.IndexFunc(s[1:], isRuneInvalidForOtherCharacters) != -1 {
		return &ErrInvalidName{"have second and remaining characters contain only [A-Za-z0-9_]"}
	}
	return nil
}
Example #8
0
// Parse returns a Version struct filled with the epoch, version and revision
// specified in input. It verifies the version string as a whole, just like
// dpkg(1), and even returns roughly the same error messages.
func Parse(input string) (Version, error) {
	result := Version{}
	trimmed := strings.TrimSpace(input)
	if trimmed == "" {
		return result, fmt.Errorf("version string is empty")
	}

	if strings.IndexFunc(trimmed, unicode.IsSpace) != -1 {
		return result, fmt.Errorf("version string has embedded spaces")
	}

	colon := strings.Index(trimmed, ":")
	if colon != -1 {
		epoch, err := strconv.ParseInt(trimmed[:colon], 10, 64)
		if err != nil {
			return result, fmt.Errorf("epoch: %v", err)
		}
		if epoch < 0 {
			return result, fmt.Errorf("epoch in version is negative")
		}
		result.Epoch = uint(epoch)
	}

	result.Version = trimmed[colon+1:]
	if len(result.Version) == 0 {
		return result, fmt.Errorf("nothing after colon in version number")
	}
	if hyphen := strings.LastIndex(result.Version, "-"); hyphen != -1 {
		result.Revision = result.Version[hyphen+1:]
		result.Version = result.Version[:hyphen]
	}

	if len(result.Version) > 0 && !unicode.IsDigit(rune(result.Version[0])) {
		return result, fmt.Errorf("version number does not start with digit")
	}

	if strings.IndexFunc(result.Version, func(c rune) bool {
		return !cisdigit(c) && !cisalpha(c) && c != '.' && c != '-' && c != '+' && c != '~' && c != ':'
	}) != -1 {
		return result, fmt.Errorf("invalid character in version number")
	}

	if strings.IndexFunc(result.Revision, func(c rune) bool {
		return !cisdigit(c) && !cisalpha(c) && c != '.' && c != '+' && c != '~'
	}) != -1 {
		return result, fmt.Errorf("invalid character in revision number")
	}

	return result, nil
}
Example #9
0
// maybeConvertMessagesToNewFormat scans the accounts directory for messages
// under the old naming scheme and updates them to use the new
// naming scheme that includes millisecond delivery time at the beginning.
func maybeConvertMessagesToNewFormat(baseDirectory string) error {
	accountsPath := filepath.Join(baseDirectory, "accounts")
	accountsDir, err := os.Open(accountsPath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}
	defer accountsDir.Close()

	accounts, err := accountsDir.Readdir(0)
	if err != nil {
		return err
	}

	for _, ent := range accounts {
		account := ent.Name()
		if len(account) != 64 || strings.IndexFunc(account, notLowercaseHex) != -1 {
			continue
		}

		accountPath := filepath.Join(accountsPath, account)
		accountDir, err := os.Open(accountPath)
		if err != nil {
			return err
		}
		ents, err := accountDir.Readdir(0)
		accountDir.Close()
		if err != nil {
			return err
		}

		for _, ent := range ents {
			name := ent.Name()
			if len(name) != 64 || strings.IndexFunc(name, notLowercaseHex) != -1 {
				continue
			}

			oldName := filepath.Join(accountPath, name)
			newName := filepath.Join(accountPath, timeToFilenamePrefix(ent.ModTime())+name)
			if err := os.Rename(oldName, newName); err != nil {
				return err
			}
		}
	}

	return nil
}
Example #10
0
func (f *fields) next() string {
	notIsSpace := func(r rune) bool { return !unicode.IsSpace(r) }

	l := f.line[f.pos:]
	i := strings.IndexFunc(l, notIsSpace)
	if i < 0 {
		return ""
	}
	j := i + strings.IndexFunc(l[i:], unicode.IsSpace)
	if j < i {
		j = len(l)
	}
	f.pos += j
	return l[i:j]
}
Example #11
0
File: main.go Project: uriel/hk
func put(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	q := r.URL.Query()
	plat := q.Get(":plat")
	cmd := q.Get(":cmd")
	ver := q.Get(":ver")
	if strings.IndexFunc(plat, badIdentRune) >= 0 ||
		strings.IndexFunc(cmd, badIdentRune) >= 0 ||
		strings.IndexFunc(ver, badVersionRune) >= 0 {
		http.Error(w, "bad character in path", 400)
		return
	}

	body, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, 10e6))
	if err != nil && err.Error() == "http: request body too large" {
		http.Error(w, "too big", 413)
		return
	}
	if err != nil {
		log.Println(err)
		http.Error(w, "internal error", 500)
		return
	}

	var buf bytes.Buffer
	gz, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
	gz.Name = cmd + "-" + ver
	gz.Write(body)
	gz.Close()
	sha1, err := s3put(buf.Bytes(), gz.Name+".gz")
	if err != nil {
		log.Println(err)
		w.WriteHeader(500)
		return
	}

	_, err = db.Exec(`
		insert into release (plat, cmd, ver, sha1)
		values ($1, $2, $3, $4)
	`, plat, cmd, ver, sha1)
	if err != nil {
		log.Println(err)
		http.Error(w, "internal error", 500)
		return
	}
	w.WriteHeader(201)
	w.Write([]byte("created\n"))
}
Example #12
0
func moveDotRightWord(ed *Editor) {
	// Move to first space
	p := strings.IndexFunc(ed.line[ed.dot:], unicode.IsSpace)
	if p == -1 {
		ed.dot = len(ed.line)
		return
	}
	ed.dot += p
	// Move to first nonspace
	p = strings.IndexFunc(ed.line[ed.dot:], notSpace)
	if p == -1 {
		ed.dot = len(ed.line)
		return
	}
	ed.dot += p
}
Example #13
0
func htmlMessage(line weechat.LineData) template.HTML {
	if !strings.Contains(line.Message, "://") {
		// fast path.
		return template.HTML(template.HTMLEscapeString(line.Message))
	}
	buf := new(bytes.Buffer)
	for msg := line.Message; len(msg) > 0; {
		idx := strings.Index(msg, "://")
		switch {
		case idx >= 4 && msg[idx-4:idx] == "http":
			buf.WriteString(msg[:idx-4])
			msg = msg[idx-4:]
		case idx >= 5 && msg[idx-5:idx] == "https":
			buf.WriteString(msg[:idx-5])
			msg = msg[idx-5:]
		default:
			buf.WriteString(msg)
			msg = ""
			continue
		}
		space := strings.IndexFunc(msg, unicode.IsSpace)
		if space < 0 {
			space = len(msg)
		}
		u := msg[:space]
		msg = msg[space:]
		if _, err := url.Parse(u); err == nil {
			fmt.Fprintf(buf, `<a href="%s">%s</a>`, u, u)
		} else {
			buf.WriteString(u)
		}
	}
	return template.HTML(buf.String())
}
Example #14
0
func FirstWord(s string) string {
	i := strings.IndexFunc(s, func(r rune) bool { return !unicode.IsLetter(r) })
	if i != -1 {
		return s[0:i]
	}
	return s
}
Example #15
0
func mustBuild() (ver string) {
	tag := string(bytes.TrimSpace(mustCmd("git", "describe")))
	if tag[0] != 'v' {
		log.Fatal("bad tag name: ", tag)
	}
	ver = tag[1:]
	if strings.IndexFunc(ver, badVersionRune) >= 0 {
		log.Fatal("bad tag name: ", tag)
	}
	// TODO(kr): verify signature
	url := distURL + buildName + "-" + ver + "-" + buildPlat + ".json"
	if _, err := fetchBytes(url); err == nil {
		log.Fatal("already built: ", ver)
	}

	f, err := os.Create("relver.go")
	if err != nil {
		log.Fatal(err)
	}
	_, err = fmt.Fprintf(f, relverGo, ver)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("go build -tags release -o " + buildName)
	cmd := exec.Command("go", "build", "-tags", "release", "-o", buildName)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err != nil {
		log.Fatal("go build -tags release: ", err)
	}
	return ver
}
func (s *sysutilProviderLinux) ListDevices() ([]string, error) {
	result := []string{}

	f, err := os.Open(s.proc_path + "/partitions")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	scan := bufio.NewScanner(f)
	for scan.Scan() {
		if len(scan.Text()) < 1 {
			continue
		}

		table := strings.Fields(scan.Text())
		if table[0] == "8" && strings.IndexFunc(table[3], unicode.IsDigit) < 0 {
			result = append(result, table[3])
		}

	}

	return result, nil

}
Example #17
0
// Word returns a string with the word of the view's internal buffer
// at the position corresponding to the point (x, y).
func (v *View) Word(x, y int) (string, error) {
	x, y, err := v.realPosition(x, y)
	if err != nil {
		return "", err
	}

	if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
		return "", errors.New("invalid point")
	}

	str := lineType(v.lines[y]).String()

	nl := strings.LastIndexFunc(str[:x], indexFunc)
	if nl == -1 {
		nl = 0
	} else {
		nl = nl + 1
	}
	nr := strings.IndexFunc(str[x:], indexFunc)
	if nr == -1 {
		nr = len(str)
	} else {
		nr = nr + x
	}
	return string(str[nl:nr]), nil
}
Example #18
0
// splitComparatorVersion splits the comparator from the version.
// Spaces between the comparator and the version are not allowed.
// Input must be free of leading or trailing spaces.
func splitComparatorVersion(s string) (string, string, error) {
	i := strings.IndexFunc(s, unicode.IsDigit)
	if i == -1 {
		return "", "", fmt.Errorf("Could not get version from string: %q", s)
	}
	return strings.TrimSpace(s[0:i]), s[i:], nil
}
Example #19
0
func init() {
	rawPath := getRawPath()

	// If there are no separators in rawPath, we've presumably been invoked from the path
	// and should qualify the path accordingly.
	idx := strings.IndexFunc(rawPath, func(r rune) bool {
		return r == '/' || r == filepath.Separator
	})
	if idx < 0 {
		abs, err := exec.LookPath(rawPath)
		if err != nil {
			return
		}

		Abs = abs
	} else {
		abs, err := filepath.Abs(rawPath)
		if err != nil {
			return
		}

		Abs = abs
	}

	initProgramName()
}
Example #20
0
// SplitCapitalize splits a string by its uppercase letters and capitalize the
// first letter of the string
func SplitCapitalize(name string) string {
	if val, ok := splitCapitalizeExceptions[strings.ToLower(name)]; ok {
		return val
	}

	var words []string
	l := 0
	for s := name; s != ""; s = s[l:] {
		l = strings.IndexFunc(s[1:], unicode.IsUpper) + 1
		if l <= 0 {
			l = len(s)
		}
		words = append(words, s[:l])
	}

	name = ""

	for _, element := range words {
		name += element + " "
	}

	name = strings.ToLower(name[:len(name)-1])
	name = strings.ToUpper(string(name[0])) + name[1:]

	return name
}
Example #21
0
// ParseSize parses the string as a size, in mebibytes.
//
// The string must be a is a non-negative number with
// an optional multiplier suffix (M, G, T, P, E, Z, or Y).
// If the suffix is not specified, "M" is implied.
func ParseSize(str string) (MB uint64, err error) {
	// Find the first non-digit/period:
	i := strings.IndexFunc(str, func(r rune) bool {
		return r != '.' && !unicode.IsDigit(r)
	})
	var multiplier float64 = 1
	if i > 0 {
		suffix := str[i:]
		multiplier = 0
		for j := 0; j < len(sizeSuffixes); j++ {
			base := string(sizeSuffixes[j])
			// M, MB, or MiB are all valid.
			switch suffix {
			case base, base + "B", base + "iB":
				multiplier = float64(sizeSuffixMultiplier(j))
				break
			}
		}
		if multiplier == 0 {
			return 0, errors.Errorf("invalid multiplier suffix %q, expected one of %s", suffix, []byte(sizeSuffixes))
		}
		str = str[:i]
	}

	val, err := strconv.ParseFloat(str, 64)
	if err != nil || val < 0 {
		return 0, errors.Errorf("expected a non-negative number, got %q", str)
	}
	val *= multiplier
	return uint64(math.Ceil(val)), nil
}
Example #22
0
func (t *CSVTrans) formatLine(lineStr *string, dict map[string]core.Value, nilMap map[string]int) {

	arr := strings.Split(*lineStr, ",")
	needReplace := false
	for idx, str := range arr {

		if strings.IndexFunc(str, matchF) == -1 {
			continue
		}

		if value, ok := dict[str]; ok {

			arr[idx] = value.ToString()
			needReplace = true
		} else {

			if _, ok := nilMap[str]; !ok {

				nilMap[str] = 0
			}
		}

	}

	if needReplace {
		*lineStr = strings.Join(arr, ",")
	}

}
Example #23
0
func TestRFC952(t *testing.T) {
	t.Parallel()

	testFunc(t, "RFC952", RFC952, cases{
		"4abc123":                                   "abc123",
		"-4abc123":                                  "abc123",
		"fd%gsf---gs7-f$gs--d7fddg-123":             "fdgsf---gs7-fgs--d7fddg1",
		"89fdgsf---gs7-fgs--d7fddg-123":             "fdgsf---gs7-fgs--d7fddg1",
		"89fdgsf---gs7-fgs--d7fddg---123":           "fdgsf---gs7-fgs--d7fddg1",
		"89fdgsf---gs7-fgs--d7fddg-":                "fdgsf---gs7-fgs--d7fddg",
		"chronos with a space AND MIXED CASE-2.0.1": "chronoswithaspaceandmixe",
		"chronos with a space AND----------MIXED--": "chronoswithaspaceandmixe",
	})

	quickCheckFunc(t, "RFC952", RFC952, cases{
		"doesn't start with numbers or dashes": func(s string) bool {
			return 0 != strings.IndexFunc(RFC952(s), func(r rune) bool {
				return r == '-' || (r >= '0' && r <= '9')
			})
		},
		"isn't longer than 24 chars": func(s string) bool {
			return len(RFC952(s)) <= 24
		},
	})
}
Example #24
0
func (tool *ClassTool) pushLine(line []byte) {

	lineStr := strings.TrimSpace(string(line))
	switch {
	case strings.HasPrefix(lineStr, "/*") || tool._hide:
		tool._hide = !strings.HasSuffix(lineStr, "*/")
		return
	case classB(lineStr):
		return
	}

	formatB := lineStr[strings.Index(lineStr, "\"")+1:]
	arrStr := strings.Split(formatB, "\"")

	m := tool._focus._dict
	idx := 0
	for _, str := range arrStr {

		if idx%2 == 0 && strings.IndexFunc(str, unicodeF) != -1 && !classBEx(str) {

			item := tool._dict._dict[str]
			if item == nil {

				tool._dict._dict[str] = nil
			} else {

				m[str] = item
			}
		}
		idx++
	}

}
Example #25
0
// baseで指定した数値をもとに、sで記述された値をパースし返します。
// baseを使うのは、s内で単位として"%"が使われた場合のみです。
// 単位に"px"が使われた場合と単位がない場合は、
// 単位を省いた数字の部分を数値に変換して返します。
func parseRelSize(base int, s string) (int, error) {
	i := strings.IndexFunc(s, func(c rune) bool {
		// TODO: cが数字の場合はfalse、そうでない場合はtrueを返す。
		// なお、iにはここがtrueになった箇所(インデックス)が入る。
		// ヒント:unicodeパッケージのドキュメントを見てみよう。
		return !unicode.IsNumber(c)
	})

	// TODO: 数字のみだった場合は、単位なしの数値のみとし、
	// sをint型に変換して返す。
	// ヒント:stringsパッケージのドキュメントを見て、struct.IndexFuncの戻り値を調べよう。
	if i < 0 {
		return strconv.Atoi(s)
	}

	// TODO:sのうち、数字だけの部分をint型に変換する。
	v, err := strconv.Atoi(s[:i])
	if err != nil {
		return 0, ErrInvalidSize
	}

	switch s[i:] {
	// TODO: "%"が指定された場合は、baseを100%として値を計算する。
	case "%":
		return int(float64(base) * float64(v) / 100), nil
	case "px":
		return v, nil
	default:
		// TODO: "%"と"px"以外の単位が指定された場合は、ErrUnkownUnitエラーを返す。
		return 0, ErrUnkownUnit
	}
}
Example #26
0
func Join(args []string) string {
	if 1 == len(args) {
		return args[0]
	} else {
		var buf bytes.Buffer
		for idx, word := range args {
			if 0 != idx {
				buf.WriteString(" ")
			}

			if strings.Contains(word, "\"") {
				buf.WriteString("\"")
				EscapeTo(word, &buf, true)
				buf.WriteString("\"")
			} else if p := strings.IndexFunc(word, unicode.IsSpace); p >= 0 {
				buf.WriteString("\"")
				buf.WriteString(word)
				buf.WriteString("\"")
			} else {
				buf.WriteString(word)
			}
		}

		return buf.String()
	}
}
Example #27
0
func (extract *TLDExtract) Extract(u string) *Result {
	input := u
	u = strings.ToLower(u)
	u = schemaregex.ReplaceAllString(u, "")
	i := strings.Index(u, "@")
	if i != -1 {
		u = u[i+1:]
	}

	index := strings.IndexFunc(u, func(r rune) bool {
		switch r {
		case '&', '/', '?', ':', '#':
			return true
		}
		return false
	})
	if index != -1 {
		u = u[0:index]
	}

	if strings.HasSuffix(u, ".html") {
		u = u[0 : len(u)-len(".html")]
	}
	if extract.debug {
		fmt.Printf("%s;%s\n", u, input)
	}
	return extract.extract(u)
}
Example #28
0
File: encode.go Project: qband/down
func writeStringKey(w io.Writer, key string) error {
	if len(key) == 0 || strings.IndexFunc(key, invalidKeyRune) != -1 {
		return ErrInvalidKey
	}
	_, err := io.WriteString(w, key)
	return err
}
Example #29
0
func initWords() (err error) {
	fh, err := os.Open(wordFile)
	if err != nil {
		return err
	}
	defer fh.Close()
	bufreader := bufio.NewReader(fh)
	words = make([]string, 0)

	var line []byte
	for {
		line, _, err = bufreader.ReadLine()
		if err != nil {
			if err == io.EOF {
				break
			} else {
				return err
			}
		}
		thisline := string(line)
		if -1 != strings.IndexFunc(thisline, NonLetter) {
			continue
		}
		words = append(words, thisline)
	}

	return nil
}
Example #30
0
File: volumes.go Project: bac/juju
func newStorageConfig(attrs map[string]interface{}) (*storageConfig, error) {
	out, err := storageConfigChecker.Coerce(attrs, nil)
	if err != nil {
		return nil, errors.Annotate(err, "validating MAAS storage config")
	}
	coerced := out.(map[string]interface{})
	var tags []string
	switch v := coerced[tagsAttribute].(type) {
	case []string:
		tags = v
	case string:
		fields := strings.Split(v, ",")
		for _, f := range fields {
			f = strings.TrimSpace(f)
			if len(f) == 0 {
				continue
			}
			if i := strings.IndexFunc(f, unicode.IsSpace); i >= 0 {
				return nil, errors.Errorf("tags may not contain whitespace: %q", f)
			}
			tags = append(tags, f)
		}
	}
	return &storageConfig{tags: tags}, nil
}