├── .Rbuildignore ├── .gitignore ├── .travis.yml ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── NEWS.md ├── R ├── add_license.R ├── demo_lesson.R ├── global.R ├── google_form_decode.R ├── lesson_to_html.R ├── new_lesson.R ├── pack.R ├── set_lesson.R ├── swirlify.R ├── test_lesson.R ├── tools.R ├── utils.R └── wq.R ├── README.md ├── cran-comments.md ├── inst ├── swirlify-app │ ├── help.md │ ├── server.R │ └── ui.R └── test │ ├── correct_responses.csv │ └── diacritics_greek_cyrillic.csv ├── man ├── add_license.Rd ├── add_to_manifest.Rd ├── count_questions.Rd ├── demo_lesson.Rd ├── find_questions.Rd ├── get_current_lesson.Rd ├── google_form_decode.Rd ├── lesson_to_html.Rd ├── make_pathname.Rd ├── new_lesson.Rd ├── pack_course.Rd ├── set_lesson.Rd ├── swirl_courses_dir.Rd ├── swirlify.Rd ├── test_course.Rd ├── test_lesson.Rd ├── testit.Rd ├── unpack_course.Rd ├── wq_command.Rd ├── wq_figure.Rd ├── wq_message.Rd ├── wq_multiple.Rd ├── wq_numerical.Rd ├── wq_script.Rd ├── wq_text.Rd └── wq_video.Rd ├── revdep ├── check.R └── checks.rds ├── swirlify.Rproj └── tests ├── testthat.R └── testthat ├── test_google_form_decode.R ├── test_pack.R ├── test_test_lesson.R └── test_tools.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.travis\.yml$ 4 | ^.*.html$ 5 | ^cran-comments\.md$ 6 | ^revdep$ 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .DS_Store 5 | *.html 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: r 2 | 3 | matrix: 4 | include: 5 | - r: release 6 | - r: oldrel 7 | - r: devel 8 | 9 | cache: packages 10 | sudo: false 11 | 12 | notifications: 13 | email: 14 | on_success: always 15 | on_failure: always -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: swirlify 2 | Title: A Toolbox for Writing 'swirl' Courses 3 | Description: A set of tools for writing and sharing interactive courses 4 | to be used with swirl. 5 | URL: http://swirlstats.com 6 | Version: 0.5.2 7 | License: MIT + file LICENSE 8 | Authors@R: c( 9 | person("Sean", "Kross", , "sean@seankross.com", c("aut", "cre")), 10 | person("Nick", "Carchedi", role = "aut"), 11 | person("Chih-Cheng", "Liang", role = "ctb"), 12 | person("Wush", "Wu", role = "ctb") 13 | ) 14 | Depends: 15 | R (>= 3.2.0) 16 | Imports: 17 | swirl (>= 2.4.2), 18 | stringr, 19 | yaml, 20 | rmarkdown, 21 | whisker, 22 | shiny, 23 | shinyAce, 24 | base64enc, 25 | readr 26 | Encoding: UTF-8 27 | Roxygen: list(wrap = FALSE) 28 | Suggests: 29 | testthat, 30 | digest 31 | RoxygenNote: 6.0.1 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2018 2 | COPYRIGHT HOLDER: Team swirl -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(add_license) 4 | export(add_to_manifest) 5 | export(count_questions) 6 | export(demo_lesson) 7 | export(find_questions) 8 | export(get_current_lesson) 9 | export(google_form_decode) 10 | export(lesson_to_html) 11 | export(make_pathname) 12 | export(new_lesson) 13 | export(pack_course) 14 | export(set_lesson) 15 | export(swirl_courses_dir) 16 | export(swirlify) 17 | export(test_course) 18 | export(test_lesson) 19 | export(testit) 20 | export(unpack_course) 21 | export(wq_command) 22 | export(wq_figure) 23 | export(wq_message) 24 | export(wq_multiple) 25 | export(wq_numerical) 26 | export(wq_script) 27 | export(wq_text) 28 | export(wq_video) 29 | import(shiny) 30 | import(shinyAce) 31 | import(swirl) 32 | importFrom(base64enc,base64decode) 33 | importFrom(readr,read_csv) 34 | importFrom(rmarkdown,render) 35 | importFrom(stringr,str_detect) 36 | importFrom(stringr,str_split) 37 | importFrom(stringr,str_trim) 38 | importFrom(swirl,install_course_directory) 39 | importFrom(swirl,swirl) 40 | importFrom(whisker,whisker.render) 41 | importFrom(yaml,yaml.load_file) 42 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # swirlify 0.5.2 2 | 3 | * Fixed failing tests by integrating new features in testthat 2.0. 4 | 5 | # swirlify 0.5.1 6 | 7 | * Fixed issue where Ace editor was not working on Windows. 8 | 9 | * Added `make_pathname()`. 10 | 11 | * Added `swirl_courses_dir()`. 12 | 13 | # swirlify 0.5.0 14 | 15 | * Removed `lp()`. 16 | 17 | * Deprecated `testit()`. `testit()` will be removed in swirlify 0.6. 18 | 19 | * Added `demo_lesson()` which replaces `testit()`. 20 | 21 | * Added `google_form_decode()` to help course authors evaluate their students' 22 | progress in a swirl couse. 23 | 24 | * Added `swirlify()` which starts a Shiny app for authoring swirl lessons. 25 | 26 | # swirlify 0.4 27 | 28 | * Removed `swirlify_help()`. 29 | 30 | * Changed `demo_lesson()` to `testit()`, although it will be changed back before 31 | swirlify 0.5. This was done because of compatibility issues with swirl. 32 | 33 | * Added `lp()` for quickly finding the file path to the current lesson.yaml. 34 | 35 | # swirlify 0.3.3 36 | 37 | * `test_lesson()` and `test_course()` now check for proper lesson formatting. 38 | 39 | * Added tests for packing and unpacking a lesson. 40 | 41 | # swirlify 0.3.2 42 | 43 | * Added `add_license()` for easy course licensing. 44 | 45 | * Changed `count_units()` to `count_questions()`. 46 | 47 | * Changed `test_lesson()` to `demo_lesson()`. 48 | 49 | # swirlify 0.3.1 50 | 51 | * Removed shiny authoring tool. Development of this tool will continue as a 52 | separate project. 53 | 54 | * Added `pack_course()` and `unpack_course()` to help with sharing courses in 55 | the `.swc` file format. 56 | 57 | * Changed the API for all of the question writing functions which now start 58 | with the prefix `wq_`. This is meant to be used with tab-completion to make 59 | writing lessons easier. 60 | 61 | * Changed `swirl2html()` to `lesson_to_html()`. 62 | 63 | * Changed `hlp()` to `swirlify_help()`. 64 | 65 | # swirlify 0.3.0.99 66 | 67 | * Fix typo in `swirl2html()` documentation. 68 | 69 | # swirlify 0.3 70 | 71 | * Deprecate `author_lesson()`, which is the old R Markdown approach. We me eventually introduce a more efficient R Markdown style that is neatly integrated with the current YAML approach. 72 | 73 | * Change `new_yaml()` to `new_lesson()` with notice to user. 74 | 75 | # swirlify 0.2.3 76 | 77 | * Normalize file paths using `normalizePath()` in case user specifies a relative path to a lesson, then changes their working directory. 78 | 79 | * Allow user to specify path to YAML lesson as an argument to `set_lesson()`. 80 | 81 | * Update `hlp()` menu. 82 | 83 | # swirlify 0.2.2 84 | 85 | * Add check to `testit()` to make sure that the lesson being tested is listed in the course `MANIFEST`, if one exists in the course directory. 86 | 87 | * Add commented out `AUTO_DETECT_NEWVAR <- FALSE` to customTests.R template created by `new_yaml()`. Setting this variable equal to `FALSE` can prevent double evaluation of printing and plotting commands. 88 | 89 | * Update `hlp()` output to include `count_units()` and `testit(from, to)`. 90 | 91 | # swirlify 0.2.1 92 | 93 | * Add `swirl2html()` to convert swirl lessons formatted in YAML to R markdown (Rmd) and html files. The output is a stylized webpage that acts as a standalone tutorial based on the original swirl lesson. 94 | 95 | # swirlify 0.2 96 | 97 | * Add a more bare bones YAML authoring toolset initiated by `new_yaml()`. `hlp()` gives a list of related functions. We plan to integrate this new approach more closely with existing swirlify functions, but it functions well as is. 98 | -------------------------------------------------------------------------------- /R/add_license.R: -------------------------------------------------------------------------------- 1 | #' Add a LICENSE.txt file to your course 2 | #' 3 | #' Licensing your course is important if you want to share your course. For more 4 | #' information see \url{https://github.com/swirldev/swirlify/wiki/Licensing-Your-Course}. 5 | #' For more information about Creative Commons licenses see \url{http://creativecommons.org/licenses/}. 6 | #' For more information about software licenses see \url{http://www.gnu.org/licenses/license-list.en.html}. 7 | #' 8 | #' @param author The author of the course. This can be an organization. 9 | #' @param year The year the course was written. 10 | #' @param open_source_content If \code{TRUE} a Creative Commons content license 11 | #' will be included pertaining to the content of your course. 12 | #' @param content_license Specify which Creative Commons license you would like 13 | #' to use for the content of your course. This must be equal to one of the 14 | #' following: \code{"CC BY 4.0"}, \code{"CC BY-SA 4.0"}, \code{"CC BY-ND 4.0"}, 15 | #' \code{"CC BY-NC 4.0"}, \code{"CC BY-NC-SA 4.0"}, \code{"CC BY-NC-ND 4.0"}, 16 | #' or \code{"CC0"}. 17 | #' @param open_source_data If \code{TRUE} a Creative Commons content license 18 | #' will be included pertaining to the data distributed with your course. 19 | #' @param data_license Currently this value must be equal to \code{"CC0"}, but 20 | #' in the future it may be able to be other values. 21 | #' @param open_source_code If \code{TRUE} a free software license 22 | #' will be included pertaining to the software included in your course. 23 | #' @param code_license Specify which open source software license you would like 24 | #' to use for the content of your course. This must be equal to one of the 25 | #' following: \code{"MIT"}, \code{"GPL3"}, \code{"CC0"}. 26 | #' @importFrom whisker whisker.render 27 | #' @export 28 | #' @examples 29 | #' \dontrun{ 30 | #' 31 | #' # Add a license with simple open source options 32 | #' add_license("Team swirl") 33 | #' 34 | #' # Add a license so that derivative works are shared alike 35 | #' add_license("Team swirl", content_license = "CC BY-SA 4.0", code_license ="GPL3") 36 | #' 37 | #' # Add a license that reserves all of the author's rights 38 | #' add_license("Team Bizzaro swirl", open_source_content = FALSE, 39 | #' open_source_data = FALSE, 40 | #' open_source_code = FALSE) 41 | #' } 42 | add_license <- function(author, year = format(Sys.Date(), "%Y"), 43 | open_source_content = TRUE, 44 | content_license = "CC BY 4.0", 45 | open_source_data = TRUE, 46 | data_license = "CC0", 47 | open_source_code = TRUE, 48 | code_license = "MIT"){ 49 | lesson_file_check() 50 | 51 | if(file.exists(file.path(getOption("swirlify_course_dir_path"), "LICENSE.txt")) && 52 | interactive()){ 53 | prompt_result <- readline(paste0("LICENSE.txt already exists for ", 54 | getOption("swirlify_course_name"), "\n", 55 | "Are you sure you want to overwrite it? Y/n ")) 56 | if(prompt_result != "Y"){ 57 | return(invisible(file.path(getOption("swirlify_course_dir_path"), "LICENSE.txt"))) 58 | } 59 | } 60 | 61 | if(!open_source_content && !open_source_data && !open_source_code){ 62 | cat(whisker.render("All code and content contained within this course is 63 | Copyright {{{year}}} {{{author}}}. All rights reserved.", list(author=author, 64 | year = year)), 65 | file = file.path(getOption("swirlify_course_dir_path"), "LICENSE.txt")) 66 | return(invisible(file.path(getOption("swirlify_course_dir_path"), "LICENSE.txt"))) 67 | } 68 | license_text <- "Copyright {{{year}}} {{{author}}}" 69 | if(open_source_content){ 70 | license_text <- paste0(license_text, "\n\nThe content of this course including but not limited to contents of the 71 | lesson.yaml files enclosed are licensed {{{content_license}}}. For more information please 72 | visit {{{content_license_url}}}") 73 | } 74 | if(open_source_data){ 75 | if(data_license != "CC0") stop(paste0("An invalid value: '", data_license, "' was provided for the add_license function.")) 76 | license_text <- paste0(license_text, "\n\nThe datasets contained in this course are dedicated to the public domain under 77 | the CC0 license. For more information please visit 78 | https://creativecommons.org/publicdomain/zero/1.0/") 79 | } 80 | if(open_source_code){ 81 | license_text <- paste0(license_text, "\n\nThe software contained in this course is subject to the following license:\n\n", 82 | whisker.render("{{{software_license}}}", list(software_license=get_license(code_license)))) 83 | } 84 | 85 | cat(whisker.render(license_text, 86 | list(year=year, author=author, 87 | content_license=content_license, 88 | content_license_url=get_cc_url(content_license) 89 | )), 90 | file = file.path(getOption("swirlify_course_dir_path"), "LICENSE.txt") 91 | ) 92 | } 93 | 94 | get_cc_url <- function(x){ 95 | cc_urls <- list( 96 | "CC BY 4.0" = "http://creativecommons.org/licenses/by/4.0/", 97 | "CC BY-SA 4.0" = "http://creativecommons.org/licenses/by-sa/4.0/", 98 | "CC BY-ND 4.0" = "http://creativecommons.org/licenses/by-nd/4.0/", 99 | "CC BY-NC 4.0" = "http://creativecommons.org/licenses/by-nc/4.0/", 100 | "CC BY-NC-SA 4.0" = "http://creativecommons.org/licenses/by-nc-sa/4.0/", 101 | "CC BY-NC-ND 4.0" = "http://creativecommons.org/licenses/by-nc-nd/4.0/", 102 | "CC0" = "https://creativecommons.org/publicdomain/zero/1.0/" 103 | ) 104 | result <- cc_urls[[x]] 105 | if(is.null(result)){ 106 | stop(paste0("An invalid value: '", x, "' was provided for the add_license function.")) 107 | } else { 108 | result 109 | } 110 | } 111 | 112 | get_license <- function(x){ 113 | license_dict <- list( 114 | "MIT" = 'Permission is hereby granted, free of charge, to any person obtaining a copy of 115 | this software and associated documentation files (the "Software"), to deal in 116 | the Software without restriction, including without limitation the rights to 117 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 118 | of the Software, and to permit persons to whom the Software is furnished to do 119 | so, subject to the following conditions: 120 | 121 | The above copyright notice and this permission notice shall be included in all 122 | copies or substantial portions of the Software. 123 | 124 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 125 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 126 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 127 | {{{author}}} BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 128 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 129 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 130 | 131 | Except as contained in this notice, the name of {{{author}}} shall not be 132 | used in advertising or otherwise to promote the sale, use or other dealings in 133 | this Software without prior written authorization from {{{author}}}.', 134 | 135 | "GPL3" = 'The programs included in this course are free software: you can redistribute them and/or modify 136 | them under the terms of the GNU General Public License as published by 137 | the Free Software Foundation, either version 3 of the License, or 138 | (at your option) any later version. 139 | 140 | This program is distributed in the hope that it will be useful, 141 | but WITHOUT ANY WARRANTY; without even the implied warranty of 142 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 143 | GNU General Public License for more details. 144 | 145 | You should have received a copy of the GNU General Public License 146 | along with this program. If not, see .', 147 | 148 | "CC0" = 'The code contained in this course is dedicated to the public domain under 149 | the CC0 license. For more information please visit 150 | https://creativecommons.org/publicdomain/zero/1.0/') 151 | result <- license_dict[[x]] 152 | if(is.null(result)){ 153 | stop(paste0("An invalid value: '", x, "' was provided for the add_license function.")) 154 | } else { 155 | result 156 | } 157 | } -------------------------------------------------------------------------------- /R/demo_lesson.R: -------------------------------------------------------------------------------- 1 | #' (Deprecated) 2 | #' 3 | #' This function is deprecated. Please use \code{demo_lesson} 4 | #' instead. 5 | #' 6 | #' @param from Question number to begin with. Defaults to beginning of lesson. 7 | #' @param to Question number to end with. Defaults to end of lesson. 8 | #' @importFrom swirl swirl install_course_directory 9 | #' @export 10 | testit <- function(from=NULL, to=NULL) { 11 | .Deprecated("demo_lesson") 12 | # Check that we're working on a lesson 13 | lesson_file_check() 14 | 15 | # Check that if MANIFEST exists, lesson is listed 16 | path2man <- file.path(getOption("swirlify_course_dir_path"), "MANIFEST") 17 | if(file.exists(path2man)) { 18 | manifest <- readLines(path2man, warn=FALSE) 19 | if(!(getOption('swirlify_lesson_dir_name') %in% manifest)) { 20 | stop("Please run add_to_manifest() before demoing", 21 | " this lesson.") 22 | } 23 | } 24 | # Install course 25 | install_course_directory(getOption("swirlify_course_dir_path")) 26 | # Run lesson in "test" mode 27 | suppressPackageStartupMessages( 28 | swirl("test", 29 | test_course=getOption("swirlify_course_name"), 30 | test_lesson=getOption("swirlify_lesson_name"), 31 | from=from, 32 | to=to)) 33 | invisible() 34 | } 35 | 36 | #' Demo the current lesson in swirl 37 | #' 38 | #' Jump right in to the current swirl lesson without needing 39 | #' to navigate swirl's menus. It's also possible to jump 40 | #' into the middle of a lesson. 41 | #' 42 | #' @param from Question number to begin with. Defaults to beginning of lesson. 43 | #' @param to Question number to end with. Defaults to end of lesson. 44 | #' @importFrom swirl swirl install_course_directory 45 | #' @importFrom stringr str_trim 46 | #' @export 47 | #' @examples 48 | #' \dontrun{ 49 | #' # Demo current lesson from beginning through the end 50 | #' demo_lesson() 51 | #' # Demo current lesson from question 5 through the end 52 | #' demo_lesson(5) 53 | #' # Demo current lesson from question 8 through question 14 54 | #' demo_lesson(8, 14) 55 | #' } 56 | demo_lesson <- function(from=NULL, to=NULL) { 57 | # Check that we're working on a lesson 58 | lesson_file_check() 59 | 60 | # Check that if MANIFEST exists, lesson is listed 61 | path2man <- file.path(getOption("swirlify_course_dir_path"), "MANIFEST") 62 | if(file.exists(path2man)) { 63 | manifest <- str_trim(readLines(path2man, warn=FALSE)) 64 | if(!(getOption('swirlify_lesson_dir_name') %in% manifest)) { 65 | stop("Please run add_to_manifest() before demoing", 66 | " this lesson.") 67 | } 68 | } 69 | # Install course 70 | install_course_directory(getOption("swirlify_course_dir_path")) 71 | # Run lesson in "test" mode 72 | suppressPackageStartupMessages( 73 | swirl("test", 74 | test_course=getOption("swirlify_course_name"), 75 | test_lesson=getOption("swirlify_lesson_name"), 76 | from=from, 77 | to=to)) 78 | invisible() 79 | } -------------------------------------------------------------------------------- /R/global.R: -------------------------------------------------------------------------------- 1 | utils::globalVariables(c("file.edit", "browseURL", "packageVersion", "select.list", "read.csv")) -------------------------------------------------------------------------------- /R/google_form_decode.R: -------------------------------------------------------------------------------- 1 | #' Decode Student's Submissions from Google Forms 2 | #' 3 | #' @param path The path to a \code{csv} file downloaded from Google Forms or 4 | #' Google Sheets which contains student's encoded responses. 5 | #' @return A data frame containing each student's results. 6 | #' @importFrom base64enc base64decode 7 | #' @importFrom readr read_csv 8 | #' @export 9 | #' @examples 10 | #' \dontrun{ 11 | #' 12 | #' # Choose the csv file yourself 13 | #' google_form_decode() 14 | #' 15 | #' # Explicity specify the path 16 | #' google_form_decode("~/Desktop/My_Course.csv") 17 | #' 18 | #' } 19 | google_form_decode <- function(path = file.choose()){ 20 | encoded <- suppressMessages(suppressWarnings(read_csv(path))) 21 | decoded <- list() 22 | 23 | for(i in 1:nrow(encoded)){ 24 | decoded[[i]] <- suppressMessages( 25 | read_csv( 26 | rawToChar( 27 | base64decode( 28 | as.character(encoded[i,2]))))) 29 | } 30 | 31 | result <- as.data.frame(do.call("rbind", decoded)) 32 | attributes(result)$spec <- NULL 33 | attributes(result)$row.names <- 1:nrow(result) 34 | result 35 | } 36 | -------------------------------------------------------------------------------- /R/lesson_to_html.R: -------------------------------------------------------------------------------- 1 | makechunk <- function(item) { 2 | out <- c("```{r, strip.white = TRUE}", item, "```") 3 | paste0(out, collapse="\n") 4 | } 5 | 6 | makechunk_silent <- function(item) { 7 | out <- c("```{r, strip.white = TRUE, echo=FALSE, message=FALSE}", item, "```") 8 | paste0(out, collapse="\n") 9 | } 10 | 11 | #' @importFrom stringr str_split str_trim 12 | makemult <- function(item) { 13 | answers <- unlist(str_split(item, ";")) 14 | answers <- str_trim(answers) 15 | nums <- paste0(seq(length(answers)), ".") 16 | paste(nums, answers, collapse="\n") 17 | } 18 | 19 | makemd <- function(unit) UseMethod("makemd") 20 | 21 | makemd.default <- function(unit) { 22 | stop("No unit class specified!", unit) 23 | } 24 | 25 | makemd.text <- function(unit) { 26 | paste(unit[['Output']], 27 | sep="\n\n") 28 | } 29 | 30 | makemd.cmd_question <- function(unit) { 31 | paste(unit[['Output']], 32 | makechunk(unit[['CorrectAnswer']]), 33 | sep="\n\n") 34 | } 35 | 36 | makemd.mult_question <- function(unit) { 37 | paste(unit[['Output']], 38 | makemult(unit[['AnswerChoices']]), 39 | unit[['CorrectAnswer']], 40 | sep="\n\n") 41 | } 42 | 43 | makemd.script <- function(unit) { 44 | # Get correct script contents 45 | script_name <- unit[["Script"]] 46 | correct_script_name <- paste0(tools::file_path_sans_ext(script_name), 47 | "-correct.R") 48 | path2les <- getOption("swirlify_lesson_dir_path") 49 | script_path <- file.path(path2les, "scripts", correct_script_name) 50 | script_contents <- readLines(script_path, warn = FALSE) 51 | paste(unit[["Output"]], 52 | makechunk(script_contents), 53 | sep = "\n\n") 54 | } 55 | 56 | #' Turn a swirl lesson into a pretty webpage 57 | #' 58 | #' Create an easily shareable HTML version of your swirl lesson. This function 59 | #' detects the lesson you are working on 60 | #' automatically via \code{getOption('swirlify_lesson_file_path')}, 61 | #' converts it to R Markdown (Rmd), then generates a stylized HTML 62 | #' document and opens it in your default browser. To prevent clutter, 63 | #' the Rmd files are not kept by default, but they can be kept 64 | #' by setting \code{keep_rmd = TRUE}. 65 | #' 66 | #' The output is formatted to be a readable, standalone tutorial. 67 | #' This means that information contained in the swirl lesson such as 68 | #' answer tests and hints are excluded from the Rmd/HTML output. 69 | #' 70 | #' @param dest_dir Destination directory (i.e. where to put the output files). 71 | #' If not set, default is the directory which contains the course directory. 72 | #' @param open_html Should the HTML file produced be opened in your browser? 73 | #' Default is \code{FALSE}. 74 | #' @param keep_rmd Should the Rmd file be kept after the HTML is 75 | #' is produced? Default is \code{FALSE}. 76 | #' @param quiet Should the Rmd rendering output be silenced? Default 77 | #' is \code{FALSE}. 78 | #' @param install_course Should the course 79 | #' be installed? Default is \code{TRUE}. 80 | #' 81 | #' @importFrom yaml yaml.load_file 82 | #' @importFrom rmarkdown render 83 | #' @importFrom swirl install_course_directory 84 | #' @export 85 | lesson_to_html <- function(dest_dir = NULL, open_html = FALSE, 86 | keep_rmd = FALSE, quiet = FALSE, 87 | install_course = TRUE) { 88 | if(!is.logical(open_html)) { 89 | stop("Argument 'open_html' must be TRUE or FALSE!") 90 | } 91 | if(!is.logical(keep_rmd)) { 92 | stop("Argument 'keep_rmd' must be TRUE or FALSE!") 93 | } 94 | if(!is.logical(quiet)) { 95 | stop("Argument 'quiet' must be TRUE or FALSE!") 96 | } 97 | if(!is.logical(install_course)) { 98 | stop("Argument 'install_course' must be TRUE or FALSE!") 99 | } 100 | #if(!require(rmarkdown)) { 101 | # stop("You must install the rmarkdown package to use this feature!") 102 | #} 103 | # Check that a lesson is set 104 | lesson_file_check() 105 | # Get course directory and confirm destination dir 106 | course_dir <- getOption('swirlify_course_dir_path') 107 | # If no dest dir is specified, use the lesson dir 108 | if(is.null(dest_dir)) { 109 | dest_dir <- dirname(getOption("swirlify_course_dir_path")) 110 | } 111 | # Check that dest_dir is valid 112 | if(!file.exists(dest_dir)) { 113 | stop(dest_dir, " does not exist!") 114 | } 115 | # Expand path 116 | dest_dir <- normalizePath(dest_dir) 117 | # Install course 118 | if(install_course) install_course_directory(course_dir) 119 | # Set path to lesson file 120 | lessonPath <- getOption('swirlify_lesson_file_path') 121 | # Set rmd file name 122 | rmd_filename <- paste0(getOption("swirlify_lesson_dir_name"), ".Rmd") 123 | # Set destination file for Rmd 124 | destrmd <- file.path(dest_dir, rmd_filename) 125 | # Load yaml 126 | les <- yaml.load_file(lessonPath) 127 | # Get and remove meta 128 | meta <- unlist(les[1]) 129 | les <- les[-1] 130 | # Write meta to document header 131 | cat('---', 132 | paste('title:', meta['Lesson']), 133 | 'output:', 134 | ' html_document:', 135 | ' theme: spacelab', 136 | '---\n', 137 | sep="\n", file=destrmd) 138 | # Get initLesson.R info and write init chunk w/ no echo 139 | initpath <- file.path(dirname(lessonPath), "initLesson.R") 140 | # Get and write initialization code if initLesson.R exists 141 | if(file.exists(initpath)) { 142 | initcode <- paste0(readLines(initpath, warn=FALSE), collapse="\n") 143 | initcode <- c("suppressPackageStartupMessages(library(swirl))\n", initcode) 144 | cat(makechunk_silent(initcode), "\n\n", file=destrmd, append=TRUE) 145 | } 146 | # Write the rest of the content 147 | for(unit in les) { 148 | class(unit) <- unit[['Class']] 149 | out <- paste(makemd(unit), "\n\n") 150 | cat(out, file=destrmd, append=TRUE) 151 | invisible() 152 | } 153 | # message("Opening R Markdown file...") 154 | # file.edit(destrmd) 155 | message("Knitting html...") 156 | rmarkdown::render(destrmd, quiet = quiet) 157 | # Path to html document 158 | html_filename <- paste0(getOption("swirlify_lesson_dir_name"), ".html") 159 | desthtml <- file.path(dest_dir, html_filename) 160 | # If keep_rmd is FALSE, remove rmd file 161 | if(!keep_rmd) file.remove(destrmd) 162 | if(open_html) { 163 | message("Opening html document...") 164 | browseURL(desthtml) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /R/new_lesson.R: -------------------------------------------------------------------------------- 1 | #' Create new lesson in the YAML format. 2 | #' 3 | #' Creates a new lesson and possibly a new course in your working directory. If 4 | #' the name you provide for \code{course_name} is not a directory in your 5 | #' working directory, then a new course directory will be created. However if 6 | #' you've already started a course with the name you provide for \code{course_name} 7 | #' and that course is in your working directory, then a new lesson will be created 8 | #' inside of that course with the name you provide for \code{lesson_name}. 9 | #' 10 | #' @param lesson_name The name of the lesson. 11 | #' @param course_name The name of the course. 12 | #' @param open_lesson If \code{TRUE} the new \code{lesson.yaml} file will open 13 | #' for editing via \code{\link[utils]{file.edit}}. The default value is \code{TRUE}. 14 | #' @export 15 | #' @examples 16 | #' \dontrun{ 17 | #' # Make sure you have your working directory set to where you want to 18 | #' # create the course. 19 | #' setwd(file.path("~", "Developer", "swirl_courses")) 20 | #' 21 | #' # Make a new course with a new lesson 22 | #' new_lesson("How to use pnorm", "Normal Distribution Functions in R") 23 | #' 24 | #' # Make a new lesson in an existing course 25 | #' new_lesson("How to use qnorm", "Normal Distribution Functions in R") 26 | #' } 27 | new_lesson <- function(lesson_name, course_name, open_lesson = TRUE) { 28 | lessonDir <- file.path(gsub(" ", "_", course_name), 29 | gsub(" ", "_", lesson_name)) 30 | if(file.exists(lessonDir)) { 31 | lessonDir <- normalizePath(lessonDir) 32 | stop("Lesson already exists in ", lessonDir) 33 | } 34 | dir.create(lessonDir, recursive=TRUE) 35 | lessonDir <- normalizePath(lessonDir) 36 | message("Creating new lesson in ", lessonDir) 37 | cat("# Code placed in this file fill be executed every time the 38 | # lesson is started. Any variables created here will show up in 39 | # the user's working directory and thus be accessible to them 40 | # throughout the lesson.", 41 | file = file.path(lessonDir, "initLesson.R")) 42 | cat("# Put custom tests in this file. 43 | 44 | # Uncommenting the following line of code will disable 45 | # auto-detection of new variables and thus prevent swirl from 46 | # executing every command twice, which can slow things down. 47 | 48 | # AUTO_DETECT_NEWVAR <- FALSE 49 | 50 | # However, this means that you should detect user-created 51 | # variables when appropriate. The answer test, creates_new_var() 52 | # can be used for for the purpose, but it also re-evaluates the 53 | # expression which the user entered, so care must be taken.", 54 | file = file.path(lessonDir, "customTests.R")) 55 | cat("", 56 | file = file.path(lessonDir, "dependson.txt")) 57 | 58 | # The YAML faq, http://www.yaml.org/faq.html, encourages 59 | # use of the .yaml (as opposed to .yml) file extension 60 | # whenever possible. 61 | lesson_file <- file.path(lessonDir, "lesson.yaml") 62 | writeLines(c("- Class: meta", 63 | paste(" Course:", course_name), 64 | paste(" Lesson:", lesson_name), 65 | " Author: your name goes here", 66 | " Type: Standard", 67 | " Organization: your organization's name goes here", 68 | paste(" Version:", packageVersion("swirl"))), 69 | lesson_file) 70 | if(open_lesson){ 71 | file.edit(lesson_file) 72 | } 73 | message("If the lesson file doesn't open automatically, you can open it now to begin editing...") 74 | # Set options 75 | set_swirlify_options(lesson_file) 76 | } 77 | -------------------------------------------------------------------------------- /R/pack.R: -------------------------------------------------------------------------------- 1 | #' Create an \code{.swc} file of the course you are working on 2 | #' 3 | #' "Pack" the course you are working on into a single compressed file that is 4 | #' easy to share. Invisibly returns the path to the \code{.swc} file. 5 | #' 6 | #' @param export_path Optional, full path to the directory you want the swirl 7 | #' course file to be exported to. If not specified, then the file will appear 8 | #' in the same directory as the course directory. 9 | #' @return A string, the path to the new \code{.swc} file, invisibly. 10 | #' @export 11 | #' @examples 12 | #' \dontrun{ 13 | #' # Set any lesson in the course you want to pack 14 | #' set_lesson() 15 | #' 16 | #' # Pack the course 17 | #' pack_course() 18 | #' 19 | #' # Export the .swc file to a directory that you specify 20 | #' pack_course(file.path("~", "Desktop")) 21 | #' } 22 | pack_course <- function(export_path = NULL){ 23 | if(is.null(export_path)){ 24 | if(is.null(getOption("swirlify_course_dir_path"))){ 25 | message("It looks like you haven't set a lesson.") 26 | message("Use the set_lesson() function to set any lesson in the course you wish to pack.") 27 | return() 28 | } 29 | export_path <- dirname(getOption("swirlify_course_dir_path")) 30 | } 31 | files <- list.files(getOption("swirlify_course_dir_path"), recursive = TRUE) 32 | files_full_path <- list.files(getOption("swirlify_course_dir_path"), recursive = TRUE, full.names = TRUE) 33 | pack <- list(name = getOption("swirlify_course_dir_name"), files = list()) 34 | for(i in 1:length(files)){ 35 | pack$files[[i]] <- list(path = unlist(str_split(files[i], .Platform$file.sep)), 36 | raw_file = readBin(files_full_path[i], "raw", n = file.size(files_full_path[i])), 37 | endian = .Platform$endian) 38 | } 39 | swc_path <- file.path(export_path, paste0(getOption("swirlify_course_dir_name"), ".swc")) 40 | saveRDS(pack, swc_path) 41 | message("Your course was successfully packed!") 42 | message(paste("Your packed course can be found at:", swc_path)) 43 | invisible(swc_path) 44 | } 45 | 46 | #' Unpack an \code{.swc} file into a swirl course 47 | #' 48 | #' Invisibly returns the path to the unpacked course directory. 49 | #' 50 | #' @param file_path Optional, full path to the \code{.swc} file you wish to unpack. 51 | #' If not specified, you will be prompted to choose a file interactively. 52 | #' @param export_path Optional, full path to the directory where the swirl course 53 | #' should be exported. If not specified, the course will appear in the same 54 | #' directory as the \code{.swc} file. 55 | #' @return A string, the path to the unpacked course directory, invisibly. 56 | #' @export 57 | #' @examples 58 | #' \dontrun{ 59 | #' # Unpack a course and interactively choose a .swc file 60 | #' unpack_course() 61 | #' 62 | #' # Unpack a course where the .swc file is explicitly specified 63 | #' unpack_course(file.path("~", "Desktop", "R_Programming.swc")) 64 | #' 65 | #' # Unpack a course and specify where the .swc file is located and where the 66 | #' # course should be exported. 67 | #' unpack_course(file.path("~", "Desktop", "R_Programming.swc"), 68 | #' file.path("~", "Developer", "swirl")) 69 | #' } 70 | unpack_course <- function(file_path=file.choose(), export_path=dirname(file_path)){ 71 | # Remove trailing slash 72 | export_path <- sub(paste0(.Platform$file.sep, "$"), replacement = "", export_path) 73 | 74 | pack <- readRDS(file_path) 75 | course_path <- file.path(export_path, pack$name) 76 | if(file.exists(course_path) && interactive()){ 77 | response <- "" 78 | while(response != "Y"){ 79 | response <- select.list(c("Y", "n"), title = paste(course_path, "already exists.\nAre you sure you want to overwrite it? [Y/n]")) 80 | if(response == "n") return(invisible(course_path)) 81 | } 82 | } 83 | dir.create(course_path) 84 | for(i in 1:length(pack$files)){ 85 | 86 | # Make file's ultimate path 87 | if(length(pack$files[[i]]$path) >= 2){ 88 | lesson_file_path <- Reduce(function(x, y){file.path(x, y)}, pack$files[[i]]$path[2:length(pack$files[[i]]$path)], pack$files[[i]]$path[1]) 89 | } else { 90 | lesson_file_path <- pack$files[[i]]$path 91 | } 92 | file_path <- file.path(course_path, lesson_file_path) 93 | 94 | # If the directory the file needs to be in does not exist, create the dir 95 | if(!file.exists(dirname(file_path))){ 96 | dir.create(dirname(file_path), showWarnings = FALSE, recursive = TRUE) 97 | } 98 | 99 | writeBin(pack$files[[i]]$raw_file, file_path, endian = pack$files[[i]]$endian) 100 | } 101 | message("Your course was successfully unpacked!") 102 | message(paste("Your unpacked course can be found at:", course_path)) 103 | invisible(course_path) 104 | } 105 | -------------------------------------------------------------------------------- /R/set_lesson.R: -------------------------------------------------------------------------------- 1 | #' Select an existing lesson to work on 2 | #' 3 | #' Choose a lesson to work on with swirlify by specifying the path to the 4 | #' \code{lesson.yaml} file or interactively choose a lesson file. 5 | #' 6 | #' @param path_to_yaml Optional, full path to YAML lesson file. If not 7 | #' specified, then you will be prompted to select file interactively. 8 | #' @param open_lesson Should the lesson be opened automatically? 9 | #' Default is \code{TRUE}. 10 | #' @param silent Should the lesson be set silently? Default is 11 | #' \code{FALSE}. 12 | #' @export 13 | #' @examples 14 | #' \dontrun{ 15 | #' # Set the lesson interactively 16 | #' set_lesson() 17 | #' 18 | #' # You can also specify the path to the \code{yaml} file you wish to work on. 19 | #' set_lesson(file.path("~", "R_Programming", "Functions", "lesson.yaml")) 20 | #' } 21 | set_lesson <- function(path_to_yaml = NULL, open_lesson = TRUE, 22 | silent = FALSE) { 23 | if(!is.logical(open_lesson)) { 24 | stop("Argument 'open_lesson' must be logical!") 25 | } 26 | if(!is.logical(silent)) { 27 | stop("Argument 'silent' must be logical!") 28 | } 29 | options(swirlify_lesson_file_path = NULL) 30 | lesson_file_check(path_to_yaml) 31 | if(!silent) { 32 | message("\nThis lesson is located at ", getOption("swirlify_lesson_file_path")) 33 | message("\nIf the lesson file doesn't open automatically, you can open it now to begin editing...\n") 34 | } 35 | if(open_lesson) { 36 | file.edit(getOption("swirlify_lesson_file_path")) 37 | } 38 | invisible() 39 | } 40 | -------------------------------------------------------------------------------- /R/swirlify.R: -------------------------------------------------------------------------------- 1 | # Creates skeleton for new course/lesson. 2 | make_skeleton <- function() { 3 | # Course directory name 4 | courseDirName <- gsub(" ", "_", getOption("swirlify_course_name")) 5 | # Lesson directory name 6 | lessonDirName <- gsub(" ", "_", getOption("swirlify_lesson_name")) 7 | # Full path to lesson directory 8 | lessonDirPath <- file.path(getwd(), courseDirName, lessonDirName) 9 | # Full path to lesson file 10 | lessonPath <- file.path(lessonDirPath, "lesson.yaml") 11 | # Check if lesson directory exists 12 | if(!file.exists(lessonDirPath)) { 13 | message("\nCreating lesson directory:\n\n", lessonDirPath) 14 | dir.create(lessonDirPath, recursive=TRUE) 15 | writeLines(c("- Class: meta", 16 | paste(" Course:", getOption("swirlify_course_name")), 17 | paste(" Lesson:", getOption("swirlify_lesson_name")), 18 | paste(" Author:", getOption("swirlify_author")), 19 | " Type: Standard", 20 | paste(" Organization:", getOption("swirlify_organization")), 21 | paste(" Version:", packageVersion("swirl")), 22 | paste("\n")), 23 | lessonPath) 24 | writeLines(c( 25 | "# Put initialization code in this file. The variables you create", 26 | "# here will show up in the user's workspace when he or she begins", 27 | "# the lesson."), file.path(lessonDirPath, "initLesson.R")) 28 | writeLines("# Put custom tests in this file.", 29 | file.path(lessonDirPath,"customTests.R")) 30 | } else { 31 | message("\nLesson directory already exists:\n\n", lessonDirPath) 32 | message("\nOpening existing lesson for editing...") 33 | } 34 | # Return full path to lesson file 35 | return(lessonPath) 36 | } 37 | 38 | #' Launch a Shiny application for writing swirl lessons 39 | #' 40 | #' This function launches a user interface for writing 41 | #' swirl lessons. 42 | #' 43 | #' @param lesson_name The name of the new lesson you want to 44 | #' create. The default value is \code{NULL}. If you've 45 | #' already selected a lesson to work on using \code{\link{set_lesson}} 46 | #' then you do not need to provide a value for this argument. 47 | #' @param course_name The name of the new course you want to 48 | #' create. The default value is \code{NULL}. If you've 49 | #' already selected a course to work on using \code{\link{set_lesson}} 50 | #' then you do not need to provide a value for this argument. 51 | #' 52 | #' 53 | #' @import shiny 54 | #' @import swirl 55 | #' @import shinyAce 56 | #' @export 57 | #' @examples 58 | #' \dontrun{ 59 | #' 60 | #' # Set lesson beforehand 61 | #' set_lesson() 62 | #' swirlify() 63 | #' 64 | #' # Start a new lesson in your current directory 65 | #' swirlify("Lesson 1", "My Course") 66 | #' 67 | #' } 68 | swirlify <- function(lesson_name = NULL, course_name = NULL){ 69 | if(is.null(lesson_name) && is.null(course_name)){ 70 | lesson_fp <- getOption("swirlify_lesson_file_path") 71 | if(!is.null(lesson_fp) && file.exists(lesson_fp)){ 72 | startApp() 73 | } else { 74 | stop("Swirlify cannot find the lesson you are trying to work on. ", 75 | "Please provide arguments for both lesson_name and course_name to start a new lesson, ", 76 | "or choose a lesson to work on using set_lesson.") 77 | } 78 | } else if(!is.null(lesson_name) && !is.null(course_name)){ 79 | lesson <- get_lesson(lesson_name, course_name) 80 | if(is.na(lesson)){ 81 | new_lesson(lesson_name, course_name, open_lesson = FALSE) 82 | } else { 83 | set_lesson(lesson, open_lesson = FALSE, silent = TRUE) 84 | } 85 | startApp() 86 | } else { 87 | stop("Please provide arguments for both lesson_name and course_name to start a new course.") 88 | } 89 | invisible() 90 | } 91 | 92 | startApp <- function(){ 93 | appDir <- system.file("swirlify-app", package = "swirlify") 94 | 95 | app_results <- list() 96 | app_results <- runApp(appDir, display.mode = "normal") 97 | 98 | if(isTRUE(app_results$demo)){ 99 | course_path <- getOption("swirlify_course_dir_path") 100 | course_name <- getOption("swirlify_course_name") 101 | lesson_name <- getOption("swirlify_lesson_name") 102 | install_course_directory(course_path) 103 | app_results$demo_num <- ifelse(app_results$demo_num < 1, 1, app_results$demo_num) 104 | swirl("test", test_course = course_name, test_lesson = lesson_name, 105 | from = app_results$demo_num) 106 | } 107 | invisible() 108 | } 109 | 110 | get_lesson <- function(lesson_name, course_name){ 111 | lesson_name <- make_pathname(lesson_name) 112 | course_name <- make_pathname(course_name) 113 | lessons <- file.path(getwd(), course_name, lesson_name, c("lesson.yaml", "lesson")) 114 | Filter(file.exists, lessons)[1] 115 | } -------------------------------------------------------------------------------- /R/test_lesson.R: -------------------------------------------------------------------------------- 1 | #' Run tests on a lesson 2 | #' 3 | #' Run basic tests on all questions in the current lesson. 4 | #' 5 | #' @export 6 | #' @examples 7 | #' \dontrun{ 8 | #' # Set a lesson interactively 9 | #' set_lesson() 10 | #' 11 | #' # Run tests on that lesson 12 | #' test_lesson() 13 | #' } 14 | test_lesson <- function(){ 15 | lesson_file_check() 16 | test_lesson_by_name() 17 | } 18 | 19 | #' Run tests on a course 20 | #' 21 | #' Run basic tests on all questions in the lessons listed in the \code{MANIFEST}. 22 | #' See \code{\link{add_to_manifest}} for information about the \code{MANIFEST} 23 | #' file. 24 | #' 25 | #' @export 26 | #' @examples 27 | #' \dontrun{ 28 | #' # To test a course, set any lesson in that course as the current lesson 29 | #' set_lesson() 30 | #' 31 | #' # Run tests on every lesson in that course listed in the MANIFEST 32 | #' test_course() 33 | #' } 34 | test_course <- function(){ 35 | lesson_file_check() 36 | 37 | if(!any(file.exists(file.path(getOption("swirlify_course_dir_path"), c("LICENSE.txt", "LICENSE", "LICENCE.txt", "LICENCE"))))){ 38 | message("It seems this course does not contian a LICENSE.txt file.\nYou can easily add a license with add_license().\n") 39 | } 40 | 41 | manifest_path <- file.path(getOption("swirlify_course_dir_path"), "MANIFEST") 42 | if(!file.exists(manifest_path)){ 43 | stop("It seems there's no MANIFEST file for this course.\nPlease add one using add_to_manifest().") 44 | } 45 | manifest <- Filter(function(x){!identical(x, "")}, str_trim(readLines(manifest_path))) 46 | yaml_list <- file.path(getOption("swirlify_course_dir_path"), 47 | manifest, "lesson.yaml") 48 | for(lesson in yaml_list){ 49 | # Check to see if `lesson.yaml` or just `lesson` exists 50 | # If neither exists warn the user and go on to the next lesson 51 | if(!file.exists(lesson)){ 52 | lesson <- sub(".yaml$", "", lesson) 53 | if(!file.exists(lesson)){ 54 | message("Could not find expected lesson file:\n", lesson, ".yaml") 55 | next() 56 | } 57 | } 58 | 59 | set_lesson(path_to_yaml = lesson, open_lesson = FALSE, silent = TRUE) 60 | test_lesson_by_name() 61 | } 62 | } 63 | 64 | # Test all cmd and mult questions of any lesson of current course. 65 | #' @importFrom yaml yaml.load_file 66 | test_lesson_by_name <- function(){ 67 | message(paste("##### Begin testing:", getOption("swirlify_lesson_name"), "#####")) 68 | # .e <- environment(swirl:::any_of_exprs) 69 | # attach(.e) 70 | # on.exit(detach(.e)) 71 | # e <- new.env() 72 | 73 | course_dir_path <- getOption("swirlify_course_dir_path") 74 | lesson_dir_path <- getOption("swirlify_lesson_dir_path") 75 | les <- yaml.load_file(getOption("swirlify_lesson_file_path")) 76 | 77 | # for (R_file in c("customTests.R", "initLesson.R")){ 78 | # R_file_path <- file.path(lesson_dir_path, R_file) 79 | # if(file.exists(R_file_path)) source(R_file_path,local = e) 80 | # } 81 | 82 | qn <- 0 # question number 83 | scripts_warned_already <- FALSE 84 | for (question in les){ 85 | if(question$Class == "meta"){ 86 | for(i in c("Course", "Lesson", "Author", "Type", "Version")){ 87 | if(is.null(question[[i]])) message("Please provide a value for the ", 88 | i, " key in the meta question.") 89 | } 90 | } else if(question$Class == "cmd_question"){ 91 | for(i in c("Output", "CorrectAnswer", "AnswerTests", "Hint")){ 92 | if(is.null(question[[i]])) message("Please provide a value for the ", 93 | i, " key in question ", qn, ".") 94 | } 95 | # ne$val <- eval(parse(text=question$CorrectAnswer), envir = ne) 96 | # ne$expr <- parse(text = question$CorrectAnswer)[[1]] 97 | # if(!eval(parse(text=question$AnswerTests), envir = e)){ 98 | # message("CorrectAnswer/AnswerTests mismatch in question ", qn,".") 99 | # } 100 | } else if(question$Class == "figure"){ 101 | for(i in c("Output", "Figure", "FigureType")){ 102 | if(is.null(question[[i]])) message("Please provide a value for the ", 103 | i, " key in question ", qn, ".") 104 | } 105 | 106 | if(!is.null(question$Figure)){ 107 | fig <- file.path(getOption("swirlify_lesson_dir_path"), question$Figure) 108 | if(!file.exists(fig)){ 109 | message("Could not find figure-creating script '", question$Figure, 110 | "'") 111 | } 112 | } 113 | } else if(question$Class == "text"){ 114 | for(i in c("Output")){ 115 | if(is.null(question[[i]])) message("Please provide a value for the ", 116 | i, " key in question ", qn, ".") 117 | } 118 | } else if(question$Class == "mult_question"){ 119 | for(i in c("Output", "AnswerChoices", "CorrectAnswer", "AnswerTests", "Hint")){ 120 | if(is.null(question[[i]])) message("Please provide a value for the ", 121 | i, " key in question ", qn, ".") 122 | } 123 | # e$val <- as.character(question$CorrectAnswer) 124 | # if(!eval(parse(text = question$AnswerTests), envir = e)){ 125 | # message("CorrectAnswer/AnswerTests mismatch in question ", qn,".") 126 | # } 127 | } else if(question$Class == "exact_question"){ 128 | for(i in c("Output", "CorrectAnswer", "AnswerTests", "Hint")){ 129 | if(is.null(question[[i]])) message("Please provide a value for the ", 130 | i, " key in question ", qn, ".") 131 | } 132 | } else if(question$Class == "script"){ 133 | for(i in c("Output", "Script", "AnswerTests", "Hint")){ 134 | if(is.null(question[[i]])) message("Please provide a value for the ", 135 | i, " key in question ", qn, ".") 136 | } 137 | 138 | # Check for the existense of a scripts directory for this lesson 139 | scripts_path <- file.path(getOption("swirlify_lesson_dir_path"), "scripts") 140 | if(!file.exists(scripts_path) && !scripts_warned_already){ 141 | message("Please create a directory called 'scripts' in the lesson 142 | directory for ", getOption("swirlify_lesson_name"), ".") 143 | scripts_warned_already <- TRUE 144 | } 145 | 146 | if(!is.null(question$Script) && !scripts_warned_already){ 147 | script <- question$Script 148 | 149 | # Check for the existence of the script.R file. 150 | if(!file.exists(file.path(scripts_path, script))){ 151 | message("Could not find script '", script, "' for lesson '", 152 | getOption("swirlify_lesson_name"), "' question number ", 153 | qn, ".") 154 | } 155 | 156 | # Check for the existence of the script-correct.R file. 157 | script_correct <- paste0(sub(".R$", "", script), "-correct.R") 158 | if(!file.exists(file.path(scripts_path, script_correct))){ 159 | message("Could not find script '", script_correct, "' for lesson '", 160 | getOption("swirlify_lesson_name"), "' question number ", 161 | qn, ".") 162 | } 163 | } 164 | } else if(question$Class == "text_question"){ 165 | for(i in c("Output", "CorrectAnswer", "AnswerTests", "Hint")){ 166 | if(is.null(question[[i]])) message("Please provide a value for the ", 167 | i, " key in question ", qn, ".") 168 | } 169 | } else if(question$Class == "video"){ 170 | for(i in c("Output", "VideoLink")){ 171 | if(is.null(question[[i]])) message("Please provide a value for the ", 172 | i, " key in question ", qn, ".") 173 | } 174 | } else { 175 | message("Question ", qn, ": a question of class '", question$Class, 176 | "' is not officially supported by swirl.") 177 | } 178 | 179 | qn <- qn + 1 180 | } 181 | 182 | message("##### End testing: ", getOption("swirlify_lesson_name"), " #####\n") 183 | } 184 | -------------------------------------------------------------------------------- /R/tools.R: -------------------------------------------------------------------------------- 1 | #' See what lesson you are currently working on 2 | #' 3 | #' Prints the current lesson and course that you are working on to the console 4 | #' 5 | #' @export 6 | #' @examples 7 | #' \dontrun{ 8 | #' get_current_lesson() 9 | #' } 10 | get_current_lesson <- function() { 11 | lesson_file_check() 12 | message("\nYou are currently working on...\n") 13 | message("Lesson: ", getOption("swirlify_lesson_name")) 14 | message("Course: ", getOption("swirlify_course_name")) 15 | message("\nThis lesson is located at ", 16 | getOption("swirlify_lesson_file_path"), 17 | "\n") 18 | invisible() 19 | } 20 | 21 | #' Count number of questions in current lesson 22 | #' 23 | #' Returns and prints the number of questions in the current lesson. 24 | #' 25 | #' @importFrom yaml yaml.load_file 26 | #' @return Number of questions as an integer, invisibly 27 | #' @export 28 | #' @examples 29 | #' \dontrun{ 30 | #' count_questions() 31 | #' } 32 | count_questions <- function() { 33 | lesson_file_check() 34 | les <- yaml.load_file(getOption('swirlify_lesson_file_path')) 35 | message("Current lesson has ", length(les) - 1, " questions") 36 | invisible(length(les) - 1) 37 | } 38 | 39 | #' Get question numbers for any questions matching a regular expression 40 | #' 41 | #' @importFrom yaml yaml.load_file 42 | #' @importFrom stringr str_detect 43 | #' @param regex The regular expression to look for in the lesson. 44 | #' This gets passed along to \code{stringr::str_detect}, so the 45 | #' same rules apply. See \code{\link[stringr]{str_detect}}. 46 | #' @return A vector of integers representing question numbers. 47 | #' @export 48 | #' @examples 49 | #' \dontrun{ 50 | #' set_lesson() 51 | #' find_questions("plot") 52 | #' find_questions("which") 53 | #' } 54 | find_questions <- function(regex) { 55 | if(!is.character(regex)) { 56 | stop("Argument 'regex' must be a character string!") 57 | } 58 | lesson_file_check() 59 | les <- yaml.load_file(getOption('swirlify_lesson_file_path')) 60 | les <- les[-1] 61 | matches <- sapply(les, function(question) any(str_detect(unlist(question), regex))) 62 | which(matches) 63 | } 64 | 65 | # Checks that you are working on a lesson 66 | lesson_file_check <- function(path2yaml = NULL){ 67 | while(is.null(getOption("swirlify_lesson_file_path")) || 68 | !file.exists(getOption("swirlify_lesson_file_path"))) { 69 | if(!is.null(path2yaml)) { 70 | if(file.exists(path2yaml)) { 71 | lesson_file <- path2yaml 72 | } else { 73 | stop("There is no YAML lesson file at the specified file path!") 74 | } 75 | } else { 76 | message("\nPress Enter to select the YAML file for the lesson you want to work on...") 77 | readline() 78 | lesson_file <- file.choose() 79 | } 80 | lesson_file <- normalizePath(lesson_file) 81 | set_swirlify_options(lesson_file) 82 | } 83 | # Append empty line to lesson file if necessary 84 | append_empty_line(getOption("swirlify_lesson_file_path")) 85 | } 86 | 87 | set_swirlify_options <- function(lesson_file_path) { 88 | # Get values 89 | lesson_dir_path <- normalizePath(dirname(lesson_file_path)) 90 | lesson_dir_name <- basename(lesson_dir_path) 91 | lesson_name <- gsub("_", " ", lesson_dir_name) 92 | course_dir_path <- normalizePath(dirname(lesson_dir_path)) 93 | course_dir_name <- basename(course_dir_path) 94 | course_name <- gsub("_", " ", course_dir_name) 95 | # Set options 96 | options(swirlify_lesson_file_path = lesson_file_path, 97 | swirlify_lesson_dir_path = lesson_dir_path, 98 | swirlify_lesson_dir_name = lesson_dir_name, 99 | swirlify_lesson_name = lesson_name, 100 | swirlify_course_dir_path = course_dir_path, 101 | swirlify_course_dir_name = course_dir_name, 102 | swirlify_course_name = course_name) 103 | } 104 | 105 | # Checks for empty line at end of lesson and appends one if necessary 106 | append_empty_line <- function(lesson_file_path) { 107 | les <- readLines(lesson_file_path, warn = FALSE) 108 | if(les[length(les)] != "") { 109 | # writeLines() automatically includes empty line at end of file 110 | writeLines(les, lesson_file_path) 111 | } 112 | } 113 | 114 | #' Add current lesson to course manifest 115 | #' 116 | #' The MANIFEST file located in the course directory allows you to specify 117 | #' the order in which you'd like the lessons to be listed in swirl. If the 118 | #' MANIFEST file does not exist, lessons are listed alphabetically. Invisibly 119 | #' returns the path to the MANIFEST file. 120 | #' 121 | #' @return MANIFEST file path, invisibly 122 | #' @importFrom stringr str_detect 123 | #' @export 124 | #' @examples 125 | #' \dontrun{ 126 | #' # Check what lesson you're working on, then add it to the MANIFEST 127 | #' get_current_lesson() 128 | #' add_to_manifest() 129 | #' } 130 | add_to_manifest <- function() { 131 | lesson_file_check() 132 | 133 | course_dir_path <- getOption("swirlify_course_dir_path") 134 | lesson_dir_name <- getOption("swirlify_lesson_dir_name") 135 | man_path <- file.path(course_dir_path, "MANIFEST") 136 | if(!file.exists(man_path)){ 137 | cat(lesson_dir_name, "\n", file = man_path, append = TRUE, sep = "") 138 | ensure_file_ends_with_newline(man_path) 139 | return(invisible(man_path)) 140 | } 141 | 142 | # See if it's already listed 143 | man_contents <- readLines(man_path, warn = FALSE) 144 | found <- str_detect(man_contents, lesson_dir_name) 145 | if(any(found)) { 146 | message("\nLesson '", lesson_dir_name, "' already listed in the course manifest!\n") 147 | return(invisible(man_path)) 148 | } 149 | 150 | # Make sure file ends with blank line 151 | cat(lesson_dir_name, "\n", file = man_path, append = TRUE, sep = "") 152 | ensure_file_ends_with_newline(man_path) 153 | invisible(man_path) 154 | } 155 | 156 | ensure_file_ends_with_newline <- function(path){ 157 | if(!ends_with_newline(path)) { 158 | cat("\n", file = path, append = TRUE) 159 | } 160 | } 161 | 162 | #' Find the directory where swirl courses are stored 163 | #' 164 | #' @export 165 | #' @return A string with the path to where swirl is searching for courses. 166 | swirl_courses_dir <- function(){ 167 | scd <- getOption("swirl_courses_dir") 168 | if (is.null(scd)) { 169 | file.path(find.package("swirl"), "Courses") 170 | } 171 | else { 172 | scd 173 | } 174 | } 175 | 176 | #' Replace spaces in strings with underscores 177 | #' 178 | #' Useful for creating paths to a particular swirl course, as you might want 179 | #' to do in files like \code{initLesson.R}. 180 | #' @importFrom stringr str_trim 181 | #' @param name A vector of strings. 182 | #' @export 183 | #' @return A string vector where spaces are replaced with underscores. 184 | #' @examples 185 | #' make_pathname("Developing Data Products") 186 | #' # "Developing_Data_Products" 187 | #' 188 | #' make_pathname(c("R Programming", "Exploratory Data Analysis")) 189 | #' # "R_Programming" "Exploratory_Data_Analysis" 190 | make_pathname <- function(name){ 191 | gsub(" ", "_", str_trim(name)) 192 | } -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | # This function was 'borrowed' from hadley/devtools 2 | # check whether the specified file ends with newline 3 | ends_with_newline <- function(path) { 4 | conn <- file(path, open = "rb", raw = TRUE) 5 | on.exit(close(conn)) 6 | seek(conn, where = -1, origin = "end") 7 | lastByte <- readBin(conn, "raw", n = 1) 8 | lastByte == 0x0a 9 | } 10 | 11 | # Takes a plain English name and turns it into a more proper 12 | # file/directory name 13 | 14 | #' @importFrom stringr str_trim 15 | make_pathname <- function(name) { 16 | gsub(" ", "_", str_trim(name)) 17 | } 18 | 19 | # Borrowed from hadley/devtools 20 | rule <- function(title = "") { 21 | width <- getOption("width") - nchar(title) - 1 22 | message("\n", title, paste(rep("-", width, collapse = "")), "\n") 23 | } -------------------------------------------------------------------------------- /R/wq.R: -------------------------------------------------------------------------------- 1 | #' Template for output without a question 2 | #' 3 | #' @param output Text that is displayed to the user. 4 | #' @importFrom whisker whisker.render 5 | #' @export 6 | #' @examples 7 | #' \dontrun{ 8 | #' # While writing a new lesson by hand just use: 9 | #' wq_message() 10 | #' 11 | #' # If converting from another format to a swirl course you may want to sue the 12 | #' # API: 13 | #' wq_message("Welcome to a course on the central limit theorem.") 14 | #' } 15 | wq_message <- function(output = "put your text output here"){ 16 | lesson_file_check() 17 | template <- "\n- Class: text\n Output: {{{output}}}\n" 18 | cat(whisker.render(template, list(output=output)), 19 | file=getOption("swirlify_lesson_file_path"), append=TRUE) 20 | invisible() 21 | } 22 | 23 | #' Template for multiple choice question 24 | #' 25 | #' @param output Text that is displayed to the user. 26 | #' @param answer_choices A vector of strings containing a user's choices. 27 | #' @param correct_answer A string that designates the correct answer. 28 | #' @param answer_tests An internal function from \code{swirl} for testing the 29 | #' user's choice. See \code{\link[swirl]{AnswerTests}}. 30 | #' @param hint A string that is printed to the console if the user answers this 31 | #' question incorrectly. 32 | #' @importFrom whisker whisker.render 33 | #' @export 34 | #' @examples 35 | #' \dontrun{ 36 | #' # While writing a new lesson by hand just use: 37 | #' wq_multiple() 38 | #' 39 | #' # If converting from another format to a swirl course you may want to sue the 40 | #' # API: 41 | #' wq_multiple("Which of the following is not a planet in our solar system?", 42 | #' c("Venus", "Saturn", "Pluto"), "Pluto", "omnitest(correctVal= 'Pluto')", 43 | #' "It's the smallest celestial body you can choose.") 44 | #' } 45 | wq_multiple <- function(output = "ask the multiple choice question here", 46 | answer_choices = c("ANS", "2", "3"), 47 | correct_answer = "ANS", 48 | answer_tests = "omnitest(correctVal= 'ANS')", 49 | hint = "hint"){ 50 | lesson_file_check() 51 | answer_choices <- paste(answer_choices, collapse = ";") 52 | template <- "\n- Class: mult_question 53 | Output: {{{output}}} 54 | AnswerChoices: {{{answer_choices}}} 55 | CorrectAnswer: {{{correct_answer}}} 56 | AnswerTests: {{{answer_tests}}} 57 | Hint: {{{hint}}}\n" 58 | cat(whisker.render(template, list(output = output, answer_choices = answer_choices, 59 | correct_answer = correct_answer, answer_tests = answer_tests, 60 | hint = hint)), 61 | file=getOption("swirlify_lesson_file_path"), append=TRUE) 62 | invisible() 63 | } 64 | 65 | #' Template for R command question 66 | #' 67 | #' @param output Text that is displayed to the user. 68 | #' @param correct_answer A string that designates the correct answer, in this 69 | #' case an R expression or a value. 70 | #' @param answer_tests An internal function from \code{swirl} for testing the 71 | #' user's choice. See \code{\link[swirl]{AnswerTests}}. 72 | #' @param hint A string that is printed to the console if the user answers this 73 | #' question incorrectly. 74 | #' @importFrom whisker whisker.render 75 | #' @export 76 | #' @examples 77 | #' \dontrun{ 78 | #' # While writing a new lesson by hand just use: 79 | #' wq_command() 80 | #' 81 | #' # If converting from another format to a swirl course you may want to sue the 82 | #' # API: 83 | #' wq_command("Assign the value 5 to the variable x.", 84 | #' "x <- 5", "omnitest(correctExpr='x <- 5')", "Just type: x <- 5") 85 | #' } 86 | wq_command <- function(output = "explain what the user must do here", 87 | correct_answer = "EXPR or VAL", 88 | answer_tests = "omnitest(correctExpr='EXPR', correctVal=VAL)", 89 | hint="hint"){ 90 | lesson_file_check() 91 | template <- "\n- Class: cmd_question 92 | Output: {{{output}}} 93 | CorrectAnswer: {{{correct_answer}}} 94 | AnswerTests: {{{answer_tests}}} 95 | Hint: {{{hint}}}\n" 96 | cat(whisker.render(template, list(output = output, correct_answer = correct_answer, 97 | answer_tests = answer_tests, hint = hint)), 98 | file=getOption("swirlify_lesson_file_path"), append=TRUE) 99 | invisible() 100 | } 101 | 102 | #' Template for R script question 103 | #' 104 | #' @param output Text that is displayed to the user. 105 | #' @param answer_tests An internal function from \code{swirl} for testing the 106 | #' user's choice. See \code{\link[swirl]{AnswerTests}}. 107 | #' @param hint A string that is printed to the console if the user answers this 108 | #' question incorrectly. 109 | #' @param script The name of the script template to be opened. This template 110 | #' should be in a directory called \code{scripts} located inside the lesson 111 | #' directory. 112 | #' @importFrom whisker whisker.render 113 | #' @export 114 | #' @examples 115 | #' \dontrun{ 116 | #' # While writing a new lesson by hand just use: 117 | #' wq_script() 118 | #' 119 | #' # If converting from another format to a swirl course you may want to sue the 120 | #' # API: 121 | #' wq_script("Write a function that adds three numbers.", 122 | #' "add_three_test()", "Something like: add3 <- function(x, y, z){x+y+z}", 123 | #' "add-three.R") 124 | #' } 125 | wq_script <- function(output = "explain what the user must do here", 126 | answer_tests = "custom_test_name()", 127 | hint="hint", 128 | script = "script-name.R"){ 129 | lesson_file_check() 130 | template <- "\n- Class: script 131 | Output: {{{output}}} 132 | AnswerTests: {{{answer_tests}}} 133 | Hint: {{{hint}}} 134 | Script: {{{script}}}\n" 135 | cat(whisker.render(template, list(output = output, answer_tests = answer_tests, 136 | script = script, hint = hint)), 137 | file=getOption("swirlify_lesson_file_path"), append=TRUE) 138 | invisible() 139 | } 140 | 141 | #' Template for video question 142 | #' 143 | #' The \code{url} provided for \code{video_link} can be a link to any website. 144 | #' 145 | #' @param output Text that is displayed to the user. 146 | #' @param video_link A link to a url. Please make sure to use \code{http://} or 147 | #' \code{https://}. 148 | #' @importFrom whisker whisker.render 149 | #' @export 150 | #' @examples 151 | #' \dontrun{ 152 | #' # While writing a new lesson by hand just use: 153 | #' wq_video() 154 | #' 155 | #' # If converting from another format to a swirl course you may want to sue the 156 | #' # API: 157 | #' wq_video("Now Roger will show you the basics on YouTube.", 158 | #' "https://youtu.be/dQw4w9WgXcQ") 159 | #' } 160 | wq_video <- function(output = "Would you like to watch a short video about ___?", 161 | video_link = "http://address.of.video"){ 162 | lesson_file_check() 163 | # if(grepl("^'", video_link) && grepl("'$", video_link) || 164 | # grepl('^"', video_link) && grepl('"$', video_link)){ 165 | # video_link <- paste0("'", video_link, "'") 166 | # } 167 | template <- "\n- Class: video 168 | Output: {{{output}}} 169 | VideoLink: {{{video_link}}}\n" 170 | cat(whisker.render(template, list(output = output, video_link = video_link)), 171 | file=getOption("swirlify_lesson_file_path"), append=TRUE) 172 | invisible() 173 | } 174 | 175 | #' Template for figure questions 176 | #' 177 | #' @param output Text that is displayed to the user. 178 | #' @param figure An R script that produces a figure that is displayed in the R 179 | #' plotting window. 180 | #' @param figure_type Either \code{"new"} or \code{"add"}. \code{"new"} indicates 181 | #' that a new plot should be displayed, while \code{"add"} indicates that 182 | #' features are being added to a plot that is already displayed. 183 | #' @importFrom whisker whisker.render 184 | #' @export 185 | #' @examples 186 | #' \dontrun{ 187 | #' # While writing a new lesson by hand just use: 188 | #' wq_figure() 189 | #' 190 | #' # If converting from another format to a swirl course you may want to sue the 191 | #' # API: 192 | #' wq_figure("Here we can see the curve of the normal distribution.", 193 | #' "normalplot.R", "new") 194 | #' } 195 | wq_figure <- function(output = "explain the figure here", 196 | figure = "sourcefile.R", 197 | figure_type = "new"){ 198 | lesson_file_check() 199 | if(!(figure_type %in% c("new", "add"))){ 200 | stop('figure_type must be either "new" or "add"') 201 | } 202 | template <- "\n- Class: figure 203 | Output: {{{output}}} 204 | Figure: {{{figure}}} 205 | FigureType: {{{figure_type}}}\n" 206 | cat(whisker.render(template, list(output = output, figure = figure, 207 | figure_type = figure_type)), 208 | file=getOption("swirlify_lesson_file_path"), append=TRUE) 209 | invisible() 210 | } 211 | 212 | #' Template for exact numerical question 213 | #' 214 | #' @param output Text that is displayed to the user. 215 | #' @param correct_answer The numerical answer to the question. 216 | #' @param answer_tests An internal function from \code{swirl} for testing the 217 | #' user's choice. See \code{\link[swirl]{AnswerTests}}. 218 | #' @param hint A string that is printed to the console if the user answers this 219 | #' question incorrectly. 220 | #' @importFrom whisker whisker.render 221 | #' @export 222 | #' @examples 223 | #' \dontrun{ 224 | #' # While writing a new lesson by hand just use: 225 | #' wq_numerical() 226 | #' 227 | #' # If converting from another format to a swirl course you may want to sue the 228 | #' # API: 229 | #' wq_numerical("The golden ratio is closest to what integer?", 230 | #' "2", "omnitest(correctVal=2)", "It's greater than 1 and less than 3.") 231 | #' } 232 | wq_numerical <- function(output = "explain the question here", 233 | correct_answer = "42", 234 | answer_tests = "omnitest(correctVal=42)", 235 | hint = "hint"){ 236 | lesson_file_check() 237 | template <- "\n- Class: exact_question 238 | Output: {{{output}}} 239 | CorrectAnswer: {{{correct_answer}}} 240 | AnswerTests: {{{answer_tests}}} 241 | Hint: {{{hint}}}\n" 242 | cat(whisker.render(template, list(output = output, 243 | correct_answer = correct_answer, 244 | answer_tests = answer_tests, 245 | hint = hint)), 246 | file=getOption("swirlify_lesson_file_path"), append=TRUE) 247 | invisible() 248 | } 249 | 250 | #' Template for text question 251 | #' 252 | #' @param output Text that is displayed to the user. 253 | #' @param correct_answer The answer to the question in the form of a string. 254 | #' @param answer_tests An internal function from \code{swirl} for testing the 255 | #' user's choice. See \code{\link[swirl]{AnswerTests}}. 256 | #' @param hint A string that is printed to the console if the user answers this 257 | #' question incorrectly. 258 | #' @importFrom whisker whisker.render 259 | #' @export 260 | #' @examples 261 | #' \dontrun{ 262 | #' # While writing a new lesson by hand just use: 263 | #' wq_text() 264 | #' 265 | #' # If converting from another format to a swirl course you may want to sue the 266 | #' # API: 267 | #' wq_text("Where is the Johns Hopkins Bloomberg School of Public Health located?", 268 | #' "Baltimore", "omnitest(correctVal='Baltimore')", "North of Washington, south of Philadelphia.") 269 | #' } 270 | wq_text <- function(output = "explain the question here", 271 | correct_answer = "answer", 272 | answer_tests = "omnitest(correctVal='answer')", 273 | hint = "hint"){ 274 | lesson_file_check() 275 | template <- "\n- Class: text_question 276 | Output: {{{output}}} 277 | CorrectAnswer: {{{correct_answer}}} 278 | AnswerTests: {{{answer_tests}}} 279 | Hint: {{{hint}}}\n" 280 | cat(whisker.render(template, list(output = output, correct_answer = correct_answer, 281 | answer_tests = answer_tests, hint = hint)), 282 | file=getOption("swirlify_lesson_file_path"), append=TRUE) 283 | invisible() 284 | } 285 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/swirldev/swirlify.svg?branch=master)](https://travis-ci.org/swirldev/swirlify) [![CRAN version](http://www.r-pkg.org/badges/version/swirlify)](https://cran.r-project.org/package=swirlify) 2 | 3 | # swirlify 4 | 5 | swirlify is an R package that includes tools for writing and sharing swirl 6 | courses. For more information on swirl, visit [our website](http://swirlstats.com) or our [GitHub repository](https://github.com/swirldev/swirl). 7 | 8 | ## Installation 9 | 10 | ### CRAN Version 11 | 12 | ```r 13 | install.packages("swirlify") 14 | ``` 15 | 16 | ### Development Version 17 | 18 | ```r 19 | library(devtools) 20 | install_github("swirldev/swirlify", ref = "dev") 21 | ``` 22 | 23 | ## Quick Start 24 | 25 | We highly recommend using [RStudio](https://www.rstudio.com/) for authoring 26 | swirl content. 27 | 28 | ```r 29 | library(swirlify) 30 | 31 | # Create a new lesson and a new course 32 | new_lesson("My Lesson", "My Course") 33 | 34 | # Add content to the lesson in a text editor 35 | 36 | # When you are finished writing your lesson, add it to the course manifest 37 | add_to_manifest() 38 | 39 | # Convret your course into a `.swc` file so you can share it easily. 40 | pack_course() 41 | ``` 42 | 43 | ## Documentation 44 | 45 | For extensive documentation on swirlify and tips for writing swirl courses see 46 | [the swirlify website](http://swirlstats.com/swirlify/). 47 | 48 | ## Course structure 49 | 50 | swirl courses have the following structrue: 51 | 52 | - **Courses** are directories that contain all of the files, folders, and lessons 53 | associated with the course you are developing. The name of the course directory 54 | is the name of the course. For example the name of the directory that 55 | contains Team swril's R Programming course is named `R_Programming`. 56 | - **Lessons** are directories that contain single units of instruction. The 57 | name of a lesson directory is the name of that lesson. Every lesson must at 58 | least contain a `lesson.yaml` file containing lesson content. 59 | - **Questions** are written inside of the `lesson.yaml` file in each lesson 60 | directory. Students are prompted with questions in sequential order. 61 | 62 | ## Contact 63 | 64 | If you have any questions about using swirlify don't hesitate to reach out to us: 65 | info@swirlstats.com -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Release summary 2 | 3 | This is the first attempted CRAN release of swirlify 0.5.2. 4 | 5 | ## Test environments 6 | 7 | * local macOS Sierra install, R 3.4.3 8 | * Ubuntu 14.04 (on travis-ci), R 3.4.2, R 3.3.3, R-devel. 9 | * win-builder (devel) 10 | 11 | ## R CMD check results 12 | 13 | There were no ERRORs, WARNINGs or NOTEs. -------------------------------------------------------------------------------- /inst/swirlify-app/help.md: -------------------------------------------------------------------------------- 1 | # Welcome to swirlify! 2 | 3 | For the full swirlify documentation please visit http://swirlstats.com/swirlify/. 4 | 5 | # Using this App 6 | 7 | The purpose of this app is to help you write a `lesson.yaml` file for a swirl 8 | lesson. Each of the eight kinds of questions you can ask in a swirl lesson are 9 | detailed below. Each question has a template that you can select in the 10 | drop-down menu under the "Question Type" heading in the **Editor** tab. Fill in 11 | the template and then click the **Add Question** button to have the question 12 | appear in the `lesson.yaml` file. To save the changes you've made to the 13 | `lesson.yaml` file click the **Save Lesson** button. You can jump right into 14 | your lesson by clicking the **Demo Lesson** button. 15 | 16 | # Types of Questions 17 | 18 | ## The Meta Question 19 | 20 | The first question in every `lesson.yaml` is always the meta question which 21 | contains general information about the course. Below is an example of the meta 22 | question: 23 | 24 | ``` 25 | - Class: meta 26 | Course: My Course 27 | Lesson: My Lesson 28 | Author: Dr. Jennifer Bryan 29 | Type: Standard 30 | Organization: The University of British Columbia 31 | Version: 2.5 32 | ``` 33 | The meta question will not be displayed to a student. The only fields you should 34 | modify are `Author` and `Organization` fields. 35 | 36 | ## Message Questions 37 | 38 | Message questions display a string of text in the R console for the student to 39 | read. Once the student presses enter, swirl will move on to the next question. 40 | 41 | Add a message question using `wq_message()`. 42 | 43 | Here's an example message question: 44 | 45 | ``` 46 | - Class: text 47 | Output: Welcome to my first swirl course! 48 | ``` 49 | 50 | The student will see the following in the R console: 51 | 52 | ``` 53 | | Welcome to my first swirl course! 54 | 55 | ... 56 | ``` 57 | 58 | ## Command Questions 59 | 60 | Command questions prompt the student to type an expression into the R console. 61 | 62 | - The `CorrectAnswer` is entered into the console if the student uses the `skip()` 63 | function. 64 | - The `Hint` is displayed to the student if they don't get the question right. 65 | - The `AnswerTests` determine whether or not the student answered the question 66 | correctly. See the answer testing section for more information. 67 | 68 | Add a message question using `wq_command()`. 69 | 70 | Here's an example command question: 71 | 72 | ``` 73 | - Class: cmd_question 74 | Output: Add 2 and 2 together using the addition operator. 75 | CorrectAnswer: 2 + 2 76 | AnswerTests: omnitest(correctExpr='2 + 2') 77 | Hint: Just type 2 + 2. 78 | ``` 79 | 80 | The student will see the following in the R console: 81 | 82 | ``` 83 | | Add 2 and 2 together using the addition operator. 84 | 85 | > 86 | ``` 87 | 88 | ## Multiple Choice Questions 89 | 90 | Multiple choice questions present a selection of options to the student. These 91 | options are presented in a different order every time the question is seen. 92 | 93 | - The `AnswerChoices` should be a semicolon separated string of choices that 94 | the student will have to choose from. 95 | 96 | Add a message question using `wq_multiple()`. 97 | 98 | Here's an example multiple choice question: 99 | 100 | ``` 101 | - Class: mult_question 102 | Output: What is the capital of Canada? 103 | AnswerChoices: Toronto;Montreal;Ottawa;Vancouver 104 | CorrectAnswer: Ottawa 105 | AnswerTests: omnitest(correctVal='Ottawa') 106 | Hint: This city contains the Rideau Canal. 107 | ``` 108 | 109 | The student will see the following in the R console: 110 | 111 | ``` 112 | | What is the capital of Canada? 113 | 114 | 1: Toronto 115 | 2: Montreal 116 | 3: Ottawa 117 | 4: Vancouver 118 | ``` 119 | 120 | ## Figure Questions 121 | 122 | Figure questions are designed to show graphics to the student. 123 | 124 | - `Figure` is an R script located in the lesson folder that will draw the figure. 125 | - `FigureType` must be either `new` or `add`. 126 | - `new` specifies that a new figure is being drawn. 127 | - `add` specifies that more features are being added to a figure that 128 | already has been drawn, for example if you were adding a line or a legend to 129 | a plot that had been drawn in a preceding figure question. 130 | 131 | Add a message question using `wq_figure()`. 132 | 133 | Here's an example figure question: 134 | 135 | ``` 136 | - Class: figure 137 | Output: Look at this figure! 138 | Figure: draw.R 139 | FigureType: new 140 | ``` 141 | 142 | The student will see the following in the R console: 143 | 144 | ``` 145 | | Look at this figure! 146 | 147 | ... 148 | ``` 149 | 150 | The student will also see the figure in the appropriate graphics device. 151 | 152 | ## Video/URL Questions 153 | 154 | Video/URL questions give students the choice to open a URL in their web browser. 155 | 156 | - `VideoLink` is the URL that will be opened in the student's web browser. 157 | 158 | Add a message question using `wq_video()`. 159 | 160 | Here's an example video/URL question: 161 | 162 | ``` 163 | - Class: video 164 | Output: Do you want to go to Google? 165 | VideoLink: https://www.google.com/ 166 | ``` 167 | 168 | The student will see the following in the R console: 169 | 170 | ``` 171 | | Do you want to go to Google? 172 | 173 | Yes or No? 174 | ``` 175 | 176 | ## Numerical Questions 177 | 178 | Numerical questions ask the student to type an exact number into the R console. 179 | 180 | Add a message question using `wq_numerical()`. 181 | 182 | Here's an example numerical question: 183 | 184 | ``` 185 | - Class: exact_question 186 | Output: How many of the Rings of Power were forged by the elven-smiths of 187 | Eregion? 188 | CorrectAnswer: 19 189 | AnswerTests: omnitest(correctVal = 19) 190 | Hint: Three Rings for the Elven-kings under the sky, Seven for the Dwarf-lords 191 | in their halls of stone, Nine for Mortal Men doomed to die... 192 | ``` 193 | 194 | The student will see the following in the R console: 195 | 196 | ``` 197 | | How many of the Rings of Power were forged by the elven-smiths of Eregion? 198 | 199 | > 200 | ``` 201 | 202 | ## Text Questions 203 | 204 | Text questions ask the student to type an phrase into the R console. 205 | 206 | Add a message question using `wq_text()`. 207 | 208 | Here's an example text question: 209 | 210 | ``` 211 | - Class: text_question 212 | Output: What is the name of the programming language invented by 213 | John Chambers? 214 | CorrectAnswer: 'S' 215 | AnswerTests: omnitest(correctVal = 'S') 216 | Hint: What comes after R in the alphabet? 217 | ``` 218 | 219 | The student will see the following in the R console: 220 | 221 | ``` 222 | | What is the name of the programming language invented by John Chambers? 223 | 224 | ANSWER: 225 | ``` 226 | 227 | ## Script Questions 228 | 229 | Script questions might be the hardest questions to write, however the payoff 230 | in a student's understanding of how R works is proportional. Script questions 231 | require that you write a custom answer test in order to evaluate the correctness 232 | of a script that a student has written. Writing custom answer tests is covered 233 | thoroughly in the answer testing section. 234 | 235 | - `Script` is an R script that will be opened once the student reaches this 236 | question. You should include this script in a subdirectory of the lesson folder 237 | called "scripts". You should also 238 | include in the "scripts directory" a version of this script that passes the 239 | answer test. The name of the 240 | file for the correct version of the script shoud end in `-correct.R`. So if the 241 | name of the script that the student will need to edit is `script.R` there should 242 | be a corresponding `script-correct.R`. 243 | 244 | Add a message question using `wq_script()`. 245 | 246 | Here's an example script question: 247 | 248 | ``` 249 | - Class: script 250 | Output: Write a function that calculates the nth fibonacci number. 251 | AnswerTests: test_fib() 252 | Hint: You could write this function recursively! 253 | Script: fib.R 254 | ``` 255 | 256 | The student will see the following in the R console: 257 | 258 | ``` 259 | | Write a function that calculates the nth fibonacci number. 260 | 261 | > 262 | ``` 263 | 264 | Here's an example `fib.R`: 265 | 266 | ``` 267 | # Write a function that returns the nth fibonacci number. Think about 268 | # what we just reviewed with regard to writing recurisive functions. 269 | 270 | fib <- function(n){ 271 | # Write your code here. 272 | } 273 | ``` 274 | 275 | Here's an example `fib-correct.R`: 276 | 277 | ``` 278 | # Write a function that returns the nth fibonacci number. Think about 279 | # what we just reviewed with regard to writing recurisive functions. 280 | 281 | fib <- function(n){ 282 | if(n == 0 || n == 1){ 283 | return(n) 284 | } else { 285 | return(fib(n-2) + fib(n-1)) 286 | } 287 | } 288 | ``` 289 | 290 | Here's an example `customTests.R` which includes `test_fib()`: 291 | 292 | ``` 293 | test_fib <- function() { 294 | try({ 295 | func <- get('fib', globalenv()) 296 | t1 <- identical(func(1), 1) 297 | t2 <- identical(func(20), 6765) 298 | t3 <- identical(sapply(1:12, func), c(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144)) 299 | ok <- all(t1, t2, t3) 300 | }, silent = TRUE) 301 | exists('ok') && isTRUE(ok) 302 | } 303 | ``` 304 | -------------------------------------------------------------------------------- /inst/swirlify-app/server.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | 3 | shinyServer(function(input, output, session) { 4 | observeEvent(input$make_test_mult, { 5 | updateAceEditor(session, "ace_answer_tests", 6 | paste0("omnitest(correctVal='",input$ace_correct_answer ,"')")) 7 | }) 8 | 9 | observeEvent(input$make_test_cmd, { 10 | updateAceEditor(session, "ace_answer_tests", 11 | paste0("omnitest(correctExpr='",input$ace_correct_answer ,"')")) 12 | }) 13 | 14 | observeEvent(input$demo, { 15 | cat(input$ace, file = getOption("swirlify_lesson_file_path")) 16 | stopApp(list(demo = TRUE, demo_num = input$demo_num)) 17 | }) 18 | 19 | observeEvent(input$save, { 20 | cat(input$ace, file = getOption("swirlify_lesson_file_path")) 21 | updateAceEditor(session, "ace", 22 | value = gsub("\r", "", readChar(getOption("swirlify_lesson_file_path"), 23 | file.info(getOption("swirlify_lesson_file_path"))$size)) 24 | ) 25 | }) 26 | 27 | observeEvent(input$add, { 28 | cat(input$ace, file = getOption("swirlify_lesson_file_path")) 29 | 30 | if(input$qt == "Message"){ 31 | wq_message(input$ace_output) 32 | } else if(input$qt == "Command"){ 33 | wq_command(input$ace_output, input$ace_correct_answer, 34 | input$ace_answer_tests, input$ace_hint) 35 | } else if(input$qt == "Multiple Choice"){ 36 | wq_multiple(input$ace_output, input$ace_answer_choices, 37 | input$ace_correct_answer, 38 | input$ace_answer_tests, input$ace_hint) 39 | } else if(input$qt == "Figure"){ 40 | wq_figure(input$ace_output, input$ace_fig, input$figure_type) 41 | } else if(input$qt == "Script"){ 42 | wq_script(input$ace_output, input$ace_answer_tests, 43 | input$ace_hint, input$ace_script) 44 | } else if(input$qt == "Video"){ 45 | wq_video(input$ace_output, input$ace_url) 46 | } else if(input$qt == "Numerical"){ 47 | wq_numerical(input$ace_output, 48 | input$ace_correct_answer, 49 | input$ace_answer_tests, input$ace_hint) 50 | } else if(input$qt == "Text"){ 51 | wq_text(input$ace_output, 52 | input$ace_correct_answer, 53 | input$ace_answer_tests, input$ace_hint) 54 | } 55 | 56 | updateAceEditor(session, "ace", 57 | value = gsub("\r", "", readChar(getOption("swirlify_lesson_file_path"), 58 | file.info(getOption("swirlify_lesson_file_path"))$size))) 59 | }) 60 | 61 | observeEvent(input$selectnone, { 62 | updateCheckboxGroupInput(session, inputId = "autofill", 63 | choices = c("Output", "Correct Answer", 64 | "Answer Tests", "Hint", 65 | "Answer Choices", "Figure", 66 | "Script", "URL")) 67 | }) 68 | 69 | observeEvent(input$selectall, { 70 | updateCheckboxGroupInput(session, inputId = "autofill", 71 | choices = c("Output", "Correct Answer", 72 | "Answer Tests", "Hint", 73 | "Answer Choices", "Figure", 74 | "Script", "URL"), 75 | selected = c("Output", "Correct Answer", 76 | "Answer Tests", "Hint", 77 | "Answer Choices", "Figure", 78 | "Script", "URL")) 79 | }) 80 | 81 | output$ui <- renderUI({ 82 | if (is.null(input$qt)) 83 | return() 84 | 85 | boilerplate <- c("Output" = "Type your text output here.", 86 | "Correct Answer" = "Expression or Value", 87 | "Answer Tests" = "omnitest(correctExpr='EXPR', correctVal=VAL)", 88 | "Hint" = "Type a hint.", 89 | "Answer Choices" = "ANS;2;3", 90 | "Figure" = "sourcefile.R", 91 | "Script" = "script-name.R", 92 | "URL" = "http://address.of.video") 93 | 94 | for(i in names(boilerplate)){ 95 | if(!(i %in% input$autofill)){ 96 | boilerplate[i] <- "" 97 | } 98 | } 99 | 100 | switch(input$qt, 101 | "Message" = list(h4("Output"), 102 | aceEditor("ace_output", 103 | height = "120px", 104 | value = boilerplate["Output"], 105 | debounce = 500) 106 | ), 107 | 108 | "Command" = list(h4("Output"), 109 | aceEditor("ace_output", 110 | height = "120px", 111 | value = boilerplate["Output"], 112 | debounce = 500), 113 | h4("Correct Answer"), 114 | aceEditor("ace_correct_answer", 115 | height = "30px", 116 | value = boilerplate["Correct Answer"], 117 | debounce = 500), 118 | h4("Answer Tests"), 119 | aceEditor("ace_answer_tests", 120 | height = "30px", 121 | value = boilerplate["Answer Tests"], 122 | debounce = 500), 123 | actionButton("make_test_cmd", "Make Answer Test from Correct Answer"), 124 | h4("Hint"), 125 | aceEditor("ace_hint", 126 | height = "120px", 127 | value = boilerplate["Hint"], 128 | debounce = 500) 129 | ), 130 | 131 | "Multiple Choice" = list(h4("Output"), 132 | aceEditor("ace_output", 133 | height = "120px", 134 | value = boilerplate["Output"], 135 | debounce = 500), 136 | h4("Answer Choices"), 137 | aceEditor("ace_answer_choices", 138 | height = "30px", 139 | value = boilerplate["Answer Choices"], 140 | debounce = 500), 141 | h4("Correct Answer"), 142 | aceEditor("ace_correct_answer", 143 | height = "30px", 144 | value = boilerplate["Correct Answer"], 145 | debounce = 500), 146 | h4("Answer Tests"), 147 | aceEditor("ace_answer_tests", 148 | height = "30px", 149 | value = boilerplate["Answer Tests"], 150 | debounce = 500), 151 | actionButton("make_test_mult", "Make Answer Test from Correct Answer"), 152 | h4("Hint"), 153 | aceEditor("ace_hint", 154 | height = "120px", 155 | value = boilerplate["Hint"], 156 | debounce = 500) 157 | ), 158 | 159 | "Numerical" = list(h4("Output"), 160 | aceEditor("ace_output", 161 | height = "120px", 162 | value = boilerplate["Output"], 163 | debounce = 500), 164 | h4("Correct Answer"), 165 | aceEditor("ace_correct_answer", 166 | height = "30px", 167 | value = boilerplate["Correct Answer"], 168 | debounce = 500), 169 | h4("Hint"), 170 | aceEditor("ace_hint", 171 | height = "120px", 172 | value = boilerplate["Hint"], 173 | debounce = 500) 174 | ), 175 | 176 | "Text" = list(h4("Output"), 177 | aceEditor("ace_output", 178 | height = "120px", 179 | value = boilerplate["Output"], 180 | debounce = 500), 181 | h4("Correct Answer"), 182 | aceEditor("ace_correct_answer", 183 | height = "30px", 184 | value = boilerplate["Correct Answer"], 185 | debounce = 500), 186 | h4("Hint"), 187 | aceEditor("ace_hint", 188 | height = "120px", 189 | value = boilerplate["Hint"], 190 | debounce = 500) 191 | ), 192 | 193 | "Video" = list(h4("Output"), 194 | aceEditor("ace_output", 195 | height = "120px", 196 | value = boilerplate["Output"], 197 | debounce = 500), 198 | h4("URL"), 199 | aceEditor("ace_url", 200 | height = "30px", 201 | value = boilerplate["URL"], 202 | debounce = 500) 203 | ), 204 | 205 | "Figure" = list( 206 | h4("Output"), 207 | aceEditor("ace_output", 208 | height = "120px", 209 | value = boilerplate["Output"], 210 | debounce = 500), 211 | h4("Figure"), 212 | aceEditor("ace_fig", 213 | height = "30px", 214 | value = boilerplate["Figure"], 215 | debounce = 500), 216 | selectInput("figure_type", "Figure type:", 217 | choices = c("New" = "new", "Additional" = "add")) 218 | ), 219 | 220 | "Script" = list( 221 | h4("Output"), 222 | aceEditor("ace_output", 223 | height = "120px", 224 | value = boilerplate["Output"], 225 | debounce = 500), 226 | h4("Answer Tests"), 227 | aceEditor("ace_answer_tests", 228 | height = "30px", 229 | value = boilerplate["Answer Tests"], 230 | debounce = 500), 231 | h4("Hint"), 232 | aceEditor("ace_hint", 233 | height = "120px", 234 | value = boilerplate["Hint"], 235 | debounce = 500), 236 | h4("Script"), 237 | aceEditor("ace_script", 238 | height = "30px", 239 | value = boilerplate["Script"], 240 | debounce = 500) 241 | ) 242 | ) 243 | }) 244 | }) -------------------------------------------------------------------------------- /inst/swirlify-app/ui.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinyAce) 3 | 4 | shinyUI(navbarPage("swirlify 0.5", 5 | tabPanel("Editor", 6 | fluidRow( 7 | column(6, 8 | selectInput("qt", label = h3("Question Type"), 9 | choices = c("Message", "Command", "Multiple Choice", 10 | "Figure", "Script", "Video", "Numerical", 11 | "Text"), 12 | selected = "Message"), 13 | uiOutput("ui"), 14 | actionButton("add", "Add Question") 15 | ), 16 | column(6, 17 | aceEditor("ace", value = gsub("\r", "", readChar(getOption("swirlify_lesson_file_path"), 18 | file.info(getOption("swirlify_lesson_file_path"))$size)), mode = "yaml", 19 | debounce = 100), 20 | actionButton("save", "Save Lesson"), 21 | actionButton("demo", "Demo Lesson"), 22 | br(), 23 | h4("Question Number where Demo will Start"), 24 | numericInput("demo_num", NULL, 25 | value = 1, min = 1, step = 1, width = "50px") 26 | ) 27 | ) 28 | ), 29 | tabPanel("Options", 30 | h2("Auto-fill"), 31 | checkboxGroupInput("autofill", label = NULL, 32 | c("Output", "Correct Answer", 33 | "Answer Tests", "Hint", 34 | "Answer Choices", "Figure", 35 | "Script", "URL"), 36 | c("Output", "Correct Answer", 37 | "Answer Tests", "Hint", 38 | "Answer Choices", "Figure", 39 | "Script", "URL") 40 | ), 41 | actionButton("selectall", "Select All"), 42 | actionButton("selectnone", "Select None") 43 | ), 44 | tabPanel("Help", 45 | column(2), 46 | column(8,includeMarkdown("help.md")), 47 | column(2)) 48 | )) -------------------------------------------------------------------------------- /inst/test/correct_responses.csv: -------------------------------------------------------------------------------- 1 | Timestamp,Submission 2 | 2016/06/06 11:21:49 AM AST,InVzZXIiLCJjb3Vyc2VfbmFtZSIsImxlc3Nvbl9uYW1lIiwicXVlc3Rpb25fbnVtYmVyIiwiY29ycmVjdCIsImF0dGVtcHQiLCJza2lwcGVkIiwiZGF0ZXRpbWUiCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2NDE5LjM5ODEzCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2NDIzLjAxMzg1CiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODM5LjYxNzIyCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODQ2LjAzMTcxCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODY3Ljg1MzQ3CiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODk1LjkzMjk5Cg== 3 | 2016/06/06 11:27:29 AM AST,InVzZXIiLCJjb3Vyc2VfbmFtZSIsImxlc3Nvbl9uYW1lIiwicXVlc3Rpb25fbnVtYmVyIiwiY29ycmVjdCIsImF0dGVtcHQiLCJza2lwcGVkIiwiZGF0ZXRpbWUiCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2NDE5LjM5ODEzCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2NDIzLjAxMzg1CiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODM5LjYxNzIyCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODQ2LjAzMTcxCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODY3Ljg1MzQ3CiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODk1LjkzMjk5Cg== 4 | 2016/06/06 11:28:18 AM AST,InVzZXIiLCJjb3Vyc2VfbmFtZSIsImxlc3Nvbl9uYW1lIiwicXVlc3Rpb25fbnVtYmVyIiwiY29ycmVjdCIsImF0dGVtcHQiLCJza2lwcGVkIiwiZGF0ZXRpbWUiCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2NDE5LjM5ODEzCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2NDIzLjAxMzg1CiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODM5LjYxNzIyCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODQ2LjAzMTcxCiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODY3Ljg1MzQ3CiJzZWFuIiwiR29vZ2xlIEZvcm1zIENvdXJzZSIsIkxlc3NvbiAxIiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODk1LjkzMjk5Cg== 5 | -------------------------------------------------------------------------------- /inst/test/diacritics_greek_cyrillic.csv: -------------------------------------------------------------------------------- 1 | Timestamp,Submission 2 | 2016/06/06 11:21:49 AM AST,InVzZXIiLCJjb3Vyc2VfbmFtZSIsImxlc3Nvbl9uYW1lIiwicXVlc3Rpb25fbnVtYmVyIiwiY29ycmVjdCIsImF0dGVtcHQiLCJza2lwcGVkIiwiZGF0ZXRpbWUiCiJTw6vDo8WEIMOHcm/DuMWhxbwiLCLOo8+EzrHPhM65z4PPhM65zrrOriIsItCS0LLQtdC00LXQvdC40LUiLDIsVFJVRSwxLEZBTFNFLDE0NjUyMjY0MTkuMzk4MTMKIlPDq8OjxYQgw4dyb8O4xaHFvCIsIs6jz4TOsc+EzrnPg8+EzrnOus6uIiwi0JLQstC10LTQtdC90LjQtSIsMyxUUlVFLDEsRkFMU0UsMTQ2NTIyNjQyMy4wMTM4NQoiU8Orw6PFhCDDh3Jvw7jFocW8IiwizqPPhM6xz4TOuc+Dz4TOuc66zq4iLCLQktCy0LXQtNC10L3QuNC1IiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODM5LjYxNzIyCiJTw6vDo8WEIMOHcm/DuMWhxbwiLCLOo8+EzrHPhM65z4PPhM65zrrOriIsItCS0LLQtdC00LXQvdC40LUiLDMsVFJVRSwxLEZBTFNFLDE0NjUyMjY4NDYuMDMxNzEKIlPDq8OjxYQgw4dyb8O4xaHFvCIsIs6jz4TOsc+EzrnPg8+EzrnOus6uIiwi0JLQstC10LTQtdC90LjQtSIsMixUUlVFLDEsRkFMU0UsMTQ2NTIyNjg2Ny44NTM0NwoiU8Orw6PFhCDDh3Jvw7jFocW8IiwizqPPhM6xz4TOuc+Dz4TOuc66zq4iLCLQktCy0LXQtNC10L3QuNC1IiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODk1LjkzMjk5Cg== 3 | 2016/06/06 11:27:29 AM AST,InVzZXIiLCJjb3Vyc2VfbmFtZSIsImxlc3Nvbl9uYW1lIiwicXVlc3Rpb25fbnVtYmVyIiwiY29ycmVjdCIsImF0dGVtcHQiLCJza2lwcGVkIiwiZGF0ZXRpbWUiCiJTw6vDo8WEIMOHcm/DuMWhxbwiLCLOo8+EzrHPhM65z4PPhM65zrrOriIsItCS0LLQtdC00LXQvdC40LUiLDIsVFJVRSwxLEZBTFNFLDE0NjUyMjY0MTkuMzk4MTMKIlPDq8OjxYQgw4dyb8O4xaHFvCIsIs6jz4TOsc+EzrnPg8+EzrnOus6uIiwi0JLQstC10LTQtdC90LjQtSIsMyxUUlVFLDEsRkFMU0UsMTQ2NTIyNjQyMy4wMTM4NQoiU8Orw6PFhCDDh3Jvw7jFocW8IiwizqPPhM6xz4TOuc+Dz4TOuc66zq4iLCLQktCy0LXQtNC10L3QuNC1IiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODM5LjYxNzIyCiJTw6vDo8WEIMOHcm/DuMWhxbwiLCLOo8+EzrHPhM65z4PPhM65zrrOriIsItCS0LLQtdC00LXQvdC40LUiLDMsVFJVRSwxLEZBTFNFLDE0NjUyMjY4NDYuMDMxNzEKIlPDq8OjxYQgw4dyb8O4xaHFvCIsIs6jz4TOsc+EzrnPg8+EzrnOus6uIiwi0JLQstC10LTQtdC90LjQtSIsMixUUlVFLDEsRkFMU0UsMTQ2NTIyNjg2Ny44NTM0NwoiU8Orw6PFhCDDh3Jvw7jFocW8IiwizqPPhM6xz4TOuc+Dz4TOuc66zq4iLCLQktCy0LXQtNC10L3QuNC1IiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODk1LjkzMjk5Cg== 4 | 2016/06/06 11:28:18 AM AST,InVzZXIiLCJjb3Vyc2VfbmFtZSIsImxlc3Nvbl9uYW1lIiwicXVlc3Rpb25fbnVtYmVyIiwiY29ycmVjdCIsImF0dGVtcHQiLCJza2lwcGVkIiwiZGF0ZXRpbWUiCiJTw6vDo8WEIMOHcm/DuMWhxbwiLCLOo8+EzrHPhM65z4PPhM65zrrOriIsItCS0LLQtdC00LXQvdC40LUiLDIsVFJVRSwxLEZBTFNFLDE0NjUyMjY0MTkuMzk4MTMKIlPDq8OjxYQgw4dyb8O4xaHFvCIsIs6jz4TOsc+EzrnPg8+EzrnOus6uIiwi0JLQstC10LTQtdC90LjQtSIsMyxUUlVFLDEsRkFMU0UsMTQ2NTIyNjQyMy4wMTM4NQoiU8Orw6PFhCDDh3Jvw7jFocW8IiwizqPPhM6xz4TOuc+Dz4TOuc66zq4iLCLQktCy0LXQtNC10L3QuNC1IiwyLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODM5LjYxNzIyCiJTw6vDo8WEIMOHcm/DuMWhxbwiLCLOo8+EzrHPhM65z4PPhM65zrrOriIsItCS0LLQtdC00LXQvdC40LUiLDMsVFJVRSwxLEZBTFNFLDE0NjUyMjY4NDYuMDMxNzEKIlPDq8OjxYQgw4dyb8O4xaHFvCIsIs6jz4TOsc+EzrnPg8+EzrnOus6uIiwi0JLQstC10LTQtdC90LjQtSIsMixUUlVFLDEsRkFMU0UsMTQ2NTIyNjg2Ny44NTM0NwoiU8Orw6PFhCDDh3Jvw7jFocW8IiwizqPPhM6xz4TOuc+Dz4TOuc66zq4iLCLQktCy0LXQtNC10L3QuNC1IiwzLFRSVUUsMSxGQUxTRSwxNDY1MjI2ODk1LjkzMjk5Cg== 5 | -------------------------------------------------------------------------------- /man/add_license.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_license.R 3 | \name{add_license} 4 | \alias{add_license} 5 | \title{Add a LICENSE.txt file to your course} 6 | \usage{ 7 | add_license(author, year = format(Sys.Date(), "\%Y"), 8 | open_source_content = TRUE, content_license = "CC BY 4.0", 9 | open_source_data = TRUE, data_license = "CC0", open_source_code = TRUE, 10 | code_license = "MIT") 11 | } 12 | \arguments{ 13 | \item{author}{The author of the course. This can be an organization.} 14 | 15 | \item{year}{The year the course was written.} 16 | 17 | \item{open_source_content}{If \code{TRUE} a Creative Commons content license 18 | will be included pertaining to the content of your course.} 19 | 20 | \item{content_license}{Specify which Creative Commons license you would like 21 | to use for the content of your course. This must be equal to one of the 22 | following: \code{"CC BY 4.0"}, \code{"CC BY-SA 4.0"}, \code{"CC BY-ND 4.0"}, 23 | \code{"CC BY-NC 4.0"}, \code{"CC BY-NC-SA 4.0"}, \code{"CC BY-NC-ND 4.0"}, 24 | or \code{"CC0"}.} 25 | 26 | \item{open_source_data}{If \code{TRUE} a Creative Commons content license 27 | will be included pertaining to the data distributed with your course.} 28 | 29 | \item{data_license}{Currently this value must be equal to \code{"CC0"}, but 30 | in the future it may be able to be other values.} 31 | 32 | \item{open_source_code}{If \code{TRUE} a free software license 33 | will be included pertaining to the software included in your course.} 34 | 35 | \item{code_license}{Specify which open source software license you would like 36 | to use for the content of your course. This must be equal to one of the 37 | following: \code{"MIT"}, \code{"GPL3"}, \code{"CC0"}.} 38 | } 39 | \description{ 40 | Licensing your course is important if you want to share your course. For more 41 | information see \url{https://github.com/swirldev/swirlify/wiki/Licensing-Your-Course}. 42 | For more information about Creative Commons licenses see \url{http://creativecommons.org/licenses/}. 43 | For more information about software licenses see \url{http://www.gnu.org/licenses/license-list.en.html}. 44 | } 45 | \examples{ 46 | \dontrun{ 47 | 48 | # Add a license with simple open source options 49 | add_license("Team swirl") 50 | 51 | # Add a license so that derivative works are shared alike 52 | add_license("Team swirl", content_license = "CC BY-SA 4.0", code_license ="GPL3") 53 | 54 | # Add a license that reserves all of the author's rights 55 | add_license("Team Bizzaro swirl", open_source_content = FALSE, 56 | open_source_data = FALSE, 57 | open_source_code = FALSE) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /man/add_to_manifest.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tools.R 3 | \name{add_to_manifest} 4 | \alias{add_to_manifest} 5 | \title{Add current lesson to course manifest} 6 | \usage{ 7 | add_to_manifest() 8 | } 9 | \value{ 10 | MANIFEST file path, invisibly 11 | } 12 | \description{ 13 | The MANIFEST file located in the course directory allows you to specify 14 | the order in which you'd like the lessons to be listed in swirl. If the 15 | MANIFEST file does not exist, lessons are listed alphabetically. Invisibly 16 | returns the path to the MANIFEST file. 17 | } 18 | \examples{ 19 | \dontrun{ 20 | # Check what lesson you're working on, then add it to the MANIFEST 21 | get_current_lesson() 22 | add_to_manifest() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /man/count_questions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tools.R 3 | \name{count_questions} 4 | \alias{count_questions} 5 | \title{Count number of questions in current lesson} 6 | \usage{ 7 | count_questions() 8 | } 9 | \value{ 10 | Number of questions as an integer, invisibly 11 | } 12 | \description{ 13 | Returns and prints the number of questions in the current lesson. 14 | } 15 | \examples{ 16 | \dontrun{ 17 | count_questions() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /man/demo_lesson.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demo_lesson.R 3 | \name{demo_lesson} 4 | \alias{demo_lesson} 5 | \title{Demo the current lesson in swirl} 6 | \usage{ 7 | demo_lesson(from = NULL, to = NULL) 8 | } 9 | \arguments{ 10 | \item{from}{Question number to begin with. Defaults to beginning of lesson.} 11 | 12 | \item{to}{Question number to end with. Defaults to end of lesson.} 13 | } 14 | \description{ 15 | Jump right in to the current swirl lesson without needing 16 | to navigate swirl's menus. It's also possible to jump 17 | into the middle of a lesson. 18 | } 19 | \examples{ 20 | \dontrun{ 21 | # Demo current lesson from beginning through the end 22 | demo_lesson() 23 | # Demo current lesson from question 5 through the end 24 | demo_lesson(5) 25 | # Demo current lesson from question 8 through question 14 26 | demo_lesson(8, 14) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /man/find_questions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tools.R 3 | \name{find_questions} 4 | \alias{find_questions} 5 | \title{Get question numbers for any questions matching a regular expression} 6 | \usage{ 7 | find_questions(regex) 8 | } 9 | \arguments{ 10 | \item{regex}{The regular expression to look for in the lesson. 11 | This gets passed along to \code{stringr::str_detect}, so the 12 | same rules apply. See \code{\link[stringr]{str_detect}}.} 13 | } 14 | \value{ 15 | A vector of integers representing question numbers. 16 | } 17 | \description{ 18 | Get question numbers for any questions matching a regular expression 19 | } 20 | \examples{ 21 | \dontrun{ 22 | set_lesson() 23 | find_questions("plot") 24 | find_questions("which") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /man/get_current_lesson.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tools.R 3 | \name{get_current_lesson} 4 | \alias{get_current_lesson} 5 | \title{See what lesson you are currently working on} 6 | \usage{ 7 | get_current_lesson() 8 | } 9 | \description{ 10 | Prints the current lesson and course that you are working on to the console 11 | } 12 | \examples{ 13 | \dontrun{ 14 | get_current_lesson() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /man/google_form_decode.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/google_form_decode.R 3 | \name{google_form_decode} 4 | \alias{google_form_decode} 5 | \title{Decode Student's Submissions from Google Forms} 6 | \usage{ 7 | google_form_decode(path = file.choose()) 8 | } 9 | \arguments{ 10 | \item{path}{The path to a \code{csv} file downloaded from Google Forms or 11 | Google Sheets which contains student's encoded responses.} 12 | } 13 | \value{ 14 | A data frame containing each student's results. 15 | } 16 | \description{ 17 | Decode Student's Submissions from Google Forms 18 | } 19 | \examples{ 20 | \dontrun{ 21 | 22 | # Choose the csv file yourself 23 | google_form_decode() 24 | 25 | # Explicity specify the path 26 | google_form_decode("~/Desktop/My_Course.csv") 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /man/lesson_to_html.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lesson_to_html.R 3 | \name{lesson_to_html} 4 | \alias{lesson_to_html} 5 | \title{Turn a swirl lesson into a pretty webpage} 6 | \usage{ 7 | lesson_to_html(dest_dir = NULL, open_html = FALSE, keep_rmd = FALSE, 8 | quiet = FALSE, install_course = TRUE) 9 | } 10 | \arguments{ 11 | \item{dest_dir}{Destination directory (i.e. where to put the output files). 12 | If not set, default is the directory which contains the course directory.} 13 | 14 | \item{open_html}{Should the HTML file produced be opened in your browser? 15 | Default is \code{FALSE}.} 16 | 17 | \item{keep_rmd}{Should the Rmd file be kept after the HTML is 18 | is produced? Default is \code{FALSE}.} 19 | 20 | \item{quiet}{Should the Rmd rendering output be silenced? Default 21 | is \code{FALSE}.} 22 | 23 | \item{install_course}{Should the course 24 | be installed? Default is \code{TRUE}.} 25 | } 26 | \description{ 27 | Create an easily shareable HTML version of your swirl lesson. This function 28 | detects the lesson you are working on 29 | automatically via \code{getOption('swirlify_lesson_file_path')}, 30 | converts it to R Markdown (Rmd), then generates a stylized HTML 31 | document and opens it in your default browser. To prevent clutter, 32 | the Rmd files are not kept by default, but they can be kept 33 | by setting \code{keep_rmd = TRUE}. 34 | } 35 | \details{ 36 | The output is formatted to be a readable, standalone tutorial. 37 | This means that information contained in the swirl lesson such as 38 | answer tests and hints are excluded from the Rmd/HTML output. 39 | } 40 | -------------------------------------------------------------------------------- /man/make_pathname.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tools.R 3 | \name{make_pathname} 4 | \alias{make_pathname} 5 | \title{Replace spaces in strings with underscores} 6 | \usage{ 7 | make_pathname(name) 8 | } 9 | \arguments{ 10 | \item{name}{A vector of strings.} 11 | } 12 | \value{ 13 | A string vector where spaces are replaced with underscores. 14 | } 15 | \description{ 16 | Useful for creating paths to a particular swirl course, as you might want 17 | to do in files like \code{initLesson.R}. 18 | } 19 | \examples{ 20 | make_pathname("Developing Data Products") 21 | # "Developing_Data_Products" 22 | 23 | make_pathname(c("R Programming", "Exploratory Data Analysis")) 24 | # "R_Programming" "Exploratory_Data_Analysis" 25 | } 26 | -------------------------------------------------------------------------------- /man/new_lesson.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/new_lesson.R 3 | \name{new_lesson} 4 | \alias{new_lesson} 5 | \title{Create new lesson in the YAML format.} 6 | \usage{ 7 | new_lesson(lesson_name, course_name, open_lesson = TRUE) 8 | } 9 | \arguments{ 10 | \item{lesson_name}{The name of the lesson.} 11 | 12 | \item{course_name}{The name of the course.} 13 | 14 | \item{open_lesson}{If \code{TRUE} the new \code{lesson.yaml} file will open 15 | for editing via \code{\link[utils]{file.edit}}. The default value is \code{TRUE}.} 16 | } 17 | \description{ 18 | Creates a new lesson and possibly a new course in your working directory. If 19 | the name you provide for \code{course_name} is not a directory in your 20 | working directory, then a new course directory will be created. However if 21 | you've already started a course with the name you provide for \code{course_name} 22 | and that course is in your working directory, then a new lesson will be created 23 | inside of that course with the name you provide for \code{lesson_name}. 24 | } 25 | \examples{ 26 | \dontrun{ 27 | # Make sure you have your working directory set to where you want to 28 | # create the course. 29 | setwd(file.path("~", "Developer", "swirl_courses")) 30 | 31 | # Make a new course with a new lesson 32 | new_lesson("How to use pnorm", "Normal Distribution Functions in R") 33 | 34 | # Make a new lesson in an existing course 35 | new_lesson("How to use qnorm", "Normal Distribution Functions in R") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /man/pack_course.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pack.R 3 | \name{pack_course} 4 | \alias{pack_course} 5 | \title{Create an \code{.swc} file of the course you are working on} 6 | \usage{ 7 | pack_course(export_path = NULL) 8 | } 9 | \arguments{ 10 | \item{export_path}{Optional, full path to the directory you want the swirl 11 | course file to be exported to. If not specified, then the file will appear 12 | in the same directory as the course directory.} 13 | } 14 | \value{ 15 | A string, the path to the new \code{.swc} file, invisibly. 16 | } 17 | \description{ 18 | "Pack" the course you are working on into a single compressed file that is 19 | easy to share. Invisibly returns the path to the \code{.swc} file. 20 | } 21 | \examples{ 22 | \dontrun{ 23 | # Set any lesson in the course you want to pack 24 | set_lesson() 25 | 26 | # Pack the course 27 | pack_course() 28 | 29 | # Export the .swc file to a directory that you specify 30 | pack_course(file.path("~", "Desktop")) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /man/set_lesson.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/set_lesson.R 3 | \name{set_lesson} 4 | \alias{set_lesson} 5 | \title{Select an existing lesson to work on} 6 | \usage{ 7 | set_lesson(path_to_yaml = NULL, open_lesson = TRUE, silent = FALSE) 8 | } 9 | \arguments{ 10 | \item{path_to_yaml}{Optional, full path to YAML lesson file. If not 11 | specified, then you will be prompted to select file interactively.} 12 | 13 | \item{open_lesson}{Should the lesson be opened automatically? 14 | Default is \code{TRUE}.} 15 | 16 | \item{silent}{Should the lesson be set silently? Default is 17 | \code{FALSE}.} 18 | } 19 | \description{ 20 | Choose a lesson to work on with swirlify by specifying the path to the 21 | \code{lesson.yaml} file or interactively choose a lesson file. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | # Set the lesson interactively 26 | set_lesson() 27 | 28 | # You can also specify the path to the \\code{yaml} file you wish to work on. 29 | set_lesson(file.path("~", "R_Programming", "Functions", "lesson.yaml")) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /man/swirl_courses_dir.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tools.R 3 | \name{swirl_courses_dir} 4 | \alias{swirl_courses_dir} 5 | \title{Find the directory where swirl courses are stored} 6 | \usage{ 7 | swirl_courses_dir() 8 | } 9 | \value{ 10 | A string with the path to where swirl is searching for courses. 11 | } 12 | \description{ 13 | Find the directory where swirl courses are stored 14 | } 15 | -------------------------------------------------------------------------------- /man/swirlify.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/swirlify.R 3 | \name{swirlify} 4 | \alias{swirlify} 5 | \title{Launch a Shiny application for writing swirl lessons} 6 | \usage{ 7 | swirlify(lesson_name = NULL, course_name = NULL) 8 | } 9 | \arguments{ 10 | \item{lesson_name}{The name of the new lesson you want to 11 | create. The default value is \code{NULL}. If you've 12 | already selected a lesson to work on using \code{\link{set_lesson}} 13 | then you do not need to provide a value for this argument.} 14 | 15 | \item{course_name}{The name of the new course you want to 16 | create. The default value is \code{NULL}. If you've 17 | already selected a course to work on using \code{\link{set_lesson}} 18 | then you do not need to provide a value for this argument.} 19 | } 20 | \description{ 21 | This function launches a user interface for writing 22 | swirl lessons. 23 | } 24 | \examples{ 25 | \dontrun{ 26 | 27 | # Set lesson beforehand 28 | set_lesson() 29 | swirlify() 30 | 31 | # Start a new lesson in your current directory 32 | swirlify("Lesson 1", "My Course") 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /man/test_course.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/test_lesson.R 3 | \name{test_course} 4 | \alias{test_course} 5 | \title{Run tests on a course} 6 | \usage{ 7 | test_course() 8 | } 9 | \description{ 10 | Run basic tests on all questions in the lessons listed in the \code{MANIFEST}. 11 | See \code{\link{add_to_manifest}} for information about the \code{MANIFEST} 12 | file. 13 | } 14 | \examples{ 15 | \dontrun{ 16 | # To test a course, set any lesson in that course as the current lesson 17 | set_lesson() 18 | 19 | # Run tests on every lesson in that course listed in the MANIFEST 20 | test_course() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /man/test_lesson.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/test_lesson.R 3 | \name{test_lesson} 4 | \alias{test_lesson} 5 | \title{Run tests on a lesson} 6 | \usage{ 7 | test_lesson() 8 | } 9 | \description{ 10 | Run basic tests on all questions in the current lesson. 11 | } 12 | \examples{ 13 | \dontrun{ 14 | # Set a lesson interactively 15 | set_lesson() 16 | 17 | # Run tests on that lesson 18 | test_lesson() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /man/testit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demo_lesson.R 3 | \name{testit} 4 | \alias{testit} 5 | \title{(Deprecated)} 6 | \usage{ 7 | testit(from = NULL, to = NULL) 8 | } 9 | \arguments{ 10 | \item{from}{Question number to begin with. Defaults to beginning of lesson.} 11 | 12 | \item{to}{Question number to end with. Defaults to end of lesson.} 13 | } 14 | \description{ 15 | This function is deprecated. Please use \code{demo_lesson} 16 | instead. 17 | } 18 | -------------------------------------------------------------------------------- /man/unpack_course.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pack.R 3 | \name{unpack_course} 4 | \alias{unpack_course} 5 | \title{Unpack an \code{.swc} file into a swirl course} 6 | \usage{ 7 | unpack_course(file_path = file.choose(), export_path = dirname(file_path)) 8 | } 9 | \arguments{ 10 | \item{file_path}{Optional, full path to the \code{.swc} file you wish to unpack. 11 | If not specified, you will be prompted to choose a file interactively.} 12 | 13 | \item{export_path}{Optional, full path to the directory where the swirl course 14 | should be exported. If not specified, the course will appear in the same 15 | directory as the \code{.swc} file.} 16 | } 17 | \value{ 18 | A string, the path to the unpacked course directory, invisibly. 19 | } 20 | \description{ 21 | Invisibly returns the path to the unpacked course directory. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | # Unpack a course and interactively choose a .swc file 26 | unpack_course() 27 | 28 | # Unpack a course where the .swc file is explicitly specified 29 | unpack_course(file.path("~", "Desktop", "R_Programming.swc")) 30 | 31 | # Unpack a course and specify where the .swc file is located and where the 32 | # course should be exported. 33 | unpack_course(file.path("~", "Desktop", "R_Programming.swc"), 34 | file.path("~", "Developer", "swirl")) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /man/wq_command.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wq.R 3 | \name{wq_command} 4 | \alias{wq_command} 5 | \title{Template for R command question} 6 | \usage{ 7 | wq_command(output = "explain what the user must do here", 8 | correct_answer = "EXPR or VAL", 9 | answer_tests = "omnitest(correctExpr='EXPR', correctVal=VAL)", 10 | hint = "hint") 11 | } 12 | \arguments{ 13 | \item{output}{Text that is displayed to the user.} 14 | 15 | \item{correct_answer}{A string that designates the correct answer, in this 16 | case an R expression or a value.} 17 | 18 | \item{answer_tests}{An internal function from \code{swirl} for testing the 19 | user's choice. See \code{\link[swirl]{AnswerTests}}.} 20 | 21 | \item{hint}{A string that is printed to the console if the user answers this 22 | question incorrectly.} 23 | } 24 | \description{ 25 | Template for R command question 26 | } 27 | \examples{ 28 | \dontrun{ 29 | # While writing a new lesson by hand just use: 30 | wq_command() 31 | 32 | # If converting from another format to a swirl course you may want to sue the 33 | # API: 34 | wq_command("Assign the value 5 to the variable x.", 35 | "x <- 5", "omnitest(correctExpr='x <- 5')", "Just type: x <- 5") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /man/wq_figure.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wq.R 3 | \name{wq_figure} 4 | \alias{wq_figure} 5 | \title{Template for figure questions} 6 | \usage{ 7 | wq_figure(output = "explain the figure here", figure = "sourcefile.R", 8 | figure_type = "new") 9 | } 10 | \arguments{ 11 | \item{output}{Text that is displayed to the user.} 12 | 13 | \item{figure}{An R script that produces a figure that is displayed in the R 14 | plotting window.} 15 | 16 | \item{figure_type}{Either \code{"new"} or \code{"add"}. \code{"new"} indicates 17 | that a new plot should be displayed, while \code{"add"} indicates that 18 | features are being added to a plot that is already displayed.} 19 | } 20 | \description{ 21 | Template for figure questions 22 | } 23 | \examples{ 24 | \dontrun{ 25 | # While writing a new lesson by hand just use: 26 | wq_figure() 27 | 28 | # If converting from another format to a swirl course you may want to sue the 29 | # API: 30 | wq_figure("Here we can see the curve of the normal distribution.", 31 | "normalplot.R", "new") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /man/wq_message.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wq.R 3 | \name{wq_message} 4 | \alias{wq_message} 5 | \title{Template for output without a question} 6 | \usage{ 7 | wq_message(output = "put your text output here") 8 | } 9 | \arguments{ 10 | \item{output}{Text that is displayed to the user.} 11 | } 12 | \description{ 13 | Template for output without a question 14 | } 15 | \examples{ 16 | \dontrun{ 17 | # While writing a new lesson by hand just use: 18 | wq_message() 19 | 20 | # If converting from another format to a swirl course you may want to sue the 21 | # API: 22 | wq_message("Welcome to a course on the central limit theorem.") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /man/wq_multiple.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wq.R 3 | \name{wq_multiple} 4 | \alias{wq_multiple} 5 | \title{Template for multiple choice question} 6 | \usage{ 7 | wq_multiple(output = "ask the multiple choice question here", 8 | answer_choices = c("ANS", "2", "3"), correct_answer = "ANS", 9 | answer_tests = "omnitest(correctVal= 'ANS')", hint = "hint") 10 | } 11 | \arguments{ 12 | \item{output}{Text that is displayed to the user.} 13 | 14 | \item{answer_choices}{A vector of strings containing a user's choices.} 15 | 16 | \item{correct_answer}{A string that designates the correct answer.} 17 | 18 | \item{answer_tests}{An internal function from \code{swirl} for testing the 19 | user's choice. See \code{\link[swirl]{AnswerTests}}.} 20 | 21 | \item{hint}{A string that is printed to the console if the user answers this 22 | question incorrectly.} 23 | } 24 | \description{ 25 | Template for multiple choice question 26 | } 27 | \examples{ 28 | \dontrun{ 29 | # While writing a new lesson by hand just use: 30 | wq_multiple() 31 | 32 | # If converting from another format to a swirl course you may want to sue the 33 | # API: 34 | wq_multiple("Which of the following is not a planet in our solar system?", 35 | c("Venus", "Saturn", "Pluto"), "Pluto", "omnitest(correctVal= 'Pluto')", 36 | "It's the smallest celestial body you can choose.") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /man/wq_numerical.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wq.R 3 | \name{wq_numerical} 4 | \alias{wq_numerical} 5 | \title{Template for exact numerical question} 6 | \usage{ 7 | wq_numerical(output = "explain the question here", correct_answer = "42", 8 | answer_tests = "omnitest(correctVal=42)", hint = "hint") 9 | } 10 | \arguments{ 11 | \item{output}{Text that is displayed to the user.} 12 | 13 | \item{correct_answer}{The numerical answer to the question.} 14 | 15 | \item{answer_tests}{An internal function from \code{swirl} for testing the 16 | user's choice. See \code{\link[swirl]{AnswerTests}}.} 17 | 18 | \item{hint}{A string that is printed to the console if the user answers this 19 | question incorrectly.} 20 | } 21 | \description{ 22 | Template for exact numerical question 23 | } 24 | \examples{ 25 | \dontrun{ 26 | # While writing a new lesson by hand just use: 27 | wq_numerical() 28 | 29 | # If converting from another format to a swirl course you may want to sue the 30 | # API: 31 | wq_numerical("The golden ratio is closest to what integer?", 32 | "2", "omnitest(correctVal=2)", "It's greater than 1 and less than 3.") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /man/wq_script.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wq.R 3 | \name{wq_script} 4 | \alias{wq_script} 5 | \title{Template for R script question} 6 | \usage{ 7 | wq_script(output = "explain what the user must do here", 8 | answer_tests = "custom_test_name()", hint = "hint", 9 | script = "script-name.R") 10 | } 11 | \arguments{ 12 | \item{output}{Text that is displayed to the user.} 13 | 14 | \item{answer_tests}{An internal function from \code{swirl} for testing the 15 | user's choice. See \code{\link[swirl]{AnswerTests}}.} 16 | 17 | \item{hint}{A string that is printed to the console if the user answers this 18 | question incorrectly.} 19 | 20 | \item{script}{The name of the script template to be opened. This template 21 | should be in a directory called \code{scripts} located inside the lesson 22 | directory.} 23 | } 24 | \description{ 25 | Template for R script question 26 | } 27 | \examples{ 28 | \dontrun{ 29 | # While writing a new lesson by hand just use: 30 | wq_script() 31 | 32 | # If converting from another format to a swirl course you may want to sue the 33 | # API: 34 | wq_script("Write a function that adds three numbers.", 35 | "add_three_test()", "Something like: add3 <- function(x, y, z){x+y+z}", 36 | "add-three.R") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /man/wq_text.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wq.R 3 | \name{wq_text} 4 | \alias{wq_text} 5 | \title{Template for text question} 6 | \usage{ 7 | wq_text(output = "explain the question here", correct_answer = "answer", 8 | answer_tests = "omnitest(correctVal='answer')", hint = "hint") 9 | } 10 | \arguments{ 11 | \item{output}{Text that is displayed to the user.} 12 | 13 | \item{correct_answer}{The answer to the question in the form of a string.} 14 | 15 | \item{answer_tests}{An internal function from \code{swirl} for testing the 16 | user's choice. See \code{\link[swirl]{AnswerTests}}.} 17 | 18 | \item{hint}{A string that is printed to the console if the user answers this 19 | question incorrectly.} 20 | } 21 | \description{ 22 | Template for text question 23 | } 24 | \examples{ 25 | \dontrun{ 26 | # While writing a new lesson by hand just use: 27 | wq_text() 28 | 29 | # If converting from another format to a swirl course you may want to sue the 30 | # API: 31 | wq_text("Where is the Johns Hopkins Bloomberg School of Public Health located?", 32 | "Baltimore", "omnitest(correctVal='Baltimore')", "North of Washington, south of Philadelphia.") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /man/wq_video.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wq.R 3 | \name{wq_video} 4 | \alias{wq_video} 5 | \title{Template for video question} 6 | \usage{ 7 | wq_video(output = "Would you like to watch a short video about ___?", 8 | video_link = "http://address.of.video") 9 | } 10 | \arguments{ 11 | \item{output}{Text that is displayed to the user.} 12 | 13 | \item{video_link}{A link to a url. Please make sure to use \code{http://} or 14 | \code{https://}.} 15 | } 16 | \description{ 17 | The \code{url} provided for \code{video_link} can be a link to any website. 18 | } 19 | \examples{ 20 | \dontrun{ 21 | # While writing a new lesson by hand just use: 22 | wq_video() 23 | 24 | # If converting from another format to a swirl course you may want to sue the 25 | # API: 26 | wq_video("Now Roger will show you the basics on YouTube.", 27 | "https://youtu.be/dQw4w9WgXcQ") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /revdep/check.R: -------------------------------------------------------------------------------- 1 | library("devtools") 2 | 3 | res <- revdep_check() 4 | revdep_check_save_summary() 5 | -------------------------------------------------------------------------------- /revdep/checks.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swirldev/swirlify/beeec41a12f3609294dcc1385cbe56190eb45b95/revdep/checks.rds -------------------------------------------------------------------------------- /swirlify.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | BuildType: Package 16 | PackageUseDevtools: Yes 17 | PackageInstallArgs: --no-multiarch --with-keep.source 18 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(swirlify) 3 | library(swirl) 4 | 5 | test_check("swirlify") 6 | -------------------------------------------------------------------------------- /tests/testthat/test_google_form_decode.R: -------------------------------------------------------------------------------- 1 | context("Test google_form_decode()") 2 | 3 | correct_responses <- data.frame( 4 | user = rep("sean", 6), 5 | course_name = rep("Google Forms Course", 6), 6 | lesson_name = rep("Lesson 1", 6), 7 | question_number = rep(2:3, 3), 8 | correct = rep(TRUE, 6), 9 | attempt = rep(1, 6), 10 | skipped = rep(FALSE, 6), 11 | datetime = c(1465226419.39813, 1465226423.01385, 1465226839.61722, 12 | 1465226846.03171, 1465226867.85347, 1465226895.93299), 13 | stringsAsFactors = FALSE 14 | ) 15 | 16 | diacritics_greek_cyrillic <- data.frame( 17 | user = rep("Sëãń Çroøšż", 6), 18 | course_name = rep("Στατιστική", 6), 19 | lesson_name = rep("Введение", 6), 20 | question_number = rep(2:3, 3), 21 | correct = rep(TRUE, 6), 22 | attempt = rep(1, 6), 23 | skipped = rep(FALSE, 6), 24 | datetime = c(1465226419.39813, 1465226423.01385, 1465226839.61722, 25 | 1465226846.03171, 1465226867.85347, 1465226895.93299), 26 | stringsAsFactors = FALSE 27 | ) 28 | 29 | cr_path <- system.file(file.path("test", "correct_responses.csv"), 30 | package = "swirlify") 31 | dgc_path <- system.file(file.path("test", "diacritics_greek_cyrillic.csv"), 32 | package = "swirlify") 33 | cr <- google_form_decode(cr_path) 34 | dgc <- google_form_decode(dgc_path) 35 | 36 | test_that("Google Forms can be Properly Decoded.", { 37 | expect_equal(cr, rbind(correct_responses, 38 | correct_responses, 39 | correct_responses)) 40 | }) 41 | 42 | test_that("Google Forms with diacritics can be Properly Decoded.", { 43 | skip_on_os("windows") 44 | expect_equal(dgc, rbind(diacritics_greek_cyrillic, 45 | diacritics_greek_cyrillic, 46 | diacritics_greek_cyrillic)) 47 | }) 48 | 49 | # # Google form encode 50 | # library(base64enc) 51 | # library(tibble) 52 | # library(readr) 53 | # 54 | # cr_file <- tempfile() 55 | # dgc_file <- tempfile() 56 | # 57 | # write.csv(correct_responses, file = cr_file, row.names = FALSE) 58 | # write.csv(diacritics_greek_cyrillic, file = dgc_file, row.names = FALSE) 59 | # 60 | # encoded_cr <- base64encode(cr_file) 61 | # encoded_dgc <- base64encode(dgc_file) 62 | # 63 | # write_csv( 64 | # tribble( 65 | # ~Timestamp, ~Submission, 66 | # "2016/06/06 11:21:49 AM AST", encoded_cr, 67 | # "2016/06/06 11:27:29 AM AST", encoded_cr, 68 | # "2016/06/06 11:28:18 AM AST", encoded_cr 69 | # ), "inst/test/correct_responses.csv" 70 | # ) 71 | # 72 | # write_csv( 73 | # tribble( 74 | # ~Timestamp, ~Submission, 75 | # "2016/06/06 11:21:49 AM AST", encoded_dgc, 76 | # "2016/06/06 11:27:29 AM AST", encoded_dgc, 77 | # "2016/06/06 11:28:18 AM AST", encoded_dgc 78 | # ), "inst/test/diacritics_greek_cyrillic.csv" 79 | # ) -------------------------------------------------------------------------------- /tests/testthat/test_pack.R: -------------------------------------------------------------------------------- 1 | library(digest) 2 | context("Pack/unpack a course") 3 | 4 | path <- tempdir() 5 | oldwd <- getwd() 6 | 7 | setwd(path) 8 | set.seed(4) #increase with version number 9 | 10 | let_num <- c(letters, LETTERS, rep(0:9, 2)) 11 | for(i in 1:5){ 12 | lessn <- paste(c(sample(let_num, 4, replace = TRUE), " ", 13 | sample(let_num, 5, replace = TRUE)), collapse = "") 14 | new_lesson(paste(lessn, i), "New Course", open_lesson = FALSE) 15 | wq_ <- list("wq_command()", 16 | "wq_figure()", 17 | "wq_message()", 18 | "wq_multiple()", 19 | "wq_numerical()", 20 | "wq_script()", 21 | "wq_text()", 22 | "wq_video()") 23 | qs <- sample(1:8, 20, replace = TRUE) 24 | for(j in qs){ 25 | eval(parse(text = wq_[[j]])) 26 | } 27 | add_to_manifest() 28 | } 29 | 30 | add_license("test", year = 0000) 31 | swc_path <- pack_course() 32 | setwd(oldwd) 33 | 34 | original_files <- list.files(file.path(path, "New_Course"), full.names = TRUE, recursive = TRUE) 35 | original_file_digests <- sapply(original_files, digest, algo = "sha1", file = TRUE) 36 | unlink(file.path(path, "New_Course"), recursive = TRUE, force = TRUE) 37 | 38 | unpacked_course_path <- unpack_course(swc_path) 39 | unpacked_files <- list.files(unpacked_course_path, full.names = TRUE, recursive = TRUE) 40 | unpacked_file_digests <- sapply(unpacked_files, digest, algo = "sha1", file = TRUE) 41 | 42 | test_that("Courses are the same before packing and after unpacking.", { 43 | expect_true(all(original_file_digests %in% unpacked_file_digests)) 44 | }) 45 | 46 | unlink(file.path(path, "New_Course"), recursive = TRUE, force = TRUE) 47 | unlink(file.path(path, "New_Course.swc"), recursive = TRUE, force = TRUE) 48 | -------------------------------------------------------------------------------- /tests/testthat/test_test_lesson.R: -------------------------------------------------------------------------------- 1 | context("Test test_lesson()") 2 | 3 | path <- tempdir() 4 | oldwd <- getwd() 5 | 6 | setwd(path) 7 | 8 | new_lesson("Test Lesson", "Test Course", open_lesson = FALSE) 9 | wq_command("0", "0", "0", "0") 10 | wq_figure("0", "0.R", "new") 11 | writeLines("plot(0, 0)", file.path(getOption("swirlify_lesson_dir_path"), "0.R")) 12 | wq_message("0") 13 | wq_multiple("0", c("0", "1", "2"), "0", "0", "0") 14 | wq_numerical("0", "0", "0", "0") 15 | wq_script("0", "0", "0", "s.R") 16 | scripts <- file.path(getOption("swirlify_lesson_dir_path"), "scripts") 17 | dir.create(scripts, showWarnings = FALSE) 18 | writeLines("test", file.path(scripts, "s.R")) 19 | writeLines("test", file.path(scripts, "s-correct.R")) 20 | wq_text("0", "0", "0", "0") 21 | wq_video("0", "0") 22 | 23 | message_output <- capture_messages(test_lesson()) 24 | 25 | correct_output <- c("##### Begin testing: Test Lesson #####\n", 26 | "##### End testing: Test Lesson #####\n\n") 27 | 28 | test_that("test_lesson() passes with well-formed lesson", { 29 | expect_identical(message_output, correct_output) 30 | }) 31 | 32 | setwd(oldwd) 33 | unlink(getOption("swirlify_course_dir_path"), recursive = TRUE, force = TRUE) 34 | -------------------------------------------------------------------------------- /tests/testthat/test_tools.R: -------------------------------------------------------------------------------- 1 | context("Test tools") 2 | 3 | temp_dir <- tempdir() 4 | 5 | test_that("swirl_courses_dir() is working", { 6 | swirl_options(swirl_courses_dir = temp_dir) 7 | expect_equal(temp_dir, swirl_courses_dir()) 8 | 9 | swirl_options(swirl_courses_dir = NULL) 10 | expect_true(!is.null(swirl_courses_dir())) 11 | }) 12 | 13 | test_that("make_pathname() is working", { 14 | expect_equal(make_pathname("Developing Data Products"), 15 | "Developing_Data_Products") 16 | expect_equal(make_pathname(c("R Programming", "Exploratory Data Analysis")), 17 | c("R_Programming", "Exploratory_Data_Analysis")) 18 | }) --------------------------------------------------------------------------------