Step aside Cron, this is a job for .Rprofile & logfiles!

Just as test automation is a critical part of rutheless testing, your goal for any project should be to identify taks that can be automated (and do it!).

There are a number of packages that allow you to schedule R scripts at specific times; two that come to mind are taskscheduleR (for Windows) and cronR (for Unix/Linux). We have cronR setup on RStudio server at work, and it has a nice RStudio addin which allows us to schedule tasks around any complex schedule. You can use these kind of tools to automate data collections, automate markdown reports to e-mail, or even the weather.

Alternatively, when you want a function (or script) to launch every time you start R you can place these inside .Rprofile.

But what about if you want a function to start only the first time a user logs in? This could be something simple (like a reminder) to fill in a progress report, or in this case a bit of Monday Morning (de)Motivation. This is a job for logfiles!

Let’s be straight here, I love Mondays (because rweekly is released!), but most people don’t! Therefore, I made a .csv file of 178 quotes about Monday and a folder of 83 .png images of cartoon characters sleeping. I used the .First() function in .Rprofile to create a logfile (if one doesn’t already exist) and randomly pair an image with a quote the first time a user logs in on Monday morning (using the advicegiveR package). Here’s some examples of images a user might see first thing Monday morning:

The Good

The Bad

The Ugly

Any other time of that day (or any other day of the week) it will simply call a sage piece of collected advice from the R-help forum via the fortunes package. An example:

R will always be arcane to those who do not make a serious effort to learn it. It is not meant to be intuitive and easy for casual users to just plunge into. It is far too complex and powerful for that. But the rewards are great for serious data analysts who put in the effort. – Berton Gunter R-help (August 2007)

You need to create a folder in your home directory that contains a quotes.csv, a LogFile.txt and images folder.

.First <- function(){
        today <- as.Date(Sys.Date())
        LastLog <- ""
        if(file.exists("~/iHateMondays/LogFile.txt")) {
                LogFile <- file("~/iHateMondays/LogFile.txt", open="r")
                LastLog <- readLines(LogFile, 1L)
                close(LogFile)
        }
        LogFile <- file("~/iHateMondays/LogFile.txt", open="w")
        writeLines(as.character(today), LogFile)
        close(LogFile)
        
        if(LastLog == as.character(today)) {
                # Already logged on today, just exit
                return()
        }
        
        ## If you get here, Need to run the first login code
        DOW <- weekdays(today)
        if (DOW == "Monday") {
                # I Hate Mondays!
                suppressWarnings(library(advicegiveR, warn.conflicts = FALSE))
                suppressWarnings(library(readr, warn.conflicts = FALSE))
                quotes <- read_csv("~/iHateMondays/quotes.csv", col_names = FALSE)
                x1 <- sample(1:nrow(quotes), 1)
                advice <- as.character(quotes[x1,])
                y <- list.files("~/iHateMondays/images")
                # select one randomly
                y1 <- sample(1:length(y), 1)
                image <- magick::image_read(paste0("~/iHateMondays/images", y[y1]))
                # print image
                advicegiveR::print_advice(image = image, advice = advice, textcolor = "yellow", size = 40)
        } else {
                # Fortune
                suppressWarnings(library(fortunes, warn.conflicts = FALSE))
                rnum <- sample(1:386, 1)
                print(fortune(rnum))
        }
}

Now Ideally, you should use a contrasting border around the inner text color to make it readable on any background.

The meme R package package uses a black border around text and positions text like a typical meme (top & bottom).

By including a simple helper function to split the quote into equal lengthed top/bottom sections, the code would look like this:

suppressWarnings(library(tidyverse))
word_split <- function(x, side="left", sep=" ") {
  words <- strsplit(as.character(x), sep)
  nwords <- lengths(words)
  if(side=="left") {
    start <- 1
    end <- ceiling(nwords/2)
  } else if (side=="right") {
    start <- ceiling((nwords+2)/2)
    end <- nwords
  }
  cw <- function(words, start, stop) paste(words[start:stop], collapse=sep)
  pmap_chr(list(words, start, end), cw)
}
left_words <- function(..., side) word_split(..., side="left")
right_words <- function(..., side) word_split(..., side="right")

.First <- function(){
        today <- as.Date(Sys.Date())
        LastLog <- ""
        if(file.exists("~/iHateMondays/LogFile.txt")) {
                LogFile <- file("~/iHateMondays/LogFile.txt", open="r")
                LastLog <- readLines(LogFile, 1L)
                close(LogFile)
        }
        LogFile <- file("~/iHateMondays/LogFile.txt", open="w")
        writeLines(as.character(today), LogFile)
        close(LogFile)

        if(LastLog == as.character(today)) {
                # Already logged on today, just exit
                return()
        }

        ## If you get here, Need to run the first login code
        DOW <- weekdays(today)
        if (DOW == "Monday") {
                # I Hate Mondays!
                suppressWarnings(library(readr, warn.conflicts = FALSE))
                suppressWarnings(library(meme, warn.conflicts = FALSE))
                quotes <- read_csv("~/iHateMondays/quotes.csv", col_names = FALSE)
                x1 <- sample(1:nrow(quotes), 1)
                advice <- as.character(quotes[x1,])
                top_words <- left_words(advice)
                bottom_words <- right_words(advice)
                y <- list.files("~/iHateMondays/images")
                # select one randomly
                y1 <- sample(1:length(y), 1)
                image <- magick::image_read(paste0("~/iHateMondays/images/", y[y1]))
                # print image
                meme(paste0("~/iHateMondays/images/", y[y1]), size = "1.5", upper = top_words, lower = bottom_words, color = "yellow", font = "Times")

        } else {
                # Fortune
                suppressWarnings(library(fortunes, warn.conflicts = FALSE))
                rnum <- sample(1:386, 1)
                print(fortune(rnum))
        }
}