github.com/tacerus/ldifdiff@v0.0.0-20181030102753-4dccbe38183b/input.go (about) 1 package ldifdiff 2 3 import ( 4 "bufio" 5 "errors" 6 "os" 7 "sort" 8 "strings" 9 "sync" 10 ) 11 12 /* Package only functions */ 13 14 func convertLdifStr(ldifStr string, ignoreAttr []string) (entries, error) { 15 return importRecords(ldifStr, "", ignoreAttr) 16 } 17 18 func importLdifFile(file string, ignoreAttr []string) (entries, error) { 19 entries, err := importRecords("", file, ignoreAttr) 20 if err != nil { 21 err = errors.New(err.Error() + " [" + file + "]") 22 } 23 return entries, err 24 } 25 26 /* Internal functions */ 27 28 func addLineToRecord(line *string, record *[]string, ignoreAttr []string, prevAttrSkipped *bool) error { 29 var err error 30 31 // Append continuation lines to previous line 32 if strings.HasPrefix(*line, " ") { 33 if *prevAttrSkipped { // but not lines from a skipped attribute 34 return nil 35 } 36 switch len(*record) > 0 { 37 case true: 38 prevIdx := len(*record) - 1 39 prevLine := strings.TrimSuffix((*record)[prevIdx], "\n") + 40 strings.TrimPrefix(*line, " ") 41 (*record)[prevIdx] = prevLine 42 return nil 43 case false: 44 err = errors.New("Invalid modifyStr line continuation: \"" + *line + "\"") 45 return err 46 } 47 } 48 49 // Regular line 50 if len(*line) != 0 { 51 for _, attrName := range ignoreAttr { 52 if strings.HasPrefix(*line, attrName+":") { 53 *prevAttrSkipped = true 54 return nil 55 } 56 } 57 *record = append(*record, *line) 58 *prevAttrSkipped = false 59 } 60 61 return nil 62 } 63 64 func importRecords(ldifStr, file string, ignoreAttr []string) (entries, error) { 65 var readErr, parseErr error 66 queue := make(chan []string, 10) 67 entries := make(map[string][]string) 68 69 // Read and Parse the file concurrently 70 var wg sync.WaitGroup 71 wg.Add(2) // 1 reader + 1 parser 72 switch file { 73 case "": // it's a ldifStr 74 go readStr(ldifStr, ignoreAttr, queue, &wg, &readErr) 75 default: // it's a file 76 go readFile(file, ignoreAttr, queue, &wg, &readErr) 77 } 78 go parse(entries, queue, &wg, &parseErr) 79 wg.Wait() 80 81 // Return values 82 switch { 83 case readErr != nil: 84 return entries, readErr 85 case parseErr != nil: 86 return entries, parseErr 87 default: 88 return entries, nil 89 } 90 } 91 92 func readFile(file string, ignoreAttr []string, queue chan<- []string, wg *sync.WaitGroup, err *error) { 93 defer wg.Done() 94 defer close(queue) 95 fh, osErr := os.Open(file) 96 if osErr != nil { 97 *err = osErr 98 return 99 } 100 defer fh.Close() 101 102 record := []string{} 103 scanner := bufio.NewScanner(fh) 104 var prevAttrSkipped bool // use to skip continuation lines of skipped attr 105 firstLine := true 106 for scanner.Scan() { 107 108 line := scanner.Text() 109 110 // Skip comments 111 if strings.HasPrefix(line, "#") { 112 continue 113 } 114 115 // Check if first line is a "version: *" line and skip it 116 if firstLine { 117 if strings.HasPrefix(line, "version: ") { 118 firstLine = false 119 continue 120 } else if line == "" { 121 continue 122 } 123 firstLine = false 124 } 125 126 // Import lines as records 127 *err = addLineToRecord(&line, &record, ignoreAttr, &prevAttrSkipped) 128 if *err != nil { 129 return 130 } 131 132 // Dispatch the record to buffer & reset record 133 if len(line) == 0 && len(record) != 0 { 134 queue <- record 135 record = []string{} 136 } 137 } 138 139 // Last record may be a leftover (no empty line) 140 if len(record) != 0 { 141 queue <- record 142 } 143 144 if *err == nil { 145 *err = scanner.Err() 146 } 147 } 148 149 func readStr(ldifStr string, ignoreAttr []string, queue chan<- []string, wg *sync.WaitGroup, err *error) { 150 defer wg.Done() 151 defer close(queue) 152 153 for idx, recordStr := range strings.Split(ldifStr, "\n\n") { 154 var prevAttrSkipped bool 155 record := []string{} 156 for _, line := range strings.Split(recordStr, "\n") { 157 158 // Skip comments 159 if strings.HasPrefix(line, "#") { 160 continue 161 } 162 163 if idx == 0 { // First record only 164 if strings.HasPrefix(line, "version: ") { 165 continue 166 } 167 } 168 169 *err = addLineToRecord(&line, &record, ignoreAttr, &prevAttrSkipped) 170 if *err != nil { 171 return 172 } 173 174 // Dispatch the record to buffer & reset record 175 if len(line) == 0 && len(record) != 0 { 176 queue <- record 177 record = []string{} 178 } 179 } 180 // Last record may be a leftover (no empty line) 181 if len(record) != 0 { 182 queue <- record 183 } 184 } 185 } 186 187 func parse(entries entries, queue <-chan []string, wg *sync.WaitGroup, err *error) { 188 defer wg.Done() 189 for record := range queue { 190 dn := record[0] // Find dn, should be the first line 191 if !strings.HasPrefix(dn, "dn:") { 192 *err = errors.New("No dn could be retrieved") 193 continue 194 } 195 196 // Sort the entries 197 attr := record[1:] 198 sort.Strings(attr) 199 entries[dn] = attr 200 } 201 }