-
Notifications
You must be signed in to change notification settings - Fork 207
Expand file tree
/
Copy pathParsing.java
More file actions
302 lines (285 loc) · 9.45 KB
/
Copy pathParsing.java
File metadata and controls
302 lines (285 loc) · 9.45 KB
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
/*
* SPDX-FileCopyrightText: Copyright (c) 2016-2026 Objectionary.com
* SPDX-License-Identifier: MIT
*/
package org.eolang.maven;
import com.github.lombrozo.xnav.Filter;
import com.github.lombrozo.xnav.Xnav;
import com.jcabi.log.Logger;
import com.jcabi.xml.XML;
import com.jcabi.xml.XMLDocument;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import org.cactoos.bytes.Sha256DigestOf;
import org.cactoos.io.InputOf;
import org.cactoos.iterable.Filtered;
import org.cactoos.text.HexOf;
import org.cactoos.text.TextOf;
import org.cactoos.text.UncheckedText;
import org.eolang.parser.Canonical;
import org.w3c.dom.Node;
/**
* Parse EO to XML.
*
* <p>
* This class parses all found EO sources to XMIRs.
* You can read more about XMIR format
* <a href="https://www.eolang.org/XMIR.html">here</a>
* </p>
* <p>
* The class scans all the EO sources registered in the foreign file catalog
* and then parses those that were not parsed before (i.e. do not have XMIRs yet)
* to XMIR format. The resulting XMIR files are stored in the {@link #DIR} directory.
* </p>
*
* @since 0.1
*/
final class Parsing implements Step {
/**
* Zero version.
*/
static final String ZERO = "0.0.0";
/**
* The directory where to parse to.
*/
static final String DIR = "1-parse";
/**
* Subdirectory for parsed cache.
*/
static final String CACHE = "parsed";
/**
* Foreign tojos catalog.
*/
private final TjsForeign tojos;
/**
* Target directory.
* @checkstyle MemberNameCheck (5 lines)
*/
private final Path targetDir;
/**
* Base cache directory.
* @checkstyle MemberNameCheck (5 lines)
*/
private final Path cacheDir;
/**
* Whether caching is enabled.
* @checkstyle MemberNameCheck (5 lines)
*/
private final boolean cacheEnabled;
/**
* Plugin version.
*/
private final String version;
/**
* EO sources directory (used for logging).
* @checkstyle MemberNameCheck (5 lines)
*/
private final Path sourcesDir;
/**
* Constructor.
* @param srcs Foreign tojos catalog
* @param target Target directory
* @param cache Base cache directory
* @param enabled Whether caching is enabled
* @param ver Plugin version string
* @param sources EO sources directory
* @checkstyle ParameterNumberCheck (10 lines)
*/
Parsing(
final TjsForeign srcs,
final Path target,
final Path cache,
final boolean enabled,
final String ver,
final Path sources
) {
this.tojos = srcs;
this.targetDir = target;
this.cacheDir = cache;
this.cacheEnabled = enabled;
this.version = ver;
this.sourcesDir = sources;
}
@Override
public void exec() {
final Collection<TjForeign> sources = this.tojos.withSources();
final String objects = sources.stream()
.map(TjForeign::identifier)
.filter(id -> id.contains("."))
.distinct()
.sorted()
.collect(Collectors.joining(" "));
final int total = this.parsed(
sources,
new Canonical(objects),
new UncheckedText(
new HexOf(new Sha256DigestOf(new InputOf(objects)))
).asString()
);
if (0 == total) {
if (sources.isEmpty()) {
Logger.info(
this,
"No .eo sources registered, nothing to be parsed to XMIRs (maybe you forgot to execute the \"register\" goal?)"
);
} else {
Logger.info(
this,
"No new .eo sources out of %d parsed to XMIRs",
sources.size()
);
}
} else {
Logger.info(
this, "Parsed %d new .eo sources out of %d to XMIRs",
total, sources.size()
);
}
}
/**
* Parse all the given sources to XMIRs, concurrently.
* @param sources The sources to parse
* @param pipeline The canonical parsing transform to apply
* @param digest Digest of the set of known objects (part of the cache key)
* @return Amount of parsed tojos
*/
private int parsed(
final Collection<TjForeign> sources,
final UnaryOperator<XML> pipeline,
final String digest
) {
return new Threaded<>(
new Filtered<>(TjForeign::notParsed, sources),
tojo -> this.parsed(tojo, pipeline, digest)
).total();
}
/**
* Parse EO file to XML.
* @param tojo The tojo
* @param pipeline The canonical parsing transform to apply
* @param digest Digest of the set of known objects (part of the cache key)
* @return Amount of parsed tojos
* @throws Exception If fails
*/
private int parsed(
final TjForeign tojo, final UnaryOperator<XML> pipeline, final String digest
) throws Exception {
final Path source = tojo.source();
final String name = tojo.identifier();
final Path base = this.targetDir.resolve(Parsing.DIR);
final Path target = new Place(name).make(base, MjAssemble.XMIR);
final List<Node> refs = new ArrayList<>(1);
if (this.cacheEnabled) {
new ConcurrentCache(
new Cache(
new CachePath(
this.cacheDir.resolve(Parsing.CACHE),
String.format("%s-%s", this.version, digest),
new TojoHash(tojo).get()
),
src -> {
final Node node = this.parsed(src, name, pipeline);
refs.add(node);
return new XMLDocument(node).toString();
}
)
).apply(source, target, base.relativize(target));
} else {
final Node node = this.parsed(source, name, pipeline);
new Saved(new XMLDocument(node).toString(), target).value();
refs.add(node);
}
tojo.withXmir(target).withVersion(Parsing.tojoVersion(target, refs));
final List<Xnav> errors = new Xnav(target)
.element("object")
.element("errors")
.elements(Filter.withName("error"))
.collect(Collectors.toList());
if (errors.isEmpty()) {
Logger.debug(this, "Parsed %[file]s to %[file]s", source, target);
} else {
for (final Xnav error : errors) {
Logger.error(
this,
"Failed to parse '%[file]s:%s': %s",
source,
error.attribute("line").text().orElse("0"),
error.text().orElse("")
);
}
}
return 1;
}
/**
* Source parsed to {@link Node}.
* @param source Relative source path
* @param identifier Name of the EO object as tojo identifier
* @param pipeline The canonical parsing transform to apply
* @return Parsed EO object as {@link Node}
* @throws IOException If fails to parse
*/
private Node parsed(
final Path source, final String identifier, final UnaryOperator<XML> pipeline
) throws IOException {
final EoSource.Xmir xmir = new EoSource(identifier, source, pipeline).parsed();
Logger.debug(
Parsing.class,
"Parsed program '%s' from %[file]s:%n %s",
identifier, this.sourcesDir.relativize(source.toAbsolutePath()), xmir
);
if (xmir.broken()) {
new Saved(
new TextOf(xmir.xml().toString()),
this.targetDir.resolve(
String.format("broken-%x.xmir", System.nanoTime())
)
).value();
}
return xmir.xml().inner();
}
/**
* Tojo version.
* The version can be extracted from:
* 1. Parsed {@link Node} if EO object was parsed for the first time
* 2. XML document that was already parsed before
* @param target Path to result XML document
* @param parsed List with either one parsed {@link Node} or empty
* @return Tojo version
* @throws FileNotFoundException If XML document file does not exist
*/
private static String tojoVersion(
final Path target,
final List<Node> parsed
) throws FileNotFoundException {
final Node node;
if (parsed.isEmpty()) {
node = new XMLDocument(target).inner();
} else {
node = parsed.get(0);
}
return new Xnav(node)
.element("object")
.element("metas").elements(
Filter.all(
Filter.withName("meta"),
meta -> new Xnav(meta).elements(
Filter.all(
Filter.withName("head"),
head -> head.text().map("version"::equals).orElse(false)
)
)
.findAny()
.isPresent()
)
)
.findFirst()
.map(meta -> meta.element("tail").text().orElse(Parsing.ZERO))
.orElse(Parsing.ZERO);
}
}