The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Console Messaging with handyCli

library(tidyOhdsiSolutions)

# Disable ANSI colour codes so rendered HTML output is clean
options(pkg.no_color = TRUE)

Overview

handyCli is a zero-dependency console messaging system built on base R. It provides styled, consistently prefixed messages for every severity level, structured output helpers (msg_list(), msg_kv()), progress and spinner indicators, a timing wrapper, and safe error-handling utilities.

All functions write to message() (stderr) so they are silenceable with suppressMessages() and do not pollute stdout() or function return values.


1 Basic message levels

Each message type carries a distinct prefix symbol.

msg_info("Loading configuration from disk")
#> i Loading configuration from disk
msg_success("All 42 concept sets validated")
#> v All 42 concept sets validated
msg_warn("Column 'mappedSourceCode' is missing — using NA")
#> ! Column 'mappedSourceCode' is missing — using NA
msg_danger("Connection pool exhausted (non-fatal, retrying)")
#> x Connection pool exhausted (non-fatal, retrying)
msg_process("Uploading results to the remote schema")
#> -> Uploading results to the remote schema
msg_bullet("concept_id 201826 — Type 2 Diabetes Mellitus")
#> * concept_id 201826 — Type 2 Diabetes Mellitus
msg_todo("Verify descendant flag for hypertension concept set")
#> - Verify descendant flag for hypertension concept set

2 Section structure: headers, rules, and blank lines

Use msg_header() and msg_rule() to visually group output in long-running pipelines.

msg_header("Step 1: Validate Input")
#> --------------------------- Step 1: Validate Input ---------------------------
msg_info("Checking required columns")
#> i Checking required columns
msg_success("Validation passed")
#> v Validation passed
msg_blank()
#> 
msg_header("Step 2: Build Cohort")
#> ---------------------------- Step 2: Build Cohort ----------------------------
msg_process("Assembling concept set expressions")
#> -> Assembling concept set expressions
msg_rule()
#> --------------------------------------------------------------------------------
msg_info("Pipeline complete")
#> i Pipeline complete

3 Structured output: lists and key-value tables

Named list

msg_list() is useful for displaying a set of labelled items, such as cohort components or domain breakdowns.

domains <- c(
  Condition   = "201826, 442793",
  Drug        = "1503297, 40163554",
  Measurement = "3004501"
)
msg_list(domains, header = "Concept set domains")
#> i Concept set domains
#>   > Condition: 201826, 442793
#>   > Drug: 1503297, 40163554
#>   > Measurement: 3004501

Key-value table

msg_kv() aligns keys and values into two columns — handy for configuration summaries or run metadata.

run_info <- list(
  Package   = "tidyOhdsiSolutions",
  Version   = as.character(packageVersion("tidyOhdsiSolutions")),
  R_version = paste0(R.version$major, ".", R.version$minor),
  Date      = format(Sys.Date())
)
msg_kv(run_info)
#>   Package    tidyOhdsiSolutions
#>   Version    0.1.0
#>   R_version  4.5.3
#>   Date       2026-04-08

4 Iteration patterns

4a Logging inside a loop

Combine msg_header() and message functions to annotate each iteration of a processing loop.

concept_sets <- list(
  diabetes     = c(201826L, 442793L),
  hypertension = c(320128L),
  obesity      = c(433736L, 4215968L)
)

for (nm in names(concept_sets)) {
  msg_header(nm)
  msg_info("Concepts: ", paste(concept_sets[[nm]], collapse = ", "))
  msg_success("Processed ", length(concept_sets[[nm]]), " concept(s)")
  msg_blank()
}
#> ---------------------------------- diabetes ----------------------------------
#> i Concepts: 201826, 442793
#> v Processed 2 concept(s)
#> 
#> -------------------------------- hypertension --------------------------------
#> i Concepts: 320128
#> v Processed 1 concept(s)
#> 
#> ---------------------------------- obesity -----------------------------------
#> i Concepts: 433736, 4215968
#> v Processed 2 concept(s)
#> 

4b Safe iteration with msg_try()

msg_try() wraps an expression so errors and warnings are caught and styled without stopping the loop. The on_error argument controls the behaviour: "warn" downgrades errors to styled warnings; "ignore" silences them.

sources <- list(
  schema_a = list(valid = TRUE,  rows = 1200L),
  schema_b = list(valid = FALSE, rows = 0L),
  schema_c = list(valid = TRUE,  rows = 850L)
)

results <- vector("list", length(sources))
names(results) <- names(sources)

for (nm in names(sources)) {
  results[[nm]] <- msg_try(
    on_error = "warn",
    expr = {
      src <- sources[[nm]]
      if (!src$valid) stop("Schema '", nm, "' failed validation")
      msg_success(nm, ": ", src$rows, " rows loaded")
      src$rows
    }
  )
}
#> v schema_a: 1200 rows loaded
#> ! Schema 'schema_b' failed validation
#> v schema_c: 850 rows loaded

4c Verbose mode — conditional logging inside helpers

msg_verbose() only emits output when getOption("pkg.verbose") is TRUE (or the verbose argument is set explicitly). This lets callers opt in/out without modifying the function body.

process_file <- function(path, verbose = TRUE) {
  msg_verbose("Opening: ", path, verbose = verbose)
  # ... processing ...
  msg_verbose("Done:    ", path, verbose = verbose)
  invisible(path)
}

# Verbose on (default)
process_file("data/concepts.csv")
#> i Opening: data/concepts.csv
#> i Done:    data/concepts.csv

# Verbose off
process_file("data/concepts.csv", verbose = FALSE)

5 Timing expressions with msg_timed()

msg_timed() evaluates an expression, prints a labelled elapsed-time message, and returns the result invisibly. It is composable — the timed block can be any R expression, including a whole pipeline.

result <- msg_timed(
  expr  = Sys.sleep(0.05),
  msg   = "Sleeping"
)
#> i Sleeping: 0.08s

Timing an iteration

concept_ids <- as.list(c(201826L, 442793L, 320128L, 433736L))

processed <- msg_timed(
  msg  = "Total batch time",
  expr = lapply(concept_ids, function(id) {
    msg_info("Processing concept_id ", id)
    id * 2L           # stand-in for real work
  })
)
#> i Processing concept_id 201826
#> i Processing concept_id 442793
#> i Processing concept_id 320128
#> i Processing concept_id 433736
#> i Total batch time: 0.00s

6 Error and warning handling

Raising styled errors with msg_abort()

msg_abort() throws a proper R error condition so it integrates with tryCatch() and withCallingHandlers() like any other error.

validate_schema <- function(x) {
  if (!is.data.frame(x)) {
    msg_abort("Expected a data.frame, got: ", class(x)[1])
  }
  invisible(x)
}

# Catch the error and show its message
tryCatch(
  validate_schema("not a data frame"),
  error = function(e) msg_danger("Caught: ", conditionMessage(e))
)
#> x Caught: x Expected a data.frame, got: character

Raising styled warnings with msg_warning()

msg_warning() emits a proper R warning while also printing a styled console message.

withCallingHandlers(
  {
    msg_warning("Deprecated argument 'schema' — use 'cdm_schema' instead")
    msg_info("Continuing with default")
  },
  warning = function(w) {
    # Muffle so it does not print twice
    invokeRestart("muffleWarning")
  }
)
#> i Continuing with default

msg_try() on_error modes

# "warn"    — downgrade error to a styled warning
msg_try(stop("something went wrong"), on_error = "warn")
#> ! something went wrong

# "message" — emit as a styled danger message, no stop
msg_try(stop("non-critical failure"), on_error = "message")
#> x non-critical failure

# "ignore"  — silently swallow the error
msg_try(stop("ignored error"),        on_error = "ignore")
msg_info("Execution continued after all three")
#> i Execution continued after all three

7 Debug messages

msg_debug() is a no-op unless options(pkg.debug = TRUE) is set, making it safe to leave in production code.

# Default: pkg.debug = FALSE, so nothing is printed
msg_debug("SQL query: SELECT * FROM concept WHERE ...")
msg_info("(no debug output above — pkg.debug is FALSE)")
#> i (no debug output above — pkg.debug is FALSE)
options(pkg.debug = TRUE)
msg_debug("SQL query: SELECT * FROM concept WHERE ...")
#> [DEBUG] SQL query: SELECT * FROM concept WHERE ...
options(pkg.debug = FALSE)   # reset

8 Progress bar (interactive sessions)

msg_progress() is designed for interactive terminal use. In rendered documents the \r cursor updates do not display, so the code below is shown but not evaluated.

files <- paste0("file_", seq_len(8), ".csv")
pb    <- msg_progress(length(files), prefix = "Loading")

for (f in files) {
  Sys.sleep(0.1)   # simulated I/O
  pb$tick()
}
pb$done("All files loaded")

9 Spinner (interactive sessions)

Similarly, the animated spinner is for interactive use only.

sp <- msg_spinner("Querying vocabulary server")

for (i in seq_len(30)) {
  Sys.sleep(0.05)
  sp$spin()
}
sp$done("Query complete")

10 Putting it all together — annotated pipeline

The example below combines several handyCli helpers to produce readable console output for a multi-step pipeline.

run_pipeline <- function(concept_sets, verbose = TRUE) {

  msg_header("tidyOhdsiSolutions Pipeline")
  msg_kv(list(
    Steps       = as.character(length(concept_sets)),
    Verbose     = as.character(verbose)
  ))
  msg_blank()

  results <- vector("list", length(concept_sets))
  names(results) <- names(concept_sets)

  for (nm in names(concept_sets)) {
    msg_process("Processing: ", nm)

    results[[nm]] <- msg_try(on_error = "warn", expr = {
      ids <- concept_sets[[nm]]
      if (length(ids) == 0L) stop("'", nm, "' has no concept IDs")
      msg_verbose("  concept IDs: ", paste(ids, collapse = ", "),
                  verbose = verbose)
      ids
    })
  }

  msg_blank()
  msg_rule()

  succeeded <- sum(!vapply(results, is.null, logical(1L)))
  msg_success(succeeded, " / ", length(concept_sets), " concept sets processed")

  invisible(results)
}

concept_sets <- list(
  diabetes     = c(201826L, 442793L),
  hypertension = c(320128L),
  empty_set    = integer(0)      # will trigger a warning
)

out <- run_pipeline(concept_sets, verbose = TRUE)
#> ------------------------ tidyOhdsiSolutions Pipeline -------------------------
#>   Steps    3
#>   Verbose  TRUE
#> 
#> -> Processing: diabetes
#> i   concept IDs: 201826, 442793
#> -> Processing: hypertension
#> i   concept IDs: 320128
#> -> Processing: empty_set
#> ! 'empty_set' has no concept IDs
#> 
#> --------------------------------------------------------------------------------
#> v 2 / 3 concept sets processed

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.