parent
							
								
									79f0b1a50b
								
							
						
					
					
						commit
						befb6bea22
					
				
					 7 changed files with 355 additions and 140 deletions
				
			
		|  | @ -32,6 +32,9 @@ func CreateReaderAndGuessDelimiter(rd io.Reader) (*stdcsv.Reader, error) { | ||||||
| 	var data = make([]byte, 1e4) | 	var data = make([]byte, 1e4) | ||||||
| 	size, err := rd.Read(data) | 	size, err := rd.Read(data) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		if err == io.EOF { | ||||||
|  | 			return CreateReader(bytes.NewReader([]byte{}), rune(',')), nil | ||||||
|  | 		} | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -21,10 +21,12 @@ type TableDiffCellType uint8 | ||||||
| 
 | 
 | ||||||
| // TableDiffCellType possible values.
 | // TableDiffCellType possible values.
 | ||||||
| const ( | const ( | ||||||
| 	TableDiffCellEqual TableDiffCellType = iota + 1 | 	TableDiffCellUnchanged TableDiffCellType = iota + 1 | ||||||
| 	TableDiffCellChanged | 	TableDiffCellChanged | ||||||
| 	TableDiffCellAdd | 	TableDiffCellAdd | ||||||
| 	TableDiffCellDel | 	TableDiffCellDel | ||||||
|  | 	TableDiffCellMovedUnchanged | ||||||
|  | 	TableDiffCellMovedChanged | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // TableDiffCell represents a cell of a TableDiffRow
 | // TableDiffCell represents a cell of a TableDiffRow
 | ||||||
|  | @ -53,6 +55,9 @@ type csvReader struct { | ||||||
| 	eof    bool | 	eof    bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ErrorUndefinedCell is for when a row, column coordinates do not exist in the CSV
 | ||||||
|  | var ErrorUndefinedCell = errors.New("undefined cell") | ||||||
|  | 
 | ||||||
| // createCsvReader creates a csvReader and fills the buffer
 | // createCsvReader creates a csvReader and fills the buffer
 | ||||||
| func createCsvReader(reader *csv.Reader, bufferRowCount int) (*csvReader, error) { | func createCsvReader(reader *csv.Reader, bufferRowCount int) (*csvReader, error) { | ||||||
| 	csv := &csvReader{reader: reader} | 	csv := &csvReader{reader: reader} | ||||||
|  | @ -70,7 +75,7 @@ func createCsvReader(reader *csv.Reader, bufferRowCount int) (*csvReader, error) | ||||||
| 
 | 
 | ||||||
| // GetRow gets a row from the buffer if present or advances the reader to the requested row. On the end of the file only nil gets returned.
 | // GetRow gets a row from the buffer if present or advances the reader to the requested row. On the end of the file only nil gets returned.
 | ||||||
| func (csv *csvReader) GetRow(row int) ([]string, error) { | func (csv *csvReader) GetRow(row int) ([]string, error) { | ||||||
| 	if row < len(csv.buffer) { | 	if row < len(csv.buffer) && row >= 0 { | ||||||
| 		return csv.buffer[row], nil | 		return csv.buffer[row], nil | ||||||
| 	} | 	} | ||||||
| 	if csv.eof { | 	if csv.eof { | ||||||
|  | @ -131,7 +136,11 @@ func createCsvDiffSingle(reader *csv.Reader, celltype TableDiffCellType) ([]*Tab | ||||||
| 		} | 		} | ||||||
| 		cells := make([]*TableDiffCell, len(row)) | 		cells := make([]*TableDiffCell, len(row)) | ||||||
| 		for j := 0; j < len(row); j++ { | 		for j := 0; j < len(row); j++ { | ||||||
| 			cells[j] = &TableDiffCell{LeftCell: row[j], Type: celltype} | 			if celltype == TableDiffCellDel { | ||||||
|  | 				cells[j] = &TableDiffCell{LeftCell: row[j], Type: celltype} | ||||||
|  | 			} else { | ||||||
|  | 				cells[j] = &TableDiffCell{RightCell: row[j], Type: celltype} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 		rows = append(rows, &TableDiffRow{RowIdx: i, Cells: cells}) | 		rows = append(rows, &TableDiffRow{RowIdx: i, Cells: cells}) | ||||||
| 		i++ | 		i++ | ||||||
|  | @ -141,185 +150,267 @@ func createCsvDiffSingle(reader *csv.Reader, celltype TableDiffCellType) ([]*Tab | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func createCsvDiff(diffFile *DiffFile, baseReader *csv.Reader, headReader *csv.Reader) ([]*TableDiffSection, error) { | func createCsvDiff(diffFile *DiffFile, baseReader *csv.Reader, headReader *csv.Reader) ([]*TableDiffSection, error) { | ||||||
| 	a, err := createCsvReader(baseReader, maxRowsToInspect) | 	// Given the baseReader and headReader, we are going to create CSV Reader for each, baseCSVReader and b respectively
 | ||||||
|  | 	baseCSVReader, err := createCsvReader(baseReader, maxRowsToInspect) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	headCSVReader, err := createCsvReader(headReader, maxRowsToInspect) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	b, err := createCsvReader(headReader, maxRowsToInspect) | 	// Initializing the mappings of base to head (a2bColMap) and head to base (b2aColMap) columns
 | ||||||
| 	if err != nil { | 	a2bColMap, b2aColMap := getColumnMapping(baseCSVReader, headCSVReader) | ||||||
| 		return nil, err | 
 | ||||||
|  | 	// Determines how many cols there will be in the diff table, which includes deleted columns from base and added columns to base
 | ||||||
|  | 	numDiffTableCols := len(a2bColMap) + countUnmappedColumns(b2aColMap) | ||||||
|  | 	if len(a2bColMap) < len(b2aColMap) { | ||||||
|  | 		numDiffTableCols = len(b2aColMap) + countUnmappedColumns(a2bColMap) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	a2b, b2a := getColumnMapping(a, b) | 	// createDiffTableRow takes the row # of the `a` line and `b` line of a diff (starting from 1), 0 if the line doesn't exist (undefined)
 | ||||||
| 
 | 	// in the base or head respectively.
 | ||||||
| 	columns := len(a2b) + countUnmappedColumns(b2a) | 	// Returns a TableDiffRow which has the row index
 | ||||||
| 	if len(a2b) < len(b2a) { | 	createDiffTableRow := func(aLineNum int, bLineNum int) (*TableDiffRow, error) { | ||||||
| 		columns = len(b2a) + countUnmappedColumns(a2b) | 		// diffTableCells is a row of the diff table. It will have a cells for added, deleted, changed, and unchanged content, thus either
 | ||||||
| 	} | 		// the same size as the head table or bigger
 | ||||||
| 
 | 		diffTableCells := make([]*TableDiffCell, numDiffTableCols) | ||||||
| 	createDiffRow := func(aline int, bline int) (*TableDiffRow, error) { | 		var bRow *[]string | ||||||
| 		cells := make([]*TableDiffCell, columns) | 		if bLineNum > 0 { | ||||||
| 
 | 			row, err := headCSVReader.GetRow(bLineNum - 1) | ||||||
| 		if aline == 0 || bline == 0 { |  | ||||||
| 			var ( |  | ||||||
| 				row      []string |  | ||||||
| 				celltype TableDiffCellType |  | ||||||
| 				err      error |  | ||||||
| 			) |  | ||||||
| 			if bline == 0 { |  | ||||||
| 				row, err = a.GetRow(aline - 1) |  | ||||||
| 				celltype = TableDiffCellDel |  | ||||||
| 			} else { |  | ||||||
| 				row, err = b.GetRow(bline - 1) |  | ||||||
| 				celltype = TableDiffCellAdd |  | ||||||
| 			} |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			if row == nil { | 			bRow = &row | ||||||
| 				return nil, nil | 		} | ||||||
|  | 		var aRow *[]string | ||||||
|  | 		if aLineNum > 0 { | ||||||
|  | 			row, err := baseCSVReader.GetRow(aLineNum - 1) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			for i := 0; i < len(row); i++ { | 			aRow = &row | ||||||
| 				cells[i] = &TableDiffCell{LeftCell: row[i], Type: celltype} |  | ||||||
| 			} |  | ||||||
| 			return &TableDiffRow{RowIdx: bline, Cells: cells}, nil |  | ||||||
| 		} | 		} | ||||||
| 
 | 		if aRow == nil && bRow == nil { | ||||||
| 		arow, err := a.GetRow(aline - 1) | 			// No content
 | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		brow, err := b.GetRow(bline - 1) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		if len(arow) == 0 && len(brow) == 0 { |  | ||||||
| 			return nil, nil | 			return nil, nil | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		for i := 0; i < len(a2b); i++ { | 		aIndex := 0      // tracks where we are in the a2bColMap
 | ||||||
| 			acell, _ := getCell(arow, i) | 		bIndex := 0      // tracks where we are in the b2aColMap
 | ||||||
| 			if a2b[i] == unmappedColumn { | 		colsAdded := 0   // incremented whenever we found a column was added
 | ||||||
| 				cells[i] = &TableDiffCell{LeftCell: acell, Type: TableDiffCellDel} | 		colsDeleted := 0 // incrememted whenever a column was deleted
 | ||||||
| 			} else { |  | ||||||
| 				bcell, _ := getCell(brow, a2b[i]) |  | ||||||
| 
 | 
 | ||||||
| 				celltype := TableDiffCellChanged | 		// We loop until both the aIndex and bIndex are greater than their col map, which then we are done
 | ||||||
| 				if acell == bcell { | 		for aIndex < len(a2bColMap) || bIndex < len(b2aColMap) { | ||||||
| 					celltype = TableDiffCellEqual | 			// Starting from where aIndex is currently pointing, we see if the map is -1 (dleeted) and if is, create column to note that, increment, and look at the next aIndex
 | ||||||
|  | 			for aIndex < len(a2bColMap) && a2bColMap[aIndex] == -1 && (bIndex >= len(b2aColMap) || aIndex <= bIndex) { | ||||||
|  | 				var aCell string | ||||||
|  | 				if aRow != nil { | ||||||
|  | 					if cell, err := getCell(*aRow, aIndex); err != nil { | ||||||
|  | 						if err != ErrorUndefinedCell { | ||||||
|  | 							return nil, err | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						aCell = cell | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				diffTableCells[bIndex+colsDeleted] = &TableDiffCell{LeftCell: aCell, Type: TableDiffCellDel} | ||||||
|  | 				aIndex++ | ||||||
|  | 				colsDeleted++ | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// aIndex is now pointing to a column that also exists in b, or is at the end of a2bColMap. If the former,
 | ||||||
|  | 			// we can just increment aIndex until it points to a -1 column or one greater than the current bIndex
 | ||||||
|  | 			for aIndex < len(a2bColMap) && a2bColMap[aIndex] != -1 { | ||||||
|  | 				aIndex++ | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Starting from where bIndex is currently pointing, we see if the map is -1 (added) and if is, create column to note that, increment, and look at the next aIndex
 | ||||||
|  | 			for bIndex < len(b2aColMap) && b2aColMap[bIndex] == -1 && (aIndex >= len(a2bColMap) || bIndex < aIndex) { | ||||||
|  | 				var bCell string | ||||||
|  | 				cellType := TableDiffCellAdd | ||||||
|  | 				if bRow != nil { | ||||||
|  | 					if cell, err := getCell(*bRow, bIndex); err != nil { | ||||||
|  | 						if err != ErrorUndefinedCell { | ||||||
|  | 							return nil, err | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						bCell = cell | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					cellType = TableDiffCellDel | ||||||
|  | 				} | ||||||
|  | 				diffTableCells[bIndex+colsDeleted] = &TableDiffCell{RightCell: bCell, Type: cellType} | ||||||
|  | 				bIndex++ | ||||||
|  | 				colsAdded++ | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// aIndex is now pointing to a column that also exists in a, or is at the end of b2aColMap. If the former,
 | ||||||
|  | 			// we get the a col and b col values (if they exist), figure out if they are the same or not, and if the column moved, and add it to the diff table
 | ||||||
|  | 			for bIndex < len(b2aColMap) && b2aColMap[bIndex] != -1 && (aIndex >= len(a2bColMap) || bIndex < aIndex) { | ||||||
|  | 				var diffTableCell TableDiffCell | ||||||
|  | 
 | ||||||
|  | 				var aCell *string | ||||||
|  | 				// get the aCell value if the aRow exists
 | ||||||
|  | 				if aRow != nil { | ||||||
|  | 					if cell, err := getCell(*aRow, b2aColMap[bIndex]); err != nil { | ||||||
|  | 						if err != ErrorUndefinedCell { | ||||||
|  | 							return nil, err | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						aCell = &cell | ||||||
|  | 						diffTableCell.LeftCell = cell | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					diffTableCell.Type = TableDiffCellAdd | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				cells[i] = &TableDiffCell{LeftCell: acell, RightCell: bcell, Type: celltype} | 				var bCell *string | ||||||
| 			} | 				// get the bCell value if the bRow exists
 | ||||||
| 		} | 				if bRow != nil { | ||||||
| 		for i := 0; i < len(b2a); i++ { | 					if cell, err := getCell(*bRow, bIndex); err != nil { | ||||||
| 			if b2a[i] == unmappedColumn { | 						if err != ErrorUndefinedCell { | ||||||
| 				bcell, _ := getCell(brow, i) | 							return nil, err | ||||||
| 				cells[i] = &TableDiffCell{LeftCell: bcell, Type: TableDiffCellAdd} | 						} | ||||||
|  | 					} else { | ||||||
|  | 						bCell = &cell | ||||||
|  | 						diffTableCell.RightCell = cell | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					diffTableCell.Type = TableDiffCellDel | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// if both a and b have a row that exists, compare the value and determine if the row has moved
 | ||||||
|  | 				if aCell != nil && bCell != nil { | ||||||
|  | 					moved := ((bIndex + colsDeleted) != (b2aColMap[bIndex] + colsAdded)) | ||||||
|  | 					if *aCell != *bCell { | ||||||
|  | 						if moved { | ||||||
|  | 							diffTableCell.Type = TableDiffCellMovedChanged | ||||||
|  | 						} else { | ||||||
|  | 							diffTableCell.Type = TableDiffCellChanged | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						if moved { | ||||||
|  | 							diffTableCell.Type = TableDiffCellMovedUnchanged | ||||||
|  | 						} else { | ||||||
|  | 							diffTableCell.Type = TableDiffCellUnchanged | ||||||
|  | 						} | ||||||
|  | 						diffTableCell.LeftCell = "" | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// Add the diff column to the diff row
 | ||||||
|  | 				diffTableCells[bIndex+colsDeleted] = &diffTableCell | ||||||
|  | 				bIndex++ | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return &TableDiffRow{RowIdx: bline, Cells: cells}, nil | 		return &TableDiffRow{RowIdx: bLineNum, Cells: diffTableCells}, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var sections []*TableDiffSection | 	// diffTableSections are TableDiffSections which represent the diffTableSections we get when doing a diff, each will be its own table in the view
 | ||||||
|  | 	var diffTableSections []*TableDiffSection | ||||||
| 
 | 
 | ||||||
| 	for i, section := range diffFile.Sections { | 	for i, section := range diffFile.Sections { | ||||||
| 		var rows []*TableDiffRow | 		// Each section has multiple diffTableRows
 | ||||||
|  | 		var diffTableRows []*TableDiffRow | ||||||
| 		lines := tryMergeLines(section.Lines) | 		lines := tryMergeLines(section.Lines) | ||||||
|  | 		// Loop through the merged lines to get each row of the CSV diff table for this section
 | ||||||
| 		for j, line := range lines { | 		for j, line := range lines { | ||||||
| 			if i == 0 && j == 0 && (line[0] != 1 || line[1] != 1) { | 			if i == 0 && j == 0 && (line[0] != 1 || line[1] != 1) { | ||||||
| 				diffRow, err := createDiffRow(1, 1) | 				diffTableRow, err := createDiffTableRow(1, 1) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return nil, err | 					return nil, err | ||||||
| 				} | 				} | ||||||
| 				if diffRow != nil { | 				if diffTableRow != nil { | ||||||
| 					rows = append(rows, diffRow) | 					diffTableRows = append(diffTableRows, diffTableRow) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			diffRow, err := createDiffRow(line[0], line[1]) | 			diffTableRow, err := createDiffTableRow(line[0], line[1]) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			if diffRow != nil { | 			if diffTableRow != nil { | ||||||
| 				rows = append(rows, diffRow) | 				diffTableRows = append(diffTableRows, diffTableRow) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if len(rows) > 0 { | 		if len(diffTableRows) > 0 { | ||||||
| 			sections = append(sections, &TableDiffSection{Rows: rows}) | 			diffTableSections = append(diffTableSections, &TableDiffSection{Rows: diffTableRows}) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return sections, nil | 	return diffTableSections, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // getColumnMapping creates a mapping of columns between a and b
 | // getColumnMapping creates a mapping of columns between a and b
 | ||||||
| func getColumnMapping(a *csvReader, b *csvReader) ([]int, []int) { | func getColumnMapping(baseCSVReader *csvReader, headCSVReader *csvReader) ([]int, []int) { | ||||||
| 	arow, _ := a.GetRow(0) | 	baseRow, _ := baseCSVReader.GetRow(0) | ||||||
| 	brow, _ := b.GetRow(0) | 	headRow, _ := headCSVReader.GetRow(0) | ||||||
| 
 | 
 | ||||||
| 	a2b := []int{} | 	base2HeadColMap := []int{} | ||||||
| 	b2a := []int{} | 	head2BaseColMap := []int{} | ||||||
| 
 | 
 | ||||||
| 	if arow != nil { | 	if baseRow != nil { | ||||||
| 		a2b = make([]int, len(arow)) | 		base2HeadColMap = make([]int, len(baseRow)) | ||||||
| 	} | 	} | ||||||
| 	if brow != nil { | 	if headRow != nil { | ||||||
| 		b2a = make([]int, len(brow)) | 		head2BaseColMap = make([]int, len(headRow)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for i := 0; i < len(b2a); i++ { | 	// Initializes all head2base mappings to be unmappedColumn (-1)
 | ||||||
| 		b2a[i] = unmappedColumn | 	for i := 0; i < len(head2BaseColMap); i++ { | ||||||
|  | 		head2BaseColMap[i] = unmappedColumn | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bcol := 0 | 	// Loops through the baseRow and see if there is a match in the head row
 | ||||||
| 	for i := 0; i < len(a2b); i++ { | 	for i := 0; i < len(baseRow); i++ { | ||||||
| 		a2b[i] = unmappedColumn | 		base2HeadColMap[i] = unmappedColumn | ||||||
| 
 | 		baseCell, err := getCell(baseRow, i) | ||||||
| 		acell, ea := getCell(arow, i) | 		if err == nil { | ||||||
| 		if ea == nil { | 			for j := 0; j < len(headRow); j++ { | ||||||
| 			for j := bcol; j < len(b2a); j++ { | 				if head2BaseColMap[j] == -1 { | ||||||
| 				bcell, eb := getCell(brow, j) | 					headCell, err := getCell(headRow, j) | ||||||
| 				if eb == nil && acell == bcell { | 					if err == nil && baseCell == headCell { | ||||||
| 					a2b[i] = j | 						base2HeadColMap[i] = j | ||||||
| 					b2a[j] = i | 						head2BaseColMap[j] = i | ||||||
| 					bcol = j + 1 | 						break | ||||||
| 					break | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	tryMapColumnsByContent(a, a2b, b, b2a) | 	tryMapColumnsByContent(baseCSVReader, base2HeadColMap, headCSVReader, head2BaseColMap) | ||||||
| 	tryMapColumnsByContent(b, b2a, a, a2b) | 	tryMapColumnsByContent(headCSVReader, head2BaseColMap, baseCSVReader, base2HeadColMap) | ||||||
| 
 | 
 | ||||||
| 	return a2b, b2a | 	return base2HeadColMap, head2BaseColMap | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // tryMapColumnsByContent tries to map missing columns by the content of the first lines.
 | // tryMapColumnsByContent tries to map missing columns by the content of the first lines.
 | ||||||
| func tryMapColumnsByContent(a *csvReader, a2b []int, b *csvReader, b2a []int) { | func tryMapColumnsByContent(baseCSVReader *csvReader, base2HeadColMap []int, headCSVReader *csvReader, head2BaseColMap []int) { | ||||||
| 	start := 0 | 	for i := 0; i < len(base2HeadColMap); i++ { | ||||||
| 	for i := 0; i < len(a2b); i++ { | 		headStart := 0 | ||||||
| 		if a2b[i] == unmappedColumn { | 		for base2HeadColMap[i] == unmappedColumn && headStart < len(head2BaseColMap) { | ||||||
| 			if b2a[start] == unmappedColumn { | 			if head2BaseColMap[headStart] == unmappedColumn { | ||||||
| 				rows := util.Min(maxRowsToInspect, util.Max(0, util.Min(len(a.buffer), len(b.buffer))-1)) | 				rows := util.Min(maxRowsToInspect, util.Max(0, util.Min(len(baseCSVReader.buffer), len(headCSVReader.buffer))-1)) | ||||||
| 				same := 0 | 				same := 0 | ||||||
| 				for j := 1; j <= rows; j++ { | 				for j := 1; j <= rows; j++ { | ||||||
| 					acell, ea := getCell(a.buffer[j], i) | 					baseCell, baseErr := getCell(baseCSVReader.buffer[j], i) | ||||||
| 					bcell, eb := getCell(b.buffer[j], start+1) | 					headCell, headErr := getCell(headCSVReader.buffer[j], headStart) | ||||||
| 					if ea == nil && eb == nil && acell == bcell { | 					if baseErr == nil && headErr == nil && baseCell == headCell { | ||||||
| 						same++ | 						same++ | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				if (float32(same) / float32(rows)) > minRatioToMatch { | 				if (float32(same) / float32(rows)) > minRatioToMatch { | ||||||
| 					a2b[i] = start + 1 | 					base2HeadColMap[i] = headStart | ||||||
| 					b2a[start+1] = i | 					head2BaseColMap[headStart] = i | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 			headStart++ | ||||||
| 		} | 		} | ||||||
| 		start = a2b[i] |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -328,7 +419,7 @@ func getCell(row []string, column int) (string, error) { | ||||||
| 	if column < len(row) { | 	if column < len(row) { | ||||||
| 		return row[column], nil | 		return row[column], nil | ||||||
| 	} | 	} | ||||||
| 	return "", errors.New("Undefined column") | 	return "", ErrorUndefinedCell | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // countUnmappedColumns returns the count of unmapped columns.
 | // countUnmappedColumns returns the count of unmapped columns.
 | ||||||
|  |  | ||||||
|  | @ -19,9 +19,9 @@ func TestCSVDiff(t *testing.T) { | ||||||
| 		diff  string | 		diff  string | ||||||
| 		base  string | 		base  string | ||||||
| 		head  string | 		head  string | ||||||
| 		cells [][2]TableDiffCellType | 		cells [][]TableDiffCellType | ||||||
| 	}{ | 	}{ | ||||||
| 		// case 0
 | 		// case 0 - initial commit of a csv
 | ||||||
| 		{ | 		{ | ||||||
| 			diff: `diff --git a/unittest.csv b/unittest.csv | 			diff: `diff --git a/unittest.csv b/unittest.csv | ||||||
| --- a/unittest.csv | --- a/unittest.csv | ||||||
|  | @ -29,11 +29,14 @@ func TestCSVDiff(t *testing.T) { | ||||||
| @@ -0,0 +1,2 @@ | @@ -0,0 +1,2 @@ | ||||||
| +col1,col2 | +col1,col2 | ||||||
| +a,a`, | +a,a`, | ||||||
| 			base:  "", | 			base: "", | ||||||
| 			head:  "col1,col2\na,a", | 			head: `col1,col2 | ||||||
| 			cells: [][2]TableDiffCellType{{TableDiffCellAdd, TableDiffCellAdd}, {TableDiffCellAdd, TableDiffCellAdd}}, | a,a`, | ||||||
|  | 			cells: [][]TableDiffCellType{ | ||||||
|  | 				{TableDiffCellAdd, TableDiffCellAdd}, | ||||||
|  | 				{TableDiffCellAdd, TableDiffCellAdd}}, | ||||||
| 		}, | 		}, | ||||||
| 		// case 1
 | 		// case 1 - adding 1 row at end
 | ||||||
| 		{ | 		{ | ||||||
| 			diff: `diff --git a/unittest.csv b/unittest.csv | 			diff: `diff --git a/unittest.csv b/unittest.csv | ||||||
| --- a/unittest.csv | --- a/unittest.csv | ||||||
|  | @ -43,11 +46,17 @@ func TestCSVDiff(t *testing.T) { | ||||||
| -a,a | -a,a | ||||||
| +a,a | +a,a | ||||||
| +b,b`, | +b,b`, | ||||||
| 			base:  "col1,col2\na,a", | 			base: `col1,col2 | ||||||
| 			head:  "col1,col2\na,a\nb,b", | a,a`, | ||||||
| 			cells: [][2]TableDiffCellType{{TableDiffCellEqual, TableDiffCellEqual}, {TableDiffCellEqual, TableDiffCellEqual}, {TableDiffCellAdd, TableDiffCellAdd}}, | 			head: `col1,col2 | ||||||
|  | a,a | ||||||
|  | b,b`, | ||||||
|  | 			cells: [][]TableDiffCellType{ | ||||||
|  | 				{TableDiffCellUnchanged, TableDiffCellUnchanged}, {TableDiffCellUnchanged, TableDiffCellUnchanged}, | ||||||
|  | 				{TableDiffCellAdd, TableDiffCellAdd}, | ||||||
|  | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		// case 2
 | 		// case 2 - row deleted
 | ||||||
| 		{ | 		{ | ||||||
| 			diff: `diff --git a/unittest.csv b/unittest.csv | 			diff: `diff --git a/unittest.csv b/unittest.csv | ||||||
| --- a/unittest.csv | --- a/unittest.csv | ||||||
|  | @ -56,11 +65,17 @@ func TestCSVDiff(t *testing.T) { | ||||||
|  col1,col2 |  col1,col2 | ||||||
| -a,a | -a,a | ||||||
|  b,b`, |  b,b`, | ||||||
| 			base:  "col1,col2\na,a\nb,b", | 			base: `col1,col2 | ||||||
| 			head:  "col1,col2\nb,b", | a,a | ||||||
| 			cells: [][2]TableDiffCellType{{TableDiffCellEqual, TableDiffCellEqual}, {TableDiffCellDel, TableDiffCellDel}, {TableDiffCellEqual, TableDiffCellEqual}}, | b,b`, | ||||||
|  | 			head: `col1,col2 | ||||||
|  | b,b`, | ||||||
|  | 			cells: [][]TableDiffCellType{ | ||||||
|  | 				{TableDiffCellUnchanged, TableDiffCellUnchanged}, {TableDiffCellDel, TableDiffCellDel}, | ||||||
|  | 				{TableDiffCellUnchanged, TableDiffCellUnchanged}, | ||||||
|  | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		// case 3
 | 		// case 3 - row changed
 | ||||||
| 		{ | 		{ | ||||||
| 			diff: `diff --git a/unittest.csv b/unittest.csv | 			diff: `diff --git a/unittest.csv b/unittest.csv | ||||||
| --- a/unittest.csv | --- a/unittest.csv | ||||||
|  | @ -69,11 +84,16 @@ func TestCSVDiff(t *testing.T) { | ||||||
|  col1,col2 |  col1,col2 | ||||||
| -b,b | -b,b | ||||||
| +b,c`, | +b,c`, | ||||||
| 			base:  "col1,col2\nb,b", | 			base: `col1,col2 | ||||||
| 			head:  "col1,col2\nb,c", | b,b`, | ||||||
| 			cells: [][2]TableDiffCellType{{TableDiffCellEqual, TableDiffCellEqual}, {TableDiffCellEqual, TableDiffCellChanged}}, | 			head: `col1,col2 | ||||||
|  | b,c`, | ||||||
|  | 			cells: [][]TableDiffCellType{ | ||||||
|  | 				{TableDiffCellUnchanged, TableDiffCellUnchanged}, | ||||||
|  | 				{TableDiffCellUnchanged, TableDiffCellChanged}, | ||||||
|  | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		// case 4
 | 		// case 4 - all deleted
 | ||||||
| 		{ | 		{ | ||||||
| 			diff: `diff --git a/unittest.csv b/unittest.csv | 			diff: `diff --git a/unittest.csv b/unittest.csv | ||||||
| --- a/unittest.csv | --- a/unittest.csv | ||||||
|  | @ -81,9 +101,88 @@ func TestCSVDiff(t *testing.T) { | ||||||
| @@ -1,2 +0,0 @@ | @@ -1,2 +0,0 @@ | ||||||
| -col1,col2 | -col1,col2 | ||||||
| -b,c`, | -b,c`, | ||||||
| 			base:  "col1,col2\nb,c", | 			base: `col1,col2 | ||||||
| 			head:  "", | b,c`, | ||||||
| 			cells: [][2]TableDiffCellType{{TableDiffCellDel, TableDiffCellDel}, {TableDiffCellDel, TableDiffCellDel}}, | 			head: "", | ||||||
|  | 			cells: [][]TableDiffCellType{ | ||||||
|  | 				{TableDiffCellDel, TableDiffCellDel}, | ||||||
|  | 				{TableDiffCellDel, TableDiffCellDel}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		// case 5 - renames first column
 | ||||||
|  | 		{ | ||||||
|  | 			diff: `diff --git a/unittest.csv b/unittest.csv | ||||||
|  | --- a/unittest.csv | ||||||
|  | +++ b/unittest.csv | ||||||
|  | @@ -1,3 +1,3 @@ | ||||||
|  | -col1,col2,col3 | ||||||
|  | +cola,col2,col3 | ||||||
|  |  a,b,c`, | ||||||
|  | 			base: `col1,col2,col3 | ||||||
|  | a,b,c`, | ||||||
|  | 			head: `cola,col2,col3 | ||||||
|  | a,b,c`, | ||||||
|  | 			cells: [][]TableDiffCellType{ | ||||||
|  | 				{TableDiffCellDel, TableDiffCellAdd, TableDiffCellUnchanged, TableDiffCellUnchanged}, | ||||||
|  | 				{TableDiffCellDel, TableDiffCellAdd, TableDiffCellUnchanged, TableDiffCellUnchanged}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		// case 6 - inserts a column after first, deletes last column
 | ||||||
|  | 		{ | ||||||
|  | 			diff: `diff --git a/unittest.csv b/unittest.csv | ||||||
|  | --- a/unittest.csv | ||||||
|  | +++ b/unittest.csv | ||||||
|  | @@ -1,2 +1,2 @@ | ||||||
|  | -col1,col2,col3 | ||||||
|  | -a,b,c | ||||||
|  | +col1,col1a,col2 | ||||||
|  | +a,d,b`, | ||||||
|  | 			base: `col1,col2,col3 | ||||||
|  | a,b,c`, | ||||||
|  | 			head: `col1,col1a,col2 | ||||||
|  | a,d,b`, | ||||||
|  | 			cells: [][]TableDiffCellType{ | ||||||
|  | 				{TableDiffCellUnchanged, TableDiffCellAdd, TableDiffCellDel, TableDiffCellMovedUnchanged}, | ||||||
|  | 				{TableDiffCellUnchanged, TableDiffCellAdd, TableDiffCellDel, TableDiffCellMovedUnchanged}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		// case 7 - deletes first column, inserts column after last
 | ||||||
|  | 		{ | ||||||
|  | 			diff: `diff --git a/unittest.csv b/unittest.csv | ||||||
|  | --- a/unittest.csv | ||||||
|  | +++ b/unittest.csv | ||||||
|  | @@ -1,2 +1,2 @@ | ||||||
|  | -col1,col2,col3 | ||||||
|  | -a,b,c | ||||||
|  | +col2,col3,col4 | ||||||
|  | +b,c,d`, | ||||||
|  | 			base: `col1,col2,col3 | ||||||
|  | a,b,c`, | ||||||
|  | 			head: `col2,col3,col4 | ||||||
|  | b,c,d`, | ||||||
|  | 			cells: [][]TableDiffCellType{ | ||||||
|  | 				{TableDiffCellDel, TableDiffCellUnchanged, TableDiffCellUnchanged, TableDiffCellAdd}, | ||||||
|  | 				{TableDiffCellDel, TableDiffCellUnchanged, TableDiffCellUnchanged, TableDiffCellAdd}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		// case 8 - two columns deleted, 2 added
 | ||||||
|  | 		{ | ||||||
|  | 			diff: `diff --git a/unittest.csv b/unittest.csv | ||||||
|  | --- a/unittest.csv | ||||||
|  | +++ b/unittest.csv | ||||||
|  | @@ -1,2 +1,2 @@ | ||||||
|  | -col1,col2,col | ||||||
|  | -a,b,c | ||||||
|  | +col3,col4,col5 | ||||||
|  | +c,d,e`, | ||||||
|  | 			base: `col1,col2,col3 | ||||||
|  | a,b,c`, | ||||||
|  | 			head: `col3,col4,col5 | ||||||
|  | c,d,e`, | ||||||
|  | 			cells: [][]TableDiffCellType{ | ||||||
|  | 				{TableDiffCellDel, TableDiffCellMovedUnchanged, TableDiffCellDel, TableDiffCellAdd, TableDiffCellAdd}, | ||||||
|  | 				{TableDiffCellDel, TableDiffCellMovedUnchanged, TableDiffCellDel, TableDiffCellAdd, TableDiffCellAdd}, | ||||||
|  | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -116,7 +215,7 @@ func TestCSVDiff(t *testing.T) { | ||||||
| 		assert.Len(t, section.Rows, len(c.cells), "case %d: should be %d rows", n, len(c.cells)) | 		assert.Len(t, section.Rows, len(c.cells), "case %d: should be %d rows", n, len(c.cells)) | ||||||
| 
 | 
 | ||||||
| 		for i, row := range section.Rows { | 		for i, row := range section.Rows { | ||||||
| 			assert.Len(t, row.Cells, 2, "case %d: row %d should have two cells", n, i) | 			assert.Len(t, row.Cells, len(c.cells[i]), "case %d: row %d should have %d cells", n, i, len(c.cells[i])) | ||||||
| 			for j, cell := range row.Cells { | 			for j, cell := range row.Cells { | ||||||
| 				assert.Equal(t, c.cells[i][j], cell.Type, "case %d: row %d cell %d should be equal", n, i, j) | 				assert.Equal(t, c.cells[i][j], cell.Type, "case %d: row %d cell %d should be equal", n, i, j) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -12,12 +12,18 @@ | ||||||
| 						{{if and (eq $i 0) (eq $j 0)}} | 						{{if and (eq $i 0) (eq $j 0)}} | ||||||
| 							<th class="line-num">{{.RowIdx}}</th> | 							<th class="line-num">{{.RowIdx}}</th> | ||||||
| 							{{range $j, $cell := $row.Cells}} | 							{{range $j, $cell := $row.Cells}} | ||||||
| 								{{if eq $cell.Type 2}} | 								{{if not $cell}} | ||||||
|  | 									<th></th> | ||||||
|  | 								{{else if eq $cell.Type 2}} | ||||||
| 									<th class="modified"><span class="removed-code">{{.LeftCell}}</span> <span class="added-code">{{.RightCell}}</span></th> | 									<th class="modified"><span class="removed-code">{{.LeftCell}}</span> <span class="added-code">{{.RightCell}}</span></th> | ||||||
| 								{{else if eq $cell.Type 3}} | 								{{else if eq $cell.Type 3}} | ||||||
| 									<th class="added"><span class="added-code">{{.LeftCell}}</span></th> | 									<th class="added"><span class="added-code">{{.RightCell}}</span></th> | ||||||
| 								{{else if eq $cell.Type 4}} | 								{{else if eq $cell.Type 4}} | ||||||
| 									<th class="removed"><span class="removed-code">{{.LeftCell}}</span></th> | 									<th class="removed"><span class="removed-code">{{.LeftCell}}</span></th> | ||||||
|  | 								{{else if eq $cell.Type 5}} | ||||||
|  | 									<th class="moved">{{.RightCell}}</th> | ||||||
|  | 								{{else if eq $cell.Type 6}} | ||||||
|  | 									<th class="moved"><span class="removed-code">{{.LeftCell}}</span> <span class="added-code">{{.RightCell}}</span></th> | ||||||
| 								{{else}} | 								{{else}} | ||||||
| 									<th>{{.RightCell}}</th> | 									<th>{{.RightCell}}</th> | ||||||
| 								{{end}} | 								{{end}} | ||||||
|  | @ -25,12 +31,18 @@ | ||||||
| 						{{else}} | 						{{else}} | ||||||
| 							<td class="line-num">{{if .RowIdx}}{{.RowIdx}}{{end}}</td> | 							<td class="line-num">{{if .RowIdx}}{{.RowIdx}}{{end}}</td> | ||||||
| 							{{range $j, $cell := $row.Cells}} | 							{{range $j, $cell := $row.Cells}} | ||||||
| 								{{if eq $cell.Type 2}} | 								{{if not $cell}} | ||||||
|  | 									<td></td> | ||||||
|  | 								{{else if eq $cell.Type 2}} | ||||||
| 									<td class="modified"><span class="removed-code">{{.LeftCell}}</span> <span class="added-code">{{.RightCell}}</span></td> | 									<td class="modified"><span class="removed-code">{{.LeftCell}}</span> <span class="added-code">{{.RightCell}}</span></td> | ||||||
| 								{{else if eq $cell.Type 3}} | 								{{else if eq $cell.Type 3}} | ||||||
| 									<td class="added"><span class="added-code">{{.LeftCell}}</span></td> | 									<td class="added"><span class="added-code">{{.RightCell}}</span></td> | ||||||
| 								{{else if eq $cell.Type 4}} | 								{{else if eq $cell.Type 4}} | ||||||
| 									<td class="removed"><span class="removed-code">{{.LeftCell}}</span></td> | 									<td class="removed"><span class="removed-code">{{.LeftCell}}</span></td> | ||||||
|  | 								{{else if eq $cell.Type 5}} | ||||||
|  | 									<td class="moved">{{.RightCell}}</td> | ||||||
|  | 								{{else if eq $cell.Type 6}} | ||||||
|  | 									<td class="moved"><span class="removed-code">{{.LeftCell}}</span> <span class="added-code">{{.RightCell}}</span></td> | ||||||
| 								{{else}} | 								{{else}} | ||||||
| 									<td>{{.RightCell}}</td> | 									<td>{{.RightCell}}</td> | ||||||
| 								{{end}} | 								{{end}} | ||||||
|  |  | ||||||
|  | @ -76,8 +76,10 @@ | ||||||
|   --color-diff-removed-word-bg: #fdb8c0; |   --color-diff-removed-word-bg: #fdb8c0; | ||||||
|   --color-diff-added-word-bg: #acf2bd; |   --color-diff-added-word-bg: #acf2bd; | ||||||
|   --color-diff-removed-row-bg: #ffeef0; |   --color-diff-removed-row-bg: #ffeef0; | ||||||
|  |   --color-diff-moved-row-bg: #f1f8d1; | ||||||
|   --color-diff-added-row-bg: #e6ffed; |   --color-diff-added-row-bg: #e6ffed; | ||||||
|   --color-diff-removed-row-border: #f1c0c0; |   --color-diff-removed-row-border: #f1c0c0; | ||||||
|  |   --color-diff-moved-row-border: #d0e27f; | ||||||
|   --color-diff-added-row-border: #e6ffed; |   --color-diff-added-row-border: #e6ffed; | ||||||
|   --color-diff-inactive: #f2f2f2; |   --color-diff-inactive: #f2f2f2; | ||||||
|   /* target-based colors */ |   /* target-based colors */ | ||||||
|  |  | ||||||
|  | @ -1500,6 +1500,12 @@ | ||||||
|       background-color: var(--color-diff-removed-row-bg) !important; |       background-color: var(--color-diff-removed-row-bg) !important; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     td.moved, | ||||||
|  |     th.moved, | ||||||
|  |     tr.moved { | ||||||
|  |       background-color: var(--color-diff-moved-row-bg) !important; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     tbody.section { |     tbody.section { | ||||||
|       border-top: 2px solid var(--color-secondary); |       border-top: 2px solid var(--color-secondary); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -71,8 +71,10 @@ | ||||||
|   --color-diff-removed-word-bg: #6f3333; |   --color-diff-removed-word-bg: #6f3333; | ||||||
|   --color-diff-added-word-bg: #3c653c; |   --color-diff-added-word-bg: #3c653c; | ||||||
|   --color-diff-removed-row-bg: #3c2626; |   --color-diff-removed-row-bg: #3c2626; | ||||||
|  |   --color-diff-moved-row-bg: #818044; | ||||||
|   --color-diff-added-row-bg: #283e2d; |   --color-diff-added-row-bg: #283e2d; | ||||||
|   --color-diff-removed-row-border: #634343; |   --color-diff-removed-row-border: #634343; | ||||||
|  |   --color-diff-moved-row-border: #bcca6f; | ||||||
|   --color-diff-added-row-border: #314a37; |   --color-diff-added-row-border: #314a37; | ||||||
|   --color-diff-inactive: #353846; |   --color-diff-inactive: #353846; | ||||||
|   /* target-based colors */ |   /* target-based colors */ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue