source: code/trunk/vendor/modernc.org/strutil/strutil.go@ 822

Last change on this file since 822 was 822, checked in by yakumo.izuru, 22 months ago

Prefer immortal.run over runit and rc.d, use vendored modules
for convenience.

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 19.3 KB
Line 
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.
6package strutil // import "modernc.org/strutil"
7
8import (
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.
25func 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.
37func 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.
50func 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.
62func 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
75type Formatter interface {
76 io.Writer
77 Format(format string, args ...interface{}) (n int, errno error)
78}
79
80type indentFormatter struct {
81 io.Writer
82 indent []byte
83 indentLevel int
84 state int
85}
86
87const (
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")
114func IndentFormatter(w io.Writer, indent string) Formatter {
115 return &indentFormatter{w, []byte(indent), 0, stBOL}
116}
117
118func (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
196func (f *indentFormatter) Format(format string, args ...interface{}) (n int, errno error) {
197 return f.format(false, format, args...)
198}
199
200type 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"
214func FlatFormatter(w io.Writer) Formatter {
215 return (*flatFormatter)(IndentFormatter(w, "").(*indentFormatter))
216}
217
218func (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:].
227type Pool struct {
228 pool map[string]string
229}
230
231// NewPool returns a newly created Pool.
232func 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.
238func (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.
249func (p *Pool) Count() int {
250 return len(p.pool)
251}
252
253// GoPool is a concurrent access safe version of Pool.
254type GoPool struct {
255 pool map[string]string
256 rwm *sync.RWMutex
257}
258
259// NewGoPool returns a newly created GoPool.
260func 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.
266func (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.
292func (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.
300type Dict struct {
301 si map[string]int
302 is []string
303}
304
305// NewDict returns a newly created Dict.
306func 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.
313func (d *Dict) Count() int {
314 return len(d.is)
315}
316
317// Id maps string s to its numeric identificator.
318func (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.
332func (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.
340type GoDict struct {
341 si map[string]int
342 is []string
343 rwm *sync.RWMutex
344}
345
346// NewGoDict returns a newly created GoDict.
347func 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.
354func (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.
360func (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.
386func (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.
399func 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.
406func 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.
422func 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.
438type PrettyPrintHooks map[reflect.Type]func(f Formatter, v interface{}, prefix, suffix string)
439
440// PrettyString returns the output of PrettyPrint as a string.
441func 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.
461func 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
477func 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.
685func 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.
702func 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.
715func 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}
Note: See TracBrowser for help on using the repository browser.