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