From f56551e973ef3e15fec6a1510e54ce1c85fb780e Mon Sep 17 00:00:00 2001 From: "Dr. Connie Brett" Date: Fri, 3 Jul 2020 14:11:15 -0700 Subject: [PATCH] CRAN 0.5.1 released (#15) --- DESCRIPTION | 6 +- NAMESPACE | 8 + NEWS.md | 10 +- R/appReset.R | 6 +- R/downloadFile.R | 17 +- R/downloadablePlot.R | 10 +- R/downloadableTable.R | 3 +- R/fw_helpers_external.R | 2 +- R/fw_helpers_internal.R | 18 +- R/generate_template.R | 34 +- R/logger.R | 782 ++++++++++++++++++ R/periscope.R | 13 + R/ui_helpers.R | 2 - README.md | 9 + cran-comments.md | 10 +- inst/fw_templ/server.R | 3 - man/bootstrapping.Rd | 33 + man/create_new_application.Rd | 9 +- man/downloadFile.Rd | 4 +- man/downloadablePlot.Rd | 4 +- man/downloadablePlotUI.Rd | 7 +- man/downloadableTable.Rd | 4 +- man/getHandler.Rd | 23 + man/getLogger.Rd | 22 + man/handlers-management.Rd | 46 ++ man/inbuilt-actions.Rd | 57 ++ man/logging-entrypoints.Rd | 50 ++ man/loglevels.Rd | 17 + man/resetMsgComposer.Rd | 14 + man/setLevel.Rd | 18 + man/setMsgComposer.Rd | 22 + man/set_app_parameters.Rd | 3 - man/updateOptions.Rd | 34 + tests/testthat/sample_app/server.R | 3 - tests/testthat/sample_app_no_sidebar/server.R | 3 - tests/testthat/test_create_new_application.R | 36 +- vignettes/new-application.Rmd | 15 +- 37 files changed, 1281 insertions(+), 76 deletions(-) create mode 100644 R/logger.R create mode 100644 man/bootstrapping.Rd create mode 100644 man/getHandler.Rd create mode 100644 man/getLogger.Rd create mode 100644 man/handlers-management.Rd create mode 100644 man/inbuilt-actions.Rd create mode 100644 man/logging-entrypoints.Rd create mode 100644 man/loglevels.Rd create mode 100644 man/resetMsgComposer.Rd create mode 100644 man/setLevel.Rd create mode 100644 man/setMsgComposer.Rd create mode 100644 man/updateOptions.Rd diff --git a/DESCRIPTION b/DESCRIPTION index caf255d..495b6c2 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: periscope Type: Package Title: Enterprise Streamlined 'Shiny' Application Framework -Version: 0.4.10-1 +Version: 0.5.1 Authors@R: c( person("Constance", "Brett", email="connie@aggregate-genius.com", role = c("aut", "cre")), person("Isaac", "Neuhaus", role = "aut", comment = "canvasXpress JavaScript Library Maintainer"), @@ -26,11 +26,11 @@ Imports: shinydashboard (>= 0.5), shinydashboardPlus (>= 0.5), shinyBS (>= 0.61), - logging (>= 0.7-103), lubridate (>= 1.6), DT (>= 0.2), openxlsx (>= 3.0), - ggplot2 (>= 2.2) + ggplot2 (>= 2.2), + methods RoxygenNote: 7.1.0 Suggests: knitr, diff --git a/NAMESPACE b/NAMESPACE index 55b0f33..6a37f18 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -17,6 +17,14 @@ export(downloadablePlotUI) export(downloadableTable) export(downloadableTableUI) export(get_url_parameters) +export(logdebug) +export(logerror) +export(logfine) +export(logfiner) +export(logfinest) +export(loginfo) +export(logwarn) export(remove_reset_button) export(set_app_parameters) export(ui_tooltip) +importFrom(methods,new) diff --git a/NEWS.md b/NEWS.md index f1129f2..8a51dc7 100755 --- a/NEWS.md +++ b/NEWS.md @@ -1,9 +1,15 @@ #Revisions and Change Log -### v0.4.10-1 +### v0.5.1 +* Added support for other color schemes in the dashboard +* Replaced CRAN-archived logging package functionality + + +### v0.4.10-1 * Fixed tests for compatibility with the next release of shiny -### v0.4.9-1 + +### v0.4.9-1 * Fixed url typo in readme link diff --git a/R/appReset.R b/R/appReset.R index 924e77d..fe99f89 100755 --- a/R/appReset.R +++ b/R/appReset.R @@ -36,7 +36,7 @@ if (input$resetButton && !(pending)) { # reset initially requested - logging::logwarn(paste("Application Reset requested by user. ", + logwarn(paste("Application Reset requested by user. ", "Resetting in ", (waittime / 1000), "seconds."), logger = logger) @@ -59,7 +59,7 @@ } else if (!input$resetButton && pending) { # reset cancelled by pushing the button again - logging::loginfo("Application Reset cancelled by user.", + loginfo("Application Reset cancelled by user.", logger = logger) shinyBS::createAlert( @@ -78,7 +78,7 @@ } else if (pending) { # reset timed out - logging::logwarn("Application Reset", logger = logger) + logwarn("Application Reset", logger = logger) session$reload() } }) diff --git a/R/downloadFile.R b/R/downloadFile.R index eb5c256..41da036 100755 --- a/R/downloadFile.R +++ b/R/downloadFile.R @@ -108,7 +108,7 @@ downloadFileButton <- function(id, #' @param output provided by \code{shiny::callModule} #' @param session provided by \code{shiny::callModule} #' \cr \cr -#' @param logger \link[logging:logging-package]{logging} logger to use +#' @param logger logger to use #' @param filenameroot the base text used for user-downloaded file - can be #' either a character string or a reactive expression that returns a character #' string @@ -129,7 +129,6 @@ downloadFileButton <- function(id, #' @seealso \link[periscope]{downloadFile_ValidateTypes} #' @seealso \link[periscope]{downloadFile_AvailableTypes} #' @seealso \link[shiny]{callModule} -#' @seealso \link[logging:logging-package]{logging} #' #' @examples #' # Inside server_local.R @@ -230,17 +229,17 @@ downloadFile <- function(input, output, session, logger, } else { msg <- paste(type, "could not be processed") - logging::logwarn(msg) + logwarn(msg) warning(msg) } } # error - type not handled else { msg <- paste(type, "not implemented as a download type") - logging::logwarn(msg) + logwarn(msg) warning(msg) } - logging::loginfo(paste("File downloaded in browser: <", + loginfo(paste("File downloaded in browser: <", filename(), ">"), logger = logger) } @@ -290,7 +289,7 @@ downloadFile <- function(input, output, session, logger, else { msg <- paste("Unsupported plot type for ggplot download - ", "must be in: ") - logging::logwarn(msg) + logwarn(msg) warning(msg) } } @@ -308,7 +307,7 @@ downloadFile <- function(input, output, session, logger, else { msg <- paste("Unsupported plot type for lattice download - ", "must be in: ") - logging::logwarn(msg) + logwarn(msg) warning(msg) } } @@ -316,10 +315,10 @@ downloadFile <- function(input, output, session, logger, # ------- should really never be hit else { msg <- paste(type, "not implemented as a download type") - logging::logwarn(msg) + logwarn(msg) warning(msg) } - logging::loginfo(paste("File downloaded in browser: <", + loginfo(paste("File downloaded in browser: <", filename(), ">"), logger = logger) } } diff --git a/R/downloadablePlot.R b/R/downloadablePlot.R index d5c796d..a3448ba 100755 --- a/R/downloadablePlot.R +++ b/R/downloadablePlot.R @@ -32,10 +32,9 @@ #' downloadfxns are set in the paired callModule (see the \strong{Shiny Usage} #' section) #' -#' This module is NOT compatible with the built-in (base) graphics \emph{(any -#' functions provided by the \link[graphics]{graphics} package such as plot)} -#' because they cannot be saved into an object and are directly output by the -#' system at the time of creation. +#' This module is NOT compatible with the built-in (base) graphics \emph{(such as +#' basic plot, etc.)} because they cannot be saved into an object and are directly +#' output by the system at the time of creation. #' #' @section Shiny Usage: #' Call this function at the place in ui.R where the plot should be placed. @@ -137,7 +136,7 @@ downloadablePlotUI <- function(id, #' @param output provided by \code{shiny::callModule} #' @param session provided by \code{shiny::callModule} #' \cr \cr -#' @param logger \link[logging:logging-package]{logging} logger to use +#' @param logger logger to use #' @param filenameroot the base text used for user-downloaded file - can be #' either a character string or a reactive expression returning a character #' string @@ -163,7 +162,6 @@ downloadablePlotUI <- function(id, #' #' @seealso \link[periscope]{downloadablePlotUI} #' @seealso \link[shiny]{callModule} -#' @seealso \link[logging:logging-package]{logging} #' #' @examples #' # Inside server_local.R diff --git a/R/downloadableTable.R b/R/downloadableTable.R index 467ce51..edf77af 100755 --- a/R/downloadableTable.R +++ b/R/downloadableTable.R @@ -94,7 +94,7 @@ downloadableTableUI <- function(id, #' @param output provided by \code{shiny::callModule} #' @param session provided by \code{shiny::callModule} #' \cr \cr -#' @param logger \link[logging:logging-package]{logging} logger to use +#' @param logger logger to use #' @param filenameroot the base text used for user-downloaded file - can be #' either a character string or a reactive expression returning a character #' string @@ -127,7 +127,6 @@ downloadableTableUI <- function(id, #' #' @seealso \link[periscope]{downloadableTableUI} #' @seealso \link[shiny]{callModule} -#' @seealso \link[logging:logging-package]{logging} #' #' @examples #' # Inside server_local.R diff --git a/R/fw_helpers_external.R b/R/fw_helpers_external.R index e174da4..1292690 100755 --- a/R/fw_helpers_external.R +++ b/R/fw_helpers_external.R @@ -33,7 +33,7 @@ fw_get_version <- function() { # Get User Action Log fw_get_user_log <- function() { - logging::getLogger(name = "actions") + getLogger(name = "actions") } # Framework UI Header Creation diff --git a/R/fw_helpers_internal.R b/R/fw_helpers_internal.R index 615db84..52d8e11 100755 --- a/R/fw_helpers_internal.R +++ b/R/fw_helpers_internal.R @@ -227,17 +227,17 @@ file.create(logfile) - logging::addHandler(logging::writeToFile, - file = logfile, - level = loglevel, - logger = logger, - formatter = formatter) + addHandler(writeToFile, + file = logfile, + level = loglevel, + logger = logger, + formatter = formatter) if (loglevel == "DEBUG") { - logging::addHandler(logging::writeToConsole, - level = loglevel, - logger = logger, - formatter = formatter) + addHandler(writeToConsole, + level = loglevel, + logger = logger, + formatter = formatter) } return(logfile) diff --git a/R/generate_template.R b/R/generate_template.R index e635d46..3bc2090 100755 --- a/R/generate_template.R +++ b/R/generate_template.R @@ -16,6 +16,7 @@ #' @param rightsidebar parameter to set the right sidebar. It can be TRUE/FALSE or a character #' containing the name of a shiny::icon(). #' @param leftsidebar whether the left sidebar should be enabled. +#' @param style list containing application styling properties. By default the skin is blue. #' #' @section Name: #' The \code{name} directory must not exist in \code{location}. If the code @@ -94,6 +95,7 @@ #' } #' #'@seealso \link[shiny:icon]{shiny:icon()} +#'@seealso \link[shinydashboard:dashboardPage]{shinydashboard:dashboardPage()} #' #'@examples #' # sample app named 'mytestapp' created in a temp dir @@ -105,11 +107,13 @@ #' #' # blank app named 'myblankapp' created in a temp dir #' create_new_application(name = 'myblankapp', location = tempdir()) +#' # blank app named 'myblankapp' with a green skin created in a temp dir +#' create_new_application(name = 'myblankapp', location = tempdir(), style = list(skin = "green")) #' # blank app named 'myblankapp' without a left sidebar created in a temp dir #' create_new_application(name = 'myblankapp', location = tempdir(), leftsidebar = FALSE) #' #' @export -create_new_application <- function(name, location, sampleapp = FALSE, resetbutton = TRUE, rightsidebar = FALSE, leftsidebar = TRUE) { +create_new_application <- function(name, location, sampleapp = FALSE, resetbutton = TRUE, rightsidebar = FALSE, leftsidebar = TRUE, style = list(skin = "blue")) { usersep <- .Platform$file.sep newloc <- paste(location, name, sep = usersep) @@ -134,8 +138,18 @@ create_new_application <- function(name, location, sampleapp = FALSE, resetbutto stop("Framework creation could not proceed, invalid type for rightsidebar, only logical or character allowed") } } + if (!is.null(style)) { + if (class(style) == "list") { + if (!identical(intersect("skin", names(style)), character(0)) && !identical(class(style$skin), "character")) { + stop("Framework creation could not proceed, invalid type for skin, only character allowed. See ?shinydashboard::dashboardPage for supported colors.") + } + } else { + stop("Framework creation could not proceed, invalid type for style, only list allowed") + } + } + .create_dirs(newloc, usersep) - .copy_fw_files(newloc, usersep, resetbutton, dashboard_plus, leftsidebar, right_sidebar_icon) + .copy_fw_files(newloc, usersep, resetbutton, dashboard_plus, leftsidebar, right_sidebar_icon, style) .copy_program_files(newloc, usersep, sampleapp, resetbutton, leftsidebar, dashboard_plus) message("Framework creation was successful.") @@ -167,7 +181,7 @@ create_new_application <- function(name, location, sampleapp = FALSE, resetbutto } # Create Framework Files ---------------------------- -.copy_fw_files <- function(newloc, usersep, resetbutton = TRUE, dashboard_plus = FALSE, leftsidebar = TRUE, right_sidebar_icon = NULL) { +.copy_fw_files <- function(newloc, usersep, resetbutton = TRUE, dashboard_plus = FALSE, leftsidebar = TRUE, right_sidebar_icon = NULL, style = list(skin = "blue")) { files <- c("global.R", "server.R") if (dashboard_plus) { @@ -215,7 +229,19 @@ create_new_application <- function(name, location, sampleapp = FALSE, resetbutto writeLines(ui_content, con = ui_file) close(ui_file) } - + # styling + if (!is.null(style) && identical(class(style), "list") && length(style) > 0 && + !identical(intersect("skin", names(style)), character(0)) && !identical(style, list(skin = "blue"))) { + skin_value <- style$skin + ui_file <- file(paste(newloc, "ui.R", sep = usersep), open = "r+") + ui_content <- readLines(con = ui_file) + ui_content[length(ui_content)] <- paste0(substr(ui_content[length(ui_content)], 1, nchar(ui_content[length(ui_content)]) - 1), ",") + white_space <- paste(rep(" ", ifelse(dashboard_plus, nchar("dashboardPagePlus"), nchar("dashboardPage"))), collapse = "") + ui_content[length(ui_content) + 1] <- sprintf("%s skin = '%s')", white_space, skin_value) + writeLines(ui_content, con = ui_file) + close(ui_file) + } + #subdir copies imgs <- c("loader.gif", "tooltip.png") for (file in imgs) { diff --git a/R/logger.R b/R/logger.R new file mode 100644 index 0000000..cd83b07 --- /dev/null +++ b/R/logger.R @@ -0,0 +1,782 @@ +## +## The code in this file is part of the logging package. The logging package is free +## software: you can redistribute it as well as modify it under the terms of +## the GNU General Public License as published by the Free Software +## Foundation, either version 3 of the License, or (at your option) any later +## version. +## +## this program is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with the nens libraray. If not, see http://www.gnu.org/licenses/. +## +## Copyright (c) 2009..2013 by Mario Frasca +## + + +## create the logging environment +logging.options <- new.env() + + +##' The logging levels, names and values +#' +#' This list associates names to values and vice versa.\cr +#' Names and values are the same as in the python standard logging module. +#' +loglevels <- c(NOTSET = 0, + FINEST = 1, + FINER = 4, + FINE = 7, + DEBUG = 10, + INFO = 20, + WARNING = 30, + WARN = 30, + ERROR = 40, + CRITICAL = 50, + FATAL = 50) + +namedLevel <- function(value) + UseMethod("namedLevel") + +namedLevel.default <- function(value) { + loglevels[1] +} + +namedLevel.character <- function(value) { + position <- which(names(loglevels) == value) + if (length(position) == 0) { + position <- 1 + } + loglevels[position][1] +} + +namedLevel.numeric <- function(value) { + position <- which(loglevels == value) + if (length(position) == 0) { + position <- 1 + } + loglevels[position][1] +} + +Logger <- setRefClass( + "Logger", + fields = list( + name = "character", + handlers = "list", + level = "numeric", + msg_composer = "function"), + methods = list( + getParent = function() { + # split the name on the '.' + parts <- unlist(strsplit(name, ".", fixed = TRUE)) + removed <- parts[-length(parts)] # except the last item + parent_name <- paste(removed, collapse = ".") + return(getLogger(parent_name)) + }, + + getMsgComposer = function() { + if (!is.null(msg_composer) && !is.null(functionBody(msg_composer))) { + return(msg_composer) + } + if (name != "") { + parent_logger <- getParent() + return(parent_logger$getMsgComposer()) + } + return(defaultMsgCompose) + }, + + setMsgComposer = function(composer_f) { + if (!is.function(composer_f) + || paste(formalArgs(composer_f), collapse = ", ") != "msg, ...") { + stop(paste("message composer(passed as composer_f) must be function", + " with signature function(msg, ...)")) + } + msg_composer <<- composer_f + }, + + .deducelevel = function(initial_level = loglevels[["NOTSET"]]) { + if (initial_level != loglevels[["NOTSET"]]) { + # it's proper level (set: Not for inheritance) + return(initial_level) + } + + if (level != loglevels[["NOTSET"]]) { + return(level) + } + + if (name == "") { + # assume it's FINEST, as root logger cannot inherit + return(loglevels[["FINEST"]]) + } + + # ask parent for level + parent_logger <- getParent() + return(parent_logger$.deducelevel()) + }, + + .logrecord = function(record) { + logger_level <- .deducelevel(level) + if (record$level >= logger_level) { + for (handler in handlers) { + handler_level <- .deducelevel(with(handler, level)) + if (record$level >= handler_level) { + action <- with(handler, action) + formatter <- with(handler, formatter) + action(formatter(record), handler, record) + } + } + } + + if (name != "") { + parent_logger <- getParent() + parent_logger$.logrecord(record) + } + invisible(TRUE) + }, + + log = function(msglevel, msg, ...) { + msglevel <- namedLevel(msglevel) + if (msglevel < level) { + return(invisible(FALSE)) + } + ## fine, we create the record and pass it to all handlers attached to the + ## loggers from here up to the root. + record <- list() + + composer_f <- getMsgComposer() + record$msg <- composer_f(msg, ...) + record$timestamp <- sprintf("%s", Sys.time()) + record$logger <- name + record$level <- msglevel + record$levelname <- names(which(loglevels == record$level)[1]) + + ## cascade action in private method. + .logrecord(record) + }, + + setLevel = function(new_level) { + new_level <- namedLevel(new_level) + level <<- new_level + }, + + getLevel = function() level, + + getHandler = function(handler) { + if (!is.character(handler)) + handler <- deparse(substitute(handler)) + handlers[[handler]] + }, + + removeHandler = function(handler) { + if (!is.character(handler)) # handler was passed as its action + handler <- deparse(substitute(handler)) + handlers <<- handlers[!(names(handlers) == handler)] + }, + + addHandler = function(handler, ..., level = 0, formatter = defaultFormat) { + handler_env <- new.env() + if (is.character(handler)) { + ## first parameter is handler name + handler_name <- handler + ## and hopefully action is in the dots + params <- list(...) + if ("action" %in% names(params)) { + the_action <- params[["action"]] + } else if (length(params) > 0 && is.null(names(params)[[1]])) { + the_action <- params[[1]] + } else { + stop("No action for the handler provided") + } + + assign("action", the_action, handler_env) + } else { + ## first parameter is handler action, from which we extract the name + updateOptions.environment(handler_env, action = handler) + handler_name <- deparse(substitute(handler)) + } + updateOptions.environment(handler_env, ...) + assign("level", namedLevel(level), handler_env) + assign("formatter", formatter, handler_env) + removeHandler(handler_name) + + if (with(handler_env, action)(NA, handler_env, dry = TRUE) == TRUE) { + handlers[[handler_name]] <<- handler_env + } + }, + + finest = function(...) log(loglevels["FINEST"], ...), + finer = function(...) log(loglevels["FINER"], ...), + fine = function(...) log(loglevels["FINE"], ...), + debug = function(...) log(loglevels["DEBUG"], ...), + info = function(...) log(loglevels["INFO"], ...), + warn = function(...) log(loglevels["WARN"], ...), + error = function(...) log(loglevels["ERROR"], ...) + ) # methods +) # setRefClass + +#' +#' Entry points for logging actions +#' +#' Generate a log record and pass it to the logging system.\cr +#' +#' A log record gets timestamped and will be independently formatted by each +#' of the handlers handling it.\cr +#' +#' Leading and trailing whitespace is stripped from the final message. +#' +#' @param msg the textual message to be output, or the format for the \dots +#' arguments +#' @param ... if present, msg is interpreted as a format and the \dots values +#' are passed to it to form the actual message. +#' @param logger the name of the logger to which we pass the record +#' +#' @name logging-entrypoints +#' +NULL + +#' @rdname logging-entrypoints +#' +#' @export +logdebug <- function(msg, ..., logger = "") { + .levellog(loglevels["DEBUG"], msg, ..., logger = logger) +} + +#' @rdname logging-entrypoints +#' +#' @export +logfinest <- function(msg, ..., logger = "") { + .levellog(loglevels["FINEST"], msg, ..., logger = logger) +} + +#' @rdname logging-entrypoints +#' +#' @export +logfiner <- function(msg, ..., logger = "") { + .levellog(loglevels["FINER"], msg, ..., logger = logger) +} + +#' @rdname logging-entrypoints +#' +#' @export +logfine <- function(msg, ..., logger = "") { + .levellog(loglevels["FINE"], msg, ..., logger = logger) +} + +#' @rdname logging-entrypoints +#' +#' @export +loginfo <- function(msg, ..., logger = "") { + .levellog(loglevels["INFO"], msg, ..., logger = logger) +} + +#' @rdname logging-entrypoints +#' +#' @export +logwarn <- function(msg, ..., logger = "") { + .levellog(loglevels["WARN"], msg, ..., logger = logger) +} + +#' @rdname logging-entrypoints +#' +#' @export +logerror <- function(msg, ..., logger = "") { + .levellog(loglevels["ERROR"], msg, ..., logger = logger) +} + +#' @rdname logging-entrypoints +#' +#' @param level The logging level +#' +levellog <- function(level, msg, ..., logger = "") { + # just calling .levellog + # do not simplify it as call sequence sould be same + # as for other logX functions + .levellog(level, msg, ..., logger = logger) +} + +.levellog <- function(level, msg, ..., logger = "") { + if (is.character(logger)) { + logger <- getLogger(logger) + } + logger$log(level, msg, ...) +} + + + +#' +#' Set defaults and get the named logger. +#' +#' Make sure a logger with a specific name exists and return it as a +#' \var{Logger} S4 object. if not yet present, the logger will be created and +#' given the values specified in the \dots arguments. +#' @importFrom methods new +#' @param name The name of the logger +#' @param ... Any properties you may want to set in the newly created +#' logger. These have no effect if the logger is already present. +#' +#' @return The logger retrieved or registered. +getLogger <- function(name = "", ...) { + if (name == "") { + fullname <- "logging.ROOT" + } else { + fullname <- paste("logging.ROOT", name, sep = ".") + } + + if (!exists(fullname, envir = logging.options)) { + logger <- Logger$new(name = name, + handlers = list(), + level = namedLevel("NOTSET")) + updateOptions.environment(logger, ...) + logging.options[[fullname]] <- logger + + if (fullname == "logging.ROOT") { + .basic_config(logger) + } + } + logging.options[[fullname]] +} + + + +#' +#' Bootstrapping the logging package. +#' +#' \code{basicConfig} and \code{logReset} provide a way to put the logging package +#' in a know initial state. +#' +#' @name bootstrapping +NULL + +#' @rdname bootstrapping +#' +#' @details +#' \code{basicConfig} creates the root logger, attaches a console handler(by +#' \var{basic.stdout} name) to it and sets the level of the handler to +#' \code{level}. You must not call \code{basicConfig} to for logger to work any more: +#' then root logger is created it gets initialized by default the same way as +#' \code{basicConfig} does. If you need clear logger to fill with you own handlers +#' use \code{logReset} to remove all default handlers. +#' +#' @param level The logging level of the root logger. Defaults to INFO. Please do notice that +#' this has no effect on the handling level of the handler that basicConfig attaches to the +#' root logger. +#' +#' +basicConfig <- function(level = 20) { + root_logger <- getLogger() + + updateOptions(root_logger, level = namedLevel(level)) + .basic_config(root_logger) + + invisible() +} + +#' Called from basicConfig and while creating rootLogger. +#' @noRd +.basic_config <- function(root_logger) { + stopifnot(root_logger$name == "") + root_logger$addHandler("basic.stdout", writeToConsole) +} + + +#' @rdname bootstrapping +#' +#' @details +#' \code{logReset} reinitializes the whole logging system as if the package had just been +#' loaded except it also removes all default handlers. Typically, you would want to call +#' \code{basicConfig} immediately after a call to \code{logReset}. +#' +#' +#' +logReset <- function() { + ## reinizialize the whole logging system + + ## remove all content from the logging environment + rm(list = ls(logging.options), envir = logging.options) + + root_logger <- getLogger(level = "INFO") + root_logger$removeHandler("basic.stdout") + + invisible() +} + + + +#' +#' Add a handler to or remove one from a logger. +#' +#' Use this function to maintain the list of handlers attached to a logger.\cr +#' \cr +#' \code{addHandler} and \code{removeHandler} are also offered as methods of the +#' \var{Logger} S4 class. +#' +#' @details +#' Handlers are implemented as environments. Within a logger a handler is +#' identified by its \var{name} and all handlers define at least the +#' three variables: +#' \describe{ +#' \item{level}{all records at level lower than this are skipped.} +#' \item{formatter}{a function getting a record and returning a string} +#' \item{\code{action(msg, handler)}}{a function accepting two parameters: a +#' formatted log record and the handler itself. making the handler a +#' parameter of the action allows us to have reusable action functions.} +#' } +#' +#' Being an environment, a handler may define as many variables as you +#' think you need. keep in mind the handler is passed to the action +#' function, which can check for existence and can use all variables that +#' the handler defines. +#' +#' @param handler The name of the handler, or its action +#' @param logger the name of the logger to which to attach the new handler, +#' defaults to the root logger. +#' +#' @name handlers-management +NULL + +#' @rdname handlers-management +#' +#' @param ... Extra parameters, to be stored in the handler list +#' +#' \dots may contain extra parameters that will be passed to the handler +#' action. Some elements in the \dots will be interpreted here. +#' +#' +#' +addHandler <- function(handler, ..., logger = "") { + if (is.character(logger)) { + logger <- getLogger(logger) + } + + ## this part has to be repeated here otherwise the called function + ## will deparse the argument to 'handler', the formal name given + ## here to the parameter + if (is.character(handler)) { + logger$addHandler(handler, ...) + } else { + handler_name <- deparse(substitute(handler)) + logger$addHandler(handler = handler_name, action = handler, ...) + } +} + +#' @rdname handlers-management +#' +#' +removeHandler <- function(handler, logger = "") { + if (is.character(logger)) { + logger <- getLogger(logger) + } + if (!is.character(handler)) { + # handler was passed as its action + handler <- deparse(substitute(handler)) + } + logger$removeHandler(handler) +} + +#' +#' Retrieves a handler from a logger. +#' +#' @description +#' Handlers are not uniquely identified by their name. Only within the logger to which +#' they are attached is their name unique. This function is here to allow you grab a +#' handler from a logger so you can examine and alter it. +#' +#' @description +#' Typical use of this function is in \code{setLevel(newLevel, getHandler(...))}. +#' +#' @param handler The name of the handler, or its action. +#' @param logger Optional: the name of the logger. Defaults to the root logger. +#' +#' @return The retrieved handler object. It returns NULL if handler is not registered. +#' +#' +getHandler <- function(handler, logger = "") { + if (is.character(logger)) { + logger <- getLogger(logger) + } + if (!is.character(handler)) { + # handler was passed as its action + handler <- deparse(substitute(handler)) + } + logger$getHandler(handler) +} + + + +#' +#' Set \var{logging.level} for the object. +#' +#' Alter an existing logger or handler, setting its \var{logging.level} to a new +#' value. You can access loggers by name, while you must use \code{getHandler} to +#' get a handler. +#' +#' @param level The new level for this object. Can be numeric or character. +#' @param container a logger, its name or a handler. Default is root logger. +#' +setLevel <- function(level, container = "") { + if (is.null(container)) { + stop("NULL container provided: cannot set level for NULL container") + } + + if (is.character(container)) { + container <- getLogger(container) + } + assign("level", namedLevel(level), container) +} + +#' +#' Sets message composer for logger. +#' +#' Message composer is used to compose log message out of formatting string and arguments. +#' It is function with signature \code{function(msg, ...)}. Formatting message is passed under msg +#' and formatting arguments are passed as \code{...}. +#' +#' If message composer is not set default is in use (realized with \code{sprintf}). If message +#' composer is not set for sub-logger, parent's message composer will be used. +#' +#' @param composer_f message composer function (type: function(msg, ...)) +#' @param container name of logger to reset message composer for (type: character) +#' +setMsgComposer <- function(composer_f, container = "") { + if (is.null(container)) { + stop("NULL container provided: cannot set message composer for NULL container") + } + + if (is.character(container)) { + container <- getLogger(container) + } + container$setMsgComposer(composer_f) + assign("msg_composer", composer_f, container) +} + +#' +#' Resets previously set message composer. +#' +#' @param container name of logger to reset message composer for (type: character) +#' +resetMsgComposer <- function(container = "") { + if (is.null(container)) { + stop("NULL container provided: cannot resset message composer for NULL container") + } + + if (is.character(container)) { + container <- getLogger(container) + } + assign("msg_composer", function() NULL, container) +} + +#' +#' Changes settings of logger or handler. +#' +#' @param container a logger, its name or a handler. +#' @param ... options to set for the container. +#' +updateOptions <- function(container, ...) + UseMethod("updateOptions") + +#' @describeIn updateOptions Update options for logger identified by name. +#' +updateOptions.character <- function(container, ...) { + ## container is really just the name of the container + updateOptions(getLogger(container), ...) +} + +#' @describeIn updateOptions Update options of logger or handler passed by reference. +#' +updateOptions.environment <- function(container, ...) { + ## the container is a logger + config <- list(...) + if ("level" %in% names(config)) { + config$level <- namedLevel(config$level) + } else if (get("name", container) == "") { + # root logger + config$level <- loglevels["INFO"] + } else { + config$level <- loglevels["NOTSET"] + } + + for (key in names(config)) { + if (key != "") { + assign(key, config[[key]], container) + } + } + invisible() +} + +#' @describeIn updateOptions Update options of logger or handler passed by reference. +#' +updateOptions.Logger <- function(container, ...) { + updateOptions.environment(container, ...) +} + +#' +#' Predefined(sample) handler actions +#' +#' When you define a handler, you specify its name and the associated action. +#' A few predefined actions described below are provided. +#' +#' A handler action is a function that accepts a formatted message and handler +#' configuration. +#' +#' Messages passed are filtered already regarding loglevel. +#' +#' \dots parameters are used by logging system to interact with the action. \dots can +#' contain \var{dry} key to inform action that it meant to initialize itself. In the case +#' action should return TRUE if initialization succeeded. +#' +#' If it's not a dry run \dots contain the whole preformatted \var{logging.record}. +#' A \var{logging.record} is a named list and has following structure: +#' \describe{ +#' \item{msg}{contains the real formatted message} +#' \item{level}{message level as numeric} +#' \item{levelname}{message level name} +#' \item{logger}{name of the logger that generated it} +#' \item{timestamp}{formatted message timestamp} +#' } +#' +#' @param msg A formatted message to handle. +#' @param handler The handler environment containing its options. You can +#' register the same action to handlers with different properties. +#' @param ... parameters provided by logger system to interact with the action. +#' +#' @examples +#' ## define your own function and register it with a handler. +#' ## author is planning a sentry client function. please send +#' ## any interesting function you may have written! +#' +#' @name inbuilt-actions +NULL + +#' @rdname inbuilt-actions +#' +#' @details +#' \code{writeToConsole} detects if crayon package is available and uses it +#' to color messages. The coloring can be switched off by means of configuring +#' the handler with \var{color_output} option set to FALSE. +#' +writeToConsole <- function(msg, handler, ...) { + if (length(list(...)) && "dry" %in% names(list(...))) { + if (!is.null(handler$color_output) && handler$color_output == FALSE) { + handler$color_msg <- function(msg, level_name) msg + } else { + handler$color_msg <- .build_msg_coloring() + } + return(TRUE) + } + + stopifnot(length(list(...)) > 0) + + level_name <- list(...)[[1]]$levelname + msg <- handler$color_msg(msg, level_name) + cat(paste0(msg, "\n")) +} + +.build_msg_coloring <- function() { + crayon_env <- tryCatch(asNamespace("crayon"), + error = function(e) NULL) + + default_color_msg <- function(msg, level_name) msg + if (is.null(crayon_env)) { + return(default_color_msg) + } + + if (is.null(crayon_env$make_style) || + is.null(crayon_env$combine_styles) || + is.null(crayon_env$reset)) { + return(default_color_msg) + } + + color_msg <- function(msg, level_name) { + style <- switch(level_name, + "FINEST" = crayon_env$make_style("gray80"), + "FINER" = crayon_env$make_style("gray60"), + "FINE" = crayon_env$make_style("gray60"), + "DEBUG" = crayon_env$make_style("deepskyblue4"), + "INFO" = crayon_env$reset, + "WARNING" = crayon_env$make_style("darkorange"), + "ERROR" = crayon_env$make_style("red4"), + "CRITICAL" = + crayon_env$combine_styles(crayon_env$bold, + crayon_env$make_style("red1")), + crayon_env$make_style("gray100")) + res <- paste0(style(msg), crayon_env$reset("")) + return(res) + } + return(color_msg) +} + + +#' @rdname inbuilt-actions +#' +#' @details \code{writeToFile} action expects file path to write to under +#' \var{file} key in handler options. +#' +writeToFile <- function(msg, handler, ...) { + if (length(list(...)) && "dry" %in% names(list(...))) + return(exists("file", envir = handler)) + cat(paste0(msg, "\n"), file = with(handler, file), append = TRUE) +} + +## the single predefined formatter +defaultFormat <- function(record) { + ## strip leading and trailing whitespace from the final message. + msg <- trimws(record$msg) + text <- paste(record$timestamp, + paste(record$levelname, record$logger, msg, sep = ":")) + return(text) +} + +## default way of composing msg with parameters +defaultMsgCompose <- function(msg, ...) { + optargs <- list(...) + if (is.character(msg)) { + ## invoked as ("printf format", arguments_for_format) + if (length(optargs) > 0) { + optargs <- lapply(optargs, + function(x) { + if (length(x) != 1) + x <- paste(x, collapse = ",") + x + }) + } + + # 8192 is limitation on fmt in sprintf + if (any(nchar(msg) > 8192)) { + if (length(optargs) > 0) { + stop("'msg' length exceeds maximal format length 8192") + } + + # else msg must not change in any way + return(msg) + } + if (length(optargs) > 0) { + msg <- do.call("sprintf", c(msg, optargs)) + } + return(msg) + } + + ## invoked as list of expressions + ## this assumes that the function the user calls is two levels up, e.g.: + ## loginfo -> .levellog -> logger$log -> .default_msg_composer + ## levellog -> .levellog -> logger$log -> .default_msg_composer + external_call <- sys.call(-3) + external_fn <- eval(external_call[[1]]) + matched_call <- match.call(external_fn, external_call) + matched_call <- matched_call[-1] + matched_call_names <- names(matched_call) + + ## We are interested only in the msg and ... parameters, + ## i.e. in msg and all parameters not explicitly declared + ## with the function + formal_names <- names(formals(external_fn)) + is_output_param <- + matched_call_names == "msg" | + !(matched_call_names %in% c(setdiff(formal_names, "..."))) + + label <- lapply(matched_call[is_output_param], deparse) + msg <- sprintf("%s: %s", label, c(msg, optargs)) + return(msg) +} diff --git a/R/periscope.R b/R/periscope.R index 6aa0ac9..bedd4e8 100755 --- a/R/periscope.R +++ b/R/periscope.R @@ -44,3 +44,16 @@ NULL .onLoad <- function(libname, pkgname) { #TBD } + +.onAttach <- function(libname, pkgname) { + current_location <- getwd() + server_filename <- "server.R" + if (interactive() && file.exists(file.path(current_location, c(server_filename)))) { + server_file <- file(paste(current_location, server_filename, sep = .Platform$file.sep)) + server_content <- readLines(con = server_file) + close(server_file) + if (any(grepl("library\\(logging\\)", server_content))) { + packageStartupMessage(paste("The logging package is not supported anymore. Please remove the line 'library(logging)' in", server_filename)) + } + } +} diff --git a/R/ui_helpers.R b/R/ui_helpers.R index c797444..9e59e8e 100755 --- a/R/ui_helpers.R +++ b/R/ui_helpers.R @@ -226,8 +226,6 @@ ui_tooltip <- function(id, label = "", text = "") { #' @section Shiny Usage: #' Call this function from \code{program/global.R} to set the application #' parameters. -#' -#' @seealso \link[logging:logging-package]{logging} #' #' @export set_app_parameters <- function(title, titleinfo = NULL, diff --git a/README.md b/README.md index af99bb8..0911849 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,12 @@ create_new_application("sampleapp2", location = tempdir(), sampleapp = TRUE, rig runApp('sampleapp2') ``` + +#### Sample application - custom styling + +```r +library(periscope) +create_new_application("sampleapp3", location = tempdir(), sampleapp = TRUE, style = list(skin = "green")) +runApp('sampleapp3') + +``` diff --git a/cran-comments.md b/cran-comments.md index aa1a4fd..3260e8a 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,8 +1,6 @@ ## Comments from Maintainer -This is a very minor correction to our just-accepted 0.4.10 release - we missed a file change in the merge for the release, apologies in advance. - -Fixed tests based on input from Barret Schloerke in preparation for the next release of shiny to CRAN next week. +Added functionality, testing and resolved the use of the archived logging package. --- @@ -13,13 +11,13 @@ RStudio Server Pro (Ubuntu 18.04.2) * R 3.5.3 * R 3.6.3 -* R 4.0.0 +* R 4.0.1 Travis-CI (Ubuntu 16.04.6) -* R 3.5.3 * R 3.6.3 -* R devel (2020-05-04 r78358) +* R 4.0.0 +* R devel (2020-07-03 r78773) WinBuilder diff --git a/inst/fw_templ/server.R b/inst/fw_templ/server.R index 61e2b60..954a840 100755 --- a/inst/fw_templ/server.R +++ b/inst/fw_templ/server.R @@ -7,9 +7,6 @@ # ***** DO NOT EDIT THIS FILE ***** -- # ------------------------------------------------- -library(logging) - - source(paste("program", "server_global.R", sep = .Platform$file.sep), local = TRUE) diff --git a/man/bootstrapping.Rd b/man/bootstrapping.Rd new file mode 100644 index 0000000..702c981 --- /dev/null +++ b/man/bootstrapping.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\name{bootstrapping} +\alias{bootstrapping} +\alias{basicConfig} +\alias{logReset} +\title{Bootstrapping the logging package.} +\usage{ +basicConfig(level = 20) + +logReset() +} +\arguments{ +\item{level}{The logging level of the root logger. Defaults to INFO. Please do notice that +this has no effect on the handling level of the handler that basicConfig attaches to the +root logger.} +} +\description{ +\code{basicConfig} and \code{logReset} provide a way to put the logging package +in a know initial state. +} +\details{ +\code{basicConfig} creates the root logger, attaches a console handler(by +\var{basic.stdout} name) to it and sets the level of the handler to +\code{level}. You must not call \code{basicConfig} to for logger to work any more: +then root logger is created it gets initialized by default the same way as +\code{basicConfig} does. If you need clear logger to fill with you own handlers +use \code{logReset} to remove all default handlers. + +\code{logReset} reinitializes the whole logging system as if the package had just been +loaded except it also removes all default handlers. Typically, you would want to call +\code{basicConfig} immediately after a call to \code{logReset}. +} diff --git a/man/create_new_application.Rd b/man/create_new_application.Rd index f02f0ce..ccd276e 100644 --- a/man/create_new_application.Rd +++ b/man/create_new_application.Rd @@ -10,7 +10,8 @@ create_new_application( sampleapp = FALSE, resetbutton = TRUE, rightsidebar = FALSE, - leftsidebar = TRUE + leftsidebar = TRUE, + style = list(skin = "blue") ) } \arguments{ @@ -26,6 +27,8 @@ create_new_application( containing the name of a shiny::icon().} \item{leftsidebar}{whether the left sidebar should be enabled.} + +\item{style}{list containing application styling properties. By default the skin is blue.} } \description{ Creates ready-to-use templated application files using the periscope @@ -129,10 +132,14 @@ rightsidebar = "table") # blank app named 'myblankapp' created in a temp dir create_new_application(name = 'myblankapp', location = tempdir()) +# blank app named 'myblankapp' with a green skin created in a temp dir +create_new_application(name = 'myblankapp', location = tempdir(), style = list(skin = "green")) # blank app named 'myblankapp' without a left sidebar created in a temp dir create_new_application(name = 'myblankapp', location = tempdir(), leftsidebar = FALSE) } \seealso{ \link[shiny:icon]{shiny:icon()} + +\link[shinydashboard:dashboardPage]{shinydashboard:dashboardPage()} } diff --git a/man/downloadFile.Rd b/man/downloadFile.Rd index f4464c6..3517e71 100644 --- a/man/downloadFile.Rd +++ b/man/downloadFile.Rd @@ -22,7 +22,7 @@ downloadFile( \item{session}{provided by \code{shiny::callModule} \cr \cr} -\item{logger}{\link[logging:logging-package]{logging} logger to use} +\item{logger}{logger to use} \item{filenameroot}{the base text used for user-downloaded file - can be either a character string or a reactive expression that returns a character @@ -77,6 +77,4 @@ server.R using the same id provided in \code{downloadFileButton}: \link[periscope]{downloadFile_AvailableTypes} \link[shiny]{callModule} - -\link[logging:logging-package]{logging} } diff --git a/man/downloadablePlot.Rd b/man/downloadablePlot.Rd index e382ee2..f8525e7 100644 --- a/man/downloadablePlot.Rd +++ b/man/downloadablePlot.Rd @@ -23,7 +23,7 @@ downloadablePlot( \item{session}{provided by \code{shiny::callModule} \cr \cr} -\item{logger}{\link[logging:logging-package]{logging} logger to use} +\item{logger}{logger to use} \item{filenameroot}{the base text used for user-downloaded file - can be either a character string or a reactive expression returning a character @@ -75,6 +75,4 @@ downloadfxns, visibleplot)}} \link[periscope]{downloadablePlotUI} \link[shiny]{callModule} - -\link[logging:logging-package]{logging} } diff --git a/man/downloadablePlotUI.Rd b/man/downloadablePlotUI.Rd index dfb8a82..b8effec 100644 --- a/man/downloadablePlotUI.Rd +++ b/man/downloadablePlotUI.Rd @@ -62,10 +62,9 @@ button will be hidden as there is nothing to download. The linked downloadfxns are set in the paired callModule (see the \strong{Shiny Usage} section) -This module is NOT compatible with the built-in (base) graphics \emph{(any -functions provided by the \link[graphics]{graphics} package such as plot)} -because they cannot be saved into an object and are directly output by the -system at the time of creation. +This module is NOT compatible with the built-in (base) graphics \emph{(such as +basic plot, etc.)} because they cannot be saved into an object and are directly +output by the system at the time of creation. } \section{Shiny Usage}{ diff --git a/man/downloadableTable.Rd b/man/downloadableTable.Rd index 9ef320c..2b3e406 100644 --- a/man/downloadableTable.Rd +++ b/man/downloadableTable.Rd @@ -25,7 +25,7 @@ downloadableTable( \item{session}{provided by \code{shiny::callModule} \cr \cr} -\item{logger}{\link[logging:logging-package]{logging} logger to use} +\item{logger}{logger to use} \item{filenameroot}{the base text used for user-downloaded file - can be either a character string or a reactive expression returning a character @@ -92,6 +92,4 @@ currently selected rows in the display table. \link[periscope]{downloadableTableUI} \link[shiny]{callModule} - -\link[logging:logging-package]{logging} } diff --git a/man/getHandler.Rd b/man/getHandler.Rd new file mode 100644 index 0000000..e9c8cf7 --- /dev/null +++ b/man/getHandler.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\name{getHandler} +\alias{getHandler} +\title{Retrieves a handler from a logger.} +\usage{ +getHandler(handler, logger = "") +} +\arguments{ +\item{handler}{The name of the handler, or its action.} + +\item{logger}{Optional: the name of the logger. Defaults to the root logger.} +} +\value{ +The retrieved handler object. It returns NULL if handler is not registered. +} +\description{ +Handlers are not uniquely identified by their name. Only within the logger to which +they are attached is their name unique. This function is here to allow you grab a +handler from a logger so you can examine and alter it. + +Typical use of this function is in \code{setLevel(newLevel, getHandler(...))}. +} diff --git a/man/getLogger.Rd b/man/getLogger.Rd new file mode 100644 index 0000000..e45722f --- /dev/null +++ b/man/getLogger.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\name{getLogger} +\alias{getLogger} +\title{Set defaults and get the named logger.} +\usage{ +getLogger(name = "", ...) +} +\arguments{ +\item{name}{The name of the logger} + +\item{...}{Any properties you may want to set in the newly created +logger. These have no effect if the logger is already present.} +} +\value{ +The logger retrieved or registered. +} +\description{ +Make sure a logger with a specific name exists and return it as a +\var{Logger} S4 object. if not yet present, the logger will be created and +given the values specified in the \dots arguments. +} diff --git a/man/handlers-management.Rd b/man/handlers-management.Rd new file mode 100644 index 0000000..62112e1 --- /dev/null +++ b/man/handlers-management.Rd @@ -0,0 +1,46 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\name{handlers-management} +\alias{handlers-management} +\alias{addHandler} +\alias{removeHandler} +\title{Add a handler to or remove one from a logger.} +\usage{ +addHandler(handler, ..., logger = "") + +removeHandler(handler, logger = "") +} +\arguments{ +\item{handler}{The name of the handler, or its action} + +\item{...}{Extra parameters, to be stored in the handler list + +\dots may contain extra parameters that will be passed to the handler +action. Some elements in the \dots will be interpreted here.} + +\item{logger}{the name of the logger to which to attach the new handler, +defaults to the root logger.} +} +\description{ +Use this function to maintain the list of handlers attached to a logger.\cr +\cr +\code{addHandler} and \code{removeHandler} are also offered as methods of the +\var{Logger} S4 class. +} +\details{ +Handlers are implemented as environments. Within a logger a handler is +identified by its \var{name} and all handlers define at least the +three variables: +\describe{ + \item{level}{all records at level lower than this are skipped.} + \item{formatter}{a function getting a record and returning a string} + \item{\code{action(msg, handler)}}{a function accepting two parameters: a + formatted log record and the handler itself. making the handler a + parameter of the action allows us to have reusable action functions.} +} + +Being an environment, a handler may define as many variables as you +think you need. keep in mind the handler is passed to the action +function, which can check for existence and can use all variables that +the handler defines. +} diff --git a/man/inbuilt-actions.Rd b/man/inbuilt-actions.Rd new file mode 100644 index 0000000..8e4dbc2 --- /dev/null +++ b/man/inbuilt-actions.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\name{inbuilt-actions} +\alias{inbuilt-actions} +\alias{writeToConsole} +\alias{writeToFile} +\title{Predefined(sample) handler actions} +\usage{ +writeToConsole(msg, handler, ...) + +writeToFile(msg, handler, ...) +} +\arguments{ +\item{msg}{A formatted message to handle.} + +\item{handler}{The handler environment containing its options. You can +register the same action to handlers with different properties.} + +\item{...}{parameters provided by logger system to interact with the action.} +} +\description{ +When you define a handler, you specify its name and the associated action. +A few predefined actions described below are provided. +} +\details{ +A handler action is a function that accepts a formatted message and handler +configuration. + +Messages passed are filtered already regarding loglevel. + +\dots parameters are used by logging system to interact with the action. \dots can +contain \var{dry} key to inform action that it meant to initialize itself. In the case +action should return TRUE if initialization succeeded. + +If it's not a dry run \dots contain the whole preformatted \var{logging.record}. +A \var{logging.record} is a named list and has following structure: +\describe{ + \item{msg}{contains the real formatted message} + \item{level}{message level as numeric} + \item{levelname}{message level name} + \item{logger}{name of the logger that generated it} + \item{timestamp}{formatted message timestamp} +} + +\code{writeToConsole} detects if crayon package is available and uses it +to color messages. The coloring can be switched off by means of configuring +the handler with \var{color_output} option set to FALSE. + +\code{writeToFile} action expects file path to write to under + \var{file} key in handler options. +} +\examples{ +## define your own function and register it with a handler. +## author is planning a sentry client function. please send +## any interesting function you may have written! + +} diff --git a/man/logging-entrypoints.Rd b/man/logging-entrypoints.Rd new file mode 100644 index 0000000..d62343e --- /dev/null +++ b/man/logging-entrypoints.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\name{logging-entrypoints} +\alias{logging-entrypoints} +\alias{logdebug} +\alias{logfinest} +\alias{logfiner} +\alias{logfine} +\alias{loginfo} +\alias{logwarn} +\alias{logerror} +\alias{levellog} +\title{Entry points for logging actions} +\usage{ +logdebug(msg, ..., logger = "") + +logfinest(msg, ..., logger = "") + +logfiner(msg, ..., logger = "") + +logfine(msg, ..., logger = "") + +loginfo(msg, ..., logger = "") + +logwarn(msg, ..., logger = "") + +logerror(msg, ..., logger = "") + +levellog(level, msg, ..., logger = "") +} +\arguments{ +\item{msg}{the textual message to be output, or the format for the \dots +arguments} + +\item{...}{if present, msg is interpreted as a format and the \dots values +are passed to it to form the actual message.} + +\item{logger}{the name of the logger to which we pass the record} + +\item{level}{The logging level} +} +\description{ +Generate a log record and pass it to the logging system.\cr +} +\details{ +A log record gets timestamped and will be independently formatted by each +of the handlers handling it.\cr + +Leading and trailing whitespace is stripped from the final message. +} diff --git a/man/loglevels.Rd b/man/loglevels.Rd new file mode 100644 index 0000000..4407429 --- /dev/null +++ b/man/loglevels.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\docType{data} +\name{loglevels} +\alias{loglevels} +\title{The logging levels, names and values} +\format{ +An object of class \code{numeric} of length 11. +} +\usage{ +loglevels +} +\description{ +This list associates names to values and vice versa.\cr +Names and values are the same as in the python standard logging module. +} +\keyword{datasets} diff --git a/man/resetMsgComposer.Rd b/man/resetMsgComposer.Rd new file mode 100644 index 0000000..0e84b69 --- /dev/null +++ b/man/resetMsgComposer.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\name{resetMsgComposer} +\alias{resetMsgComposer} +\title{Resets previously set message composer.} +\usage{ +resetMsgComposer(container = "") +} +\arguments{ +\item{container}{name of logger to reset message composer for (type: character)} +} +\description{ +Resets previously set message composer. +} diff --git a/man/setLevel.Rd b/man/setLevel.Rd new file mode 100644 index 0000000..438adda --- /dev/null +++ b/man/setLevel.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\name{setLevel} +\alias{setLevel} +\title{Set \var{logging.level} for the object.} +\usage{ +setLevel(level, container = "") +} +\arguments{ +\item{level}{The new level for this object. Can be numeric or character.} + +\item{container}{a logger, its name or a handler. Default is root logger.} +} +\description{ +Alter an existing logger or handler, setting its \var{logging.level} to a new +value. You can access loggers by name, while you must use \code{getHandler} to +get a handler. +} diff --git a/man/setMsgComposer.Rd b/man/setMsgComposer.Rd new file mode 100644 index 0000000..abeb407 --- /dev/null +++ b/man/setMsgComposer.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\name{setMsgComposer} +\alias{setMsgComposer} +\title{Sets message composer for logger.} +\usage{ +setMsgComposer(composer_f, container = "") +} +\arguments{ +\item{composer_f}{message composer function (type: function(msg, ...))} + +\item{container}{name of logger to reset message composer for (type: character)} +} +\description{ +Message composer is used to compose log message out of formatting string and arguments. +It is function with signature \code{function(msg, ...)}. Formatting message is passed under msg +and formatting arguments are passed as \code{...}. +} +\details{ +If message composer is not set default is in use (realized with \code{sprintf}). If message +composer is not set for sub-logger, parent's message composer will be used. +} diff --git a/man/set_app_parameters.Rd b/man/set_app_parameters.Rd index 42b07be..e96196d 100644 --- a/man/set_app_parameters.Rd +++ b/man/set_app_parameters.Rd @@ -46,6 +46,3 @@ Call this function from \code{program/global.R} to set the application parameters. } -\seealso{ -\link[logging:logging-package]{logging} -} diff --git a/man/updateOptions.Rd b/man/updateOptions.Rd new file mode 100644 index 0000000..c558648 --- /dev/null +++ b/man/updateOptions.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/logger.R +\name{updateOptions} +\alias{updateOptions} +\alias{updateOptions.character} +\alias{updateOptions.environment} +\alias{updateOptions.Logger} +\title{Changes settings of logger or handler.} +\usage{ +updateOptions(container, ...) + +\method{updateOptions}{character}(container, ...) + +\method{updateOptions}{environment}(container, ...) + +\method{updateOptions}{Logger}(container, ...) +} +\arguments{ +\item{container}{a logger, its name or a handler.} + +\item{...}{options to set for the container.} +} +\description{ +Changes settings of logger or handler. +} +\section{Methods (by class)}{ +\itemize{ +\item \code{character}: Update options for logger identified by name. + +\item \code{environment}: Update options of logger or handler passed by reference. + +\item \code{Logger}: Update options of logger or handler passed by reference. +}} + diff --git a/tests/testthat/sample_app/server.R b/tests/testthat/sample_app/server.R index 61e2b60..954a840 100644 --- a/tests/testthat/sample_app/server.R +++ b/tests/testthat/sample_app/server.R @@ -7,9 +7,6 @@ # ***** DO NOT EDIT THIS FILE ***** -- # ------------------------------------------------- -library(logging) - - source(paste("program", "server_global.R", sep = .Platform$file.sep), local = TRUE) diff --git a/tests/testthat/sample_app_no_sidebar/server.R b/tests/testthat/sample_app_no_sidebar/server.R index 61e2b60..954a840 100644 --- a/tests/testthat/sample_app_no_sidebar/server.R +++ b/tests/testthat/sample_app_no_sidebar/server.R @@ -7,9 +7,6 @@ # ***** DO NOT EDIT THIS FILE ***** -- # ------------------------------------------------- -library(logging) - - source(paste("program", "server_global.R", sep = .Platform$file.sep), local = TRUE) diff --git a/tests/testthat/test_create_new_application.R b/tests/testthat/test_create_new_application.R index aa825b9..c39ed45 100755 --- a/tests/testthat/test_create_new_application.R +++ b/tests/testthat/test_create_new_application.R @@ -1,7 +1,7 @@ context("periscope create new application") -expect_cleanup_create_new_application <- function(fullname, sampleapp = FALSE, dashboard_plus = FALSE, leftsidebar = TRUE) { +expect_cleanup_create_new_application <- function(fullname, sampleapp = FALSE, dashboard_plus = FALSE, leftsidebar = TRUE, skin = NULL) { expect_true(dir.exists(fullname)) expect_true(file.exists(paste0(fullname, "/global.R"))) expect_true(file.exists(paste0(fullname, "/server.R"))) @@ -37,6 +37,12 @@ expect_cleanup_create_new_application <- function(fullname, sampleapp = FALSE, d } else { expect_true(!file.exists(paste0(fullname, "/program/ui_sidebar_right.R"))) } + if (!is.null(skin)) { + ui_file <- file(paste0(fullname, "/ui.R"), open = "r") + ui_content <- readLines(con = ui_file) + close(ui_file) + expect_true(any(grepl(skin, ui_content))) + } # clean up unlink(fullname, TRUE) @@ -73,6 +79,15 @@ test_that("create_new_application sample right_sidebar", { expect_cleanup_create_new_application(appTemp, sampleapp = TRUE, dashboard_plus = !is.null(right_sidebar)) }) +test_that("create_new_application sample invalid right_sidebar", { + appTemp.dir <- tempdir() + appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp.dir) + appTemp.name <- gsub('\\\\|/', '', (gsub(appTemp.dir, "", appTemp, fixed = T))) + + expect_error(create_new_application(name = appTemp.name, location = appTemp.dir, sampleapp = TRUE, rightsidebar = mtcars), + "Framework creation could not proceed, invalid type for rightsidebar, only logical or character allowed") +}) + test_that("create_new_application no left sidebar", { appTemp.dir <- tempdir() appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp.dir) @@ -93,6 +108,25 @@ test_that("create_new_application no reset button", { expect_cleanup_create_new_application(appTemp, sampleapp = TRUE) }) +test_that("create_new_application custom style", { + appTemp.dir <- tempdir() + appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp.dir) + appTemp.name <- gsub('\\\\|/', '', (gsub(appTemp.dir, "", appTemp, fixed = T))) + + expect_message(create_new_application(name = appTemp.name, location = appTemp.dir, sampleapp = FALSE, rightsidebar = NULL, style = list(skin = "green")), + "Framework creation was successful.") + expect_cleanup_create_new_application(appTemp, skin = "green") +}) + +test_that("create_new_application invalid style", { + appTemp.dir <- tempdir() + appTemp <- tempfile(pattern = "TestThatApp", tmpdir = appTemp.dir) + appTemp.name <- gsub('\\\\|/', '', (gsub(appTemp.dir, "", appTemp, fixed = T))) + + expect_error(create_new_application(name = appTemp.name, location = appTemp.dir, sampleapp = FALSE, rightsidebar = NULL, style = mtcars), + "Framework creation could not proceed, invalid type for style, only list allowed") +}) + test_that("create_new_application invalid location", { expect_warning(create_new_application(name = "Invalid", location = tempfile(), sampleapp = FALSE), "Framework creation could not proceed, location=.* does not exist!") diff --git a/vignettes/new-application.Rmd b/vignettes/new-application.Rmd index 658268d..39e2f38 100755 --- a/vignettes/new-application.Rmd +++ b/vignettes/new-application.Rmd @@ -65,8 +65,8 @@ Users can create their own tabs in the right sidebar up to a maximum of 5. The right sidebar is collapsed by default, it can be opened by clicking on the (customizable) icon.
-**Note:** The blue area running across the top of the application is the header bar. This -area is reserved for framework use. +**Note:** The blue area running across the top of the application is the header bar. This +area is reserved for framework use.
@@ -150,6 +150,13 @@ createAlert(session, "sidebarRightAlert", content = "Error Alert Text") ``` +#### Styling + +The application can be created with a custom style. For now, only the color of the application header bar can be changed, +but more options will be added later on. The bar color (aka the 'skin') is by default blue, but it can also be set to "black", "purple", "green", "red" or "yellow". + +*See the Creating a Sample Application and Creating your Application sections for an example* + ### Shiny Modules #### downloadFile @@ -192,6 +199,8 @@ create_new_application(name = 'mytestapp', location = app_dir, sampleapp = TRUE, create_new_application(name = 'mytestapp', location = app_dir, sampleapp = TRUE, rightsidebar = TRUE) # application with a right sidebar using a custom icon create_new_application(name = 'mytestapp', location = app_dir, sampleapp = TRUE, rightsidebar = "table") +# application with a custom header bar color (skin) +create_new_application(name = 'mytestapp', location = app_dir, sampleapp = TRUE, style = list(skin = "green")) ``` This generates a default sample application optionally with a left/right sidebar in a subdirectory named *mytestapp* @@ -230,6 +239,8 @@ create_new_application(name = 'mytestapp', location = app_dir, resetbutton = FAL create_new_application(name = 'mytestapp', location = app_dir, rightsidebar = TRUE) # application with a right sidebar using a custom icon create_new_application(name = 'mytestapp', location = app_dir, rightsidebar = "table") +# application with a custom header bar color (skin) +create_new_application(name = 'mytestapp', location = app_dir, style = list(skin = "green")) ``` This generates a default blank application optionally with a left/right sidebar in a subdirectory named *mytestapp*