strutil.go (19778B)
1 // Copyright (c) 2014 The sortutil Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package strutil collects utils supplemental to the standard strings package. 6 package strutil // import "modernc.org/strutil" 7 8 import ( 9 "bytes" 10 "encoding/base32" 11 "encoding/base64" 12 "fmt" 13 "io" 14 "os" 15 "path/filepath" 16 "reflect" 17 "runtime" 18 "sort" 19 "strconv" 20 "strings" 21 "sync" 22 ) 23 24 // Base32ExtDecode decodes base32 extended (RFC 4648) text to binary data. 25 func Base32ExtDecode(text []byte) (data []byte, err error) { 26 n := base32.HexEncoding.DecodedLen(len(text)) 27 data = make([]byte, n) 28 decoder := base32.NewDecoder(base32.HexEncoding, bytes.NewBuffer(text)) 29 if n, err = decoder.Read(data); err != nil { 30 n = 0 31 } 32 data = data[:n] 33 return 34 } 35 36 // Base32ExtEncode encodes binary data to base32 extended (RFC 4648) encoded text. 37 func Base32ExtEncode(data []byte) (text []byte) { 38 n := base32.HexEncoding.EncodedLen(len(data)) 39 buf := bytes.NewBuffer(make([]byte, 0, n)) 40 encoder := base32.NewEncoder(base32.HexEncoding, buf) 41 encoder.Write(data) 42 encoder.Close() 43 if buf.Len() != n { 44 panic("internal error") 45 } 46 return buf.Bytes() 47 } 48 49 // Base64Decode decodes base64 text to binary data. 50 func Base64Decode(text []byte) (data []byte, err error) { 51 n := base64.StdEncoding.DecodedLen(len(text)) 52 data = make([]byte, n) 53 decoder := base64.NewDecoder(base64.StdEncoding, bytes.NewBuffer(text)) 54 if n, err = decoder.Read(data); err != nil { 55 n = 0 56 } 57 data = data[:n] 58 return 59 } 60 61 // Base64Encode encodes binary data to base64 encoded text. 62 func Base64Encode(data []byte) (text []byte) { 63 n := base64.StdEncoding.EncodedLen(len(data)) 64 buf := bytes.NewBuffer(make([]byte, 0, n)) 65 encoder := base64.NewEncoder(base64.StdEncoding, buf) 66 encoder.Write(data) 67 encoder.Close() 68 if buf.Len() != n { 69 panic("internal error") 70 } 71 return buf.Bytes() 72 } 73 74 // Formatter is an io.Writer extended by a fmt.Printf like function Format 75 type Formatter interface { 76 io.Writer 77 Format(format string, args ...interface{}) (n int, errno error) 78 } 79 80 type indentFormatter struct { 81 io.Writer 82 indent []byte 83 indentLevel int 84 state int 85 } 86 87 const ( 88 st0 = iota 89 stBOL 90 stPERC 91 stBOLPERC 92 ) 93 94 // IndentFormatter returns a new Formatter which interprets %i and %u in the 95 // Format() format string as indent and undent commands. The commands can 96 // nest. The Formatter writes to io.Writer 'w' and inserts one 'indent' 97 // string per current indent level value. 98 // Behaviour of commands reaching negative indent levels is undefined. 99 // IndentFormatter(os.Stdout, "\t").Format("abc%d%%e%i\nx\ny\n%uz\n", 3) 100 // output: 101 // abc3%e 102 // x 103 // y 104 // z 105 // The Go quoted string literal form of the above is: 106 // "abc%%e\n\tx\n\tx\nz\n" 107 // The commands can be scattered between separate invocations of Format(), 108 // i.e. the formatter keeps track of the indent level and knows if it is 109 // positioned on start of a line and should emit indentation(s). 110 // The same output as above can be produced by e.g.: 111 // f := IndentFormatter(os.Stdout, " ") 112 // f.Format("abc%d%%e%i\nx\n", 3) 113 // f.Format("y\n%uz\n") 114 func IndentFormatter(w io.Writer, indent string) Formatter { 115 return &indentFormatter{w, []byte(indent), 0, stBOL} 116 } 117 118 func (f *indentFormatter) format(flat bool, format string, args ...interface{}) (n int, errno error) { 119 buf := []byte{} 120 for i := 0; i < len(format); i++ { 121 c := format[i] 122 switch f.state { 123 case st0: 124 switch c { 125 case '\n': 126 cc := c 127 if flat && f.indentLevel != 0 { 128 cc = ' ' 129 } 130 buf = append(buf, cc) 131 f.state = stBOL 132 case '%': 133 f.state = stPERC 134 default: 135 buf = append(buf, c) 136 } 137 case stBOL: 138 switch c { 139 case '\n': 140 cc := c 141 if flat && f.indentLevel != 0 { 142 cc = ' ' 143 } 144 buf = append(buf, cc) 145 case '%': 146 f.state = stBOLPERC 147 default: 148 if !flat { 149 for i := 0; i < f.indentLevel; i++ { 150 buf = append(buf, f.indent...) 151 } 152 } 153 buf = append(buf, c) 154 f.state = st0 155 } 156 case stBOLPERC: 157 switch c { 158 case 'i': 159 f.indentLevel++ 160 f.state = stBOL 161 case 'u': 162 f.indentLevel-- 163 f.state = stBOL 164 default: 165 if !flat { 166 for i := 0; i < f.indentLevel; i++ { 167 buf = append(buf, f.indent...) 168 } 169 } 170 buf = append(buf, '%', c) 171 f.state = st0 172 } 173 case stPERC: 174 switch c { 175 case 'i': 176 f.indentLevel++ 177 f.state = st0 178 case 'u': 179 f.indentLevel-- 180 f.state = st0 181 default: 182 buf = append(buf, '%', c) 183 f.state = st0 184 } 185 default: 186 panic("unexpected state") 187 } 188 } 189 switch f.state { 190 case stPERC, stBOLPERC: 191 buf = append(buf, '%') 192 } 193 return f.Write([]byte(fmt.Sprintf(string(buf), args...))) 194 } 195 196 func (f *indentFormatter) Format(format string, args ...interface{}) (n int, errno error) { 197 return f.format(false, format, args...) 198 } 199 200 type flatFormatter indentFormatter 201 202 // FlatFormatter returns a newly created Formatter with the same functionality as the one returned 203 // by IndentFormatter except it allows a newline in the 'format' string argument of Format 204 // to pass through iff indent level is currently zero. 205 // 206 // If indent level is non-zero then such new lines are changed to a space character. 207 // There is no indent string, the %i and %u format verbs are used solely to determine the indent level. 208 // 209 // The FlatFormatter is intended for flattening of normally nested structure textual representation to 210 // a one top level structure per line form. 211 // FlatFormatter(os.Stdout, " ").Format("abc%d%%e%i\nx\ny\n%uz\n", 3) 212 // output in the form of a Go quoted string literal: 213 // "abc3%%e x y z\n" 214 func FlatFormatter(w io.Writer) Formatter { 215 return (*flatFormatter)(IndentFormatter(w, "").(*indentFormatter)) 216 } 217 218 func (f *flatFormatter) Format(format string, args ...interface{}) (n int, errno error) { 219 return (*indentFormatter)(f).format(true, format, args...) 220 } 221 222 // Pool handles aligning of strings having equal values to the same string instance. 223 // Intended use is to conserve some memory e.g. where a large number of identically valued strings 224 // with non identical backing arrays may exists in several semantically distinct instances of some structs. 225 // Pool is *not* concurrent access safe. It doesn't handle common prefix/suffix aligning, 226 // e.g. having s1 == "abc" and s2 == "bc", s2 is not automatically aligned as s1[1:]. 227 type Pool struct { 228 pool map[string]string 229 } 230 231 // NewPool returns a newly created Pool. 232 func NewPool() *Pool { 233 return &Pool{map[string]string{}} 234 } 235 236 // Align returns a string with the same value as its argument. It guarantees that 237 // all aligned strings share a single instance in memory. 238 func (p *Pool) Align(s string) string { 239 if a, ok := p.pool[s]; ok { 240 return a 241 } 242 243 s = StrPack(s) 244 p.pool[s] = s 245 return s 246 } 247 248 // Count returns the number of items in the pool. 249 func (p *Pool) Count() int { 250 return len(p.pool) 251 } 252 253 // GoPool is a concurrent access safe version of Pool. 254 type GoPool struct { 255 pool map[string]string 256 rwm *sync.RWMutex 257 } 258 259 // NewGoPool returns a newly created GoPool. 260 func NewGoPool() (p *GoPool) { 261 return &GoPool{map[string]string{}, &sync.RWMutex{}} 262 } 263 264 // Align returns a string with the same value as its argument. It guarantees that 265 // all aligned strings share a single instance in memory. 266 func (p *GoPool) Align(s string) (y string) { 267 if s != "" { 268 p.rwm.RLock() // R++ 269 if a, ok := p.pool[s]; ok { // found 270 p.rwm.RUnlock() // R-- 271 return a 272 } 273 274 p.rwm.RUnlock() // R-- 275 // not found but with a race condition, retry within a write lock 276 p.rwm.Lock() // W++ 277 defer p.rwm.Unlock() // W-- 278 if a, ok := p.pool[s]; ok { // done in a race 279 return a 280 } 281 282 // we won 283 s = StrPack(s) 284 p.pool[s] = s 285 return s 286 } 287 288 return 289 } 290 291 // Count returns the number of items in the pool. 292 func (p *GoPool) Count() int { 293 return len(p.pool) 294 } 295 296 // Dict is a string <-> id bijection. Dict is *not* concurrent access safe for assigning new ids 297 // to strings not yet contained in the bijection. 298 // Id for an empty string is guaranteed to be 0, 299 // thus Id for any non empty string is guaranteed to be non zero. 300 type Dict struct { 301 si map[string]int 302 is []string 303 } 304 305 // NewDict returns a newly created Dict. 306 func NewDict() (d *Dict) { 307 d = &Dict{map[string]int{}, []string{}} 308 d.Id("") 309 return 310 } 311 312 // Count returns the number of items in the dict. 313 func (d *Dict) Count() int { 314 return len(d.is) 315 } 316 317 // Id maps string s to its numeric identificator. 318 func (d *Dict) Id(s string) (y int) { 319 if y, ok := d.si[s]; ok { 320 return y 321 } 322 323 s = StrPack(s) 324 y = len(d.is) 325 d.si[s] = y 326 d.is = append(d.is, s) 327 return 328 } 329 330 // S maps an id to its string value and ok == true. Id values not contained in the bijection 331 // return "", false. 332 func (d *Dict) S(id int) (s string, ok bool) { 333 if id >= len(d.is) { 334 return "", false 335 } 336 return d.is[id], true 337 } 338 339 // GoDict is a concurrent access safe version of Dict. 340 type GoDict struct { 341 si map[string]int 342 is []string 343 rwm *sync.RWMutex 344 } 345 346 // NewGoDict returns a newly created GoDict. 347 func NewGoDict() (d *GoDict) { 348 d = &GoDict{map[string]int{}, []string{}, &sync.RWMutex{}} 349 d.Id("") 350 return 351 } 352 353 // Count returns the number of items in the dict. 354 func (d *GoDict) Count() int { 355 return len(d.is) 356 } 357 358 // Id maps string s to its numeric identificator. The implementation honors getting 359 // an existing id at the cost of assigning a new one. 360 func (d *GoDict) Id(s string) (y int) { 361 d.rwm.RLock() // R++ 362 if y, ok := d.si[s]; ok { // found 363 d.rwm.RUnlock() // R-- 364 return y 365 } 366 367 d.rwm.RUnlock() // R-- 368 369 // not found but with a race condition 370 d.rwm.Lock() // W++ recheck with write lock 371 defer d.rwm.Unlock() // W-- 372 if y, ok := d.si[s]; ok { // some other goroutine won already 373 return y 374 } 375 376 // a race free not found state => insert the string 377 s = StrPack(s) 378 y = len(d.is) 379 d.si[s] = y 380 d.is = append(d.is, s) 381 return 382 } 383 384 // S maps an id to its string value and ok == true. Id values not contained in the bijection 385 // return "", false. 386 func (d *GoDict) S(id int) (s string, ok bool) { 387 d.rwm.RLock() // R++ 388 defer d.rwm.RUnlock() // R-- 389 if id >= len(d.is) { 390 return "", false 391 } 392 return d.is[id], true 393 } 394 395 // StrPack returns a new instance of s which is tightly packed in memory. 396 // It is intended for avoiding the situation where having a live reference 397 // to a string slice over an unreferenced biger underlying string keeps the biger one 398 // in memory anyway - it can't be GCed. 399 func StrPack(s string) string { 400 return string([]byte(s)) // T(U(T)) intentional. 401 } 402 403 // JoinFields returns strings in flds joined by sep. Flds may contain arbitrary 404 // bytes, including the sep as they are safely escaped. JoinFields panics if 405 // sep is the backslash character or if len(sep) != 1. 406 func JoinFields(flds []string, sep string) string { 407 if len(sep) != 1 || sep == "\\" { 408 panic("invalid separator") 409 } 410 411 a := make([]string, len(flds)) 412 for i, v := range flds { 413 v = strings.Replace(v, "\\", "\\0", -1) 414 a[i] = strings.Replace(v, sep, "\\1", -1) 415 } 416 return strings.Join(a, sep) 417 } 418 419 // SplitFields splits s, which must be produced by JoinFields using the same 420 // sep, into flds. SplitFields panics if sep is the backslash character or if 421 // len(sep) != 1. 422 func SplitFields(s, sep string) (flds []string) { 423 if len(sep) != 1 || sep == "\\" { 424 panic("invalid separator") 425 } 426 427 a := strings.Split(s, sep) 428 r := make([]string, len(a)) 429 for i, v := range a { 430 v = strings.Replace(v, "\\1", sep, -1) 431 r[i] = strings.Replace(v, "\\0", "\\", -1) 432 } 433 return r 434 } 435 436 // PrettyPrintHooks allow to customize the result of PrettyPrint for types 437 // listed in the map value. 438 type PrettyPrintHooks map[reflect.Type]func(f Formatter, v interface{}, prefix, suffix string) 439 440 // PrettyString returns the output of PrettyPrint as a string. 441 func PrettyString(v interface{}, prefix, suffix string, hooks PrettyPrintHooks) string { 442 var b bytes.Buffer 443 PrettyPrint(&b, v, prefix, suffix, hooks) 444 return b.String() 445 } 446 447 // PrettyPrint pretty prints v to w. Zero values and unexported struct fields 448 // are omitted. 449 // 450 // Force printing of zero values of struct fields by including in the field tag 451 // PrettyPrint:"zero". 452 // 453 // Enable using a String method, if any, of a struct field type by including in 454 // the field tag PrettyPrint:"stringer". 455 // 456 // The tags can be combined as in PrettyPrint:"zero,stringer". The order is not 457 // important, so PrettyPrint:stringer,zero has the same effect. 458 // 459 // A hook attached to the field type has priority over the struct field tag 460 // described above. 461 func PrettyPrint(w io.Writer, v interface{}, prefix, suffix string, hooks PrettyPrintHooks) { 462 if v == nil { 463 return 464 } 465 466 f := IndentFormatter(w, "· ") 467 468 defer func() { 469 if e := recover(); e != nil { 470 f.Format("\npanic: %v", e) 471 } 472 }() 473 474 prettyPrint(nil, f, prefix, suffix, v, hooks, false, false) 475 } 476 477 func prettyPrint(protect map[interface{}]struct{}, sf Formatter, prefix, suffix string, v interface{}, hooks PrettyPrintHooks, zero, stringer bool) { 478 if v == nil { 479 return 480 } 481 482 rt := reflect.TypeOf(v) 483 if handler := hooks[rt]; handler != nil { 484 handler(sf, v, prefix, suffix) 485 return 486 } 487 488 rv := reflect.ValueOf(v) 489 if stringer { 490 if _, ok := v.(fmt.Stringer); ok { 491 sf.Format("%s%s", prefix, v) 492 sf.Format(suffix) 493 return 494 } 495 } 496 497 switch rt.Kind() { 498 case reflect.Slice: 499 if rv.Len() == 0 && !zero { 500 return 501 } 502 503 sf.Format("%s[]%T{ // len %d%i\n", prefix, rv.Index(0).Interface(), rv.Len()) 504 for i := 0; i < rv.Len(); i++ { 505 prettyPrint(protect, sf, fmt.Sprintf("%d: ", i), ",\n", rv.Index(i).Interface(), hooks, false, false) 506 } 507 suffix = strings.Replace(suffix, "%", "%%", -1) 508 sf.Format("%u}" + suffix) 509 case reflect.Array: 510 if reflect.Zero(rt).Interface() == rv.Interface() && !zero { 511 return 512 } 513 514 sf.Format("%s[%d]%T{%i\n", prefix, rv.Len(), rv.Index(0).Interface()) 515 for i := 0; i < rv.Len(); i++ { 516 prettyPrint(protect, sf, fmt.Sprintf("%d: ", i), ",\n", rv.Index(i).Interface(), hooks, false, false) 517 } 518 suffix = strings.Replace(suffix, "%", "%%", -1) 519 sf.Format("%u}" + suffix) 520 case reflect.Struct: 521 if rt.NumField() == 0 { 522 return 523 } 524 525 if reflect.DeepEqual(reflect.Zero(rt).Interface(), rv.Interface()) && !zero { 526 return 527 } 528 529 sf.Format("%s%T{%i\n", prefix, v) 530 for i := 0; i < rt.NumField(); i++ { 531 f := rv.Field(i) 532 if !f.CanInterface() { 533 continue 534 } 535 536 var stringer, zero bool 537 ft := rt.Field(i) 538 if tag, ok := ft.Tag.Lookup("PrettyPrint"); ok { 539 a := strings.Split(tag, ",") 540 for _, v := range a { 541 switch strings.TrimSpace(v) { 542 case "stringer": 543 stringer = true 544 case "zero": 545 zero = true 546 } 547 } 548 } 549 prettyPrint(protect, sf, fmt.Sprintf("%s: ", rt.Field(i).Name), ",\n", f.Interface(), hooks, zero, stringer) 550 } 551 suffix = strings.Replace(suffix, "%", "%%", -1) 552 sf.Format("%u}" + suffix) 553 case reflect.Ptr: 554 if rv.IsNil() && !zero { 555 return 556 } 557 558 rvi := rv.Interface() 559 if _, ok := protect[rvi]; ok { 560 suffix = strings.Replace(suffix, "%", "%%", -1) 561 sf.Format("%s&%T{ /* recursive/repetitive pointee not shown */ }"+suffix, prefix, rv.Elem().Interface()) 562 return 563 } 564 565 if protect == nil { 566 protect = map[interface{}]struct{}{} 567 } 568 protect[rvi] = struct{}{} 569 prettyPrint(protect, sf, prefix+"&", suffix, rv.Elem().Interface(), hooks, false, false) 570 case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8: 571 if v := rv.Int(); v != 0 || zero { 572 suffix = strings.Replace(suffix, "%", "%%", -1) 573 sf.Format("%s%v"+suffix, prefix, v) 574 } 575 case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint8: 576 if v := rv.Uint(); v != 0 || zero { 577 suffix = strings.Replace(suffix, "%", "%%", -1) 578 sf.Format("%s%v"+suffix, prefix, v) 579 } 580 case reflect.Float32, reflect.Float64: 581 if v := rv.Float(); v != 0 || zero { 582 suffix = strings.Replace(suffix, "%", "%%", -1) 583 sf.Format("%s%v"+suffix, prefix, v) 584 } 585 case reflect.Complex64, reflect.Complex128: 586 if v := rv.Complex(); v != 0 || zero { 587 suffix = strings.Replace(suffix, "%", "%%", -1) 588 sf.Format("%s%v"+suffix, prefix, v) 589 } 590 case reflect.Uintptr: 591 if v := rv.Uint(); v != 0 || zero { 592 suffix = strings.Replace(suffix, "%", "%%", -1) 593 sf.Format("%s%v"+suffix, prefix, v) 594 } 595 case reflect.UnsafePointer: 596 s := fmt.Sprintf("%p", rv.Interface()) 597 if s == "0x0" && !zero { 598 return 599 } 600 601 suffix = strings.Replace(suffix, "%", "%%", -1) 602 sf.Format("%s%s"+suffix, prefix, s) 603 case reflect.Bool: 604 if v := rv.Bool(); v || zero { 605 suffix = strings.Replace(suffix, "%", "%%", -1) 606 sf.Format("%s%v"+suffix, prefix, rv.Bool()) 607 } 608 case reflect.String: 609 s := rv.Interface().(string) 610 if s == "" && !zero { 611 return 612 } 613 614 suffix = strings.Replace(suffix, "%", "%%", -1) 615 sf.Format("%s%q"+suffix, prefix, s) 616 case reflect.Chan: 617 if reflect.Zero(rt).Interface() == rv.Interface() && !zero { 618 return 619 } 620 621 c := rv.Cap() 622 s := "" 623 if c != 0 { 624 s = fmt.Sprintf("// capacity: %d", c) 625 } 626 suffix = strings.Replace(suffix, "%", "%%", -1) 627 sf.Format("%s%s %s%s"+suffix, prefix, rt.ChanDir(), rt.Elem().Name(), s) 628 case reflect.Func: 629 if rv.IsNil() && !zero { 630 return 631 } 632 633 var in, out []string 634 for i := 0; i < rt.NumIn(); i++ { 635 x := reflect.Zero(rt.In(i)) 636 in = append(in, fmt.Sprintf("%T", x.Interface())) 637 } 638 if rt.IsVariadic() { 639 i := len(in) - 1 640 in[i] = "..." + in[i][2:] 641 } 642 for i := 0; i < rt.NumOut(); i++ { 643 out = append(out, rt.Out(i).Name()) 644 } 645 s := "(" + strings.Join(in, ", ") + ")" 646 t := strings.Join(out, ", ") 647 if len(out) > 1 { 648 t = "(" + t + ")" 649 } 650 if t != "" { 651 t = " " + t 652 } 653 suffix = strings.Replace(suffix, "%", "%%", -1) 654 sf.Format("%sfunc%s%s { ... }"+suffix, prefix, s, t) 655 case reflect.Map: 656 keys := rv.MapKeys() 657 if len(keys) == 0 && !zero { 658 return 659 } 660 661 var buf bytes.Buffer 662 nf := IndentFormatter(&buf, "· ") 663 var skeys []string 664 for i, k := range keys { 665 prettyPrint(protect, nf, "", "", k.Interface(), hooks, false, false) 666 skeys = append(skeys, fmt.Sprintf("%s%10d", buf.Bytes(), i)) 667 buf.Reset() 668 } 669 sort.Strings(skeys) 670 sf.Format("%s%T{%i\n", prefix, v) 671 for _, k := range skeys { 672 si := strings.TrimSpace(k[len(k)-10:]) 673 k = k[:len(k)-10] 674 n, _ := strconv.ParseUint(si, 10, 64) 675 mv := rv.MapIndex(keys[n]) 676 prettyPrint(protect, sf, fmt.Sprintf("%s: ", k), ",\n", mv.Interface(), hooks, false, false) 677 } 678 suffix = strings.Replace(suffix, "%", "%%", -1) 679 sf.Format("%u}" + suffix) 680 } 681 } 682 683 // Gopath returns the value of the $GOPATH environment variable or its default 684 // value if not set. 685 func Gopath() string { 686 if r := os.Getenv("GOPATH"); r != "" { 687 return r 688 } 689 690 // go1.8: https://github.com/golang/go/blob/74628a8b9f102bddd5078ee426efe0fd57033115/doc/code.html#L122 691 switch runtime.GOOS { 692 case "plan9": 693 return os.Getenv("home") 694 case "windows": 695 return filepath.Join(os.Getenv("USERPROFILE"), "go") 696 default: 697 return filepath.Join(os.Getenv("HOME"), "go") 698 } 699 } 700 701 // Homepath returns the user's home directory path. 702 func Homepath() string { 703 // go1.8: https://github.com/golang/go/blob/74628a8b9f102bddd5078ee426efe0fd57033115/doc/code.html#L122 704 switch runtime.GOOS { 705 case "plan9": 706 return os.Getenv("home") 707 case "windows": 708 return os.Getenv("USERPROFILE") 709 default: 710 return os.Getenv("HOME") 711 } 712 } 713 714 // ImportPath returns the import path of the caller or an error, if any. 715 func ImportPath() (string, error) { 716 _, file, _, ok := runtime.Caller(1) 717 if !ok { 718 return "", fmt.Errorf("runtime.Caller failed") 719 } 720 721 gopath := Gopath() 722 for _, v := range filepath.SplitList(gopath) { 723 gp := filepath.Join(v, "src") 724 path, err := filepath.Rel(gp, file) 725 if err != nil { 726 continue 727 } 728 729 return filepath.Dir(path), nil 730 } 731 732 return "", fmt.Errorf("cannot determine import path using GOPATH=%s", gopath) 733 }