github.com/gsquire/gb@v0.4.4-0.20161112235727-3982dc872064/internal/depfile/depfile.go (about)

     1  // depfile loads a file of tagged key value pairs.
     2  package depfile
     3  
     4  import (
     5  	"bufio"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/pkg/errors"
    12  )
    13  
    14  // ParseFile parses path into a tagged key value map.
    15  // See Parse for the syntax of the file.
    16  func ParseFile(path string) (map[string]map[string]string, error) {
    17  	r, err := os.Open(path)
    18  	if err != nil {
    19  		return nil, errors.Wrapf(err, "ParseFile")
    20  	}
    21  	defer r.Close()
    22  	return Parse(r)
    23  }
    24  
    25  // Parse parses the contents of r into a tagged key value map.
    26  // If successful Parse returns a map[string]map[string]string.
    27  // The format of the line is
    28  //
    29  //     name key=value [key=value]...
    30  //
    31  // Elements can be seperated by whitespace (space and tab).
    32  // Lines that do not begin with a letter or number are ignored. This
    33  // provides a simple mechanism for commentary
    34  //
    35  //     # some comment
    36  //     github.com/pkg/profile version=0.1.0
    37  //
    38  //     ; some other comment
    39  //     // third kind of comment
    40  //       lines starting with blank lines are also ignored
    41  //     github.com/pkg/sftp version=0.2.1
    42  func Parse(r io.Reader) (map[string]map[string]string, error) {
    43  	sc := bufio.NewScanner(r)
    44  	m := make(map[string]map[string]string)
    45  	var lineno int
    46  	for sc.Scan() {
    47  		line := sc.Text()
    48  		lineno++
    49  
    50  		// skip blank line
    51  		if line == "" {
    52  			continue
    53  		}
    54  
    55  		// valid lines start with a letter or number everything else is ignored.
    56  		// we don't need to worry about unicode because import paths are restricted
    57  		// to the DNS character set, which is a subset of ASCII.
    58  		if !isLetterOrNumber(line[0]) {
    59  			continue
    60  		}
    61  
    62  		name, kv, err := parseLine(line)
    63  		if err != nil {
    64  			return nil, fmt.Errorf("%d: %v", lineno, err)
    65  		}
    66  		m[name] = kv
    67  	}
    68  	return m, sc.Err()
    69  }
    70  
    71  func parseLine(line string) (string, map[string]string, error) {
    72  	args := splitLine(line)
    73  	name, rest := args[0], args[1:]
    74  	if len(rest) == 0 {
    75  		return "", nil, fmt.Errorf("%s: expected key=value pair after name", name)
    76  	}
    77  
    78  	kv, err := parseKeyVal(rest)
    79  	if err != nil {
    80  		return "", nil, fmt.Errorf("%s: %v", name, err)
    81  	}
    82  	return name, kv, nil
    83  }
    84  
    85  func parseKeyVal(args []string) (map[string]string, error) {
    86  	m := make(map[string]string)
    87  	for _, kv := range args {
    88  		if strings.HasPrefix(kv, "=") {
    89  			return nil, fmt.Errorf("expected key=value pair, missing key %q", kv)
    90  		}
    91  		if strings.HasSuffix(kv, "=") {
    92  			return nil, fmt.Errorf("expected key=value pair, missing value %q", kv)
    93  		}
    94  		args := strings.Split(kv, "=")
    95  		switch len(args) {
    96  		case 2:
    97  			key := args[0]
    98  			if v, ok := m[key]; ok {
    99  				return nil, fmt.Errorf("duplicate key=value pair, have \"%s=%s\" got %q", key, v, kv)
   100  			}
   101  			m[key] = args[1]
   102  		default:
   103  			return nil, fmt.Errorf("expected key=value pair, got %q", kv)
   104  		}
   105  	}
   106  	return m, nil
   107  }
   108  
   109  func isLetterOrNumber(r byte) bool {
   110  	switch {
   111  	case r > '0'-1 && r < '9'+1:
   112  		return true
   113  	case r > 'a'-1 && r < 'z'+1:
   114  		return true
   115  	case r > 'A'-1 && r < 'Z'+1:
   116  		return true
   117  	default:
   118  		return false
   119  	}
   120  }
   121  
   122  // splitLine is like strings.Split(string, " "), but splits
   123  // strings by any whitespace characters, discarding them in
   124  // the process.
   125  func splitLine(line string) []string {
   126  	var s []string
   127  	var start, end int
   128  	for ; start < len(line); start++ {
   129  		c := line[start]
   130  		if !isWhitespace(c) {
   131  			break
   132  		}
   133  	}
   134  	var ws bool
   135  	for end = start; end < len(line); end++ {
   136  		c := line[end]
   137  		if !isWhitespace(c) {
   138  			ws = false
   139  			continue
   140  		}
   141  		if ws == true {
   142  			start++
   143  			continue
   144  		}
   145  		ws = true
   146  		s = append(s, line[start:end])
   147  		start = end + 1
   148  	}
   149  	if start != end {
   150  		s = append(s, line[start:end])
   151  	}
   152  	return s
   153  }
   154  
   155  func isWhitespace(c byte) bool { return c == ' ' || c == '\t' }