Plugins
WARNING: The plugins API is subject to change!
Builder supports plugins to extend processing. Plugins are R packages that export a function returning a list of lifecycle methods. Note that all lifecycle methods are mandatory even if they don’t do anything.
Note, you can always make use tof the builder.ini
file for configuration if needed.
Using Plugins
Via CLI
Specify plugins with the -plugin flag:
builder -plugin package::functionVia Config File
Add plugins to your builder.ini:
plugin: package::functionMultiple Plugins
You can use multiple plugins. They are called in order.
CLI:
builder -plugin pkg1::fn1 pkg2::fn2Config (space-separated):
plugin: pkg1::fn1 pkg2::fn2Developing Plugins
Overview
A plugin is an R function that returns a list of lifecycle methods. Each method is called at a specific point during the build process.
See builder.air for a real-world example plugin.
Lifecycle Methods
setup(input, output)
Called during initialization with the input and output directory paths. Use this to store configuration or perform one-time setup.
preprocess(str, file)
Called on each file’s content before Builder processes it. Receives the file content as a string and should return the modified content.
postprocess(str, file)
Called on each file’s content after Builder processes it. Receives the file content as a string and should return the modified content.
include(type, path, object, file)
Called for each #> include directive with
parsed components:
type- The file type (e.g.,"csv","json")path- The file path to includeobject- The variable name for the resultfile- The source file being processed
Return NULL to use default processing, or return a
replacement line.
end()
Called when Builder finishes processing all files. Use this for cleanup or final operations.
Examples
A simple minifier plugin that removes empty lines and joins with semicolons (It’s doing it wrong, don’t actually use this!):
#' @export
plugin <- function() {
list(
setup = function(input, output, ...) {
# Store paths or initialize state
},
preprocess = function(str, file, ...) {
# Modify content before processing
lines <- strsplit(str, "\n")[[1]]
lines <- lines[nzchar(trimws(lines))]
paste(lines, collapse = ";")
},
postprocess = function(str, file, ...) {
# Modify content after processing
str
},
include = function(type, path, object, file, ...) {
# Handle #> include directives
# Return NULL to use default processing
NULL
},
end = function(...) {
# Cleanup or final operations
}
)
}A plugin that uses readr to read CSV files
(and overrides the default csv type which uses the
built-in read.csv):
#' @export
plugin <- function() {
enabled <- FALSE
list(
setup = function(input, output, ...) {
enabled <<- requireNamespace("readr", quietly = TRUE)
warning("readr not installed, fallback on default `csv` support")
},
preprocess = function(str, file, ...) {},
postprocess = function(str, file, ...) {},
include = function(type, path, object, file, ...) {
if(!enabled) return(NULL)
if(type != "csv") return(NULL)
readr::read_csv(path)
},
end = function(...) {
}
)
}A formatter plugin that uses air to format the files:
#' @export
plugin <- function() {
out_dir <- NULL
list(
setup = function(input, output, ...) {
out_dir <<- output
},
preprocess = function(str, file, ...) {},
postprocess = function(str, file, ...) {},
end = function(...) {
if (is.null(out_dir)) {
return()
}
system2("air", c("format", out_dir))
},
include = function(type, path, object, file, ...) {}
)
}Tips
- Use
...in function signatures for forward compatibility - Methods that don’t modify content can return
NULL - If your plugin requires configuration, read from a config file
in
setup() - Plugins are called in the order they are specified
Using R6 Classes
For plugins with complex state or when you want inheritance, you can use R6 classes:
#' @export
Plugin <- R6::R6Class("Plugin",
private = list(
out_dir = NULL,
file_count = 0
),
public = list(
setup = function(input, output, ...) {
private$out_dir <- output
},
preprocess = function(str, file, ...) {
str
},
postprocess = function(str, file, ...) {
private$file_count <- private$file_count + 1
str
},
include = function(type, path, object, file, ...) {
NULL
},
end = function(...) {
message(sprintf("Processed %d files", private$file_count))
}
)
)
#' @export
plugin <- \() Plugin$new()This approach offers:
- Encapsulated state - Private fields instead
of
<<-assignments - Inheritance - Create plugin families with shared behavior
- Testability - Instantiate and test methods directly