1// Copyright 2018 The CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package strings implements simple functions to manipulate UTF-8 encoded
16// strings.package strings.
17//
18// Some of the functions in this package are specifically intended as field
19// constraints. For instance, MaxRunes as used in this CUE program
20//
21// import "strings"
22//
23// myString: strings.MaxRunes(5)
24//
25// specifies that the myString should be at most 5 code points.
26package strings
27
28import (
29 "fmt"
30 "strings"
31 "unicode"
32 "unicode/utf8"
33)
34
35// ByteAt reports the ith byte of the underlying strings or byte.
36func ByteAt(b []byte, i int) (byte, error) {
37 if i < 0 || i >= len(b) {
38 return 0, fmt.Errorf("index out of range")
39 }
40 return b[i], nil
41}
42
43// ByteSlice reports the bytes of the underlying string data from the start
44// index up to but not including the end index.
45func ByteSlice(b []byte, start, end int) ([]byte, error) {
46 if start < 0 || start > end || end > len(b) {
47 return nil, fmt.Errorf("index out of range")
48 }
49 return b[start:end], nil
50}
51
52// Runes returns the Unicode code points of the given string.
53func Runes(s string) []rune {
54 return []rune(s)
55}
56
57// MinRunes reports whether the number of runes (Unicode codepoints) in a string
58// is at least a certain minimum. MinRunes can be used a field constraint to
59// except all strings for which this property holds.
60func MinRunes(s string, min int) bool {
61 // TODO: CUE strings cannot be invalid UTF-8. In case this changes, we need
62 // to use the following conversion to count properly:
63 // s, _ = unicodeenc.UTF8.NewDecoder().String(s)
64 return utf8.RuneCountInString(s) >= min
65}
66
67// MaxRunes reports whether the number of runes (Unicode codepoints) in a string
68// exceeds a certain maximum. MaxRunes can be used a field constraint to
69// except all strings for which this property holds
70func MaxRunes(s string, max int) bool {
71 // See comment in MinRunes implementation.
72 return utf8.RuneCountInString(s) <= max
73}
74
75// ToTitle returns a copy of the string s with all Unicode letters that begin
76// words mapped to their title case.
77func ToTitle(s string) string {
78 // Use a closure here to remember state.
79 // Hackish but effective. Depends on Map scanning in order and calling
80 // the closure once per rune.
81 prev := ' '
82 return strings.Map(
83 func(r rune) rune {
84 if unicode.IsSpace(prev) {
85 prev = r
86 return unicode.ToTitle(r)
87 }
88 prev = r
89 return r
90 },
91 s)
92}
93
94// ToCamel returns a copy of the string s with all Unicode letters that begin
95// words mapped to lower case.
96func ToCamel(s string) string {
97 // Use a closure here to remember state.
98 // Hackish but effective. Depends on Map scanning in order and calling
99 // the closure once per rune.
100 prev := ' '
101 return strings.Map(
102 func(r rune) rune {
103 if unicode.IsSpace(prev) {
104 prev = r
105 return unicode.ToLower(r)
106 }
107 prev = r
108 return r
109 },
110 s)
111}
112
113// SliceRunes returns a string of the underlying string data from the start index
114// up to but not including the end index.
115func SliceRunes(s string, start, end int) (string, error) {
116 runes := []rune(s)
117 if start < 0 || start > end || end > len(runes) {
118 return "", fmt.Errorf("index out of range")
119 }
120 return string(runes[start:end]), nil
121}