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+ }
0 commit comments