kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/languageserver/pathmap/mapper.go (about) 1 /* 2 * Copyright 2017 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package pathmap provides utilities for matching and generating paths 18 // based on a pattern string. 19 package pathmap // import "kythe.io/kythe/go/languageserver/pathmap" 20 21 import ( 22 "fmt" 23 "net/url" 24 "path/filepath" 25 "regexp" 26 "strings" 27 ) 28 29 // Mapper provides path parsing and generating with named segments 30 // See NewMapper for details on construction 31 type Mapper struct { 32 // Regexp used for parsing strings to variables 33 re *regexp.Regexp 34 // Array of segments used for generating strings from variables 35 seg []segment 36 } 37 38 // NewMapper produces a Mapper object from a pattern string. 39 // Patterns strings are paths that have named segments that are extracted during 40 // parsing and populated during generation. 41 // Example: 42 // 43 // m := NewMapper("/dir/:segment/home/:rest*") 44 // s, err := m.Parse("/dir/foo/home/hello/world") // {"segment": "foo", "rest": "hello/world"}, nil 45 // p := m.Generate(s) // /dir/foo/home/hello/world 46 func NewMapper(pat string) (*Mapper, error) { 47 // All handling below assumes unix-style '/' separated paths 48 pat = filepath.ToSlash(pat) 49 50 // This is a sanity check to ensure that the path is not malformed. 51 // By prepending the / we stop url from getting confused if there's 52 // a leading colon 53 u, err := url.Parse("/" + pat) 54 if err != nil { 55 return nil, fmt.Errorf("pattern (%s) is not a valid path: '%v'", pat, err) 56 } 57 if u.EscapedPath() != u.Path { 58 return nil, fmt.Errorf("pattern (%s) is requires escaping", pat) 59 } 60 61 var ( 62 segments []segment 63 patReg = "^" 64 ) 65 66 for i, seg := range strings.Split(pat, "/") { 67 // A slash must precede any segment after the first 68 if i != 0 { 69 patReg += "/" 70 segments = append(segments, staticsegment("/")) 71 } 72 73 // If the segment is empty there's nothing else to do 74 if seg == "" { 75 continue 76 } 77 78 // If the segment starts with a ':' then it is a named segment 79 if seg[0] == ':' { 80 var segname string 81 if seg[len(seg)-1] == '*' { 82 segname = seg[1 : len(seg)-1] 83 patReg += fmt.Sprintf("(?P<%s>(?:[^/]+/)*(?:[^/]+))", segname) 84 } else { 85 segname = seg[1:] 86 patReg += fmt.Sprintf("(?P<%s>(?:[^/]+))", segname) 87 } 88 segments = append(segments, varsegment(segname)) 89 } else { 90 segments = append(segments, staticsegment(seg)) 91 patReg += regexp.QuoteMeta(seg) 92 } 93 } 94 patReg += "$" 95 96 re, err := regexp.Compile(patReg) 97 if err != nil { 98 return nil, fmt.Errorf("error compiling regex for pattern (%s):\nRegex: %s\nError: %v", pat, patReg, err) 99 } 100 101 return &Mapper{ 102 re: re, 103 seg: segments, 104 }, nil 105 } 106 107 // Parse extracts named segments from the path provided 108 // All segments must appear in the path 109 func (m Mapper) Parse(path string) (map[string]string, error) { 110 // The regex is based on unix-style paths so we need to convert 111 path = filepath.ToSlash(path) 112 113 match := m.re.FindStringSubmatch(path) 114 if len(match) == 0 { 115 return nil, fmt.Errorf("path (%s) did not match regex (%v)", path, m.re) 116 } 117 118 out := make(map[string]string) 119 // The first SubexpName is "" so we skip it 120 for i, v := range m.re.SubexpNames()[1:] { 121 // The first match is the full string so add 1 122 out[v] = match[i+1] 123 } 124 return out, nil 125 } 126 127 // Generate produces a path from a map of segment values. 128 // All required values must be present 129 func (m Mapper) Generate(vars map[string]string) (string, error) { 130 var gen string 131 for _, s := range m.seg { 132 seg, err := s.str(vars) 133 if err != nil { 134 return "", err 135 } 136 gen += seg 137 } 138 // Convert back to local path format at the end 139 return filepath.FromSlash(gen), nil 140 } 141 142 type segment interface { 143 str(map[string]string) (string, error) 144 } 145 146 type staticsegment string 147 148 func (s staticsegment) str(map[string]string) (string, error) { 149 return string(s), nil 150 } 151 152 type varsegment string 153 154 func (v varsegment) str(p map[string]string) (string, error) { 155 if s, ok := p[string(v)]; ok { 156 return s, nil 157 } 158 return "", fmt.Errorf("path generation failure. Missing key: %s", v) 159 }