forked from objectionary/eo
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMjParse.java
More file actions
213 lines (204 loc) · 7.04 KB
/
Copy pathMjParse.java
File metadata and controls
213 lines (204 loc) · 7.04 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
/*
* 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.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.stream.Collectors;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.cactoos.iterable.Filtered;
import org.cactoos.text.TextOf;
import org.w3c.dom.Node;
/**
* Parse EO to XML.
*
* <p>
* This is the initial goal that 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 goal scans all the EO sources registered in the foreign file catalog
* (see {@link MjRegister} and {@link MjPull}) 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
*/
@Mojo(
name = "parse",
defaultPhase = LifecyclePhase.GENERATE_SOURCES,
threadSafe = true,
requiresDependencyResolution = ResolutionScope.COMPILE
)
public final class MjParse extends MjSafe {
/**
* 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";
@Override
public void exec() {
final long start = System.currentTimeMillis();
final Collection<TjForeign> tojos = this.scopedTojos().withSources();
final int total = new Threaded<>(
new Filtered<>(TjForeign::notParsed, tojos),
this::parsed
).total();
if (0 == total) {
if (tojos.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",
tojos.size()
);
}
} else {
Logger.info(
this, "Parsed %d new .eo sources out of %d to XMIRs in %[ms]s",
total, tojos.size(), System.currentTimeMillis() - start
);
}
}
/**
* Parse EO file to XML.
*
* @param tojo The tojo
* @return Amount of parsed tojos
* @throws IOException If fails
*/
private int parsed(final TjForeign tojo) throws Exception {
final Path source = tojo.source();
final String name = tojo.identifier();
final Path base = this.targetDir.toPath().resolve(MjParse.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.cache.toPath().resolve(MjParse.CACHE),
this.plugin.getVersion(),
new TojoHash(tojo).get()
),
src -> {
final Node node = this.parsed(src, name);
refs.add(node);
return new XMLDocument(node).toString();
}
)
).apply(source, target, base.relativize(target));
} else {
refs.add(this.parsed(source, name));
}
tojo.withXmir(target).withVersion(MjParse.version(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
* @return Parsed EO object as {@link Node}
* @throws IOException If fails to parse
*/
private Node parsed(final Path source, final String identifier) throws IOException {
final EoSource.Xmir xmir = new EoSource(identifier, source).parsed();
Logger.debug(
MjParse.class,
"Parsed program '%s' from %[file]s:\n %s",
identifier, this.sourcesDir.toPath().relativize(source.toAbsolutePath()), xmir
);
if (xmir.broken()) {
new Saved(
new TextOf(xmir.xml().toString()),
this.targetDir.toPath().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 version(
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(MjParse.ZERO))
.orElse(MjParse.ZERO);
}
}