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