R/tailwind_cli.R

Defines functions compile_tailwindcss is_tailwindcss_installed get_cli_executable install_tailwindcss_cli

Documented in compile_tailwindcss install_tailwindcss_cli is_tailwindcss_installed

#' Installs the 'TailwindCSS' CLI
#'
#' @description This will download the 'TailwindCSS' standalone CLI to the
#' current working directory.
#'
#' @details This will download the 'TailwindCSS' standalone CLI to the current
#'   working directory.
#'   See [here](https://tailwindcss.com/blog/standalone-cli) for details on the
#'   standalone CLI. This saves you from having to install 'node.js'.
#'
#'   On the mac, after installing the CLI, you need to make sure that the file
#'   is executable to run it. For Mac, the easiest way to do so is to ensure
#'   you're in the correct working directory in R and type
#'   `system("chmod +x tailwindcss")`.
#'   Alternatively, you could `cd` to the directory in terminal and then run
#'   `chmod +x tailwindcss`.
#'
#'
#' @param overwrite if existing installations should be overwritten
#' @param version the version to install, default is latest
#' @param verbose if the version etc should be reported
#'
#' @export
#' @return invisibly the path to the cli program
#'
#' @seealso [compile_tailwindcss]
#' @examples
#' if (interactive()) {
#'   install_tailwindcss_cli()
#' }
install_tailwindcss_cli <- function(overwrite = FALSE, version = "latest", verbose = FALSE) {
  if (is_tailwindcss_installed() && !overwrite) {
    stop("Found existing tailwindcss installation. Abort installation!")
  }

  info <- Sys.info()
  # 1) find system, either linux, macos, or windows
  sys <- tolower(info[["sysname"]])
  # TODO: not sure if this is catches all Mac OS?!
  if (sys == "darwin") sys <- "macos"

  # 2) find architecture
  # TODO: does this distinguish between x64 and ARM64 in all cases?
  arch <- if (info[["machine"]] == "x86-64") "x64" else "arm64"

  file <- paste("tailwindcss",
    if (sys == "windows") {
      "windows-x64.exe"
    } else {
      paste(sys, arch, sep = "-")
    },
    sep = "-"
  )

  # 3) get latest release version
  url <- "https://github.com/tailwindlabs/tailwindcss/releases"
  if (version == "latest") {
    html <- readLines(url)
    h1 <- html[grepl("\\<h1\\>", html)][1]
    # Extract release version
    version <- gsub(".*releases/tag/(v[0-9]+.[0-9]+.[0-9]+).*", "\\1", h1)
  }
  if (verbose == TRUE) {
    cat(paste0(
      "Trying to download tailwindcss CLI version ", version, "\n",
      "  from ", url, "\n"
    ))
  }
  unlink(file)
  download_url <- paste0(url, "/download/", version, "/", file)
  suppressWarnings({
    a <- try(utils::download.file(download_url, file), silent = TRUE)
  })

  if (inherits(a, "try-error")) {
    stop(sprintf(
      paste0(
        "Could not download tailwindcss CLI.\n",
        "  Either tailwindcss CLI version %s could not be found or another error occured.\n",
        "  Please make sure that the version is available from\n  %s"
      ),
      version, url
    ))
  }

  # 4) rename file to tailwindcss(.exe)
  target <- "tailwindcss"
  if (sys == "windows") target <- paste0(target, ".exe")
  file.rename(file, target)

  if (sys == "macos") system("chmod +x tailwindcss")

  cat(sprintf(paste0(
    "Success: installed tailwindcss version %s as '%s'!\n",
    "Next you must ensure that the tailwind css file is executable.\n",
    "Type `?install_tailwindcss_cli` to read more about how to do this"
  ), version, target))

  return(invisible(target))
}

# internal helper
get_cli_executable <- function(tailwindcss = NULL) {
  if (is.null(tailwindcss)) {
    if (Sys.info()[["sysname"]] == "Windows") {
      tailwindcss <- "tailwindcss.exe"
    } else {
      tailwindcss <- "./tailwindcss"
    }
  }
  return(tailwindcss)
}


#' Checks if 'TailwindCSS' CLI is installed
#'
#' To install the CLI of 'TailwindCSS', please follow the instructions of
#' ['TailwindCSS' releases](https://github.com/tailwindlabs/tailwindcss/releases).
#' Make sure that you either provide the direction to the executable as the
#' first argument to this function or put it in a folder on your PATH variable.
#'
#' @param tailwindcss name and path to the executable
#' @param verbose report version number etc
#'
#' @return TRUE/FALSE if the CLI is installed
#' @export
#'
#' @examples
#' if (interactive() == TRUE) {
#'   is_tailwindcss_installed()
#' }
is_tailwindcss_installed <- function(tailwindcss = NULL, verbose = FALSE) {
  tailwindcss <- get_cli_executable(tailwindcss)

  cmd <- paste(tailwindcss, "-h")
  r <- try(system(cmd, intern = TRUE), silent = TRUE)

  if (inherits(r, "try-error") || length(r) <= 2) {
    if (verbose) {
      warning(paste(
        "Could not find CLI tailwindcss.",
        "Please follow install instructions and put it in your PATH or supply the path to this function",
        "Download: https://github.com/tailwindlabs/tailwindcss/releases",
        attr(r, "condition"),
        sep = "\n"
      ))
    }
    return(FALSE)
  }

  version <- gsub("tailwindcss +", "", r[[2]])
  if (verbose) {
    cat(sprintf("Found tailwindcss version %s\n", version))
  }
  return(TRUE)
}


#' Starts the 'TailwindCSS' CLI
#'
#' See also [tailwind docs](https://tailwindcss.com/blog/standalone-cli)
#'
#' @param infile the 'TailwindCSS' file (eg containing the `@tailwind` directives). Relative to basedir
#' @param outfile the target css file, where tailwind will write the css to.
#'   Relative to basedir
#' @param watch if the files should be continuously monitored (versus only
#'   compile the css once), default is False
#' @param tailwindcss name and path to the executable
#' @param verbose print information
#' @param minify if the code should be minified, default is FALSE
#' @param content content paths to remove unused classes, default is current dir
#'
#' @return the outfile invisibly
#' @export
#'
#' @seealso [install_tailwindcss_cli]
#' @examples
#' if (interactive()) {
#'   temp <- tempdir()
#'   owd <- setwd(temp)
#'
#'   infile <- "custom.css"
#'   writeLines("@tailwind base;", infile)
#'   outfile <- "out.css"
#'
#'   # file.copy(system.file("examples", "01-Old_Faithful", "app.R", package = "shiny.tailwind"),
#'   #           "app.R", overwrite = TRUE)
#'
#'   # write a mini shiny UI
#'   writeLines("
#'     library(shiny)
#'     div(class = \"page-div\",
#'         div(class = \"w-full text-center py-12\",
#'             h1(\"Hello World\")
#'         )
#'     )", "app.R")
#'
#'   tailwindcss <- NULL # can be set to the executable file
#'   compile_tailwindcss(infile, outfile, tailwindcss = tailwindcss)
#'   cat(paste(readLines(outfile)[1:20], collapse = "\n"))
#'
#'   setwd(owd)
#' }
compile_tailwindcss <- function(infile, outfile,
                                watch = FALSE, minify = FALSE,
                                content = ".",
                                tailwindcss = NULL, verbose = FALSE) {
  stopifnot(length(infile) == 1)
  stopifnot(length(outfile) == 1)

  if (!is_tailwindcss_installed(tailwindcss = tailwindcss)) {
    stop("Could not find an installation of tailwindcss!")
  }
  tailwindcss <- get_cli_executable(tailwindcss)

  conf_file <- "tailwind.config.js"

  # Create config if there is none
  if (!file.exists(conf_file)) {
    cat(sprintf(
      "Could not find %s, copying default from shiny.tailwind\n",
      conf_file
    ))

    file.copy(
      system.file("default-tailwind.config.js", package = "shiny.tailwind"),
      conf_file
    )
  }

  cmd <- paste(
    tailwindcss, "--config", conf_file,
    "--input", infile, "--output", outfile,
    if (watch) "--watch",
    if (minify) "--minify"
  )
  if (verbose) cat(paste0("Runnding tailwindcss CLI command:\n  ", cmd))
  a <- try(system(cmd, intern = TRUE), silent = TRUE)
  if (inherits(a, "try-error")) {
    cat(gsub("\\\\r\\\\n", "\n", a))
    stop("Could not execute tailwindcss CLI with error\n", a)
  }

  return()
}

Try the shiny.tailwind package in your browser

Any scripts or data that you put into this service are public.

shiny.tailwind documentation built on Oct. 13, 2022, 9:06 a.m.