Plugin API
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
BabelfshTerminologyPluginThe 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.resourceTypeone ofResourceType.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,BabelfshContextand 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:
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 valuesfailValidation(): Terminate app execution by calling this invalidateDependentArgumentsor 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 aList<String>. Should you need to perform validation, you canfailValidationin the provided optional lambda.String.makeList(): Split a comma-separated list of stringsjsonStringToStringMap(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 aList<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.
Last updated