Plugin API

BabelFSH has been recently refactored to utilize a real modular build with self-contained plugins. This is not yet documented.

The base plugin API is intentionally very simple. Every plugin is identified by a plugin ID (lower-case, non-numeric with only hyphens allowed), and specifies its command line arguments using a command line parser library.

Plugins are (currently) included at compile time, with no lazy loading from JAR files currently being supported. If there is a need for that functionality, someone would have to figure out library loading and plugin registratio in Kotlin 😉.

BabelfshTerminologyPlugin

The base class for the plugin API is the abstract class BabelfshTerminologyPlugin:

abstract class BabelfshTerminologyPlugin<ResourceContainerClass : ResourceContainer<*, *>, ResourceContentContainerClass : BabelfshConversionResult, ArgType : PluginArguments>(
    val pluginId: String,
    val babelfshContext: BabelfshContext,
    val resourceType: ResourceType,
    val attribution: String?,
    val extraHelp: String?,
    val shortHelp: String,
    val argumentConstructor: (ArgParser, Path?) -> ArgType
) {

    private val logger = LoggerFactory.getLogger(javaClass)

    fun parseArgs(pluginCommentData: PluginCommentData, resource: TsResourceData): ArgType {
        return mainBody(programName = "BabelFSH $resourceType plugin '$pluginId'", columns = getTerminalWidth()) {
            // body of parseArgs
        }
    }
    
    private fun ArgParser.parseIntoArgType(workingDirectory: Path?) = this.parseInto { argumentConstructor(this, workingDirectory) }

    abstract fun produceContent(
        args: PluginArguments, resource: ResourceContainerClass
    ): List<ResourceContentContainerClass>

    protected fun failConversion(s: String): Nothing {
        throw ConversionFailedException(s)
    }

    fun getHelp() {
        mainBody(columns = getTerminalWidth()) {
            val helpParser = ArgParser(
                args = arrayOf("--help"), helpFormatter = BabelfshHelpFormatter(this)
            )
            helpParser.parseIntoArgType(null)
        }

    }
}

Constructor Arguments

  • pluginId: The unique Plugin ID, which must conform to /[a-z-]+/ .

  • babelfshContext: A data class that provides the FHIR context objects, the working directory, the current release version, and some specialized settings. All plugins need access to this object.

  • resourceType one of ResourceType.CodeSystem, ResourceType.ValueSet or ResourceType.ConceptMap .

  • attribution: If the plugin needs to credit the work of others, do leave a note here.

  • extraHelp /shortHelp: all plugins need to declare a short help string. If needed, you can also declare a longer help string, to explain the intention of this plugin.

  • argumentConstructor : A bit of unavoidable boilerplate to instantiate the respective plugin argument class. ALWAYS Looks like { parser, workingDirectory -> PluginTypeName(parser, workingDirectory} . Due to Java Type Erasure, this is sadly unavoidable: at run time, the resource factory doesn't know the type of the plugin arguments.

Functions

  • produceContent : This is the main substance of any plugin. In this method, you would take in the arguments, BabelfshContext and the already-generated resource skeleton (should you need to read attributes from it), and return a list of data items (for CS: concepts, for CM: groups, for VS: ??)

  • failConversion: Should you need to abort execution, you can terminate with a clear message here.

Argument Classes

All plugins need to declare their arguments using classes that inherit PluginArguments. This class takes the argument partser and working directory path as constructor arguments, and then declares arguments using delegate functions. The API of the PluginArguments class is quite simple, there is a single public method that's pre-defined to be a no-op:

  • validateDependentArguments(): should you need to validate relationships between your arguments, e.g., if one argument requires another, or is exclusive with another, you can do this by overriding this method.

All arguments are declared using delegates, i.e. using by parser.x , where x can (generally) be one of:

Delegate
Return Type

flagging

Boolean

if provided, the flag will be true

storing<T>

T

T is generally inferred by the Kotlin compiler, but normally T. If you provide a lambda (parser.storing("-e", "--example", help="Foo") { toInt() }, the string value will be converted.

adding<T>

T

mapping<T>

T

Provide a list of Pair (using the "--fast" to Mode.FAST syntax) and you will limit the options the user has.

Otherwise, the abstract PluginArguments provides some helper functions that you might find useful:

  • String.preprocess() : Remove quotes and whitespace from argument values

  • failValidation() : Terminate app execution by calling this in validateDependentArguments or other validators.

  • String.convertToAbsolutePath() : if you need to read a file in your plugin, this converts the string argument to a Path, while validating that the file exists and is readable.

  • jsonArrayToStringList(string: String, validation: ((String) -> Unit)?) : Convert a JSON array that's provided as a String to a List<String> . Should you need to perform validation, you can failValidation in the provided optional lambda.

  • String.makeList() : Split a comma-separated list of strings

  • jsonStringToStringMap(s: String, validation: ((Map<String, String>) -> Unit)? = null): Decode a JSON object to a string-string-map

  • <T> decodeJsonArray : De-serialize a typed (serializable) list of JSON objects to a List<T>.

Example Argument Class

This argument class, from the EDQM Plugin, uses a number of optional and required arguments, makeList, as well as the validateDependentArguments function. The plugin is actually special in that it reads some arguments from the environment rather than the command line to make sure that credentials don't leak in FSH code, and it is not possible to directly provide the respective arguments in the command line.

For the implementation of your command line, please be referred to the documentation of the command line library, xenomachina/kotlin-argparser on GitHub!

Last updated