scalameta / mdoc

Typechecked markdown documentation for Scala
https://scalameta.org/mdoc/
Apache License 2.0
394 stars 81 forks source link

Request for a PostModifier start/end of life-cycle method #213

Open hmf opened 4 years ago

hmf commented 4 years ago

Hope I am not overstepping my bounds here but I have a use-case that I cannot get around and this request seems to be a good way to solve it. Any suggestion for an alternative workaround is welcome. Some background and context. I am using a PostModifier to process code fences that generate plots via a JavaScript library (in this case PlotlyJS). In order to generate static documents (PDF) via Laika I need to render and convert those plots into raster images (png, tiff, etc). To do this I need to instantiate a JavaFX application (with a WebKit component) that will execute the JS script and render the plots headless to files.

So what is the problem? This process is executed as a Miil module (akin to an SBT plug-in) in a forked JVM. Because of the JavaFX application daemon thread, the spawned job will not terminate. In order to termnate this task I need to explicitly stop the headless javaFX application. However I can only do this when I know that MDoc has finished all processing. Hence the request to add such a method. Here I can place a call that stops the javaFX thread. Once no more files need processing, MDoc closes the PostModifier.

So my question are: is this viable and acceptable? Are their mechanisms for me to do this shutdown without the need for such changes?

TIA

olafurpg commented 4 years ago

Thank you for reporting! Big 👍 from me. Would you be interested in contributing this feature?

One question: how should the start/end methods work under --watch mode?

You might be able to get some inspiration for how to implement this by looking at postProcess on PreModifier

https://github.com/scalameta/mdoc/blob/3afbd52afae896e975701547d94779c02a8aedbd/mdoc/src/main/scala/mdoc/PreModifier.scala#L20

olafurpg commented 4 years ago

Example: the mdoc:js modifier is a PreModifier that collects all mdoc:js code fences during process

https://github.com/scalameta/mdoc/blob/3afbd52afae896e975701547d94779c02a8aedbd/mdoc-js/src/main/scala-2.12/mdoc/modifiers/JsModifier.scala#L219

During postProcess, we use the Scala.js compiler to link a JavaScript file and add links to the generated JS https://github.com/scalameta/mdoc/blob/3afbd52afae896e975701547d94779c02a8aedbd/mdoc-js/src/main/scala-2.12/mdoc/modifiers/JsModifier.scala#L127

hmf commented 4 years ago

@olafurpg Ok, I will take a look at the links you show and see what I can come up with.

In regards to the --watch flag I confess I did not think about this. I will have to give it some more though. But right now I think that the strategy I outlined above will won't work. The JavaFX platform cannot be started more than once per JVM.

Will come back to you on this.

olafurpg commented 4 years ago

One thing we can do it several callbacks for different events 🤔

hmf commented 4 years ago

I had to revisit the stuff I made in order to determine how to stop/start the JavaFX application and this can be used within MDoc. I have two sets of operations:

  1. One that starts and stops the JFX platform and application
  2. One that starts the JFX platform once and keeps it alive but restarts the application

Seem to be working correctly.

We have 2 different cases:

  1. Lunching MDoc and executing the PostModifier in a forked JVM once
  2. Lunching MDoc and executing the PostModifier in the build tool JVM repeatedly

The problem here is how to support both scenarios at the same time. In case 1 I can start and stop both platform and application with a postProcess method (the constructor will be used for the start). That solves the issue.

In case 2 I would have to :

  1. Initialize the Platform when MDoc starts up
  2. Start the application when the PostModifier starts (use the constructor?)
  3. Stop the application when the PostModifier stops
  4. Stop the Platform when MDoc stops

The above steps lets us use MDoc with --watch (no need to restart between watches). It also subsumes case 1. So I would need 4 methods:

  1. MDoc onStart
  2. PostModifier preProcess
  3. PostModifier postProcess
  4. MDoc onExit

Don't know if a onWatchIterationEndmay be useful for someone else.

Is this ok? Can it be done with minor changes to the current code base ?

TIA

Side note:

I am using MDoc as a Mill task. Mill has the -w/--watch and .runBackground modes. This means that I will use Mill to scan and check for any changes in the MDoc sources. Not as efficient as MDoc's watch but it allows me to play nicely with the build tool's interactive mode. I can try using MDoc directly but will experiment with this later.

olafurpg commented 4 years ago

@hmf thanks for the update. Feel free to add only the hooks that you need. We can add onWatchIterationEnd separately.

I am using MDoc as a Mill task. Mill has the -w/--watch and .runBackground modes.

The reason mdoc has its own --watch is to be able to reuse the compiler instance between compilations. The 2nd/3rd/... compilations are usually 10x faster than the 1st compilation.