scripts/generate-options.R
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env Rscript
#
# generate-options.R
#
# Copyright (C) 2022 by Posit Software, PBC
#
# Unless you have received this program directly from Posit Software pursuant
# to the terms of a commercial license agreement with Posit Software, then
# this program is licensed to you under the terms of version 3 of the
# GNU Affero General Public License. This program is distributed WITHOUT
# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
# This script generates binary options for various RStudio Server binaries,
# creating the necessary boiler plate options code as well as creating
# documentation for the options in the form of appendices to the admin
# guide.
#
# To add options, do the following:
#
# 1. Add your option to the JSON options file corresponding to the binary
# that the option belongs to
# 2. In a terminal, navigate to this folder (src/cpp/)
# 3. Run this script: /generate-options.R
# 4. Commit the .gen.hpp files and the admin guide documentation files
require(here)
require(jsonlite)
require(stringi)
require(stringr)
require(tibble)
root <- here::here()
setwd(root)
defaultOptionsFiles <- c(
"src/cpp/server/server-options.json",
"src/cpp/session/session-options.json"
)
generateCopyright <- function (filename) {
sprintf("/*
* %s
*
* Copyright (C) 2022 by Posit Software, PBC
*
* Unless you have received this program directly from Posit Software pursuant
* to the terms of a commercial license agreement with Posit Software, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/", filename)
}
generateHandEditWarning <- function () {
"// THIS FILE WAS AUTOMATICALLY GENERATED BY A TOOL (generate-options.R) - DO NOT HAND EDIT IT"
}
generateQmdHandEditWarning <- function (sourceFile, overlayFile) {
overlayLine <- if (!is.null(overlayFile) && file.exists(overlayFile)) {
sprintf("\n(and %s in rstudio/rstudio-pro for Pro-only options)", overlayFile)
} else {
""
}
sprintf("<!--
THIS FILE WAS AUTOMATICALLY GENERATED BY A TOOL (generate-options.R) - DO NOT HAND EDIT IT.
To modify these options, edit %s in rstudio/rstudio%s,
then re-run: Rscript scripts/generate-options.R
-->", sourceFile, overlayLine)
}
generateIncludeGuard <- function (includeGuardName) {
sprintf("#ifndef %s
#define %s", includeGuardName, includeGuardName)
}
generateEndIncludeGuard <- function (includeGuardName) {
sprintf("#endif // %s", includeGuardName)
}
generateIncludes <- function (includes) {
includeStr <- ""
for (include in includes) {
if (!startsWith(include, "<")) {
include <- sprintf("\"%s\"", include)
}
includeStr <- paste0(includeStr, sprintf("#include %s\n", include))
}
includeStr
}
generateNamespace <- function (namespaceName) {
namespaces <- strsplit(namespaceName, "::")
namespaceStr <- ""
for (namespace in namespaces[[1]]) {
namespaceStr <- paste0(namespaceStr, sprintf("namespace %s {\n", namespace))
}
namespaceStr
}
generateEndNamespace <- function (namespaceName) {
namespaces <- strsplit(namespaceName, "::")
namespaceStr <- ""
for (namespace in rev(namespaces[[1]])) {
namespaceStr <- paste0(namespaceStr, sprintf("} // namespace %s\n", namespace))
}
namespaceStr
}
# converts a type specified within an options json file to the corresponding cpp type
cppTypeFromJsonType <- function (type) {
if (!is.null(type)) {
if (identical(type, "string")) {
type <- "std::string"
} else if (identical(type, "stringList")) {
type <- "std::vector<std::string>"
}
}
type
}
# generates qmd documentation for the given options
generateQmd <- function (optionsJson, overlayOptionsJson, sourceFile, overlayFile) {
metadata <- optionsJson$metadata
configFile <- metadata$configFile
outputDocFile <- metadata$outputDocFile
if (is.null(outputDocFile)) {
stop(sprintf("No outputDocFile specified for %s config", configFile))
}
# header for Quarto file
qmd <- paste("---",
paste0("title: \"", configFile, "\""),
"aliases:",
paste0(" - /rstudio-configuration-1.html#", configFile),
"---",
"",
generateQmdHandEditWarning(sourceFile, overlayFile),
"",
sep = "\n")
docDescription <- metadata$docDescription
if (is.null(docDescription)) {
docDescription <-
sprintf("The following is a list of available options that can be specified in the `%s` configuration file",
configFile)
}
qmd <- paste0(qmd, "\n", docDescription, "\n\n")
# combine options and overlay options
options <- optionsJson$options
overlayOptions <- overlayOptionsJson$options
for (category in names(overlayOptions)) {
for (option in overlayOptions[[category]]) {
option[["isOverlay"]] <- TRUE
options[[category]][[length(options[[category]]) + 1]] <- option
}
}
for (category in names(options)) {
categoryOptions <- ""
for (option in options[[category]]) {
optionName <- option$name
if (is.null(optionName)) {
# no name means this is a hidden option that should not be documented
next
}
isHidden <- option$isHidden
if (isTRUE(isHidden)) {
# if marked hidden, do not document this option
next
}
if (identical(typeof(optionName), "list")) {
optionConstant <- optionName[["constant"]]
optionName <- optionName[["value"]]
if (is.null(optionName)) {
stop(sprintf("No user constant value specified for %s - required for documentation", optionConstant))
}
}
description <- option$description
type <- option$tempType
if (is.null(type)) {
type <- option$type
}
# fix up type for commonly used non-boost types
if (identical(type, "core::FilePath")) {
type <- "string"
} else if (identical(type, "stringList")) {
type <- "string"
}
default <- option$defaultValue
if (identical(type, "string") && (is.null(default) || identical(default, "") || identical(default, "std::string()"))) {
default <- "<empty string>"
} else if (identical(type, "bool") && identical(typeof(default), "logical")) {
default <- if (default) "1" else "0"
} else if (identical(typeof(default), "list")) {
default <- default[["description"]]
if (is.null(default)) {
stop(sprintf("No code description given for default value for %s - required for documentation", optionName))
}
}
isDeprecated <- option$isDeprecated
if (isTRUE(isDeprecated)) {
description <- sprintf("%s \n**This option is deprecated and should not be used.**", description)
}
proHeader <- ""
isOverlay <- option$isOverlay
if (isTRUE(isOverlay)) {
proHeader <- "{.pro-header}"
}
categoryOptions <- paste0(categoryOptions,
sprintf("#### **%s** %s \n", optionName, proHeader),
description, " \n \n",
sprintf("Type: %s \n", type),
sprintf("Default: `%s `\n \n", default))
}
if (str_length(categoryOptions) > 0) {
qmd <- paste(qmd, sprintf("### *%s* Settings", category), sep="\n")
qmd <- paste(qmd, categoryOptions, sep="\n\n")
}
}
writeLines(qmd, con = outputDocFile)
}
# generates the include guard name
getIncludeGuard <- function (metadata) {
# generate include guard
includeGuard <- metadata$includeGuard
if (is.null(includeGuard)) {
# generate include guard from output file name when not specified
includeGuard <- str_replace_all(toupper(basename(metadata$outputSourceFile)), fixed("."), "_")
}
includeGuard
}
# generates options CPP code in boost::program_options format
generateProgramOptions <- function (optionsJson, overlayOptionsJson) {
metadata <- optionsJson$metadata
outputSourceFile <- metadata$outputSourceFile
if (is.null(outputSourceFile)) {
stop("Required metadata parameter outputSourceFile not specified")
}
# generate copyright notice
sourceFilename <- basename(outputSourceFile)
generatedContents <- generateCopyright(sourceFilename)
# generate hand edit warning
generatedContents <- paste(generatedContents, generateHandEditWarning(), sep="\n\n")
# generate include guard
includeGuard <- getIncludeGuard(metadata)
generatedContents <- paste(generatedContents, generateIncludeGuard(includeGuard), sep="\n\n")
# generate include directives
includes <- c("<string>", "<map>", "<shared_core/FilePath.hpp>", "<core/ProgramOptions.hpp>")
additionalIncludes <- metadata$additionalIncludes
includes <- c(includes, additionalIncludes)
generatedContents <- paste(generatedContents, generateIncludes(includes), sep="\n\n")
# generate namespace
namespace <- metadata$namespace
generatedContents <- paste(generatedContents, generateNamespace(namespace), sep="\n\n")
# generate class which holds the options
classContents <- paste0("class GeneratedOptions\n",
"{\n",
"public:\n",
" virtual ~GeneratedOptions() {}\n",
" virtual core::ProgramStatus read(int argc,\n",
" char * const argv[],\n",
" std::ostream& osWarnings) = 0;")
# generate accessors and members
accessors <- "public:\n"
members <- "protected:\n"
options <- optionsJson$options
for (category in names(options)) {
for (option in options[[category]]) {
# build property for this option
name <- option$name
type <- cppTypeFromJsonType(option$type)
if (is.null(type)) {
stop(sprintf("No type specified for option %s", name))
}
# fix up member type for commonly used non-boost types
memberType <- type
if (identical(memberType, "core::FilePath")) {
memberType <- "std::string"
}
memberName <- option$memberName
if (!is.null(memberName)) {
members <- paste0(members, sprintf(" %s %s;\n", memberType, memberName))
}
# build accessor for this option
skipAccessor <- option$skipAccessorGeneration
if (is.null(skipAccessor)) {
skipAccessor <- FALSE
}
if (skipAccessor) {
next
}
accessorName <- option$accessorName
if (is.null(accessorName)) {
if (!is.null(memberName)) {
# default accessor to the member name without any trailing underscores
accessorName <- str_replace(memberName, fixed("_"), "")
} else {
stop(sprintf("One of accessorName or memberName must be specified for option %s", name))
}
}
accessorCode <- ""
accessorValue <- option$accessorValue
if (is.null(accessorValue)) {
# handle any implicit conversions that we should support
if (identical(type, "core::FilePath")) {
accessorCode <- sprintf("return core::FilePath(%s);", memberName)
} else if (identical(category, "allow") && identical(type, "bool")) {
# Only bool "allow" options OR in the overlay flag. Applying this to
# non-bool options (e.g. the int abort-free-mem-percent) collapses the
# value to 0/1 (issue #3062), so they fall through to a plain return.
accessorCode <- sprintf("return %s || allowOverlay();", memberName)
} else {
accessorCode <- sprintf("return %s;", memberName)
}
} else {
accessorCode <- accessorValue[["code"]]
if (is.null(accessorCode)) {
stop(sprintf("Invalid accessorValue specified - must be a code specification: %s", accessorValue))
}
}
accessors <- paste0(accessors, sprintf(" %s %s() const { %s }\n", type, accessorName, accessorCode))
}
}
# generate the buildOptions function which handles the boost options interface
buildOptions <- paste0("protected:\n",
" rstudio::core::program_options::OptionsDescription\n",
" buildOptions(")
buildOptionsArgs <- c()
categoryVars <- c()
for (category in names(options)) {
# transform category names into output parameters
categoryParam <- category
substr(categoryParam, 1, 1) <- toupper(substr(categoryParam, 1, 1))
categoryParam <- paste0("p", categoryParam)
categoryVars[category] <- categoryParam
buildOptionsArgs <- append(buildOptionsArgs, sprintf("boost::program_options::options_description* %s", categoryParam))
}
for (category in names(options)) {
for (option in options[[category]]) {
tempName <- option$tempName
tempType <- cppTypeFromJsonType(option$tempType)
if (!is.null(tempName) && !is.null(tempType)) {
buildOptionsArgs <- append(buildOptionsArgs, sprintf("%s* %s", tempType, tempName))
}
}
}
buildOptionsArgsStr <- paste(buildOptionsArgs, collapse=",\n ")
buildOptions <- paste0(buildOptions,
buildOptionsArgsStr,
")\n{\n",
" using namespace rstudio::core;\n",
" using namespace boost::program_options;\n")
# now generate the meat of the function, the boost program options integration
boostOptions <- c()
for (category in names(options)) {
if (length(boostOptions) > 0) {
# stamp the ending semicolon for the last option in the previous category
boostOptions <- paste0(boostOptions, ";")
}
boostOptions <- paste(boostOptions, sprintf("%s->add_options()", categoryVars[[category]]), sep="\n\n ")
for (option in options[[category]]) {
optionName <- option$name
if (is.null(optionName)) {
# no member name means this is likely just an accessor option and should not be a true option
# simply continue to the next
next
}
# fixup constants for names
isOptionNameConstant <- FALSE
if (identical(typeof(optionName), "list")) {
optionName <- optionName[["constant"]]
isOptionNameConstant <- TRUE
}
isShortNameConstant <- FALSE
shortName <- option$shortName
if (identical(typeof(shortName), "list")) {
shortName <- shortName[["constant"]]
isShortNameConstant <- TRUE
}
storageType <- cppTypeFromJsonType(option$tempType)
if (is.null(storageType)) {
storageType <- cppTypeFromJsonType(option$type)
if (is.null(storageType)) {
stop(sprintf("No variable type defined for option %s", optionName))
}
}
# last minute type fixes for commonly used non-boost types
if (identical(storageType, "core::FilePath")) {
storageType <- "std::string"
}
storageMember <- option$tempName
if (is.null(storageMember)) {
# if no temp variable was defined, then use the member variable
storageMember <- paste0("&", option$memberName)
if (is.null(storageMember)) {
stop(sprintf("No memberName or tempName defined for option %s", optionName))
}
}
# fix up the default value based on the variable type
defaultValue <- option$defaultValue
if (is.null(defaultValue)) {
# resolve the default value based on the variable type
if (identical(storageType, "std::string")) {
defaultValue <- "std::string()"
} else if (identical(storageType, "bool")) {
defaultValue <- "true"
} else if (identical(storageType, "std::vector<std::string>")) {
defaultValue <- "std::vector<std::string>()"
} else {
stop(sprintf("No defaultValue specified for option %s, and could not be inferred", optionName))
}
} else {
defaultValueCode <- if (identical(typeof(defaultValue), "list")) defaultValue[["code"]] else NULL
if (is.null(defaultValueCode)) {
if (identical(storageType, "std::string")) {
if (defaultValue != "std::string()") {
# strings need to be quoted
defaultValue <- sprintf("\"%s\"", defaultValue)
}
} else if (identical(storageType, "bool")) {
defaultValue <- if (defaultValue) "true" else "false"
}
} else {
defaultValue <- defaultValueCode
}
}
implicitValueStr <- ""
implicitValue <- option$implicitValue
if (is.null(implicitValue)) {
implicitValueStr <- ""
} else {
implicitValueCode <- if (identical(typeof(implicitValue), "list")) implicitValue[["code"]] else NULL
if (is.null(implicitValueCode)) {
if (identical(storageType, "std::string")) {
if (implicitValue != "std::string()") {
# strings need to be quoted
implicitValue <- sprintf("\"%s\"", implicitValue)
}
} else if (identical(storageType, "bool")) {
implicitValue <- if (implicitValue) "true" else "false"
}
} else {
implicitValue <- defaultValueCode
}
implicitValueStr <- sprintf("->implicit_value(%s)", implicitValue)
}
isMultitoken <- option$isMultitoken
if (is.null(isMultitoken)) {
isMultitoken <- FALSE
}
multitoken <- ""
if (isMultitoken) {
multitoken <- "->multitoken()"
}
description <- option$description
if (is.null(description)) {
stop(sprintf("No description specified for option %s", optionName))
}
description <- str_replace_all(description, fixed("\""), "\\\"")
description <- sprintf("\"%s\"", description)
optionName <- if (isOptionNameConstant) optionName else sprintf("\"%s\"", optionName)
if (!is.null(shortName)) {
shortName <- if (isShortNameConstant) shortName else sprintf("\"%s\"", shortName)
shortName <- sprintf("%s", shortName)
optionName <- sprintf("%s \",\" %s", optionName, shortName)
}
optionSet <- paste0(sprintf(" (%s,\n", optionName),
sprintf(" value<%s>(%s)->default_value(%s)%s%s,\n", storageType, storageMember, defaultValue, implicitValueStr, multitoken),
sprintf(" %s)", description))
boostOptions <- paste(boostOptions, optionSet, sep="\n")
}
}
boostOptions <- trimws(boostOptions, "left")
boostOptions <- paste0(boostOptions, ";")
buildOptions <- paste(buildOptions, boostOptions, sep="\n ")
# generate config file information
configFile <- metadata$configFile
binary <- str_replace(configFile, fixed(".conf"), "")
buildOptions <- paste0(buildOptions,
"\n\n",
sprintf(" FilePath defaultConfigPath = core::system::xdg::findSystemConfigFile(\"%s configuration\", \"%s\");\n", binary, configFile),
" std::string configFile = defaultConfigPath.exists() ?\n",
" defaultConfigPath.getAbsolutePath() : \"\";\n",
sprintf(" return program_options::OptionsDescription(\"%s\", configFile);", binary))
# close out the buildOptions function
buildOptions <- paste0(buildOptions, "\n}")
classContents <- paste(classContents, buildOptions, sep="\n\n")
classContents <- paste(classContents, accessors, sep="\n\n")
classContents <- paste(classContents, members, sep="\n\n")
classContents <- paste(classContents, " virtual bool allowOverlay() const { return false; };\n")
# finally, close out the class
classContents <- paste0(classContents, "};")
classContents <- paste(classContents, generateEndNamespace(namespace), sep="\n\n")
classContents <- paste(classContents, generateEndIncludeGuard(includeGuard), sep="\n\n")
generatedContents <- paste(generatedContents, classContents, sep="\n")
writeLines(generatedContents, con = outputSourceFile)
# generate overlay options if applicable
if (!is.null(overlayOptionsJson)) {
generateOverlayOptions(overlayOptionsJson)
}
}
generateOverlayOptions <- function (overlayOptionsJson) {
metadata <- overlayOptionsJson$metadata
if (is.null(metadata)) {
stop("Overlay options missing required metadata parameter")
}
outputSourceFile <- metadata$outputSourceFile
if (is.null(outputSourceFile)) {
stop("Overlay options missing required metadata parameter outputSourceFile")
}
outputHeaderFile <- metadata$outputHeaderFile
if (is.null(outputHeaderFile)) {
stop("Overlay options missing required metadata parameter outputHeaderFile")
}
# generate copyright notice
sourceFilename <- basename(outputSourceFile)
headerFilename <- basename(outputHeaderFile)
generatedHeaderContents <- generateCopyright(headerFilename)
generatedSourceContents <- generateCopyright(sourceFilename)
# generate hand edit warning
warning <- generateHandEditWarning()
generatedSourceContents <- paste(generatedSourceContents, warning, sep="\n\n")
generatedHeaderContents <- paste(generatedHeaderContents, warning, sep="\n\n")
# generate include of header file in cpp file
pos <- str_locate(outputHeaderFile, "include/")[2]
if (!is.na(pos)) {
includeDirective <- sprintf("#include <%s>", substr(outputHeaderFile, pos + 1, str_length(outputHeaderFile)))
}
generatedSourceContents <- paste(generatedSourceContents, includeDirective, sep="\n\n")
# generate include guard
includeGuard <- getIncludeGuard(metadata)
generatedHeaderContents <- paste(generatedHeaderContents, generateIncludeGuard(includeGuard), sep="\n\n")
# generate include directives
includes <- c("<string>", "<map>", "<shared_core/FilePath.hpp>", "<core/ProgramOptions.hpp>")
additionalIncludes <- metadata$additionalIncludes
if (!is.null(additionalIncludes)) {
includes <- c(includes, additionalIncludes)
}
generatedHeaderContents <- paste(generatedHeaderContents, generateIncludes(includes), sep="\n\n")
# generate #define directives for all of the specified overlay options constants
options <- overlayOptionsJson$options
constants <- data.frame(Constant=character(), Value=character())
for (category in names(options)) {
for (option in options[[category]]) {
optionName <- option$name
if (is.null(optionName)) {
stop("Option with no name specified in overlay options. All overlay options must specify a name.")
}
optionConstant <- optionName[["constant"]]
if (is.null(optionConstant)) {
stop(sprintf("Overlay option %s specified without a constant. All overlay option names must be constants.", optionName))
}
optionConstantValue <- optionName[["value"]]
if (is.null(optionConstantValue)) {
stop(sprintf("Overlay option %s constant specified without a value. All constants must have values for documentation.", optionName))
}
skipDefine <- optionName[["skipDefine"]]
if (!is.null(skipDefine) && skipDefine) {
# this definition should be skipped - it is most likely an external definition
next
}
constants <- constants %>% add_row(Constant = optionConstant, Value = optionConstantValue)
}
}
# add additional constants
additionalConstants <- metadata$additionalConstants
if (!is.null(additionalConstants)) {
for (constant in additionalConstants) {
constants <- constants %>% add_row(Constant = constant[["constant"]], Value = constant[["value"]])
}
}
defines <- ""
nameLengths <- c()
for (i in seq_len(nrow(constants))) {
constant <- constants[i,]
# ensure the constant values line up properly by tracking how many spaces they should have after the constant
optionConstant <- constant[["Constant"]]
optionConstantValue <- constant[["Value"]]
constantLength <- str_length(optionConstant)
nameLengths <- append(nameLengths, constantLength)
defines <- paste0(defines, "#define ", optionConstant, sprintf("<SPACER%s>", i), sprintf("\"%s\"", optionConstantValue), "\n")
}
# fill in spacers to line up the constants to make them more readable
iter <- 1
maxNameLengths <- max(unlist(nameLengths))
for (i in nameLengths) {
spaces <- strrep(" ", maxNameLengths - i + 1)
defines <- str_replace_all(defines, fixed(sprintf("<SPACER%s>", iter)), spaces)
iter <- iter+1
}
generatedHeaderContents <- paste(generatedHeaderContents, defines, sep="\n\n")
# generate namespace
namespace <- metadata$namespace
generatedSourceContents <- paste(generatedSourceContents, generateNamespace(namespace), sep="\n\n")
# generate addOverlayOptions function
addOverlayOptionsFunc <- paste0("void Options::addOverlayOptions(\n ")
categoryVars <- c()
overlayOptionsArgs <- c()
for (category in names(options)) {
# transform category names into output parameters
categoryParam <- category
substr(categoryParam, 1, 1) <- toupper(substr(categoryParam, 1, 1))
categoryParam <- paste0("p", categoryParam)
categoryVars[category] <- categoryParam
overlayOptionsArgs <- append(overlayOptionsArgs, sprintf("boost::program_options::options_description* %s", categoryParam))
}
overlayOptionsArgsStr <- paste(overlayOptionsArgs, collapse = ",\n ")
addOverlayOptionsFunc <- paste0(addOverlayOptionsFunc, overlayOptionsArgsStr, ")\n{\n",
" using namespace boost::program_options;\n")
# now generate the meat of the function, the boost program options integration
boostOptions <- c()
for (category in names(options)) {
if (length(boostOptions) > 0) {
# stamp the ending semicolon for the last option in the previous category
boostOptions <- paste0(boostOptions, ";")
}
boostOptions <- paste(boostOptions, sprintf("%s->add_options()", categoryVars[[category]]), sep="\n\n ")
for (option in options[[category]]) {
optionName <- option$name
optionName <- optionName[["constant"]]
type <- option$type
defaultValue <- option$defaultValue
if (is.null(defaultValue)) {
defaultValue <- "std::string()"
} else {
defaultValueCode <- if (identical(typeof(defaultValue), "list")) defaultValue[["code"]] else NULL
if (is.null(defaultValueCode)) {
if (defaultValue != "std::string()") {
if (identical(type, "bool")) {
# convert TRUE and FALSE to "1" and "0"
if (identical(typeof(defaultValue), "logical")) {
defaultValue <- if (defaultValue) "1" else "0"
}
}
# strings need to be quoted
defaultValue <- sprintf("\"%s\"", defaultValue)
}
} else {
defaultValue <- defaultValueCode
}
}
description <- option$description
if (is.null(description)) {
stop(sprintf("No description specified for option %s", optionName))
}
description <- str_replace_all(description, fixed("\""), "\\\"")
description <- sprintf("\"%s\"", description)
optionSet <- paste0(sprintf(" (%s,\n", optionName),
sprintf(" value<std::string>(&overlayOptions_[%s])->default_value(%s),\n", optionName, defaultValue),
sprintf(" %s)", description))
boostOptions <- paste(boostOptions, optionSet, sep="\n")
}
}
boostOptions <- trimws(boostOptions, "left")
boostOptions <- paste0(boostOptions, ";")
addOverlayOptionsFunc <- paste(addOverlayOptionsFunc, boostOptions, sep="\n ")
generatedSourceContents <- paste(generatedSourceContents, addOverlayOptionsFunc, sep="\n")
# close out the function
generatedSourceContents <- paste(generatedSourceContents, "}", sep="\n")
generatedSourceContents <- paste(generatedSourceContents, generateEndNamespace(namespace), sep="\n\n")
generatedHeaderContents <- paste(generatedHeaderContents, generateEndIncludeGuard(includeGuard), sep="\n\n")
writeLines(generatedSourceContents, con = outputSourceFile)
writeLines(generatedHeaderContents, con = outputHeaderFile)
}
# generates options CPP code and admin guide documentation for the given options JSON file
generate <- function (optionsFile) {
optionsJson <- jsonlite::read_json(optionsFile)
metadata <- optionsJson$metadata
if (is.null(metadata)) {
stop(sprintf("Options file %s missing required metadata section", optionsFile))
}
generatorType <- metadata$generatorType
if (is.null(generatorType)) {
generatorType <- "ProgramOptions"
}
if (identical(generatorType, "ProgramOptions")) {
directory <- dirname(optionsFile)
filename <- tools::file_path_sans_ext(basename(optionsFile))
ext <- tools::file_ext(optionsFile)
overlayFile = file.path(directory, sprintf("%s-overlay.%s", filename, ext))
overlayOptionsJson <- NULL
if (file.exists(overlayFile)) {
overlayOptionsJson <- jsonlite::read_json(overlayFile)
}
cat(sprintf("Generating program options from %s\n", optionsFile))
if (!is.null(overlayOptionsJson)) {
cat(sprintf("Using overlay options file %s\n", overlayFile))
}
generateProgramOptions(optionsJson, overlayOptionsJson)
cat("Generating documentation\n")
generateQmd(optionsJson, overlayOptionsJson, optionsFile, overlayFile)
} else {
stop(sprintf("Requested generator type %s is not supported", generatorType))
}
}
main <- function () {
args <- commandArgs(trailingOnly=TRUE)
if (length(args) == 0) {
args <- defaultOptionsFiles
}
for (optionsFile in args) {
if (!file.exists(optionsFile)) {
stop(sprintf("Options file %s does not exist - ensure your working directory is set to this script's location",
optionsFile))
}
generate(optionsFile)
}
}
main()