From eda13866cbe5cd3b0f4cd63be5f3110f18795bf1 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 14 Jul 2020 11:05:09 -0400 Subject: [PATCH 01/10] Add polyglot tutorial --- .Rbuildignore | 1 + .gitignore | 1 + inst/tutorials/polyglot/polyglot.Rmd | 54 +++++++++++++++++++++++ tests/testthat/test-available-tutorials.R | 1 + 4 files changed, 57 insertions(+) create mode 100644 inst/tutorials/polyglot/polyglot.Rmd diff --git a/.Rbuildignore b/.Rbuildignore index 6dd488dd4..27418b733 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -16,6 +16,7 @@ rsconnect/ ^\.github/ ^inst/tutorials/.*\.html$ +^inst/tutorials/.*\.sqlite$ ^revdep$ ^LICENSE\.md$ ^cran-comments\.md$ diff --git a/.gitignore b/.gitignore index 38e70bcbc..a3d7773a4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ issues # html tutorial output inst/tutorials/*/*.html +inst/tutorials/*/*.sqlite # deployment output rsconnect/ diff --git a/inst/tutorials/polyglot/polyglot.Rmd b/inst/tutorials/polyglot/polyglot.Rmd new file mode 100644 index 000000000..67c2744b2 --- /dev/null +++ b/inst/tutorials/polyglot/polyglot.Rmd @@ -0,0 +1,54 @@ +--- +title: "Multi-language exercises" +author: "Zoë Wilkinson Saldaña" +output: learnr::tutorial +runtime: shiny_prerendered +--- + +```{r setup, include = FALSE} +library(learnr) + +# altered from https://datacarpentry.org/R-ecology-lesson/05-r-and-databases.html +if (!file.exists("portal_mammals.sqlite")) { + download.file(url = "https://ndownloader.figshare.com/files/2292171", + destfile = "portal_mammals.sqlite", mode = "wb") +} +mammals <- DBI::dbConnect(RSQLite::SQLite(), "portal_mammals.sqlite") +``` + +The following two code exercises run language agnostic code. It will finally evaluate to either 5 or 10. + +```{r r-not-python, exercise=TRUE, exercise.lines = 5} +# if running in R, this should return 10 +# if running in Python, this should return FALSE and 5 +x = 5 +x <-10 +print(x) +``` + +```{python python-not-r, exercise=TRUE, exercise.lines = 5} +# if running in R, this should return 10 +# if running in Python, this should return FALSE and 5 +x = 5 +x <-10 +print(x) +``` + +### Bash + +Execute exercises in bash + +```{bash bash-not-r, exercise = TRUE} +echo "hello world" +date +``` + +### SQL + +Use a SQL connection defined in a setup chunk. + +```{sql sql-not-r, exercise = TRUE, connection="mammals"} +SELECT * +FROM `surveys` +LIMIT 10 +``` diff --git a/tests/testthat/test-available-tutorials.R b/tests/testthat/test-available-tutorials.R index 5feb78612..4b0799539 100644 --- a/tests/testthat/test-available-tutorials.R +++ b/tests/testthat/test-available-tutorials.R @@ -25,6 +25,7 @@ test_that("Tutorial names are retrieved", { - ex-data-summarise : \"Summarise Tables\" - ex-setup-r : \"Set Up\" - hello : \"Hello, Tutorial!\" + - polyglot : \"Multi-language exercises\" - quiz_question : \"Tutorial Quiz Questions in `learnr`\" - setup-chunks : \"Chained setup chunks\" - slidy : \"Slidly demo\"" From bef3d9b296d96a27b9c668493dbfad40fe23c7a3 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 14 Jul 2020 13:34:52 -0400 Subject: [PATCH 02/10] Update polyglot.Rmd --- inst/tutorials/polyglot/polyglot.Rmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inst/tutorials/polyglot/polyglot.Rmd b/inst/tutorials/polyglot/polyglot.Rmd index 67c2744b2..45a98d3eb 100644 --- a/inst/tutorials/polyglot/polyglot.Rmd +++ b/inst/tutorials/polyglot/polyglot.Rmd @@ -16,6 +16,8 @@ if (!file.exists("portal_mammals.sqlite")) { mammals <- DBI::dbConnect(RSQLite::SQLite(), "portal_mammals.sqlite") ``` +### Python or R? + The following two code exercises run language agnostic code. It will finally evaluate to either 5 or 10. ```{r r-not-python, exercise=TRUE, exercise.lines = 5} From f44fc0b19d0557bb1d0f222863771fbb3c8780e1 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 2 Sep 2020 15:21:58 -0400 Subject: [PATCH 03/10] Pass the engine through and display it --- R/knitr-hooks.R | 4 +++- docs/exercises.Rmd | 12 ++++++++---- inst/lib/tutorial/tutorial.css | 5 +++++ inst/lib/tutorial/tutorial.js | 8 +++++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/R/knitr-hooks.R b/R/knitr-hooks.R index 2c1377ced..cbbeaa0d8 100644 --- a/R/knitr-hooks.R +++ b/R/knitr-hooks.R @@ -234,9 +234,11 @@ install_knitr_hooks <- function() { completion <- as.numeric(options$exercise.completion %||% 1 > 0) diagnostics <- as.numeric(options$exercise.diagnostics %||% 1 > 0) startover <- as.numeric(options$exercise.startover %||% 1 > 0) - caption <- ifelse(is.null(options$exercise.cap), "Code", options$exercise.cap) + caption <- options[["exercise.cap"]] %||% "Code" + engine_caption <- options[["exercise.cap.engine"]] %||% options$engine %||% "" paste0('
+exercise.cap.engine +Caption for exercise chunk engine (defaults to the chunk engine value) + + exercise.eval Whether to pre-evaluate the exercise so the reader can see some default output (defaults to FALSE). - + exercise.lines Lines of code for exercise editor (default to size of code chunk). - + exercise.timelimit Number of seconds to limit execution time to (defaults to 30). - + exercise.checker Function used to check exercise answers (e.g., gradethis::grade_learnr()). @@ -315,7 +319,7 @@ See the table below for a full description of all the arguments supplied to `exe ## Exercise Caption -By default exercises are displayed with caption of "Code". However, in some cases you may want either a custom per-chunk caption or a generic caption with a different connodation (e.g. "Exercise" or "Sandbox"). For example: +By default exercises are displayed with caption of "Code". However, in some cases you may want either a custom per-chunk caption or a generic caption with a different connotation (e.g. "Exercise" or "Sandbox"). For example:
diff --git a/inst/lib/tutorial/tutorial.css b/inst/lib/tutorial/tutorial.css index a50b97d96..2141342b0 100644 --- a/inst/lib/tutorial/tutorial.css +++ b/inst/lib/tutorial/tutorial.css @@ -117,3 +117,8 @@ height: 100%; border: solid 1px #cccccc; } + +.tutorial-exercise-engine { + color: #c0c0c0; + margin-right: 10px; +} \ No newline at end of file diff --git a/inst/lib/tutorial/tutorial.js b/inst/lib/tutorial/tutorial.js index 565ffa63c..851c8132b 100644 --- a/inst/lib/tutorial/tutorial.js +++ b/inst/lib/tutorial/tutorial.js @@ -851,6 +851,8 @@ Tutorial.prototype.$initializeExerciseEditors = function() { // capture label and caption var label = exercise.attr('data-label'); var caption = exercise.attr('data-caption'); + var engine_txt = exercise.attr('data-engine'); + // helper to create an id function create_id(suffix) { @@ -895,7 +897,11 @@ Tutorial.prototype.$initializeExerciseEditors = function() { // creating heading var panel_heading = $('
'); - panel_heading.text(caption); + // add engine if it exists + if (engine_txt && engine_txt.length > 0) { + panel_heading.append($('').text(engine_txt)); + } + panel_heading.append($('').text(caption)); input_div.append(panel_heading); // create body From 8f135b115937d4f5ece0f30054aba154a05ff57e Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 2 Sep 2020 15:22:54 -0400 Subject: [PATCH 04/10] If it is a non-r language, disable diagnostics and code completion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Cass (Z.) Wilkinson Saldaña <12399460+zoews@users.noreply.github.com> --- inst/lib/tutorial/tutorial.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/inst/lib/tutorial/tutorial.js b/inst/lib/tutorial/tutorial.js index 851c8132b..a866cc34f 100644 --- a/inst/lib/tutorial/tutorial.js +++ b/inst/lib/tutorial/tutorial.js @@ -972,6 +972,13 @@ Tutorial.prototype.$initializeExerciseEditors = function() { // get the engine var engine = chunk_options["engine"] + if (engine != "r") { + // disable ace editor diagnostics if a non-r langauge engine is set + diagnostics = null; + // disable auto-completion if a non-r language engine is set + completion = null; + } + // set tutorial options/data editor.tutorial = { label: label, From b45f7ece5c8b51e5ecce125444075a6d58ad5b5e Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 2 Sep 2020 15:54:36 -0400 Subject: [PATCH 05/10] Pass the engine through to the checking functions --- R/exercise.R | 14 +++++++++----- docs/exercises.Rmd | 4 ++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/R/exercise.R b/R/exercise.R index 3e833a2c2..c46aa4aa0 100644 --- a/R/exercise.R +++ b/R/exercise.R @@ -210,7 +210,8 @@ evaluate_exercise <- function(exercise, envir, evaluate_global_setup = FALSE) { envir_result = NULL, evaluate_result = NULL, envir_prep = envir_prep, - last_value = NULL + last_value = NULL, + engine = exercise$engine ) if (is_exercise_result(checker_feedback)) { return(checker_feedback) @@ -232,7 +233,8 @@ evaluate_exercise <- function(exercise, envir, evaluate_global_setup = FALSE) { envir_result = envir, evaluate_result = rmd_results$evaluate_result, envir_prep = envir_prep, - last_value = rmd_results$last_value + last_value = rmd_results$last_value, + engine = exercise$engine ) } @@ -246,7 +248,7 @@ evaluate_exercise <- function(exercise, envir, evaluate_global_setup = FALSE) { try_checker <- function(exercise, name, check_code, envir_result, evaluate_result, envir_prep, last_value, - html_output) { + engine) { checker_func <- tryCatch( get_checker_func(exercise, name, envir_prep), error = function(e) { @@ -267,7 +269,8 @@ try_checker <- function(exercise, name, check_code, envir_result, envir_result = envir_result, evaluate_result = evaluate_result, envir_prep = envir_prep, - last_value = last_value + last_value = last_value, + engine = engine ) # Throw better error messaging if the checker function signature is ill-defined missing_args <- setdiff(names(args), checker_args) @@ -402,7 +405,8 @@ render_exercise <- function(exercise, envir, envir_prep) { envir_result = envir, evaluate_result = evaluate_result, envir_prep = envir_prep, - last_value = e + last_value = e, + engine = exercise$engine ) if (is_exercise_result(checker_feedback)) { return(checker_feedback) diff --git a/docs/exercises.Rmd b/docs/exercises.Rmd index a9aead50e..107530ecf 100644 --- a/docs/exercises.Rmd +++ b/docs/exercises.Rmd @@ -308,6 +308,10 @@ See the table below for a full description of all the arguments supplied to `exe The last value of the evaluated chunk. +engine +The engine value of the evaluated chunk. + + ... Unused (include for compatibility with parameters to be added in the future) From 80b707d79b7611e2ffa35f911a4e3a0c0f3b0382 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 2 Sep 2020 15:57:23 -0400 Subject: [PATCH 06/10] Update NEWS.md --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index 36afe3116..ce0286032 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,7 @@ learnr (development version) * Added a new `tutorial_options()`, namely `exercise.error.checker`, for customizing feedback when exercise submission code produces an evaluation error. This option accepts a function with the same arguments as `exercise.checker`. Use `gradethis::grade_learnr_error()` for a sensible default for this option. ([#403](https://github.com/rstudio/learnr/pull/403)) * Added an event handler system, with the functions `event_register_handler()` and `one_time()`. There is also a new event `"section_viewed"`, which is triggered when a new section becomes visible. ([#398](https://github.com/rstudio/learnr/pull/398)) * Previously, when a question submission was reset, it would be recorded as a `"question_submission"` event with the value `reset=TRUE`. Now it a separate event, `"reset_question_submission"`. ([#398](https://github.com/rstudio/learnr/pull/398)) +* Added a new `polyglot` tutorial to learnr. This tutorial displays mixing R, python, and sql exercises. See [`run_tutorial("polyglot", "learnr")`](https://learnr-examples.shinyapps.io/polyglot) for a an example. ([#397](https://github.com/rstudio/learnr/pull/397)) ## Minor new features and improvements @@ -27,6 +28,8 @@ learnr (development version) * Added error handling when user specifies a non-existent label for `exercise.setup` option with an error message. ([#390](https://github.com/rstudio/learnr/pull/390)) * We no longer forward the checker code to browser (in html), but instead cache it. ([#390](https://github.com/rstudio/learnr/pull/390)) * We no longer display an invisible exercise result warning automatically. Instead, authors must set the exercise chunk option `exercise.warn_invisible = TRUE` to display an invisible result warning message. ([#373](https://github.com/rstudio/learnr/pull/373)) +* With the addition of multi-language support, the exercise's execution language is displayed in the top left corner of the exercise. This can be overwritten by setting the chunk option `exercise.cap.engine`. ([#397](https://github.com/rstudio/learnr/pull/397)) +* `engine` is now passed to the `exercise.checker` to help distinguish what language is being checked in the exercise. ([#397](https://github.com/rstudio/learnr/pull/397)) ## Bug fixes From 39eb8954b12aa7f7ddb6700436528c019e2dfc25 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Thu, 3 Sep 2020 14:07:01 -0400 Subject: [PATCH 07/10] Use `.toLowerCase()` Co-authored-by: Carson Sievert --- inst/lib/tutorial/tutorial.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/lib/tutorial/tutorial.js b/inst/lib/tutorial/tutorial.js index a866cc34f..e339721d6 100644 --- a/inst/lib/tutorial/tutorial.js +++ b/inst/lib/tutorial/tutorial.js @@ -972,7 +972,7 @@ Tutorial.prototype.$initializeExerciseEditors = function() { // get the engine var engine = chunk_options["engine"] - if (engine != "r") { + if (engine.toLowerCase() != "r") { // disable ace editor diagnostics if a non-r langauge engine is set diagnostics = null; // disable auto-completion if a non-r language engine is set From dc1b25c7945a791c4d56f0a369b0a8bc00fc7354 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Thu, 3 Sep 2020 17:41:35 -0400 Subject: [PATCH 08/10] Change default exercise caption to `{engine} code` --- NEWS.md | 2 +- R/knitr-hooks.R | 4 +--- docs/exercises.Rmd | 24 ++++++++++-------------- inst/lib/tutorial/tutorial.css | 5 ----- inst/lib/tutorial/tutorial.js | 8 +------- 5 files changed, 13 insertions(+), 30 deletions(-) diff --git a/NEWS.md b/NEWS.md index ce0286032..ff1a4a98e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -28,7 +28,7 @@ learnr (development version) * Added error handling when user specifies a non-existent label for `exercise.setup` option with an error message. ([#390](https://github.com/rstudio/learnr/pull/390)) * We no longer forward the checker code to browser (in html), but instead cache it. ([#390](https://github.com/rstudio/learnr/pull/390)) * We no longer display an invisible exercise result warning automatically. Instead, authors must set the exercise chunk option `exercise.warn_invisible = TRUE` to display an invisible result warning message. ([#373](https://github.com/rstudio/learnr/pull/373)) -* With the addition of multi-language support, the exercise's execution language is displayed in the top left corner of the exercise. This can be overwritten by setting the chunk option `exercise.cap.engine`. ([#397](https://github.com/rstudio/learnr/pull/397)) +* With the addition of multi-language support, the default caption for an exercise has been switched to `"{engine} code"`. This can be overwritten by setting the chunk option `exercise.cap`. ([#397](https://github.com/rstudio/learnr/pull/397)) * `engine` is now passed to the `exercise.checker` to help distinguish what language is being checked in the exercise. ([#397](https://github.com/rstudio/learnr/pull/397)) ## Bug fixes diff --git a/R/knitr-hooks.R b/R/knitr-hooks.R index cbbeaa0d8..50f3d5d49 100644 --- a/R/knitr-hooks.R +++ b/R/knitr-hooks.R @@ -234,11 +234,9 @@ install_knitr_hooks <- function() { completion <- as.numeric(options$exercise.completion %||% 1 > 0) diagnostics <- as.numeric(options$exercise.diagnostics %||% 1 > 0) startover <- as.numeric(options$exercise.startover %||% 1 > 0) - caption <- options[["exercise.cap"]] %||% "Code" - engine_caption <- options[["exercise.cap.engine"]] %||% options$engine %||% "" + caption <- options[["exercise.cap"]] %||% paste0(options$engine, " code") paste0('
-exercise.cap.engine -Caption for exercise chunk engine (defaults to the chunk engine value) - - exercise.eval Whether to pre-evaluate the exercise so the reader can see some default output (defaults to FALSE). - + exercise.lines Lines of code for exercise editor (default to size of code chunk). - + exercise.timelimit Number of seconds to limit execution time to (defaults to 30). - + exercise.checker Function used to check exercise answers (e.g., gradethis::grade_learnr()). - + exercise.error.check.code A string containing R code to use for checking code when an exercise evaluation error occurs (e.g., "gradethis::grade_code()"). - + exercise.completion Whether to enable code completion in the exercise editor. - + exercise.diagnostics Whether to enable code diagnostics in the exercise editor. - + exercise.startover Whether to include a "Start Over" button for the exercise. - + exercise.warn_invisible Whether to display an invisible result warning if the last value returned is invisible. @@ -109,7 +105,7 @@ You can however arrange for setup code to be run before evaluation of an exercis
- + 5. You can chain exercise chunks as well, but keep in mind that pre-filled code is used to serve as the setup code instead of user input. For example, if you turned `filtered-flights` back to an exercise, the pre-filled code is used as setup for the `arrange` exercise that use it as its setup:
@@ -260,7 +256,7 @@ If, at any one of these stages, feedback should be provided, then `exercise.chec -Below is a rough sketch of how one might implement an `exercise.checker` function. Notice how the presence of `envir_result` may be used to determine which type of check is being done (i.e., code or result check). +Below is a rough sketch of how one might implement an `exercise.checker` function. Notice how the presence of `envir_result` may be used to determine which type of check is being done (i.e., code or result check).
diff --git a/inst/lib/tutorial/tutorial.css b/inst/lib/tutorial/tutorial.css index 2141342b0..a50b97d96 100644 --- a/inst/lib/tutorial/tutorial.css +++ b/inst/lib/tutorial/tutorial.css @@ -117,8 +117,3 @@ height: 100%; border: solid 1px #cccccc; } - -.tutorial-exercise-engine { - color: #c0c0c0; - margin-right: 10px; -} \ No newline at end of file diff --git a/inst/lib/tutorial/tutorial.js b/inst/lib/tutorial/tutorial.js index e339721d6..a1d218954 100644 --- a/inst/lib/tutorial/tutorial.js +++ b/inst/lib/tutorial/tutorial.js @@ -851,8 +851,6 @@ Tutorial.prototype.$initializeExerciseEditors = function() { // capture label and caption var label = exercise.attr('data-label'); var caption = exercise.attr('data-caption'); - var engine_txt = exercise.attr('data-engine'); - // helper to create an id function create_id(suffix) { @@ -897,11 +895,7 @@ Tutorial.prototype.$initializeExerciseEditors = function() { // creating heading var panel_heading = $('
'); - // add engine if it exists - if (engine_txt && engine_txt.length > 0) { - panel_heading.append($('').text(engine_txt)); - } - panel_heading.append($('').text(caption)); + panel_heading.text(caption); input_div.append(panel_heading); // create body From b4b7a9f09983348ce66424cae0410a1d3f1aab27 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Thu, 3 Sep 2020 17:43:50 -0400 Subject: [PATCH 09/10] Update NEWS.md --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index ff1a4a98e..44905b97c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -28,7 +28,7 @@ learnr (development version) * Added error handling when user specifies a non-existent label for `exercise.setup` option with an error message. ([#390](https://github.com/rstudio/learnr/pull/390)) * We no longer forward the checker code to browser (in html), but instead cache it. ([#390](https://github.com/rstudio/learnr/pull/390)) * We no longer display an invisible exercise result warning automatically. Instead, authors must set the exercise chunk option `exercise.warn_invisible = TRUE` to display an invisible result warning message. ([#373](https://github.com/rstudio/learnr/pull/373)) -* With the addition of multi-language support, the default caption for an exercise has been switched to `"{engine} code"`. This can be overwritten by setting the chunk option `exercise.cap`. ([#397](https://github.com/rstudio/learnr/pull/397)) +* With the addition of multi-language support, the default caption for an exercise has been switched to the combination of the exercise engine and `" code"`. This can be overwritten by setting the chunk option `exercise.cap`. ([#397](https://github.com/rstudio/learnr/pull/397)) * `engine` is now passed to the `exercise.checker` to help distinguish what language is being checked in the exercise. ([#397](https://github.com/rstudio/learnr/pull/397)) ## Bug fixes From 7fa9f03c6643d905bae129d7ddd9e0e76c96dc80 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 8 Sep 2020 11:11:24 -0400 Subject: [PATCH 10/10] Update author --- inst/tutorials/polyglot/polyglot.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/tutorials/polyglot/polyglot.Rmd b/inst/tutorials/polyglot/polyglot.Rmd index 45a98d3eb..a89ec3ba0 100644 --- a/inst/tutorials/polyglot/polyglot.Rmd +++ b/inst/tutorials/polyglot/polyglot.Rmd @@ -1,6 +1,6 @@ --- title: "Multi-language exercises" -author: "Zoë Wilkinson Saldaña" +author: "Cass (Z.) Wilkinson Saldaña" output: learnr::tutorial runtime: shiny_prerendered ---