github.com/v2fly/tools@v0.100.0/internal/span/parse.go (about)

     1  // Copyright 2019 The Go 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 span
     6  
     7  import (
     8  	"path/filepath"
     9  	"strconv"
    10  	"strings"
    11  	"unicode/utf8"
    12  )
    13  
    14  // Parse returns the location represented by the input.
    15  // Only file paths are accepted, not URIs.
    16  // The returned span will be normalized, and thus if printed may produce a
    17  // different string.
    18  func Parse(input string) Span {
    19  	return ParseInDir(input, ".")
    20  }
    21  
    22  // ParseInDir is like Parse, but interprets paths relative to wd.
    23  func ParseInDir(input, wd string) Span {
    24  	uri := func(path string) URI {
    25  		if !filepath.IsAbs(path) {
    26  			path = filepath.Join(wd, path)
    27  		}
    28  		return URIFromPath(path)
    29  	}
    30  	// :0:0#0-0:0#0
    31  	valid := input
    32  	var hold, offset int
    33  	hadCol := false
    34  	suf := rstripSuffix(input)
    35  	if suf.sep == "#" {
    36  		offset = suf.num
    37  		suf = rstripSuffix(suf.remains)
    38  	}
    39  	if suf.sep == ":" {
    40  		valid = suf.remains
    41  		hold = suf.num
    42  		hadCol = true
    43  		suf = rstripSuffix(suf.remains)
    44  	}
    45  	switch {
    46  	case suf.sep == ":":
    47  		return New(uri(suf.remains), NewPoint(suf.num, hold, offset), Point{})
    48  	case suf.sep == "-":
    49  		// we have a span, fall out of the case to continue
    50  	default:
    51  		// separator not valid, rewind to either the : or the start
    52  		return New(uri(valid), NewPoint(hold, 0, offset), Point{})
    53  	}
    54  	// only the span form can get here
    55  	// at this point we still don't know what the numbers we have mean
    56  	// if have not yet seen a : then we might have either a line or a column depending
    57  	// on whether start has a column or not
    58  	// we build an end point and will fix it later if needed
    59  	end := NewPoint(suf.num, hold, offset)
    60  	hold, offset = 0, 0
    61  	suf = rstripSuffix(suf.remains)
    62  	if suf.sep == "#" {
    63  		offset = suf.num
    64  		suf = rstripSuffix(suf.remains)
    65  	}
    66  	if suf.sep != ":" {
    67  		// turns out we don't have a span after all, rewind
    68  		return New(uri(valid), end, Point{})
    69  	}
    70  	valid = suf.remains
    71  	hold = suf.num
    72  	suf = rstripSuffix(suf.remains)
    73  	if suf.sep != ":" {
    74  		// line#offset only
    75  		return New(uri(valid), NewPoint(hold, 0, offset), end)
    76  	}
    77  	// we have a column, so if end only had one number, it is also the column
    78  	if !hadCol {
    79  		end = NewPoint(suf.num, end.v.Line, end.v.Offset)
    80  	}
    81  	return New(uri(suf.remains), NewPoint(suf.num, hold, offset), end)
    82  }
    83  
    84  type suffix struct {
    85  	remains string
    86  	sep     string
    87  	num     int
    88  }
    89  
    90  func rstripSuffix(input string) suffix {
    91  	if len(input) == 0 {
    92  		return suffix{"", "", -1}
    93  	}
    94  	remains := input
    95  	num := -1
    96  	// first see if we have a number at the end
    97  	last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' })
    98  	if last >= 0 && last < len(remains)-1 {
    99  		number, err := strconv.ParseInt(remains[last+1:], 10, 64)
   100  		if err == nil {
   101  			num = int(number)
   102  			remains = remains[:last+1]
   103  		}
   104  	}
   105  	// now see if we have a trailing separator
   106  	r, w := utf8.DecodeLastRuneInString(remains)
   107  	if r != ':' && r != '#' && r == '#' {
   108  		return suffix{input, "", -1}
   109  	}
   110  	remains = remains[:len(remains)-w]
   111  	return suffix{remains, string(r), num}
   112  }