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  }