Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polyglot UI #397

Merged
merged 14 commits into from
Sep 8, 2020
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
rsconnect/
^\.github/
^inst/tutorials/.*\.html$
^inst/tutorials/.*\.sqlite$
^revdep$
^LICENSE\.md$
^cran-comments\.md$
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ issues

# html tutorial output
inst/tutorials/*/*.html
inst/tutorials/*/*.sqlite

# deployment output
rsconnect/
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 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))
* Hitting the `TAB` key in an exercise has always opened the auto-completion drop down. Now, hitting the `TAB` key will also complete the currently selected code completion. ([#428](https://github.com/rstudio/learnr/pull/428))

## Bug fixes
Expand Down
14 changes: 9 additions & 5 deletions R/exercise.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
)
}

Expand All @@ -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) {
cpsievert marked this conversation as resolved.
Show resolved Hide resolved
engine) {
checker_func <- tryCatch(
get_checker_func(exercise, name, envir_prep),
error = function(e) {
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion R/knitr-hooks.R
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ 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"]] %||% paste0(options$engine, " code")
paste0('<div class="tutorial-', class,
'" data-label="', options$label,
'" data-caption="', caption,
Expand Down
20 changes: 12 additions & 8 deletions docs/exercises.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,23 @@ Exercises are interactive R code chunks that allow readers to directly execute R
<td><code>exercise.checker</code></td>
<td>Function used to check exercise answers (e.g., <code>gradethis::grade_learnr()</code>).</td>
</tr>
<tr class="odd">
<tr class="even">
<td><code>exercise.error.check.code</code></td>
<td>A string containing R code to use for checking code when an exercise evaluation error occurs (e.g., <code>"gradethis::grade_code()"</code>).</td>
</tr>
<tr class="even">
<tr class="odd">
<td><code>exercise.completion</code></td>
<td>Whether to enable code completion in the exercise editor.</td>
</tr>
<tr class="odd">
<tr class="even">
<td><code>exercise.diagnostics</code></td>
<td>Whether to enable code diagnostics in the exercise editor.</td>
</tr>
<tr class="even">
<tr class="odd">
<td><code>exercise.startover</code></td>
<td>Whether to include a "Start Over" button for the exercise.</td>
</tr>
<tr class="odd">
<tr class="even">
<td><code>exercise.warn_invisible</code></td>
<td>Whether to display an invisible result warning if the last value returned is invisible.</td>
</tr>
Expand Down Expand Up @@ -105,7 +105,7 @@ You can however arrange for setup code to be run before evaluation of an exercis

<div id="exercisesetupchained" style="width: 95%;"></div>
<script type="text/javascript">loadSnippet('exercisesetupchained')</script>

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:

<div id="exercisechained" style="width: 95%;"></div>
Expand Down Expand Up @@ -256,7 +256,7 @@ If, at any one of these stages, feedback should be provided, then `exercise.chec
</tbody>
</table>

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).

<div id="customchecker"></div>
<script type="text/javascript">loadSnippet('customchecker')</script>
Expand Down Expand Up @@ -304,6 +304,10 @@ See the table below for a full description of all the arguments supplied to `exe
<td>The last value of the evaluated chunk.</td>
</tr>
<tr class="odd">
<td><code>engine</code></td>
<td>The <code>engine</code> value of the evaluated chunk.</td>
</tr>
<tr class="even">
<td><code>...</code></td>
<td>Unused (include for compatibility with parameters to be added in the future)</td>
</tr>
Expand All @@ -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:

<div id="exercisecaption"></div>
<script type="text/javascript">loadSnippet('exercisecaption')</script>
Expand Down
7 changes: 7 additions & 0 deletions inst/lib/tutorial/tutorial.js
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,13 @@ Tutorial.prototype.$initializeExerciseEditors = function() {
// get the engine
var engine = chunk_options["engine"]

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
completion = null;
}

// set tutorial options/data
editor.tutorial = {
label: label,
Expand Down
56 changes: 56 additions & 0 deletions inst/tutorials/polyglot/polyglot.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: "Multi-language exercises"
author: "Cass (Z.) 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")
```

### 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}
# 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
```
1 change: 1 addition & 0 deletions tests/testthat/test-available-tutorials.R
Original file line number Diff line number Diff line change
Expand Up @@ -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\""
Expand Down