summaryrefslogtreecommitdiff
path: root/support/texplate/source/main/java/org/islandoftex/texplate/model/TemplateProcessing.java
blob: eccaf6c73206d2d4111f9913a3eda598d9667c49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
// SPDX-License-Identifier: BSD-3-Clause
package org.islandoftex.texplate.model;

import io.vavr.control.Either;
import org.islandoftex.texplate.util.MergingUtils;
import org.islandoftex.texplate.util.MessageUtils;
import org.islandoftex.texplate.util.PathUtils;
import org.islandoftex.texplate.util.ValidatorUtils;
import picocli.CommandLine;
import picocli.CommandLine.Option;

import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * The template processing class.
 *
 * @version 1.0
 * @since 1.0
 */
@CommandLine.Command(
        usageHelpWidth = 70,
        name = "texplate"
)
public class TemplateProcessing implements Callable<Integer> {

    // the file output, which will hold the
    // result of the merging of both template
    // and context data map from command line
    @Option(
            names = {"-o", "--output"},
            description = "The output file in which the chosen "
                    + "template will be effectively written. Make sure "
                    + "the directory has the correct permissions for "
                    + "writing the output file.",
            required = true,
            type = Path.class
    )
    private Path output;

    // the template name
    @Option(
            names = {"-t", "--template"},
            description = "The template and. The tool will "
                    + "search both user and system locations and set the "
                    + "template model accordingly, based on your specs."
    )
    private String template;

    // the context data map that holds
    // a set of key/value pairs to be
    // merged with the template
    @Option(
            names = {"-m", "--map"},
            description = "The contextual map that provides the "
                    + "data to be merged in the template. This parameter "
                    + "can be used multiple times. You can specify a map "
                    + "entry with the key=value syntax (mind the entry "
                    + "separator).",
            arity = "1..*"
    )
    private Map<String, String> map;

    @Option(
            names = {"-c", "--config"},
            description = "The configuration file in which the tool "
                    + "can read template data, for automation purposes. Make "
                    + "sure to follow the correct specification when writing "
                    + "a configuration file.",
            type = Path.class
    )
    private Path configuration;

    /**
     * The application logic, enclosed as a call method.
     *
     * @return An integer value denoting the exit status.
     * @throws Exception An exception was raised in the application logic.
     */
    @Override
    public Integer call() throws Exception {

        // the exit status, originally
        // set as a valid value
        int exit = 0;

        // configuration halt flag, indicating
        // whether the tool has to end earlier
        boolean halt = false;

        Map<String, Object> cmap = new HashMap<>();

        // ensure the context data map
        // is at least instantiated
        ensureMap();

        // there is a configuration file
        // found in the command line
        if (has(configuration)) {

            // print the configuration
            // check line for status
            MessageUtils.line("Checking configuration");

            // try to read the configuration
            // file into a proper object
            Either<Throwable, Configuration> configChecking = Configuration.
                    of(configuration).toEither();

            // the configuration file
            // seems to be valid, proceed
            if (configChecking.isRight()) {

                // print status line
                MessageUtils.status(true);

                // get the configuration file
                Configuration config = configChecking.get();

                // check if the configuration
                // has a proper template
                if (has(config.getTemplate())) {

                    // if so, build the template if, and
                    // only if, there's no one already
                    // set through command line
                    template = ensure(template, config.getTemplate());
                }

                // check if the configuration
                // has a proper string/string map
                if (has(config.getMap())) {

                    // set the main configuration
                    // map to be dealt later on
                    cmap = config.getMap();
                }

                // print header about tidying
                // up configuration variables
                MessageUtils.line("Adjusting variables from file");
                MessageUtils.status(true);
                System.out.println();

            } else {

                // an error occurred, print it
                // set exit code and halt
                MessageUtils.status(false);
                MessageUtils.error(configChecking.getLeft());
                exit = -1;
                halt = true;
            }
        } else {

            // print header regarding
            // no config file found
            MessageUtils.line("Configuration file mode disabled");
            MessageUtils.status(true);

            // print a header regarding
            // full command line mode
            MessageUtils.line("Entering full command line mode");

            // there's no configuration file, so we
            // need to check whether there is not a
            // pattern set in the command line
            if (!has(template)) {

                // print status
                MessageUtils.status(false);

                // print message
                MessageUtils.error(new Exception("The template was not set "
                        + "in the command line through the -t/--template "
                        + "option. If not explicitly specified in a "
                        + "configuration file, this option becomes mandatory, "
                        + "so make sure to define it  either in the command "
                        + "line or in a proper configuration file."));

                exit = -1;
                halt = true;

            } else {

                // print status
                MessageUtils.status(true);
                System.out.println();
            }

        }

        // check whether we should
        // halt prematurely
        if (!halt) {

            // initial message, preparing our
            // hearts to the actual merging :)
            System.out.println("Please, wait...");
            System.out.println();

            // now we need to obtain the actual
            // template from a file stored either
            // in the user home or in the system
            MessageUtils.line("Obtaining reference");

            // let us try to get the corresponding
            // file from the template pattern
            Either<Throwable, Path> fileChecking = PathUtils.
                    getTemplatePath(template).toEither();

            // the actual template file was
            // found, so we can proceed to
            // the next phase
            if (fileChecking.isRight()) {

                // updates the current
                // status accordingly
                MessageUtils.status(true);

                // now it's time to compose the template
                // object from its corresponding file
                MessageUtils.line("Composing template");

                // attempts to retrieve the template
                // attributes from the referenced file
                // to the actual template object
                Either<Throwable, Template> templateComposition
                        = Template.of(fileChecking.get()).toEither();

                // the template composition was successful,
                // so we can move on to the next phase
                if (templateComposition.isRight()) {

                    // updates the current
                    // status accordingly
                    MessageUtils.status(true);

                    // once the template object is populated,
                    // we need to verify if both template and
                    // data map are not somehow conflicting
                    MessageUtils.line("Validating data");
                    Either<Throwable, Map<String, String>> dataValidation
                            = ValidatorUtils.validate(templateComposition.get(),
                            map).toEither();

                    // the data validation was consistent,
                    // so now the merging can be applied
                    if (dataValidation.isRight()) {

                        // updates the current
                        // status accordingly
                        MessageUtils.status(true);

                        // now it's the final phase, in which
                        // both template and data are merged
                        MessageUtils.line("Merging template and data");

                        // merge both template and context data
                        // map
                        Either<Throwable, Long> merging = MergingUtils.
                                merge(templateComposition.get(),
                                        dataValidation.get(),
                                        output, cmap).toEither();

                        // the merging was successful,
                        // so now there's nothing else
                        // to do, yay!
                        if (merging.isRight()) {

                            // updates the current
                            // status accordingly
                            MessageUtils.status(true);

                            // print the final messge and
                            // tell the user everything
                            // went smooth!
                            System.out.println();
                            System.out.println("Done! Enjoy your template!");
                            System.out.println("Written: "
                                    + getSize(merging.get()));
                        } else {

                            // updates the current
                            // status accordingly
                            MessageUtils.status(false);

                            // the merging failed, so the
                            // exception is displayed and
                            // the exit status is updated
                            MessageUtils.error(merging.getLeft());
                            exit = -1;
                        }
                    } else {

                        // updates the current
                        // status accordingly
                        MessageUtils.status(false);

                        // the data validation failed, so
                        // the exception is displayed and
                        // the exit status is updated
                        MessageUtils.error(dataValidation.getLeft());
                        exit = -1;
                    }
                } else {

                    // updates the current
                    // status accordingly
                    MessageUtils.status(false);

                    // the template composition failed,
                    // so the exception is displayed and
                    // the exit status is updated
                    MessageUtils.error(templateComposition.getLeft());
                    exit = -1;
                }
            } else {

                // updates the current
                // status accordingly
                MessageUtils.status(false);

                // the file checking failed, so
                // the exception is displayed and
                // the exit status is updated
                MessageUtils.error(fileChecking.getLeft());
                exit = -1;
            }
        }

        // the exit status is returned,
        // denoting whether the application
        // was able to merge both template
        // and data accordingly
        return exit;
    }

    /**
     * Ensures the data map is never pointed to a null reference.
     */
    private void ensureMap() {

        // if the map is null, simply
        // create a new instance
        if (!has(map)) {
            map = new HashMap<>();
        }
    }

    /**
     * Gets the file size in a human readable format.
     *
     * @param bytes The file size, in bytes.
     * @return The file size in a human readable format.
     */
    private String getSize(long bytes) {
        if (bytes < 1024) {
            return bytes + " B";
        } else {
            int exponent = (int) (Math.log(bytes) / Math.log(1024));
            return String.format("%.1f %cB", bytes / Math.pow(1024, exponent),
                    "KMGTPE".charAt(exponent - 1));
        }
    }

    /**
     * Ensures the first parameter is not null, or sets it to the second one.
     *
     * @param <T>    The type.
     * @param first  First parameter.
     * @param second Second parameter.
     * @return Either the first or the second one.
     */
    private <T> T ensure(T first, T second) {
        return !has(first) ? second : first;
    }

    /**
     * Checks whether the object exists.
     *
     * @param object The objects.
     * @return Boolean value indicating whether the object exists.
     */
    private boolean has(Object object) {
        return object != null;
    }

}