Skip to content

Commit

Permalink
Make data/ directory automatically accessible to exercises (#539)
Browse files Browse the repository at this point in the history
Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
Co-authored-by: Barret Schloerke <barret@rstudio.com>
  • Loading branch information
3 people committed Jul 9, 2021
1 parent a24555f commit 14d679a
Show file tree
Hide file tree
Showing 16 changed files with 583 additions and 523 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ learnr (development version)
* 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))
* Text throughout the learnr interface can be customized or localized using the new `language` argument of `tutorial()`. Translations for English and French are provided and contributes will be welcomed. Read more about these features in `vignette("multilang", package = "learnr")`. ([#456](https://github.com/rstudio/learnr/pull/456), [#479](https://github.com/rstudio/learnr/pull/479))
* When a "data/" directory is found in the same directory as the tutorial R Markdown document, it is now automatically made available within exercises. An alternative directory can be specified using the `tutorial.data_dir` global option. ([#539](https://github.com/rstudio/learnr/pull/539))

## Minor new features and improvements

Expand Down
34 changes: 34 additions & 0 deletions R/data_dir.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
copy_data_dir <- function(source_dir, exercise_dir) {
if (is.null(source_dir)) {
# First check options(), then environment variable, then default to "data/"
source_dir <- getOption(
"tutorial.data_dir",
Sys.getenv("TUTORIAL_DATA_DIR", if (dir.exists("data")) "data" else "")
)
}

if (identical(source_dir, "")) {
return(invisible(NULL))
}

if (!dir.exists(source_dir)) {
rlang::abort(
"An error occurred with the tutorial: the data directory does not exist.",
class = "learnr_missing_source_data_dir"
)
}

dest_dir <- file.path(exercise_dir, "data")
dir.create(dest_dir)

if (!dir.exists(dest_dir)) {
rlang::abort(
"An error occurred with the tutorial: we weren't able to create the data directory for this exercise.",
class = "learnr_missing_dest_data_dir"
)
}

file.copy(dir(source_dir, full.names = TRUE), dest_dir, recursive = TRUE)

return(invisible(dest_dir))
}
41 changes: 36 additions & 5 deletions R/exercise.R
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,11 @@ required_names <- c("code", "label", "options", "chunks", require_items)
# been executed, so they'd typically use `FALSE`, the default. Remote
# evaluators, if they choose to use this function, might want to include the
# global setup.
evaluate_exercise <- function(exercise, envir, evaluate_global_setup = FALSE) {
evaluate_exercise <- function(
exercise, envir, evaluate_global_setup = FALSE, data_dir = NULL
) {
# Protect global options and environment vars from permanent modification
withr::local_options(list())
withr::local_envvar(as.list(Sys.getenv()))
local_restore_options_and_envvars()

# adjust exercise version to match the current learnr version
exercise <- upgrade_exercise(
Expand All @@ -312,6 +313,9 @@ evaluate_exercise <- function(exercise, envir, evaluate_global_setup = FALSE) {
dir.create(exercise_dir)
on.exit(unlink(exercise_dir), add = TRUE)

# Copy files from data directory into exercise
copy_data_dir(data_dir, exercise_dir)

checker_feedback <- NULL
# Run the checker pre-evaluation _if_ there is code checking to do
if (length(exercise$code_check)) {
Expand Down Expand Up @@ -434,8 +438,7 @@ get_checker_func <- function(exercise, name, envir) {

render_exercise <- function(exercise, envir) {
# Protect global options and environment vars from modification by student
withr::local_options(list())
withr::local_envvar(as.list(Sys.getenv()))
local_restore_options_and_envvars()

# Make sure exercise (& setup) chunk options and code are prepped for rendering
exercise <- prepare_exercise(exercise)
Expand Down Expand Up @@ -789,6 +792,34 @@ merge_options <- function(preserved_opts, inherited_opts, static_opts = list())
opts[!grepl("^exercise", names(opts))]
}

local_restore_options_and_envvars <- function(.local_envir = parent.frame()) {
local_restore_options(.local_envir)
local_restore_envvars(.local_envir)
}

local_restore_options <- function(.local_envir = parent.frame()) {
opts <- options()
withr::defer(restore_options(opts), envir = .local_envir)
}

local_restore_envvars <- function(.local_envir = parent.frame()) {
envvars <- Sys.getenv()
withr::defer(restore_envvars(envvars), envir = .local_envir)
}

restore_options <- function(old) {
current <- options()
nulls <- setdiff(names(current), names(old))
old[nulls] <- list(NULL)
options(old)
}

restore_envvars <- function(old) {
current <- Sys.getenv()
nulls <- setdiff(names(current), names(old))
Sys.unsetenv(nulls)
do.call(Sys.setenv, as.list(old))
}

#' An Exercise Checker for Debugging
#'
Expand Down
4 changes: 2 additions & 2 deletions docs/_site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ output:
theme: cosmo
highlight: textmate
toc: true
toc_depth: 2
toc_depth: 3
toc_float:
collapsed: false
collapsed: true
css: styles.css
includes:
in_header: _include/header.html
104 changes: 32 additions & 72 deletions docs/examples.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,22 @@

<title>Examples</title>

<script src="site_libs/header-attrs-2.9/header-attrs.js"></script>
<script src="site_libs/jquery-1.11.3/jquery.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="site_libs/bootstrap-3.3.5/css/cosmo.min.css" rel="stylesheet" />
<script src="site_libs/bootstrap-3.3.5/js/bootstrap.min.js"></script>
<script src="site_libs/bootstrap-3.3.5/shim/html5shiv.min.js"></script>
<script src="site_libs/bootstrap-3.3.5/shim/respond.min.js"></script>
<style>h1 {font-size: 34px;}
h1.title {font-size: 38px;}
h2 {font-size: 30px;}
h3 {font-size: 24px;}
h4 {font-size: 18px;}
h5 {font-size: 16px;}
h6 {font-size: 12px;}
code {color: inherit; background-color: rgba(0, 0, 0, 0.04);}
pre:not([class]) { background-color: white }</style>
<script src="site_libs/navigation-1.1/tabsets.js"></script>
<link href="site_libs/highlightjs-9.12.0/textmate.css" rel="stylesheet" />
<script src="site_libs/highlightjs-9.12.0/highlight.js"></script>
Expand Down Expand Up @@ -47,11 +57,6 @@
</style>

<style type="text/css">code{white-space: pre;}</style>
<style type="text/css">
pre:not([class]) {
background-color: white;
}
</style>
<script type="text/javascript">
if (window.hljs) {
hljs.configure({languages: []});
Expand All @@ -64,32 +69,6 @@



<style type="text/css">
h1 {
font-size: 34px;
}
h1.title {
font-size: 38px;
}
h2 {
font-size: 30px;
}
h3 {
font-size: 24px;
}
h4 {
font-size: 18px;
}
h5 {
font-size: 16px;
}
h6 {
font-size: 12px;
}
.table th:not([align]) {
text-align: left;
}
</style>


<link rel="stylesheet" href="styles.css" type="text/css" />
Expand All @@ -102,10 +81,6 @@
margin-left: auto;
margin-right: auto;
}
code {
color: inherit;
background-color: rgba(0, 0, 0, 0.04);
}
img {
max-width:100%;
}
Expand All @@ -121,40 +96,13 @@
summary {
display: list-item;
}
pre code {
padding: 0;
}
</style>


<style type="text/css">
/* padding for bootstrap navbar */
body {
padding-top: 51px;
padding-bottom: 40px;
}
/* offset scroll position for anchor links (for fixed navbar) */
.section h1 {
padding-top: 56px;
margin-top: -56px;
}
.section h2 {
padding-top: 56px;
margin-top: -56px;
}
.section h3 {
padding-top: 56px;
margin-top: -56px;
}
.section h4 {
padding-top: 56px;
margin-top: -56px;
}
.section h5 {
padding-top: 56px;
margin-top: -56px;
}
.section h6 {
padding-top: 56px;
margin-top: -56px;
}
.dropdown-submenu {
position: relative;
}
Expand Down Expand Up @@ -182,7 +130,7 @@
margin-right: -10px;
}
.dropdown-submenu:hover>a:after {
border-left-color: #ffffff;
border-left-color: #adb5bd;
}
.dropdown-submenu.pull-left {
float: none;
Expand All @@ -194,7 +142,7 @@
}
</style>

<script>
<script type="text/javascript">
// manage active state of menu based on current page
$(document).ready(function () {
// active menu anchor
Expand All @@ -205,10 +153,23 @@
var menuAnchor = $('a[href="' + href + '"]');

// mark it active
menuAnchor.parent().addClass('active');
menuAnchor.tab('show');

// if it's got a parent navbar menu mark it active as well
menuAnchor.closest('li.dropdown').addClass('active');

// Navbar adjustments
var navHeight = $(".navbar").first().height() + 15;
var style = document.createElement('style');
var pt = "padding-top: " + navHeight + "px; ";
var mt = "margin-top: -" + navHeight + "px; ";
var css = "";
// offset scroll position for anchor links (for fixed navbar)
for (var i = 1; i <= 6; i++) {
css += ".section h" + i + "{ " + pt + mt + "}\n";
}
style.innerHTML = "body {" + pt + "padding-bottom: 40px; }\n" + css;
document.head.appendChild(style);
});
</script>

Expand All @@ -220,7 +181,6 @@
max-height: 500px;
min-height: 44px;
overflow-y: auto;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
}
Expand Down Expand Up @@ -318,7 +278,7 @@
<ul class="nav navbar-nav navbar-right">
<li>
<a href="https://github.com/rstudio/learnr">
<span class="fas fa-github"></span>
<span class="fa fa-github"></span>

</a>
</li>
Expand All @@ -327,7 +287,7 @@
</div><!--/.container -->
</div><!--/.navbar -->

<div class="fluid-row" id="header">
<div id="header">



Expand Down Expand Up @@ -460,7 +420,7 @@ <h1 class="title toc-ignore">Examples</h1>

$(document).ready(function () {
$('.tabset-dropdown > .nav-tabs > li').click(function () {
$(this).parent().toggleClass('nav-tabs-open')
$(this).parent().toggleClass('nav-tabs-open');
});
});
</script>
Expand Down
Loading

0 comments on commit 14d679a

Please sign in to comment.