github.com/verygoodsoftwarenotvirus/cartogopher@v0.0.0-20160213112503-f5fe2b6d4bd1/reader.go (about)

     1  package cartogopher
     2  
     3  import (
     4  	"encoding/csv"
     5  	"fmt"
     6  	"io"
     7  )
     8  
     9  // MapReader contains all our necessary data for the various methods to function
    10  type MapReader struct {
    11  	Headers        []string
    12  	HeaderIndexMap map[string]int
    13  	Reader         *csv.Reader
    14  }
    15  
    16  // CreateHeaderIndexMap creates a map of header strings to their indices in the array generated by
    17  // encoding/csv's reader. For instance, if your CSV file looks something like
    18  // this:
    19  // 	---------------------
    20  // 	| one | two | three |
    21  // 	---------------------
    22  // 	|  A  |  B  |   C   |
    23  // 	---------------------
    24  // Go's generated array for the header row will be [ "one", "two", "three" ].
    25  // Cartogopher's generated map for the header row will be { "one": 1, "two": 2, "three": 3 }
    26  func (m *MapReader) CreateHeaderIndexMap() {
    27  	headerIndexMap := make(map[string]int, len(m.Headers))
    28  
    29  	for index, header := range m.Headers {
    30  		headerIndexMap[header] = index
    31  	}
    32  
    33  	m.HeaderIndexMap = headerIndexMap
    34  }
    35  
    36  // CreateRowMap takes a given CSV array and returns a map of column names to the values contained therein.
    37  // For instance, if your CSV file looks something like this:
    38  // 	---------------------
    39  // 	| one | two | three |
    40  // 	---------------------
    41  // 	|  A  |  B  |   C   |
    42  // 	---------------------
    43  // The return result will be:
    44  // 	{
    45  // 		"one": "A",
    46  // 		"two": "B",
    47  // 		"three": "C",
    48  // 	}
    49  // Note that this requires the HeaderIndexMap to be created and not a null value.
    50  func (m MapReader) CreateRowMap(csvRow []string) map[string]string {
    51  	result := map[string]string{}
    52  	for header, index := range m.HeaderIndexMap {
    53  		result[header] = csvRow[index]
    54  	}
    55  
    56  	return result
    57  }
    58  
    59  // Read mimics the built-in CSV reader Read method, returning one row of
    60  // the CSV. The only difference here being that obviously we return a
    61  // map instead of a slice.
    62  func (m MapReader) Read() (map[string]string, error) {
    63  	csvRow, err := m.Reader.Read()
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	return m.CreateRowMap(csvRow), nil
    68  }
    69  
    70  // ReadAll mimics the built-in CSV reader ReadAll method
    71  func (m MapReader) ReadAll() ([]map[string]string, error) {
    72  	records, err := m.Reader.ReadAll()
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	results := []map[string]string{}
    77  	for _, record := range records {
    78  		results = append(results, m.CreateRowMap(record))
    79  	}
    80  	return results, nil
    81  }
    82  
    83  // NewReader returns a new MapReader struct. It can be created the same way
    84  // a regular CSV file is created, by providing it with a reference to a file
    85  // reader, ideally one that points to a CSV file.  I'm using an interface
    86  // here so that, should the need arise, you can provide your CSV to the
    87  // package in a variety of non-file based ways. Note that here we read the
    88  // first row of the file without setting any non-standard values for the
    89  // CSV package's Reader struct. If it becomes apparent that the ability to
    90  // change these parameters is vital, then I'm more than happy to figure out
    91  // an idiomatic way to accomplish that task.
    92  func NewReader(file io.Reader) (*MapReader, error) {
    93  	// Create our reader
    94  	reader := csv.NewReader(file)
    95  
    96  	// Create our resulting struct
    97  	output := &MapReader{}
    98  
    99  	inputHeaders, err := reader.Read()
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	// Use our methods (defined above) to populate our struct fields
   105  	output.Headers = inputHeaders
   106  	output.Reader = reader
   107  	output.CreateHeaderIndexMap()
   108  
   109  	if output.HeaderIndexMap == nil {
   110  		return nil, fmt.Errorf("error assigning header to index map for CSV file")
   111  	}
   112  
   113  	return output, nil
   114  }