1 package fr.inrae.agroclim.indicators;
2
3 import java.io.BufferedWriter;
4 import java.io.IOException;
5 import java.nio.file.Path;
6 import java.nio.file.Paths;
7 import java.text.DateFormat;
8 import java.text.SimpleDateFormat;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.Collections;
12 import java.util.Date;
13 import java.util.HashSet;
14 import java.util.LinkedList;
15 import java.util.List;
16 import java.util.Locale;
17 import java.util.Set;
18
19 import fr.inrae.agroclim.indicators.exception.IndicatorsException;
20 import fr.inrae.agroclim.indicators.exception.type.CommonErrorType;
21 import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
22 import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
23 import fr.inrae.agroclim.indicators.exception.type.XmlErrorType;
24 import fr.inrae.agroclim.indicators.model.Knowledge;
25 import fr.inrae.agroclim.indicators.model.LocalizedString;
26 import fr.inrae.agroclim.indicators.model.Note;
27 import fr.inrae.agroclim.indicators.model.Parameter;
28 import fr.inrae.agroclim.indicators.model.TimeScale;
29 import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
30 import fr.inrae.agroclim.indicators.model.indicator.Indicator;
31 import fr.inrae.agroclim.indicators.resources.I18n;
32 import fr.inrae.agroclim.indicators.util.StringUtils;
33 import fr.inrae.agroclim.indicators.util.Utf8BufferedWriter;
34 import lombok.extern.log4j.Log4j2;
35
36
37
38
39
40
41 @Log4j2
42 public class GenerateMarkdown {
43
44
45
46
47 private static final String FRONT_MATTER = """
48 ---
49 title: "%s"
50 description: "%s"
51 keywords: "%s"
52 date: %s
53 ---
54
55 """;
56
57
58
59
60
61
62 public static void main(final String[] args) throws IndicatorsException, IOException {
63 LOGGER.traceEntry("Arguments: {}", Arrays.asList(args));
64
65 final String outDir;
66
67 final String languageSep;
68 if (args != null && args.length > 0) {
69 outDir = args[0];
70 if (args.length > 1) {
71 languageSep = args[1];
72 } else {
73 languageSep = ".";
74 }
75 } else {
76 outDir = System.getProperty("java.io.tmpdir");
77 languageSep = ".";
78 }
79
80 for (final Locale locale : Arrays.asList(Locale.ENGLISH, Locale.FRENCH)) {
81 final String languageCode = locale.getLanguage();
82 Path path = Paths.get(outDir, "errors" + languageSep + languageCode + ".md");
83 var instance = new GenerateMarkdown(locale, null);
84 instance.writeErrorMdFile(path);
85 for (final TimeScale timescale : TimeScale.values()) {
86 LOGGER.trace("Generating files for {}...", timescale);
87 LOGGER.trace("Generating files for {}...", timescale);
88 instance = new GenerateMarkdown(locale, timescale);
89 final String suffix = "-" + timescale.name().toLowerCase();
90 path = Paths.get(outDir, "indicators" + suffix + languageSep + languageCode + ".md");
91 instance.writeIndicatorsMdFiles(path);
92 path = Paths.get(outDir, "indicators" + suffix + ".csv");
93 instance.writeIndicatorsCsvFiles(path);
94 path = Paths.get(outDir, "parameters" + suffix + ".csv");
95 instance.writeParametersCsvFiles(path);
96 LOGGER.trace("Generating files for {}... done", timescale);
97 }
98 }
99 }
100
101
102
103
104
105
106
107
108 private static void writeLn(final BufferedWriter writer,
109 final String... values) throws IOException {
110 final int nb = values.length;
111 writer.write("| ");
112 for (int i = 0; i < nb; i++) {
113 writer.write(values[i]);
114 if (i < nb - 1) {
115 writer.write(" | ");
116 }
117 }
118 writer.write(" |\n");
119 }
120
121
122
123 private final String created;
124
125
126
127
128 private final I18n i18n;
129
130
131
132 private final Knowledge knowledge;
133
134
135
136 private final Locale locale;
137
138
139
140
141
142
143
144
145 public GenerateMarkdown(final Locale l, final TimeScale timescale) throws IndicatorsException {
146 final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
147 created = df.format(new Date());
148 final String bundleName = "fr.inrae.agroclim.indicators.resources.messages";
149 i18n = new I18n(bundleName, l);
150 this.locale = l;
151 if (timescale == null) {
152 this.knowledge = null;
153 } else {
154 this.knowledge = Knowledge.load(timescale);
155 }
156 }
157
158
159
160
161
162
163
164 public void writeErrorMdFile(final Path path) throws IOException {
165 LOGGER.trace(path);
166 final String indicatorsVersion = fr.inrae.agroclim.indicators.resources.Version.getString("version");
167 try (BufferedWriter writer = new Utf8BufferedWriter(path)) {
168 writer.write(String.format(FRONT_MATTER,
169 i18n.get("markdown.error.title"),
170 i18n.get("markdown.error.description"),
171 i18n.get("markdown.error.keywords"),
172 created));
173 writer.write(i18n.format("markdown.error.version", indicatorsVersion) + "\n\n");
174 writer.write(i18n.get("markdown.error.feedback") + "\n\n");
175 writeLn(writer, i18n.get("markdown.error.fullcode"), i18n.get("markdown.error.name"),
176 i18n.get("markdown.error.message"));
177 writer.write("|:----------|:-----------|:-----------|\n");
178 final List<CommonErrorType> types = new ArrayList<>();
179 types.addAll(Arrays.asList(XmlErrorType.values()));
180 types.addAll(Arrays.asList(ResourceErrorType.values()));
181 types.addAll(Arrays.asList(ComputationErrorType.values()));
182 String previousCat = "";
183 for (final CommonErrorType type : types) {
184 var cat = type.getCategory().getCategory(i18n);
185 if (!previousCat.equals(cat)) {
186 var catCode = type.getCategory().getCode();
187 writeLn(writer, "**" + i18n.get("markdown.error.category") + " `" + catCode + "` - " + cat + "**");
188 previousCat = cat;
189 }
190 var fullCode = type.getFullCode();
191 var name = type.getName();
192 var description = i18n.get(type.getI18nKey());
193 writeLn(writer, fullCode, name, description);
194 }
195 }
196 }
197
198
199
200
201
202
203
204 public void writeIndicatorsCsvFiles(final Path path) throws IOException {
205 LOGGER.trace(path);
206 try (BufferedWriter writer = new Utf8BufferedWriter(path)) {
207 writer.write("id;nom_en;nom_fr;description_en;description_fr;variables;param\u00e8tres;notes\n");
208 for (final CompositeIndicator comp : knowledge.getIndicators()) {
209 for (final Indicator ind : comp.getIndicators()) {
210 writer.write(ind.getId());
211 writer.write(";");
212 writer.write(ind.getName("en"));
213 writer.write(";");
214 if (!ind.getName("fr").equals(ind.getName("en"))) {
215 writer.write(ind.getName("fr"));
216 }
217 writer.write(";");
218 final String description = ind.getDescription("fr");
219 if (!description.equals(ind.getDescription("en"))) {
220 writer.write(ind.getDescription("en"));
221 }
222 writer.write(";");
223 writer.write(description);
224 writer.write(";");
225 final List<String> variables = new LinkedList<>();
226 if (ind.getVariables() != null && !ind.getVariables().isEmpty()) {
227 ind.getVariables().forEach(variable -> variables.add(variable.getName()));
228 Collections.sort(variables);
229 writer.write(String.join(", ", variables));
230 }
231 writer.write(";");
232 final List<String> parameters = new LinkedList<>();
233 if (ind.getParameters() != null
234 && !ind.getParameters().isEmpty()) {
235 ind.getParameters().forEach(param -> parameters.add(param.getId()));
236 Collections.sort(parameters);
237 writer.write(String.join(", ", parameters));
238 }
239
240 writer.write(";");
241 final List<String> notes = new LinkedList<>();
242 if (ind.getNotes() != null && !ind.getNotes().isEmpty()) {
243 ind.getNotes().forEach(note -> notes.add(note.getId()));
244 writer.write(String.join(", ", notes));
245 }
246 writer.write("\n");
247 }
248 }
249 }
250 }
251
252
253
254
255
256 public void writeIndicatorsMdFiles(final Path path) throws IOException {
257 final String languageCode = locale.getLanguage();
258 final TimeScale timescale = knowledge.getTimescale();
259
260 LOGGER.trace(path);
261 try (BufferedWriter mdWriter = new Utf8BufferedWriter(path)) {
262 final long nb = knowledge.getIndicators().stream().mapToInt(comp -> comp.getIndicators().size()).sum();
263 final String indicatorsVersion = fr.inrae.agroclim.indicators.resources.Version.getString("version");
264 final String frontMatter = """
265 ---
266 title: %s
267 description: %s
268 keywords: %s
269 date: %s
270 ---
271
272 """;
273 mdWriter.write(String.format(frontMatter,
274 i18n.get("markdown.title." + timescale.name().toLowerCase()),
275 i18n.get("markdown.description." + timescale.name().toLowerCase()),
276 i18n.get("markdown.keywords"),
277 created));
278 mdWriter.write(i18n.format("markdown.indicators.version", indicatorsVersion) + "\n\n"
279 + "## " + i18n.format("markdown.indicators." + timescale.name().toLowerCase(), nb) + "\n");
280 writeLn(mdWriter, i18n.get("markdown.id"), i18n.get("markdown.name"), i18n.get("markdown.description"),
281 i18n.get("markdown.variables"), i18n.get("markdown.parameters"),
282 i18n.get("markdown.unit") + " [^1]", i18n.get("markdown.notes"));
283 mdWriter.write("|:---|:-----|:------------|:----------|:-----------|:-----------|:-----------|\n");
284
285 final Set<String> allVariables = new HashSet<>();
286 for (final CompositeIndicator comp : knowledge.getIndicators()) {
287 writeLn(mdWriter, "**" + comp.getName(languageCode) + "**");
288 for (final Indicator ind : comp.getIndicators()) {
289 final List<String> variables = new LinkedList<>();
290 if (ind.getVariables() != null
291 && !ind.getVariables().isEmpty()) {
292 ind.getVariables().forEach(variable -> variables.add(variable.getName()));
293 Collections.sort(variables);
294 allVariables.addAll(variables);
295 }
296 final List<String> parameters = new LinkedList<>();
297 if (ind.getParameters() != null
298 && !ind.getParameters().isEmpty()) {
299 ind.getParameters().forEach(param -> parameters.add(param.getId()));
300 Collections.sort(parameters);
301 }
302 String unit = "";
303 if (ind.getUnit() != null) {
304 List<LocalizedString> symbols = ind.getUnit().getSymbols();
305 if (symbols != null && !symbols.isEmpty()) {
306 unit = LocalizedString.getString(symbols, languageCode);
307 }
308 if (unit == null || unit.isBlank()) {
309 final List<LocalizedString> labels = ind.getUnit().getLabels();
310 if (labels != null && !labels.isEmpty()) {
311 unit = LocalizedString.getString(labels, languageCode);
312 }
313 }
314 }
315
316 final List<String> notes = new LinkedList<>();
317 if (ind.getNotes() != null && !ind.getNotes().isEmpty()) {
318 ind.getNotes().forEach(note -> {
319 final String anchor = note.getId();
320 notes.add("<a href='#" + anchor + "'>" + note.getId() + "</a>");
321 });
322 }
323 writeLn(mdWriter, ind.getId(), ind.getName(languageCode),
324 ind.getDescription(languageCode), String.join(", ", variables),
325 String.join(", ", parameters), unit, String.join(", ", notes));
326 }
327 }
328
329 mdWriter.write("""
330
331 ###\s""" + i18n.get("markdown.parameters") + "\n"
332 + "| " + i18n.get("markdown.id") + " | " + i18n.get("markdown.description") + " |\n"
333 + "|:---|:------------|\n");
334
335 for (final Parameter param : knowledge.getParameters()) {
336 writeLn(mdWriter, param.getId(), param.getDescription(languageCode));
337 }
338
339 mdWriter.write("""
340
341 ###\s""" + i18n.get("markdown.variables") + "\n"
342 + "| " + i18n.get("markdown.id") + " | " + i18n.get("markdown.description") + " |\n"
343 + "|:---|:------------|\n");
344 allVariables.stream().sorted().forEach(variable -> {
345 try {
346 mdWriter.write("| ");
347 mdWriter.write(variable);
348 mdWriter.write(" | ");
349 mdWriter.write(i18n.get("Variable." + variable.toUpperCase() + ".description"));
350 mdWriter.write(" |\n");
351 } catch (final IOException ex) {
352 LOGGER.catching(ex);
353 }
354 });
355
356
357 if (knowledge.getNotes() != null && !knowledge.getNotes().isEmpty()) {
358 mdWriter.write("""
359
360 ###\s""" + i18n.get("markdown.notes") + "\n"
361 + "| " + i18n.get("markdown.reference") + " | " + i18n.get("markdown.description") + " |\n"
362 + "|:---|:------------|\n");
363 for (final Note note : knowledge.getNotes()) {
364 final String anchor;
365
366
367 final String id = note.getId();
368 if (StringUtils.isDoiRef(id)) {
369 anchor = String.format(
370 "<a id=\"%1$s\" href=\"https://doi.org/%1$s\" target=\"_blank\">%1$s</a>",
371 id);
372 } else {
373 anchor = String.format("<a id=\"%1$s\">%1$s</a>", id);
374 }
375
376 writeLn(mdWriter, anchor, note.getDescription());
377 }
378 }
379
380 mdWriter.write("\n\n[^1]: " + i18n.get("markdown.unit.footnote"));
381 }
382 }
383
384
385
386
387
388
389
390 public void writeParametersCsvFiles(final Path path) throws IOException {
391 LOGGER.trace(path);
392 try (BufferedWriter paramWriter = new Utf8BufferedWriter(path)) {
393 paramWriter.write("id;description_fr;description_en\n");
394 for (final Parameter param : knowledge.getParameters()) {
395 paramWriter.write(param.getId());
396 paramWriter.write(";");
397 paramWriter.write(param.getDescription("fr"));
398 paramWriter.write(";");
399 paramWriter.write(param.getDescription("en"));
400 paramWriter.write("\n");
401 }
402 }
403 }
404 }