blob: ca3c5e1a7740af62521971b01047a4b8320b9637 [file] [log] [blame]
package ldifdiff
import (
"bufio"
"errors"
"os"
"sort"
"strings"
"sync"
)
/* Package only functions */
func convertLdifStr(ldifStr string, ignoreAttr []string) (entries, error) {
return importRecords(ldifStr, "", ignoreAttr)
}
func importLdifFile(file string, ignoreAttr []string) (entries, error) {
entries, err := importRecords("", file, ignoreAttr)
if err != nil {
err = errors.New(err.Error() + " [" + file + "]")
}
return entries, err
}
/* Internal functions */
func addLineToRecord(line *string, record *[]string, ignoreAttr []string, prevAttrSkipped *bool) error {
var err error
// Append continuation lines to previous line
if strings.HasPrefix(*line, " ") {
if *prevAttrSkipped { // but not lines from a skipped attribute
return nil
}
switch len(*record) > 0 {
case true:
prevIdx := len(*record) - 1
prevLine := strings.TrimSuffix((*record)[prevIdx], "\n") +
strings.TrimPrefix(*line, " ")
(*record)[prevIdx] = prevLine
return nil
case false:
err = errors.New("Invalid modifyStr line continuation: \"" + *line + "\"")
return err
}
}
// Regular line
if len(*line) != 0 {
for _, attrName := range ignoreAttr {
if strings.HasPrefix(*line, attrName+":") {
*prevAttrSkipped = true
return nil
}
}
*record = append(*record, *line)
*prevAttrSkipped = false
}
return nil
}
func importRecords(ldifStr, file string, ignoreAttr []string) (entries, error) {
var readErr, parseErr error
queue := make(chan []string, 10)
entries := make(map[string][]string)
// Read and Parse the file concurrently
var wg sync.WaitGroup
wg.Add(2) // 1 reader + 1 parser
switch file {
case "": // it's a ldifStr
go readStr(ldifStr, ignoreAttr, queue, &wg, &readErr)
default: // it's a file
go readFile(file, ignoreAttr, queue, &wg, &readErr)
}
go parse(entries, queue, &wg, &parseErr)
wg.Wait()
// Return values
switch {
case readErr != nil:
return entries, readErr
case parseErr != nil:
return entries, parseErr
default:
return entries, nil
}
}
func readFile(file string, ignoreAttr []string, queue chan<- []string, wg *sync.WaitGroup, err *error) {
defer wg.Done()
defer close(queue)
fh, osErr := os.Open(file)
if osErr != nil {
*err = osErr
return
}
defer fh.Close()
record := []string{}
scanner := bufio.NewScanner(fh)
var prevAttrSkipped bool // use to skip continuation lines of skipped attr
firstLine := true
for scanner.Scan() {
line := scanner.Text()
// Skip comments
if strings.HasPrefix(line, "#") {
continue
}
// Check if first line is a "version: *" line and skip it
if firstLine {
if strings.HasPrefix(line, "version: ") {
firstLine = false
continue
} else if line == "" {
continue
}
firstLine = false
}
// Import lines as records
*err = addLineToRecord(&line, &record, ignoreAttr, &prevAttrSkipped)
if *err != nil {
return
}
// Dispatch the record to buffer & reset record
if len(line) == 0 && len(record) != 0 {
queue <- record
record = []string{}
}
}
// Last record may be a leftover (no empty line)
if len(record) != 0 {
queue <- record
}
if *err == nil {
*err = scanner.Err()
}
}
func readStr(ldifStr string, ignoreAttr []string, queue chan<- []string, wg *sync.WaitGroup, err *error) {
defer wg.Done()
defer close(queue)
for idx, recordStr := range strings.Split(ldifStr, "\n\n") {
var prevAttrSkipped bool
record := []string{}
for _, line := range strings.Split(recordStr, "\n") {
// Skip comments
if strings.HasPrefix(line, "#") {
continue
}
if idx == 0 { // First record only
if strings.HasPrefix(line, "version: ") {
continue
}
}
*err = addLineToRecord(&line, &record, ignoreAttr, &prevAttrSkipped)
if *err != nil {
return
}
// Dispatch the record to buffer & reset record
if len(line) == 0 && len(record) != 0 {
queue <- record
record = []string{}
}
}
// Last record may be a leftover (no empty line)
if len(record) != 0 {
queue <- record
}
}
}
func parse(entries entries, queue <-chan []string, wg *sync.WaitGroup, err *error) {
defer wg.Done()
for record := range queue {
dn := record[0] // Find dn, should be the first line
if !strings.HasPrefix(dn, "dn:") {
*err = errors.New("No dn could be retrieved")
continue
}
// Sort the entries
attr := record[1:]
sort.Strings(attr)
entries[dn] = attr
}
}