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' }