99import static software .amazon .smithy .docgen .core .DocgenUtils .normalizeNewlines ;
1010import static software .amazon .smithy .docgen .core .DocgenUtils .runCommand ;
1111
12+ import java .util .ArrayList ;
1213import java .util .List ;
1314import java .util .Set ;
1415import java .util .logging .Logger ;
2223import software .amazon .smithy .docgen .core .sections .sphinx .RequirementsSection ;
2324import software .amazon .smithy .docgen .core .sections .sphinx .WindowsMakeSection ;
2425import software .amazon .smithy .docgen .core .writers .SphinxMarkdownWriter ;
26+ import software .amazon .smithy .model .node .Node ;
27+ import software .amazon .smithy .model .node .ObjectNode ;
28+ import software .amazon .smithy .model .node .StringNode ;
2529import software .amazon .smithy .model .shapes .ServiceShape ;
2630import software .amazon .smithy .utils .SmithyInternalApi ;
2731
4751 * build the docs. Any dependencies here will be installed into the environment
4852 * used to run {@code sphinx-build}.
4953 * </ul>
54+ *
55+ * This integration supports several customization options. To see all those options,
56+ * see {@link SphinxSettings}. These settings are configured similarly to the doc
57+ * generation plugin settings. Below is an example {@code smithy-build.json} with
58+ * sphinx project auto build disabled.
59+ *
60+ * <pre>{@code
61+ * {
62+ * "version": "1.0",
63+ * "projections": {
64+ * "sphinx-markdown": {
65+ * "plugins": {
66+ * "docgen": {
67+ * "service": "com.example#DocumentedService",
68+ * "format": "sphinx-markdown",
69+ * "integrations": {
70+ * "sphinx": {
71+ * "autoBuild": false
72+ * }
73+ * }
74+ * }
75+ * }
76+ * }
77+ * }
78+ * }
79+ * }</pre>
5080 */
5181@ SmithyInternalApi
5282public final class SphinxIntegration implements DocIntegration {
@@ -55,12 +85,19 @@ public final class SphinxIntegration implements DocIntegration {
5585 private static final Logger LOGGER = Logger .getLogger (SphinxIntegration .class .getName ());
5686
5787 // The default requirements needed to build the docs.
58- private static final List <String > REQUIREMENTS = List .of (
88+ private static final List <String > BASE_REQUIREMENTS = List .of (
5989 "Sphinx==7.2.6" ,
6090 "myst-parser==2.0.0" ,
6191 "linkify-it-py==2.0.2"
6292 );
6393
94+ private SphinxSettings settings = SphinxSettings .fromNode (Node .objectNode ());
95+
96+ @ Override
97+ public String name () {
98+ return "sphinx" ;
99+ }
100+
64101 @ Override
65102 public byte priority () {
66103 // Run at the end so that any integration-generated changes can happen.
@@ -83,7 +120,6 @@ public void customize(DocGenerationContext context) {
83120 ));
84121 return ;
85122 }
86- // TODO: add some way to disable project file generation
87123 LOGGER .info ("Generating Sphinx project files." );
88124 writeRequirements (context );
89125 writeConf (context );
@@ -93,8 +129,13 @@ public void customize(DocGenerationContext context) {
93129
94130 private void writeRequirements (DocGenerationContext context ) {
95131 context .writerDelegator ().useFileWriter ("requirements.txt" , writer -> {
96- writer .pushState (new RequirementsSection (context , REQUIREMENTS ));
97- REQUIREMENTS .forEach (writer ::write );
132+ // Merge base and configured requirements into a single immutable list
133+ List <String > requirements = new ArrayList <>(BASE_REQUIREMENTS );
134+ requirements .addAll (settings .extraDependencies ());
135+ requirements = List .copyOf (requirements );
136+
137+ writer .pushState (new RequirementsSection (context , requirements ));
138+ requirements .forEach (writer ::write );
98139 writer .popState ();
99140 });
100141 }
@@ -114,10 +155,11 @@ private void writeConf(DocGenerationContext context) {
114155 release = $2S
115156 templates_path = ["_templates"]
116157 html_static_path = ["_static"]
117- html_theme = "alabaster"
158+ html_theme = $3S
118159 """ ,
119160 serviceSymbol .getName (),
120- service .getVersion ());
161+ service .getVersion (),
162+ settings .theme ());
121163
122164 if (context .docFormat ().name ().equals (MARKDOWN_FORMAT )) {
123165 writer .write ("""
@@ -209,6 +251,12 @@ private void writeMakefile(DocGenerationContext context) {
209251 }
210252
211253 private void runSphinx (DocGenerationContext context ) {
254+ if (!settings .autoBuild ()) {
255+ LOGGER .info ("Auto-build has been disabled. Skipping sphinx-build." );
256+ logManualBuildInstructions (context );
257+ return ;
258+ }
259+
212260 var baseDir = context .fileManifest ().getBaseDir ();
213261
214262 LOGGER .info ("Flushing writers in preparation for sphinx-build." );
@@ -236,7 +284,7 @@ private void runSphinx(DocGenerationContext context) {
236284 runCommand ("./venv/bin/pip install -r requirements.txt" , baseDir );
237285
238286 // Finally, run sphinx itself.
239- runCommand ("./venv/bin/sphinx-build -M html content build" , baseDir );
287+ runCommand ("./venv/bin/sphinx-build -M " + settings . format () + " content build" , baseDir );
240288
241289 System .out .printf (normalizeNewlines ("""
242290 Successfully built HTML docs. They can be found in "%1$s".
@@ -247,21 +295,22 @@ private void runSphinx(DocGenerationContext context) {
247295 environment docs for information on how to activate it: \
248296 https://docs.python.org/3/library/venv.html#how-venvs-work
249297
250- Once the environment is activated, run `make html ` from "%3$s" to \
251- to build the docs, substituting html for whatever format you wish \
298+ Once the environment is activated, run `make %4$s ` from "%3$s" to \
299+ to build the docs, substituting %4$s for whatever format you wish \
252300 to build.
253301
254302 To build the docs without activating the virtual environment, simply \
255- run `./venv/bin/sphinx-build -M html content build` from "%3$s", \
256- similarly substituting html for your desired format.
303+ run `./venv/bin/sphinx-build -M %4$s content build` from "%3$s", \
304+ similarly substituting %4$s for your desired format.
257305
258306 See sphinx docs for other output formats you can choose: \
259307 https://www.sphinx-doc.org/en/master/usage/builders/index.html
260308
261309 """ ),
262- baseDir .resolve ("build/html" ),
310+ baseDir .resolve ("build/" + settings . format () ),
263311 baseDir .resolve ("venv" ),
264- baseDir
312+ baseDir ,
313+ settings .format ()
265314 );
266315 } catch (CodegenException e ) {
267316 LOGGER .warning ("Unable to automatically build HTML docs: " + e );
@@ -279,13 +328,86 @@ private void logManualBuildInstructions(DocGenerationContext context) {
279328 instead install them from your system package manager, or another \
280329 source.
281330
282- Once the dependencies are installed, run `make html ` from \
331+ Once the dependencies are installed, run `make %2$s ` from \
283332 "%1$s". Other output formats can also be built. See sphinx docs for \
284333 other output formats: \
285334 https://www.sphinx-doc.org/en/master/usage/builders/index.html
286335
287336 """ ),
288- context .fileManifest ().getBaseDir ()
337+ context .fileManifest ().getBaseDir (),
338+ settings .format ()
289339 );
290340 }
341+
342+ /**
343+ * Settings for sphinx projects, regardless of their intermediate format.
344+ *
345+ * <p>These settings can be set in the {@code smithy-build.json} file under the
346+ * {@code sphinx} key of the doc generation plugin's {@code integrations} config.
347+ * The following example shows a {@code smithy-build.json} configuration that sets
348+ * the default sphinx output format to be dirhtml instead of html.
349+ *
350+ * <pre>{@code
351+ * {
352+ * "version": "1.0",
353+ * "projections": {
354+ * "sphinx-markdown": {
355+ * "plugins": {
356+ * "docgen": {
357+ * "service": "com.example#DocumentedService",
358+ * "format": "sphinx-markdown",
359+ * "integrations": {
360+ * "sphinx": {
361+ * "format": "dirhtml"
362+ * }
363+ * }
364+ * }
365+ * }
366+ * }
367+ * }
368+ * }
369+ * }</pre>
370+ *
371+ * @param format The sphinx output format that will be built automatically during
372+ * generation. The default is html. See
373+ * <a href="https://www.sphinx-doc.org/en/master/usage/builders/index.html">
374+ * sphinx docs</a> for other output format options.
375+ * @param theme The sphinx html theme to use. The default is alabaster. If your
376+ * chosen theme requires a python dependency to be added, use the
377+ * {@link #extraDependencies} setting.
378+ * @param extraDependencies Any extra python dependencies that should be added to
379+ * the {@code requirements.txt} file for the sphinx project.
380+ * These can be particularly useful for custom {@link #theme}s.
381+ * @param autoBuild Whether to automatically attempt to build the generated sphinx
382+ * project. The default is true. This will attempt to discover Python
383+ * 3 on the path, create a virtual environment inside the output
384+ * directory, install all the dependencies into that virtual environment,
385+ * and finally run sphinx-build.
386+ */
387+ public record SphinxSettings (
388+ String format ,
389+ String theme ,
390+ List <String > extraDependencies ,
391+ boolean autoBuild
392+ ) {
393+ /**
394+ * Load the settings from an {@code ObjectNode}.
395+ *
396+ * @param node the {@code ObjectNode} to load settings from.
397+ * @return loaded settings based on the given node.
398+ */
399+ public static SphinxSettings fromNode (ObjectNode node ) {
400+ List <String > extraDependencies = List .of ();
401+ if (node .containsMember ("extraDependencies" )) {
402+ var array = node .expectArrayMember ("extraDependencies" );
403+ extraDependencies = array .getElementsAs (StringNode ::getValue );
404+ }
405+ return new SphinxSettings (
406+ node .getStringMemberOrDefault ("format" , "html" ),
407+ node .getStringMemberOrDefault ("theme" , "alabaster" ),
408+ extraDependencies ,
409+ node .getBooleanMemberOrDefault ("autoBuild" , true )
410+ );
411+ }
412+ }
291413}
0 commit comments