1 // Copyright 2013-2017 the openage authors. See copying.md for legal info.
2 
3 #pragma once
4 
5 #include <cstdlib>
6 #include <cstring>
7 #include <fstream>
8 #include <string>
9 #include <unordered_map>
10 #include <vector>
11 
12 #include "../error/error.h"
13 #include "compiler.h"
14 #include "fslike/native.h"
15 #include "path.h"
16 
17 
18 namespace openage {
19 namespace util {
20 
21 
22 /**
23  * Collection of multiple csv files.
24  * Read from a packed csv that contains all the data.
25  *
26  * Then, data can be read recursively.
27  */
28 class CSVCollection {
29 public:
30 
31 	/**
32 	 * Type for storing csv data:
33 	 * {filename: [line, ...]}.
34 	 */
35 	using csv_file_map_t = std::unordered_map<std::string, std::vector<std::string>>;
36 
37 	/**
38 	 * Initialize the collection by reading the given file.
39 	 * This file must contain the data that this collection is made up of.
40 	 */
41 	explicit CSVCollection(const Path &entryfile);
42 	virtual ~CSVCollection() = default;
43 
44 
45 	/**
46 	 * This function is the entry point to load the whole file tree recursively.
47 	 *
48 	 * Should be called again from the .recurse() method of the struct.
49 	 *
50 	 * The internal flow is as follows:
51 	 * * read entries of the given files
52 	 *   (call to the generated field parsers (the fill function))
53 	 * * then, recurse into referenced subdata entries
54 	 *   (this implementation is generated)
55 	 * * from there, reach this function again to read each subdata entry.
56 	 *
57 	 */
58 	template<class lineformat>
read(const std::string & filename)59 	std::vector<lineformat> read(const std::string &filename) const {
60 
61 		// first read the content from the data
62 		auto result = this->get_data<lineformat>(filename);
63 
64 		std::string dir = dirname(filename);
65 
66 		size_t line_count = 0;
67 
68 		// then recurse into each subdata entry.
69 		for (auto &entry : result) {
70 			line_count += 1;
71 
72 			if (not entry.recurse(*this, dir)) {
73 				throw Error{
74 					MSG(err)
75 					<< "Failed to read follow-up files for "
76 					<< filename << ":" << line_count
77 				};
78 			}
79 		}
80 
81 		return result;
82 	}
83 
84 
85 	/**
86 	 * Parse the data from one file in the map.
87 	 */
88 	template<typename lineformat>
get_data(const std::string & filename)89 	std::vector<lineformat> get_data(const std::string &filename) const {
90 
91 		size_t line_count = 0;
92 
93 		std::vector<lineformat> ret;
94 
95 		// locate the data in the collection
96 		auto it = this->data.find(filename);
97 
98 		if (it != std::end(this->data)) {
99 			const std::vector<std::string> &lines = it->second;
100 
101 			for (auto &line : lines) {
102 				line_count += 1;
103 				lineformat current_line_data;
104 
105 				// use the line copy to fill the current line struct.
106 				int error_column = current_line_data.fill(line);
107 				if (error_column != -1) {
108 					throw Error{
109 						ERR
110 						<< "Failed to read CSV file: "
111 						<< filename << ":" << line_count << ":" << error_column
112 						<< ": error parsing " << line
113 					};
114 				}
115 
116 				ret.push_back(current_line_data);
117 			}
118 		}
119 		else {
120 			throw Error{
121 				ERR << "File was not found in csv cache: '"
122 				    << filename << "'"
123 			};
124 		}
125 
126 		return ret;
127 	}
128 
129 protected:
130 	csv_file_map_t data;
131 };
132 
133 
134 /**
135  * Referenced file tree structure.
136  *
137  * Used for storing information for subtype members
138  * that need to be recursed.
139  */
140 template<class lineformat>
141 struct csv_subdata {
142 	/**
143 	 * File where to read subdata from.
144 	 * This name is relative to the file that defined the subdata!
145 	 */
146 	std::string filename;
147 
148 	/**
149 	 * Data that was read from the file with this->filename.
150 	 */
151 	std::vector<lineformat> data;
152 
153 	/**
154 	 * Read the data of the lineformat from the collection.
155 	 * Can descend recursively into dependencies.
156 	 */
readcsv_subdata157 	bool read(const CSVCollection &collection, const std::string &basedir) {
158 		std::string next_file = basedir;
159 		if (basedir.size() > 0) {
160 			next_file += fslike::PATHSEP;
161 		}
162 		next_file += this->filename;
163 
164 		this->data = collection.read<lineformat>(next_file);
165 		return true;
166 	}
167 
168 	/**
169 	 * Convenience operator to access data
170 	 */
171 	const lineformat &operator [](size_t idx) const {
172 		return this->data[idx];
173 	}
174 };
175 
176 
177 /**
178  * read a single csv file.
179  * call the destination struct .fill() method for actually storing line data
180  */
181 template<typename lineformat>
read_csv_file(const Path & path)182 std::vector<lineformat> read_csv_file(const Path &path) {
183 
184 	File csv = path.open();
185 
186 	std::vector<lineformat> ret;
187 	size_t line_count = 0;
188 
189 	for (auto &line : csv.get_lines()) {
190 		line_count += 1;
191 
192 		// ignore comments and empty lines
193 		if (line.empty() || line[0] == '#') {
194 			continue;
195 		}
196 
197 		lineformat current_line_data;
198 
199 		// use the line copy to fill the current line struct.
200 		int error_column = current_line_data.fill(line);
201 		if (error_column != -1) {
202 			throw Error{
203 				ERR
204 				<< "Failed to read CSV file: "
205 				<< csv << ":" << line_count << ":" << error_column
206 				<< ": error parsing " << line
207 			};
208 		}
209 
210 		ret.push_back(current_line_data);
211 	}
212 
213 	return ret;
214 }
215 
216 }} // openage::util
217