Skip to content

Commit f0b1b62

Browse files
committed
Refactor navigation script to Java
Fixes #10 Fixes #11 Signed-off-by: Andres Almiray <aalmiray@gmail.com>
1 parent 676c32c commit f0b1b62

File tree

11 files changed

+247
-3
lines changed

11 files changed

+247
-3
lines changed
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
//JAVA 11+
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.nio.file.FileVisitResult;
6+
import java.nio.file.FileVisitor;
7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
9+
import java.nio.file.Paths;
10+
import java.nio.file.attribute.BasicFileAttributes;
11+
import java.util.Comparator;
12+
import java.util.LinkedHashMap;
13+
import java.util.LinkedHashSet;
14+
import java.util.Map;
15+
import java.util.Optional;
16+
import java.util.Set;
17+
import java.util.TreeMap;
18+
import java.util.stream.Stream;
19+
20+
import static java.lang.System.lineSeparator;
21+
import static java.nio.charset.StandardCharsets.UTF_8;
22+
import static java.nio.file.FileVisitResult.CONTINUE;
23+
24+
public class generate_navigation {
25+
public static void main(String... args) throws Exception {
26+
if (null == args || args.length != 1) {
27+
System.err.println("❌ Usage: java generate_navigation.java [DIRECTORY]");
28+
System.exit(1);
29+
}
30+
31+
var pwd = Paths.get(args[0]);
32+
var featuresPath = Paths.get(pwd + "/features/");
33+
var categoriesPath = Paths.get(pwd + "/docs/modules/categories").normalize().toAbsolutePath();
34+
var versionsPath = Paths.get(pwd + "/docs/modules/versions").normalize().toAbsolutePath();
35+
var partialsPath = Paths.get(pwd + "/docs/modules/features/partials").normalize().toAbsolutePath();
36+
37+
var categoriesMap = new TreeMap<String, Map<String, Path>>();
38+
var versionsMap = new TreeMap<String, Map<String, Path>>();
39+
40+
FeatureFinder featureFinder = new FeatureFinder();
41+
Files.walkFileTree(featuresPath, featureFinder);
42+
43+
if (!featureFinder.success) {
44+
System.err.println("❌ Unexpected error occurred while searching for feature files");
45+
System.exit(1);
46+
}
47+
48+
if (!featureFinder.duplicates.isEmpty()) {
49+
System.err.println("❌ Found duplicate feature files. Filenames must be unique. Rename and retry");
50+
featureFinder.duplicates.forEach(System.err::println);
51+
System.exit(1);
52+
}
53+
54+
var features = new LinkedHashSet<Feature>();
55+
for (Map.Entry<String, Path> e : featureFinder.features.entrySet()) {
56+
var page = e.getKey();
57+
var feature = e.getValue();
58+
59+
System.out.printf("➡️ Processing %s%n", page);
60+
var version = extractAttribute(feature, ":database-version:");
61+
var categories = extractAttribute(feature, ":database-category:").split(" ");
62+
63+
for (String category : categories) {
64+
categoriesMap.computeIfAbsent(category, c -> new TreeMap<>())
65+
.put(page, feature);
66+
}
67+
68+
versionsMap.computeIfAbsent(version, v -> new TreeMap<>())
69+
.put(page, feature);
70+
71+
features.add(new Feature(feature, version, categories));
72+
}
73+
74+
System.out.printf("🛠 Generating pages%n");
75+
generateNavigation(categoriesPath, categoriesMap);
76+
generateNavigation(versionsPath, versionsMap);
77+
copyFeatures(features, partialsPath);
78+
}
79+
80+
private static void generateNavigation(Path path, Map<String, Map<String, Path>> data) throws IOException {
81+
StringBuilder b = new StringBuilder("* xref:index.adoc[]");
82+
b.append(lineSeparator());
83+
84+
for (Map.Entry<String, Map<String, Path>> e : data.entrySet()) {
85+
var k = e.getKey();
86+
var v = e.getValue();
87+
88+
System.out.printf("🔖 Generating %s%n", k);
89+
var index = path.resolve("pages/" + k + "/index.adoc");
90+
// create page if it does not exist
91+
if (!Files.exists(index)) {
92+
Files.createDirectories(index.getParent());
93+
Files.write(index, ("= " + k + lineSeparator()).getBytes(UTF_8));
94+
}
95+
96+
b.append("** xref:" + k + "/index.adoc[]")
97+
.append(lineSeparator());
98+
for (Map.Entry<String, Path> g : v.entrySet()) {
99+
var f = g.getKey();
100+
var p = g.getValue();
101+
102+
b.append("*** xref:" + k + "/" + f + "[]")
103+
.append(lineSeparator());
104+
105+
// create partial
106+
System.out.printf("📝 Generating %s/%s%n", k, f);
107+
var partial = path.resolve("pages/" + k + "/" + f);
108+
Files.write(partial, ("include::features:partial$" + f + "[]" + lineSeparator()).getBytes(UTF_8));
109+
}
110+
}
111+
Files.write(path.resolve("nav.adoc"), b.toString().getBytes(UTF_8));
112+
}
113+
114+
private static void copyFeatures(LinkedHashSet<Feature> features, Path partials) throws IOException {
115+
deleteFiles(partials);
116+
Files.createDirectories(partials);
117+
118+
for (Feature feature : features) {
119+
var content = new String(Files.readAllBytes(feature.path));
120+
content = content.replace("[[feature_summary]]", feature.asSummary());
121+
Files.write(partials.resolve(feature.path.getFileName()), content.getBytes(UTF_8));
122+
}
123+
}
124+
125+
private static String extractAttribute(Path file, String attributeName) throws IOException {
126+
Optional<String> line = Files.lines(file)
127+
.filter(s -> s.startsWith(attributeName))
128+
.findFirst();
129+
130+
if (line.isEmpty()) {
131+
System.err.printf("❌ Missing %s in %s%n", attributeName, file.toAbsolutePath());
132+
System.exit(1);
133+
}
134+
135+
return line.get().substring(attributeName.length() + 1).trim();
136+
}
137+
138+
private static void deleteFiles(Path path) throws IOException {
139+
if (Files.exists(path)) {
140+
try (Stream<Path> stream = Files.walk(path)) {
141+
stream
142+
.sorted(Comparator.reverseOrder())
143+
.map(Path::toFile)
144+
.forEach(File::delete);
145+
}
146+
Files.deleteIfExists(path);
147+
}
148+
}
149+
150+
private static class FeatureFinder implements FileVisitor<Path> {
151+
private final Map<String, Path> features = new LinkedHashMap<>();
152+
private final Set<Path> duplicates = new LinkedHashSet<>();
153+
private boolean success = true;
154+
155+
@Override
156+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
157+
return CONTINUE;
158+
}
159+
160+
@Override
161+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
162+
String filename = file.getFileName().toString();
163+
if (filename.endsWith(".adoc")) {
164+
if (!features.containsKey(filename)) {
165+
features.put(filename, file);
166+
} else {
167+
duplicates.add(file);
168+
}
169+
}
170+
return CONTINUE;
171+
}
172+
173+
@Override
174+
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
175+
return CONTINUE;
176+
}
177+
178+
@Override
179+
public FileVisitResult visitFileFailed(Path file, IOException e) {
180+
success = false;
181+
return CONTINUE;
182+
}
183+
}
184+
185+
private static class Feature {
186+
private final Path path;
187+
private final String version;
188+
private final String[] categories;
189+
190+
private Feature(Path path, String version, String[] categories) {
191+
this.path = path;
192+
this.version = version;
193+
this.categories = categories;
194+
}
195+
196+
private String asSummary() {
197+
StringBuilder b = new StringBuilder("[horizontal]");
198+
b.append(lineSeparator());
199+
200+
b.append("Version:: ")
201+
.append("xref:versions:")
202+
.append(version)
203+
.append("/index.adoc[]")
204+
.append(lineSeparator());
205+
206+
b.append("Categories:: ");
207+
for (int i = 0; i < categories.length; i++) {
208+
var category = categories[i];
209+
if (i > 0) b.append(", ");
210+
b.append("xref:categories:")
211+
.append(category)
212+
.append("/index.adoc[]");
213+
}
214+
b.append(lineSeparator());
215+
216+
return b.toString();
217+
}
218+
}
219+
}

.github/workflows/push.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@ jobs:
1111
- name: Checkout
1212
uses: actions/checkout@v3
1313

14+
- name: Setup Java
15+
uses: actions/setup-java@v3
16+
with:
17+
java-version: 17
18+
distribution: oracle
19+
1420
- name: Generate navigation
1521
shell: bash
1622
run: |
17-
bash .github/scripts/generate-navigation.sh
23+
java .github/scripts/generate_navigation.java "$(pwd)"
1824
1925
- name: Run Antora
2026
uses: kameshsampath/antora-site-action@master

.github/workflows/site.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ jobs:
1010
- name: Checkout
1111
uses: actions/checkout@v3
1212

13+
- name: Setup Java
14+
uses: actions/setup-java@v3
15+
with:
16+
java-version: 17
17+
distribution: oracle
18+
1319
- name: Generate navigation
1420
shell: bash
1521
run: |
16-
bash .github/scripts/generate-navigation.sh
22+
java .github/scripts/generate_navigation.java "$(pwd)"
1723
1824
- name: Run Antora
1925
uses: kameshsampath/antora-site-action@master

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ detect rendering issues or typos before the changes go live.
1818
### Dependencies
1919

2020
1. Install Antora as explained at https://docs.antora.org/antora/latest/install-and-run-quickstart/
21+
1. Install Java 11 or greater https://www.oracle.com/java/technologies/downloads/
2122

2223
### Build
2324

features/boolean.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
:database-version: 23.2.0
33
:database-category: sql
44

5+
[[feature_summary]]
6+
57
The `BOOLEAN` data type enables the storage and processing of `TRUE` and `FALSE` values.
68

79
Boolean values can be used as table column values or inside SQL query expressions.

features/feature.adoctemplate

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
:database-version: ###Database version###
33
:database-category: ###Category###
44

5+
[[feature_summary]]
6+
57
Replace this text with a brief feature description.
68

79

features/identity-columns.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
:database-version: 12.1.0.1
33
:database-category: sql
44

5+
[[feature_summary]]
6+
57
The identity columns feature can be used to designate one column in the table as the identity for the row.
68
The database will automatically assign an increasing integer value from a sequence generator to the identity column for each subsequent INSERT statement.
79
This feature is sometimes also referred to as `autoincrement`.

features/if-not-exists.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
:database-version: 23.2.0
33
:database-category: sql
44

5+
[[feature_summary]]
6+
57
The `IF [NOT] EXISTS` syntax can be used to ignore errors when dropping objects
68
that do not exist or create objects that already exist.
79

features/plsql-function-in-sql.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
:database-version: 12.1.0.1
33
:database-category: sql plsql
44

5+
[[feature_summary]]
6+
57
This feature enables users to write anonymous PL/SQL functions for the lifetime of a SQL query.
68

79
The PL/SQL functions can be specified in the `WITH` clause (Common Table Expression (CTE)) and then referenced in one or

features/table-value-constructor.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
:database-version: 23.2.0
33
:database-category: sql
44

5+
[[feature_summary]]
6+
57
The Table Value Constructor can be used to generate multiple
68
table values (e.g. rows) in a single invocation.
79

0 commit comments

Comments
 (0)