├── .Rbuildignore
├── .gitattributes
├── .gitignore
├── .lintr
├── .travis.yml
├── DESCRIPTION
├── LICENSE
├── LICENSE.md
├── NAMESPACE
├── R
├── assertions.R
├── bucket_problem.R
├── css.R
├── expectations.R
├── incrementor.R
├── methods.R
├── parsons-package.R
├── parsons_problem.R
├── question_bucket.R
├── question_parsons.R
└── wrap_labels.R
├── README.Rmd
├── README.md
├── _pkgdown.yml
├── codecov.yml
├── inst
├── examples
│ ├── example_parsons.R
│ └── example_question_parsons.R
├── htmlwidgets
│ └── plugins
│ │ └── parsons
│ │ ├── _colors.scss
│ │ ├── bucket_list.css
│ │ ├── bucket_list.scss
│ │ ├── parsons.css
│ │ ├── parsons.scss
│ │ ├── parsons_old.css
│ │ ├── rank_list.css
│ │ └── rank_list.scss
├── shiny-examples
│ ├── problem_type
│ │ └── app.R
│ └── simple
│ │ └── app.R
└── tutorials
│ ├── bucket
│ └── tutorial_bucket.Rmd
│ └── parsons
│ ├── tutorial_parsons.Rmd
│ └── tutorial_parsons.html
├── logo.svg
├── man-roxygen
└── question_parsons_description.R
├── man
├── bucket_problem.Rd
├── expectations.Rd
├── figures
│ ├── logo.svg
│ ├── parsons-logo.png
│ ├── parsons_app.gif
│ ├── parsons_app_initial.png
│ └── parsons_app_submit.png
├── parsons.Rd
├── parsons_problem.Rd
├── question_bucket.Rd
├── question_parsons.Rd
├── question_ui_try_again.bucket_question.Rd
└── question_ui_try_again.parsons_question.Rd
├── parsons.Rproj
├── pkgdown
└── favicon
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-152x152.png
│ ├── apple-touch-icon-180x180.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-76x76.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ └── favicon.ico
├── scripts
├── compile_css.R
├── deploy_apps.R
├── deploy_on_travis.R
└── load_all_shim.R
├── tests
├── testthat.R
└── testthat
│ ├── test-expectations.R
│ ├── test-learnr-question_parsons.R
│ └── test-parsons.R
└── vignettes
├── .gitignore
└── introduction.Rmd
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^.*\.Rproj$
2 | ^\.Rproj\.user$
3 | ^LICENSE\.md$
4 | ^Readme\.md$
5 | ^README\.Rmd$
6 | ^\.travis\.yml$
7 | ^\.lintr$
8 | ^doc$
9 | ^Meta$
10 | ^scripts/
11 | ^man-roxygen/
12 | ^docs$
13 | ^_pkgdown\.yml$
14 | ^pkgdown$
15 | ^codecov\.yml$
16 | ^README\.html$
17 | ^logo.svg$
18 | ^inst/tutorials/parsons/rsconnect
19 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.html linguist-documentation=true
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | docs/
5 | Meta
6 | README.html
7 | doc
8 | inst/tutorials/parsons/rsconnect
9 | inst/doc
10 | inst/tutorials/*/*.html
11 |
--------------------------------------------------------------------------------
/.lintr:
--------------------------------------------------------------------------------
1 | linters: with_defaults(
2 | line_length_linter(100),
3 | NULL
4 | )
5 | exclusions: list(
6 | "tests/testthat/test-htmltools.R",
7 | )
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r
2 |
3 | language: R
4 | cache: packages
5 |
6 | notifications:
7 | email:
8 | on_success: change
9 | on_failure: change
10 |
11 | matrix:
12 | include:
13 | - name: "Release"
14 | r: release
15 | after_success:
16 | - Rscript -e 'covr::codecov()'
17 | - find . -name testthat.Rout | xargs cat
18 | after_failure:
19 | - find . -name testthat.Rout | xargs cat
20 | before_cache: Rscript -e 'remotes::install_cran("pkgdown")'
21 | deploy:
22 | provider: script
23 | script: Rscript -e 'pkgdown::deploy_site_github()'
24 | skip_cleanup: true
25 |
26 | - name: "Old Release"
27 | r: oldrel
28 |
29 | - name: "Devel"
30 | r: devel
31 |
32 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: parsons
2 | Type: Package
3 | Title: Create Parsons Programming Problems in 'learnr` Tutorials
4 | Version: 0.0.3.9000
5 | Date: 2019-08-19
6 | Authors@R: c(
7 | person("Andrie", "de Vries", role = c("cre", "aut"), email = "apdevries@gmail.com"),
8 | person("Barret", "Schloerke", role = "ctb", email = "barret@rstudio.com")
9 | )
10 | Description: Create Parsons Programming Problems in 'learnr' Tutorials.
11 | URL: https://github.com/rstudio/parsons
12 | BugReports: https://github.com/rstudio/parsons/issues
13 | License: MIT + file LICENSE
14 | LazyData: TRUE
15 | Imports:
16 | sortable (>= 0.3.1),
17 | htmltools,
18 | htmlwidgets,
19 | learnr (>= 0.9.2),
20 | shiny,
21 | assertthat,
22 | utils,
23 | rlang
24 | Suggests:
25 | knitr,
26 | testthat (>= 2.1.0),
27 | withr,
28 | rmarkdown
29 | Remotes:
30 | rstudio/sortable,
31 | rstudio/learnr
32 | VignetteBuilder: knitr
33 | RoxygenNote: 6.1.1
34 | Roxygen: list(markdown = TRUE)
35 | Encoding: UTF-8
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | YEAR: 2019
2 | COPYRIGHT HOLDER: Andrie de Vries
3 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright (c) 2019 Andrie de Vries
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | S3method(fail_if,"function")
4 | S3method(fail_if,character)
5 | S3method(fail_if,default)
6 | S3method(pass_if,"function")
7 | S3method(pass_if,character)
8 | S3method(pass_if,default)
9 | S3method(print,bucket)
10 | S3method(print,parsons)
11 | S3method(question_is_correct,bucket_question)
12 | S3method(question_is_correct,parsons_question)
13 | S3method(question_ui_completed,bucket_question)
14 | S3method(question_ui_completed,parsons_question)
15 | S3method(question_ui_initialize,bucket_question)
16 | S3method(question_ui_initialize,parsons_question)
17 | S3method(question_ui_try_again,bucket_question)
18 | S3method(question_ui_try_again,parsons_question)
19 | export(bucket_problem)
20 | export(contains_all)
21 | export(contains_any)
22 | export(fail_if)
23 | export(message_if)
24 | export(parsons_problem)
25 | export(pass_if)
26 | export(question_bucket)
27 | export(question_parsons)
28 | importFrom(assertthat,"on_failure<-")
29 | importFrom(assertthat,assert_that)
30 | importFrom(assertthat,is.string)
31 | importFrom(learnr,disable_all_tags)
32 | importFrom(learnr,mark_as)
33 | importFrom(learnr,question_is_correct)
34 | importFrom(learnr,question_is_valid)
35 | importFrom(learnr,question_ui_completed)
36 | importFrom(learnr,question_ui_initialize)
37 | importFrom(learnr,question_ui_try_again)
38 | importFrom(rlang,list2)
39 | importFrom(sortable,add_rank_list)
40 | importFrom(sortable,bucket_list)
41 | importFrom(sortable,is_sortable_options)
42 | importFrom(sortable,sortable_options)
43 | importFrom(utils,modifyList)
44 |
--------------------------------------------------------------------------------
/R/assertions.R:
--------------------------------------------------------------------------------
1 | #' @importFrom assertthat assert_that on_failure<-
2 |
3 | is_input_id <- function(x) {
4 | is.null(x) || (is.character(x) && length(x) == 1 && !is.na(x))
5 | }
6 |
7 | on_failure(is_input_id) <- function(call, env) {
8 | paste0(deparse(call$x), " is not a string (length 1 character)")
9 | }
10 |
11 | # --------------------------------------------------------------------
12 |
13 |
14 | is_header <- function(x) {
15 | is.null(x) || (is.character(x) && length(x) == 1 && !is.na(x))
16 | }
17 |
18 | on_failure(is_header) <- function(call, env) {
19 | paste0(deparse(call$x), " is not a string (length 1 character)")
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/R/bucket_problem.R:
--------------------------------------------------------------------------------
1 | #' @importFrom sortable sortable_options is_sortable_options
2 | #' @importFrom sortable add_rank_list bucket_list
3 | #'
4 | #' @importFrom learnr question_ui_initialize
5 | #' @importFrom learnr question_ui_completed
6 | #' @importFrom learnr question_is_valid
7 | #' @importFrom learnr question_is_correct
8 | #'
9 | #' @importFrom learnr question_ui_try_again
10 | #'
11 | #' @importFrom learnr mark_as
12 | #' @importFrom learnr disable_all_tags
13 | NULL
14 |
15 |
16 |
17 | #' Create a bucket problem (experimental).
18 | #'
19 | #' This function implements the bucket problem, as exposed by
20 | #' [question_bucket()]. Most users will only use this function inside a
21 | #' `learnr` tutorial, so please see the documentation at [question_bucket()]
22 | #'
23 | #'
24 | #' @inheritParams sortable::rank_list
25 | #' @inheritParams sortable::bucket_list
26 | #'
27 | #' @param initial Vector with initial values for problem (to appear in left
28 | #' column). Note: this must be a super-set of all answers.
29 | #'
30 | #' @param text Vector of headings for each column.
31 | #'
32 | #' @param input_id Character vector of `input_id` to pass (individually) to
33 | #' [rank_list()].
34 | #'
35 | #'
36 | #' @inheritParams sortable::bucket_list
37 | #'
38 | #' @export
39 | # @example inst/examples/example_bucket.R
40 | #'
41 | #'
42 | # @examples
43 | # ## Example of a shiny app
44 | # if (interactive()) {
45 | # app <- system.file("shiny-examples/bucket_app.R", package = "parsons")
46 | # shiny::runApp(app)
47 | # }
48 | bucket_problem <- function(
49 | initial,
50 | text = c("Drag from here", "Construct your solution here"),
51 | header = NULL,
52 | input_id,
53 | group_name,
54 | class = "default-sortable default-bucket",
55 | options = sortable_options(
56 | # emptyInsertThreshold = 150
57 | ),
58 | orientation = c("horizontal", "vertical")
59 | ) {
60 | if (is.character(initial)) initial <- list(initial, NULL)
61 | assert_that(is_sortable_options(options))
62 | if (missing(group_name) || is.null(group_name)) {
63 | group_name <- increment_bucket_group()
64 | }
65 | if (missing(input_id) || is.null(input_id)) {
66 | input_id <- paste0(group_name, c("_1", "_2"))
67 | }
68 |
69 | if (length(input_id) == 1) {
70 | input_id <- list(paste0(input_id, "_1"), input_id)
71 | }
72 | orientation <- match.arg(orientation)
73 |
74 | z <- bucket_list(
75 | header = header,
76 | class = class,
77 | add_rank_list(text = text[1],
78 | labels = initial[[1]],
79 | input_id = input_id[[1]],
80 | options = options
81 | ),
82 | add_rank_list(text = text[2],
83 | labels = initial[[2]],
84 | input_id = input_id[[2]],
85 | options = options
86 | ),
87 | group_name = group_name,
88 | orientation = orientation
89 | )
90 |
91 | min_height <- 50 * (length(initial[[1]]) + 1)
92 | z <- htmltools::tagList(
93 | z,
94 | bucket_dependencies(),
95 | htmltools::tags$style(
96 | sprintf(
97 | htmltools::HTML(".rank-list-container {min-height: %spx;}"),
98 | min_height
99 | )
100 | )
101 | )
102 | as.bucket_problem(z)
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/R/css.R:
--------------------------------------------------------------------------------
1 | css_dependency <- function(name, files) {
2 | list(
3 | htmltools::htmlDependency(
4 | name,
5 | version = utils::packageVersion("parsons"),
6 | src = "htmlwidgets/plugins/parsons",
7 | package = "parsons",
8 | stylesheet = files,
9 | all_files = FALSE
10 | )
11 | )
12 | }
13 |
14 |
15 |
16 | parsons_dependencies <- function() {
17 | css_dependency("parsons", "parsons.css")
18 | }
19 |
20 | bucket_dependencies <- function() {
21 | css_dependency("parsons", "parsons.css")
22 | }
23 |
--------------------------------------------------------------------------------
/R/expectations.R:
--------------------------------------------------------------------------------
1 | is_answer <- function(x){
2 | inherits(x, "tutorial_question_answer")
3 | }
4 |
5 | is.expectation_pass <- function(x) {
6 | inherits(x, "parsons_expectation_pass")
7 | }
8 |
9 | is.expectation_fail <- function(x) {
10 | inherits(x, "parsons_expectation_fail")
11 | }
12 |
13 |
14 | expectation_pass <- function (fun, message = "failure") {
15 | structure(
16 | class = c("parsons_expectation_pass", "list"),
17 | list(fun = fun, message = message)
18 | )
19 | }
20 |
21 | expectation_fail <- function (fun, message = "failure") {
22 | structure(
23 | class = c("parsons_expectation_fail", "list"),
24 | list(fun = fun, message = message)
25 | )
26 | }
27 |
28 |
29 | eval_expectation <- function(exp, answer_list) {
30 | isTRUE(exp$fun(answer_list))
31 | }
32 |
33 |
34 |
35 | # pass_if -----------------------------------------------------------------
36 |
37 | #' Add expectations to a parsons problem.
38 | #'
39 | #' @param f One of:
40 | #' * A character vector, indicating an exact match
41 | #' * A function of the function `function(x){...}` that evaluates to TRUE or FALSE
42 | #' * A function of the form `~ .`, as used by the tidy evaluation, e.g. in [purrr::map]
43 | #' @param message Message to display if `fun` evaluates to TRUE
44 | #'
45 | #' @rdname expectations
46 | #'
47 | #' @export
48 | pass_if <- function(f, message = NULL){
49 | UseMethod("pass_if", f)
50 | }
51 |
52 | #' @export
53 | pass_if.character <- function(f, message = NULL) {
54 | learnr::answer(f, correct = TRUE, message = message)
55 | }
56 |
57 |
58 |
59 | #' @export
60 | pass_if.default <- function(f, message = NULL) {
61 | expectation_pass(
62 | fun = rlang::as_function(f),
63 | message = message
64 | )
65 | }
66 |
67 | #' @export
68 | pass_if.function <- function(f, message = NULL) {
69 | expectation_pass(
70 | fun = rlang::as_function(f),
71 | message = message
72 | )
73 | }
74 |
75 |
76 | # fail_if -----------------------------------------------------------------
77 |
78 | #' @rdname expectations
79 | #' @export
80 | fail_if <- function(f, message = "Incorrect"){
81 | UseMethod("fail_if", f)
82 | }
83 |
84 | #' @export
85 | fail_if.character <- function(f, message = "Incorrect") {
86 | learnr::answer(f, correct = FALSE, message = message)
87 | }
88 |
89 | #' @export
90 | fail_if.default <- function(f, message = "Incorrect") {
91 | expectation_fail(
92 | fun = rlang::as_function(f),
93 | message = message
94 | )
95 | }
96 |
97 | #' @export
98 | fail_if.function <- function(f, message = "Incorrect") {
99 | expectation_fail(
100 | fun = rlang::as_function(f),
101 | message = message
102 | )
103 | }
104 |
105 |
106 |
107 | # message_if --------------------------------------------------------------
108 |
109 |
110 | #' @export
111 | #' @rdname expectations
112 | message_if <- function(f) {
113 | f
114 | }
115 |
116 | eval_message <- function(f, answer_list) {
117 | UseMethod("eval_message", f)
118 | }
119 |
120 | eval_message.character <- function(f, answer_list){
121 | f
122 | }
123 |
124 | eval_message.default <- function(f, answer_list) {
125 | # browser()
126 | idx <- rlang::as_function(f)(answer_list)
127 | paste(answer_list[idx], collapse = ", ")
128 | }
129 |
130 |
131 | # -------------------------------------------------------------------------
132 |
133 | # all_of <- function(.x){
134 | # force(.x)
135 | # input <- .x
136 | # function(x)length(input) == length(x) && all(sort(input) == sort(x))
137 | # }
138 |
139 | # pass_if_all_of <- function(zz, message = NULL) {
140 | # f <- function(x)~identical(sort(x), sort(zz))
141 | # expectation_pass(
142 | # fun = rlang::as_function(f),
143 | # message = message
144 | # )
145 | # }
146 |
147 | #' @rdname expectations
148 | #' @param ... Combined into an answer list
149 | #' @param x Answer to test
150 | #' @export
151 | contains_all <- function(x, ...){
152 | y <- unlist(rlang::list2(...))
153 | identical(sort(x), sort(y))
154 | }
155 |
156 | #' @rdname expectations
157 | #' @export
158 | contains_any <- function(x, ...){
159 | y <- unlist(rlang::list2(...))
160 | any(x %in% y)
161 | }
162 |
163 |
--------------------------------------------------------------------------------
/R/incrementor.R:
--------------------------------------------------------------------------------
1 | incrementor <- function(prefix = "increment_"){
2 | i <- 0
3 | function(){
4 | i <<- i + 1
5 | paste0(prefix, i)
6 | }
7 | }
8 |
9 | increment_parsons <- incrementor("parsons_list_id_")
10 | increment_parsons_group <- incrementor("parsons_group_")
11 |
12 |
13 | increment_bucket <- incrementor("bucket_list_id_")
14 | increment_bucket_group <- incrementor("bucket_group_")
15 |
--------------------------------------------------------------------------------
/R/methods.R:
--------------------------------------------------------------------------------
1 | is_parsons_problem <- function(x) {
2 | inherits(x, "parsons_problem")
3 | }
4 |
5 | as.parsons_problem <- function(x){
6 | class(x) <- c("parsons_problem", class(x))
7 | x
8 | }
9 |
10 | #' @export
11 | print.parsons <- function(x, ...){
12 | htmltools::html_print(x)
13 | }
14 |
15 |
16 |
17 |
18 | is_bucket_problem <- function(x) {
19 | inherits(x, "bucket_problem")
20 | }
21 |
22 | as.bucket_problem <- function(x){
23 | class(x) <- c("bucket_problem", class(x))
24 | x
25 | }
26 |
27 | #' @export
28 | print.bucket <- function(x, ...){
29 | htmltools::html_print(x)
30 | }
31 |
--------------------------------------------------------------------------------
/R/parsons-package.R:
--------------------------------------------------------------------------------
1 | #' @importFrom assertthat assert_that is.string
2 | #' @name parsons
3 | #' @aliases parsons-package parsons
4 | "_PACKAGE"
5 |
6 |
7 | # The following block is used by usethis to automatically manage
8 | # roxygen namespace tags. Modify with care!
9 | ## usethis namespace: start
10 | ## usethis namespace: end
11 | NULL
12 |
13 |
14 |
--------------------------------------------------------------------------------
/R/parsons_problem.R:
--------------------------------------------------------------------------------
1 | #' @importFrom sortable sortable_options is_sortable_options
2 | #' @importFrom sortable add_rank_list bucket_list
3 | #'
4 | #' @importFrom learnr question_ui_initialize
5 | #' @importFrom learnr question_ui_completed
6 | #' @importFrom learnr question_is_valid
7 | #' @importFrom learnr question_is_correct
8 | #'
9 | #' @importFrom learnr question_ui_try_again
10 | #'
11 | #' @importFrom learnr mark_as
12 | #' @importFrom learnr disable_all_tags
13 | NULL
14 |
15 |
16 |
17 | #' Create a parsons problem (experimental).
18 | #'
19 | #' This function implements the parsons problem, as exposed by
20 | #' [question_parsons()]. Most users will only use this function inside a
21 | #' `learnr` tutorial, so please see the documentation at [question_parsons()]
22 | #'
23 | #'
24 | #' @inheritParams sortable::rank_list
25 | #' @inheritParams sortable::bucket_list
26 | #'
27 | #' @param initial Vector with initial values for problem (to appear in left
28 | #' column). Note: this must be a super-set of all answers.
29 | #'
30 | #' @param text Vector of headings for each column.
31 | #'
32 | #' @param input_id Character vector of `input_id` to pass (individually) to
33 | #' [rank_list()].
34 | #'
35 | #'
36 | #' @param problem_type One of `base`, `ggplot2` or `tidyverse`, indicating the type of
37 | #' problem statement. For `tidyverse`, the resulting answer will
38 | #' automatically append a ` %>% ` at the end of each answer, and for `ggplot2`
39 | #' every line will be followed by a ` + `.
40 | #'
41 | #' @inheritParams sortable::bucket_list
42 | #'
43 | #' @export
44 | #' @example inst/examples/example_parsons.R
45 | #'
46 | #' @references https://js-parsons.github.io/
47 | #'
48 | #'
49 | #' @examples
50 | #' ## Example of a shiny app
51 | #' if (interactive()) {
52 | #' app <- system.file("shiny-examples/parsons_app.R", package = "parsons")
53 | #' shiny::runApp(app)
54 | #' }
55 | parsons_problem <- function(
56 | initial,
57 | text = c("Drag from here", "Construct your solution here"),
58 | header = NULL,
59 | input_id,
60 | group_name,
61 | problem_type = c("base", "ggplot2", "tidyverse"),
62 | class = "default-sortable default-parsons",
63 | options = sortable_options(
64 | # emptyInsertThreshold = 150
65 | ),
66 | orientation = c("horizontal", "vertical")
67 | ) {
68 | if (is.character(initial)) initial <- list(initial, NULL)
69 | assert_that(is_sortable_options(options))
70 | if (missing(group_name) || is.null(group_name)) {
71 | group_name <- increment_parsons_group()
72 | }
73 | if (missing(input_id) || is.null(input_id)) {
74 | input_id <- paste0(group_name, c("_1", "_2"))
75 | }
76 |
77 | if (length(input_id) == 1) {
78 | input_id <- list(paste0(input_id, "_1"), input_id)
79 | }
80 | orientation <- match.arg(orientation)
81 | problem_type <- match.arg(problem_type)
82 |
83 | z <- bucket_list(
84 | header = header,
85 | class = class,
86 | add_rank_list(text = text[1],
87 | labels = wrap_labels(initial[[1]], r_type = problem_type),
88 | input_id = input_id[[1]],
89 | options = options
90 | ),
91 | add_rank_list(text = text[2],
92 | labels = wrap_labels(initial[[2]], r_type = problem_type),
93 | input_id = input_id[[2]],
94 | options = options
95 | ),
96 | group_name = group_name,
97 | orientation = orientation
98 | )
99 |
100 | min_height <- 50 * (length(initial[[1]]) + 1)
101 | z <- htmltools::tagList(
102 | z,
103 | parsons_dependencies(),
104 | htmltools::tags$style(
105 | sprintf(
106 | htmltools::HTML(".rank-list-container {min-height: %spx;}"),
107 | min_height
108 | )
109 | )
110 | )
111 | as.parsons_problem(z)
112 | }
113 |
114 |
--------------------------------------------------------------------------------
/R/question_bucket.R:
--------------------------------------------------------------------------------
1 | #' @importFrom utils modifyList
2 | NULL
3 |
4 | # is element a list of answers
5 | is_answer_list <- function(x){
6 | all(vapply(x, function(x)inherits(x, "tutorial_question_answer"), FUN.VALUE = logical(1)))
7 | }
8 |
9 |
10 | # promotes embedded list of answers to one level higher
11 | promote_answerlist <- function(x){
12 | z <- list()
13 | for (i in seq_along(x)){
14 | if (length(x[[i]]) == 1) {
15 | z <- append(z, x[i])
16 | } else {
17 | y <- x[[i]]
18 | if (is_answer_list(y)) {
19 | for (j in seq_along(y))
20 | z <- append(z, y[j])
21 | } else {
22 | z <- append(z, y)
23 | }
24 | }
25 | }
26 | z
27 | }
28 |
29 |
30 | #' bucket problem question for learnr tutorials (experimental).
31 | #'
32 | #'
33 | #' @param initial Initial value of answer options. This must be a character vector.
34 | #'
35 | #' @param ... One or more answers. Passed to [learnr::question()].
36 | #' @inheritParams learnr::question
37 | #' @inheritParams bucket_problem
38 | #'
39 | # @param type Must be "bucket_question"
40 | #' @param correct Text to print for a correct answer (defaults to "Correct!")
41 | #'
42 | #' @export
43 | #' @examples
44 | #' ## Example of bucket problem inside a learn tutorial
45 | #' if (interactive()) {
46 | #' learnr::run_tutorial("bucket", package = "parsons")
47 | #' }
48 | question_bucket <- function(
49 | initial,
50 | ...,
51 | text = c("Drag from here", "Construct your solution here"),
52 | orientation = c("horizontal", "vertical"),
53 | correct = "Correct!",
54 | incorrect = "Incorrect",
55 | try_again = incorrect,
56 | message = NULL,
57 | post_message = NULL,
58 | loading = c("Loading: "),
59 | submit_button = "Submit Answer",
60 | try_again_button = "Try Again",
61 | allow_retry = TRUE,
62 | random_answer_order = TRUE,
63 | options = sortable_options()
64 |
65 |
66 | ) {
67 | dots <- list2(...)
68 |
69 | # initialize answers with a dummy that can never appear
70 | answers <- list(learnr::answer(paste(initial, sep = ""), correct = TRUE))
71 |
72 | # append any other provided answers
73 | answers <- append(
74 | answers,
75 | dots[vapply(dots, is_answer, FUN.VALUE = logical(1))]
76 | )
77 | pass <- dots[vapply(dots, is.expectation_pass, FUN.VALUE = logical(1))]
78 | fail <- dots[vapply(dots, is.expectation_fail, FUN.VALUE = logical(1))]
79 |
80 | orientation <- match.arg(orientation)
81 |
82 | z <- do.call(
83 | learnr::question,
84 | append(
85 | answers,
86 | list(
87 | text = NULL,
88 | type = "bucket_question",
89 | correct = correct,
90 | incorrect = incorrect,
91 | try_again = try_again,
92 | message = message,
93 | post_message = post_message,
94 | loading = loading,
95 | submit_button = submit_button,
96 | try_again_button = try_again_button,
97 | allow_retry = allow_retry,
98 | random_answer_order = random_answer_order,
99 | options = list(
100 | initial = initial,
101 | text = text,
102 | pass = pass,
103 | fail = fail,
104 | sortable_options = options
105 | )
106 | )
107 | )
108 | )
109 | z
110 | }
111 |
112 |
113 | #' @export
114 | question_ui_initialize.bucket_question <- function(question, answer_input, ...) {
115 |
116 | labels <- question$options$initial
117 | if (isTRUE(question$random_answer_order)) { # and we should randomize the order
118 | shuffle <- shiny::repeatable(sample, question$seed)
119 | labels <- shuffle(labels)
120 | }
121 |
122 |
123 | # return the bucket htmlwidget
124 | z <- bucket_problem(
125 | input_id = c(question$ids$question, question$ids$answer),
126 | initial = list(
127 | setdiff(labels, answer_input),
128 | answer_input
129 | ),
130 | text = question$options$text,
131 | orientation = question$options$orientation,
132 | options = question$options$sortable_options,
133 | ...
134 | )
135 | z
136 | }
137 |
138 |
139 |
140 | #' @export
141 | question_ui_completed.bucket_question <- function(question, answer_input, ...) {
142 | # TODO display correct values with X or √ compared to best match
143 | # TODO DON'T display correct values (listen to an option?)
144 |
145 | labels <- question$options$initial
146 | if (isTRUE(question$random_answer_order)) { # and we should randomize the order
147 | shuffle <- shiny::repeatable(sample, question$seed)
148 | labels <- shuffle(labels)
149 | }
150 |
151 | new_options <- modifyList(
152 | question$options$sortable_options,
153 | sortable_options(disabled = TRUE)
154 | )
155 |
156 | disable_all_tags(
157 | bucket_problem(
158 | input_id = c(question$ids$question, question$ids$answer),
159 | initial = list(
160 | setdiff(labels, answer_input),
161 | answer_input
162 | ),
163 | text = question$options$text,
164 | orientation = question$options$orientation,
165 | options = new_options,
166 | ...
167 | )
168 | )
169 | }
170 |
171 |
172 | #' Disable input after student submitted answer.
173 | #'
174 | #' @inheritParams learnr::question_disable_input
175 | #'
176 | #' @param question Question object
177 | #' @param answer_input user input value
178 | #' @param ... not used
179 | #'
180 | #' @export
181 | question_ui_try_again.bucket_question <- function(question, answer_input, ...) {
182 | # TODO display correct values with X or √ compared to best match
183 | # TODO DON'T display correct values (listen to an option?)
184 | labels <- question$options$initial
185 | if (isTRUE(question$random_answer_order)) { # and we should randomize the order
186 | shuffle <- shiny::repeatable(sample, question$seed)
187 | labels <- shuffle(labels)
188 | }
189 | new_options <- modifyList(
190 | question$options$sortable_options,
191 | sortable_options(disabled = TRUE)
192 | )
193 |
194 | disable_all_tags(
195 | bucket_problem(
196 | input_id = c(question$ids$question, question$ids$answer),
197 | initial = list(
198 | setdiff(labels, answer_input),
199 | answer_input
200 | ),
201 | text = question$options$text,
202 | orientation = question$options$orientation,
203 | options = new_options,
204 | ...
205 | )
206 | )
207 | }
208 |
209 |
210 | #' @inheritParams learnr::question_disable_input
211 | #' @export
212 | question_is_correct.bucket_question <- function(question, answer_input, ...) {
213 | # for each possible answer, check if it matches
214 | for (answer in question$answers) {
215 | if (identical(answer$option, answer_input)) {
216 | # if it matches, return the correct-ness and its message
217 | return(mark_as(answer$correct, answer$message))
218 | }
219 | }
220 |
221 | # for each possible expectation, check if it matches
222 | pass_expectations <- question$options$pass
223 |
224 | for (exp in pass_expectations) {
225 | if (eval_expectation(exp, answer_input)) {
226 | # if it matches, return the correct-ness and its message
227 | return(mark_as(TRUE, messages = exp$message))
228 | }
229 | }
230 |
231 | # for each possible expectation, check if it matches
232 | fail_expectations <- question$options$fail
233 |
234 | # browser()
235 |
236 | for (exp in fail_expectations) {
237 | if (eval_expectation(exp, answer_input)) {
238 | # if it matches, return the correct-ness and its message
239 | return(mark_as(FALSE, eval_message(exp$message, answer_input)))
240 | }
241 | }
242 |
243 |
244 | # no match found. not correct
245 | mark_as(FALSE, NULL)
246 | }
247 |
--------------------------------------------------------------------------------
/R/question_parsons.R:
--------------------------------------------------------------------------------
1 | #' @importFrom utils modifyList
2 | #' @importFrom rlang list2
3 | NULL
4 |
5 | #' Parsons problem question for learnr tutorials (experimental).
6 | #'
7 | #' @template question_parsons_description
8 | #'
9 | #' @param initial Initial value of answer options. This must be a character vector.
10 | #'
11 | #' @param ... One or more answers. Passed to [learnr::question()].
12 | #' @inheritParams learnr::question
13 | #' @inheritParams parsons_problem
14 | #'
15 | # @param type Must be "parsons_question"
16 | #' @param correct Text to print for a correct answer (defaults to "Correct!")
17 | #'
18 | #' @export
19 | #' @examples
20 | #' ## Example of parsons problem inside a learn tutorial
21 | #' if (interactive()) {
22 | #' learnr::run_tutorial("parsons", package = "parsons")
23 | #' }
24 | question_parsons <- function(
25 | initial,
26 | ...,
27 | problem_type = c("base", "ggplot2", "tidyverse"),
28 | orientation = c("horizontal", "vertical"),
29 | correct = "Correct!",
30 | incorrect = "Incorrect",
31 | try_again = incorrect,
32 | message = NULL,
33 | post_message = NULL,
34 | loading = c("Loading: "),
35 | submit_button = "Give feedback",
36 | try_again_button = "Try Again",
37 | allow_retry = TRUE,
38 | random_answer_order = TRUE,
39 | options = sortable_options()
40 |
41 |
42 | ) {
43 | dots <- list2(...)
44 | answers <- dots[vapply(dots, is_answer, FUN.VALUE = logical(1))]
45 | pass <- dots[vapply(dots, is.expectation_pass, FUN.VALUE = logical(1))]
46 | fail <- dots[vapply(dots, is.expectation_fail, FUN.VALUE = logical(1))]
47 |
48 | problem_type <- match.arg(problem_type)
49 | orientation <- match.arg(orientation)
50 |
51 | z <- do.call(
52 | learnr::question,
53 | append(
54 | answers,
55 | list(
56 | text = NULL,
57 | type = "parsons_question",
58 | correct = correct,
59 | incorrect = incorrect,
60 | try_again = try_again,
61 | message = message,
62 | post_message = post_message,
63 | loading = loading,
64 | submit_button = submit_button,
65 | try_again_button = try_again_button,
66 | allow_retry = allow_retry,
67 | random_answer_order = random_answer_order,
68 | options = list(
69 | initial = initial,
70 | pass = pass,
71 | fail = fail,
72 | problem_type = problem_type,
73 | sortable_options = options
74 | )
75 | )
76 | )
77 | )
78 | z
79 | }
80 |
81 |
82 | #' @export
83 | question_ui_initialize.parsons_question <- function(question, answer_input, ...) {
84 |
85 | labels <- question$options$initial
86 | if (isTRUE(question$random_answer_order)) { # and we should randomize the order
87 | shuffle <- shiny::repeatable(sample, question$seed)
88 | labels <- shuffle(labels)
89 | }
90 |
91 |
92 | # return the parsons htmlwidget
93 | z <- parsons_problem(
94 | input_id = c(question$ids$question, question$ids$answer),
95 | initial = list(
96 | setdiff(labels, answer_input),
97 | answer_input
98 | ),
99 | problem_type = question$options$problem_type,
100 | orientation = question$options$orientation,
101 | options = question$options$sortable_options,
102 | ...
103 | )
104 | z
105 | }
106 |
107 |
108 |
109 | #' @export
110 | question_ui_completed.parsons_question <- function(question, answer_input, ...) {
111 | # TODO display correct values with X or √ compared to best match
112 | # TODO DON'T display correct values (listen to an option?)
113 |
114 | labels <- question$options$initial
115 | if (isTRUE(question$random_answer_order)) { # and we should randomize the order
116 | shuffle <- shiny::repeatable(sample, question$seed)
117 | labels <- shuffle(labels)
118 | }
119 |
120 | new_options <- modifyList(
121 | question$options$sortable_options,
122 | sortable_options(disabled = TRUE)
123 | )
124 |
125 | disable_all_tags(
126 | parsons_problem(
127 | input_id = c(question$ids$question, question$ids$answer),
128 | initial = list(
129 | setdiff(labels, answer_input),
130 | answer_input
131 | ),
132 | problem_type = question$options$problem_type,
133 | orientation = question$options$orientation,
134 | options = new_options,
135 | ...
136 | )
137 | )
138 | }
139 |
140 |
141 | #' Disable input after student submitted answer.
142 | #'
143 | #' @inheritParams learnr::question_disable_input
144 | #'
145 | #' @param question Question object
146 | #' @param answer_input user input value
147 | #' @param ... not used
148 | #'
149 | #' @export
150 | question_ui_try_again.parsons_question <- function(question, answer_input, ...) {
151 | # TODO display correct values with X or √ compared to best match
152 | # TODO DON'T display correct values (listen to an option?)
153 | labels <- question$options$initial
154 | if (isTRUE(question$random_answer_order)) { # and we should randomize the order
155 | shuffle <- shiny::repeatable(sample, question$seed)
156 | labels <- shuffle(labels)
157 | }
158 | new_options <- modifyList(
159 | question$options$sortable_options,
160 | sortable_options(disabled = TRUE)
161 | )
162 |
163 | disable_all_tags(
164 | parsons_problem(
165 | input_id = c(question$ids$question, question$ids$answer),
166 | initial = list(
167 | setdiff(labels, answer_input),
168 | answer_input
169 | ),
170 | problem_type = question$options$problem_type,
171 | orientation = question$options$orientation,
172 | options = new_options,
173 | ...
174 | )
175 | )
176 | }
177 |
178 |
179 | #' @inheritParams learnr::question_disable_input
180 | #' @export
181 | question_is_correct.parsons_question <- function(question, answer_input, ...) {
182 | # for each possible answer, check if it matches
183 | for (answer in question$answers) {
184 | if (identical(answer$option, answer_input)) {
185 | # if it matches, return the correct-ness and its message
186 | return(mark_as(answer$correct, answer$message))
187 | }
188 | }
189 |
190 | # for each possible expectation, check if it matches
191 | pass_expectations <- question$options$pass
192 |
193 | for (exp in pass_expectations) {
194 | if (eval_expectation(exp, answer_input)) {
195 | # if it matches, return the correct-ness and its message
196 | return(mark_as(TRUE, messages = exp$message))
197 | }
198 | }
199 |
200 | # for each possible expectation, check if it matches
201 | fail_expectations <- question$options$fail
202 |
203 | for (exp in fail_expectations) {
204 | if (eval_expectation(exp, answer_input)) {
205 | # if it matches, return the correct-ness and its message
206 | return(mark_as(FALSE, messages = exp$message))
207 | }
208 | }
209 |
210 |
211 | # no match found. not correct
212 | mark_as(FALSE, NULL)
213 | }
214 |
--------------------------------------------------------------------------------
/R/wrap_labels.R:
--------------------------------------------------------------------------------
1 |
2 | wrap_labels <- function(labels,
3 | class = "rank-list-item-internal",
4 | r_type = c("base", "ggplot2", "tidyverse")){
5 | r_type <- match.arg(r_type)
6 | lapply(labels, function(x) {
7 | htmltools::tags$div(
8 | class = paste(class, r_type),
9 | x
10 | )
11 | })
12 | }
13 |
14 | # initial = c(
15 | # "iris",
16 | # "mutate(...)",
17 | # "summarize(...)",
18 | # "print()"
19 | # )
20 | # wrap_labels(initial)
21 |
--------------------------------------------------------------------------------
/README.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | output: github_document
3 | ---
4 |
5 |
6 |
7 | ```{r, include = FALSE}
8 | knitr::opts_chunk$set(
9 | collapse = TRUE,
10 | comment = "#>",
11 | fig.path = "man/figures/README-",
12 | out.width = "100%"
13 | )
14 | ```
15 |
16 | # parsons
17 |
18 |
19 |
20 | [](https://travis-ci.org/rstudio/parsons)
21 | [](https://cran.r-project.org/package=parsons)
22 | [](http://www.rpackages.io/package/parsons)
23 | [](https://codecov.io/gh/rstudio/parsons?branch=master)
24 | [](https://www.tidyverse.org/lifecycle/#experimental)
25 |
26 |
27 |
28 | Use the `parsons` package to create Parsons problems for teaching progamming. You can create custom questions in your `learnr` tutorials.
29 |
30 |
31 | ## Installation
32 |
33 | ~~You can install the released version of parsons from [CRAN](https://CRAN.R-project.org) with:~~
34 |
35 | ```r
36 | ~~install.packages("parsons")~~
37 | ```
38 |
39 | And the development version from [GitHub](https://github.com/rstudio/parsons) with:
40 |
41 | ```r
42 | # install.packages("remotes")
43 | remotes::install_github("rstudio/parsons")
44 | ```
45 |
46 |
47 | ## Examples
48 |
49 |
50 | ### Parsons problems
51 |
52 | A Parsons problem is a specific type of question, useful for teaching programming, where all the lines of code are given, but the student must provide the correct order.
53 |
54 | The `parsons()` function has experimental support for parsons problems.
55 |
56 |
A Parsons problem gives the student some programming statements in random order. The student then constructs an answer by dragging the statements into the correct order.
124 |Here is an example. Drag the statements from the top panel to the bottom panel to produce “Hello World!”.
125 |To create a parsons problem in a learnr
tutorial, use the question_parson()
function.
You must provide at minimum:
135 |The initial
set of values, as a character vector
A “correct” answer
Note that, when using only this minimal specification, the only feedback the student will ever get is “Incorrect, try again”.
140 |question_parsons(
141 | initial = c(
142 | "Hello",
143 | "World",
144 | "!"
145 | ),
146 | answer(
147 | c(
148 | "Hello",
149 | "World",
150 | "!"
151 | ),
152 | correct = TRUE
153 | ),
154 | problem_type = "base"
155 | )
156 | An alternative way to specify the correct answer(s) is to supply a pass_if()
statement. Using pass_if()
and fail_if()
is a powerful way to provide feedback to your students.
These pass_if()
and fail_if()
statements are evaluated in turn, until the first expectation evaluates to TRUE
, and the leanr
will provide the feedback in the message
.
You can specify pass_if()
as well as fail_if()
in any of the following ways:
A character vector, that must be matched exactly, or
A function in the tradition form function(x) ...
, e.g. function(x) length(x) < 3
A function specified using the rlang
tidy evaluation formula notation, e.g. ~length(.) < 3
This next example will give feedback if the length of the answer is 2 or fewer items.
173 |question_parsons(
174 | initial = c(
175 | "Hello",
176 | "World",
177 | "!"
178 | ),
179 | pass_if(
180 | c(
181 | "Hello",
182 | "World",
183 | "!"
184 | )
185 | ),
186 | fail_if(
187 | ~length(.) <= 2,
188 | "Provide more answers"
189 | ),
190 | problem_type = "base"
191 | )
192 | Create a statement that uses iris
data, then does some mutation and finally creates a summary.
question_parsons(
202 | initial = c(
203 | "iris",
204 | "mutate(...)",
205 | "summarize(...)",
206 | "print()"
207 | ),
208 | pass_if(
209 | c(
210 | "iris",
211 | "mutate(...)",
212 | "summarize(...)"
213 | )
214 | ),
215 | fail_if(
216 | ~length(.) < 2,
217 | message = "Include at least two answers"
218 | ),
219 | fail_if(
220 | function(x){"print()" %in% x},
221 | message = "You should not include print() in your answer"
222 | ),
223 | fail_if(
224 | ~{.[1] != "iris"},
225 | message = "Your solution should start with 'iris'"
226 | ),
227 | fail_if(
228 | ~length(.) < 3,
229 | message = "Use at least 3 elements to form your answer"
230 | ),
231 |
232 | problem_type = "tidyverse"
233 | )
234 | } tag, so not strictly speaking a header.)} 21 | 22 | \item{input_id}{Character vector of \code{input_id} to pass (individually) to 23 | \code{\link[=rank_list]{rank_list()}}.} 24 | 25 | \item{group_name}{Passed to \code{SortableJS} as the group name. Also the input 26 | value set in Shiny. (\code{input[[group_name]]})} 27 | 28 | \item{class}{A css class applied to the rank list. This can be used to 29 | define custom styling.} 30 | 31 | \item{options}{Options to be supplied to \link{sortable_js} object. See \link{sortable_options} for more details} 32 | 33 | \item{orientation}{Either \code{horizontal} or \code{vertical}, and specifies the 34 | layout of the components on the page.} 35 | } 36 | \description{ 37 | This function implements the bucket problem, as exposed by 38 | \code{\link[=question_bucket]{question_bucket()}}. Most users will only use this function inside a 39 | \code{learnr} tutorial, so please see the documentation at \code{\link[=question_bucket]{question_bucket()}} 40 | } 41 | -------------------------------------------------------------------------------- /man/expectations.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/expectations.R 3 | \name{pass_if} 4 | \alias{pass_if} 5 | \alias{fail_if} 6 | \alias{message_if} 7 | \alias{contains_all} 8 | \alias{contains_any} 9 | \title{Add expectations to a parsons problem.} 10 | \usage{ 11 | pass_if(f, message = NULL) 12 | 13 | fail_if(f, message = "Incorrect") 14 | 15 | message_if(f) 16 | 17 | contains_all(x, ...) 18 | 19 | contains_any(x, ...) 20 | } 21 | \arguments{ 22 | \item{f}{One of: 23 | \itemize{ 24 | \item A character vector, indicating an exact match 25 | \item A function of the function \code{function(x){...}} that evaluates to TRUE or FALSE 26 | \item A function of the form \code{~ .}, as used by the tidy evaluation, e.g. in \link[purrr:map]{purrr::map} 27 | }} 28 | 29 | \item{message}{Message to display if \code{fun} evaluates to TRUE} 30 | 31 | \item{x}{Answer to test} 32 | 33 | \item{...}{Combined into an answer list} 34 | } 35 | \description{ 36 | Add expectations to a parsons problem. 37 | } 38 | -------------------------------------------------------------------------------- /man/figures/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 732 | -------------------------------------------------------------------------------- /man/figures/parsons-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/man/figures/parsons-logo.png -------------------------------------------------------------------------------- /man/figures/parsons_app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/man/figures/parsons_app.gif -------------------------------------------------------------------------------- /man/figures/parsons_app_initial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/man/figures/parsons_app_initial.png -------------------------------------------------------------------------------- /man/figures/parsons_app_submit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/man/figures/parsons_app_submit.png -------------------------------------------------------------------------------- /man/parsons.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/parsons-package.R 3 | \docType{package} 4 | \name{parsons} 5 | \alias{parsons} 6 | \alias{_PACKAGE} 7 | \alias{parsons-package} 8 | \title{parsons: Create Parsons Programming Problems in 'learnr` Tutorials} 9 | \description{ 10 | Create Parsons Programming Problems in 'learnr' Tutorials. 11 | } 12 | \seealso{ 13 | Useful links: 14 | \itemize{ 15 | \item \url{https://github.com/rstudio/parsons} 16 | \item Report bugs at \url{https://github.com/rstudio/parsons/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Andrie de Vries \email{apdevries@gmail.com} 22 | 23 | Other contributors: 24 | \itemize{ 25 | \item Barret Schloerke \email{barret@rstudio.com} [contributor] 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /man/parsons_problem.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/parsons_problem.R 3 | \name{parsons_problem} 4 | \alias{parsons_problem} 5 | \title{Create a parsons problem (experimental).} 6 | \usage{ 7 | parsons_problem(initial, text = c("Drag from here", 8 | "Construct your solution here"), header = NULL, input_id, group_name, 9 | problem_type = c("base", "ggplot2", "tidyverse"), 10 | class = "default-sortable default-parsons", 11 | options = sortable_options(), orientation = c("horizontal", 12 | "vertical")) 13 | } 14 | \arguments{ 15 | \item{initial}{Vector with initial values for problem (to appear in left 16 | column). Note: this must be a super-set of all answers.} 17 | 18 | \item{text}{Vector of headings for each column.} 19 | 20 | \item{header}{Text that appears at the top of the bucket list. (This is 21 | encoded as an HTML \code{
} tag, so not strictly speaking a header.)} 22 | 23 | \item{input_id}{Character vector of \code{input_id} to pass (individually) to 24 | \code{\link[=rank_list]{rank_list()}}.} 25 | 26 | \item{group_name}{Passed to \code{SortableJS} as the group name. Also the input 27 | value set in Shiny. (\code{input[[group_name]]})} 28 | 29 | \item{problem_type}{One of \code{base}, \code{ggplot2} or \code{tidyverse}, indicating the type of 30 | problem statement. For \code{tidyverse}, the resulting answer will 31 | automatically append a \code{\%>\%} at the end of each answer, and for \code{ggplot2} 32 | every line will be followed by a \code{+}.} 33 | 34 | \item{class}{A css class applied to the rank list. This can be used to 35 | define custom styling.} 36 | 37 | \item{options}{Options to be supplied to \link{sortable_js} object. See \link{sortable_options} for more details} 38 | 39 | \item{orientation}{Either \code{horizontal} or \code{vertical}, and specifies the 40 | layout of the components on the page.} 41 | } 42 | \description{ 43 | This function implements the parsons problem, as exposed by 44 | \code{\link[=question_parsons]{question_parsons()}}. Most users will only use this function inside a 45 | \code{learnr} tutorial, so please see the documentation at \code{\link[=question_parsons]{question_parsons()}} 46 | } 47 | \examples{ 48 | ## Example of a shiny app 49 | if (interactive()) { 50 | app <- system.file("shiny-examples/parsons_app.R", package = "parsons") 51 | shiny::runApp(app) 52 | } 53 | ## -- example-parsons ----------------------------------------------------- 54 | 55 | ## Parson's problem 56 | 57 | parsons_problem( 58 | header = "This is an example of a Parsons problem", 59 | text = c("Drag from here", "Construct your solution here"), 60 | initial = c( 61 | "iris", 62 | "mutate(...)", 63 | "summarize(...)", 64 | "print()" 65 | ), 66 | input_id = "input_parsons" 67 | ) 68 | 69 | 70 | parsons_problem( 71 | initial = c( 72 | "iris", 73 | "mutate(...)", 74 | "summarize(...)", 75 | "print()" 76 | ), 77 | input_id = "input_parsons" 78 | ) 79 | 80 | } 81 | \references{ 82 | https://js-parsons.github.io/ 83 | } 84 | -------------------------------------------------------------------------------- /man/question_bucket.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/question_bucket.R 3 | \name{question_bucket} 4 | \alias{question_bucket} 5 | \title{bucket problem question for learnr tutorials (experimental).} 6 | \usage{ 7 | question_bucket(initial, ..., text = c("Drag from here", 8 | "Construct your solution here"), orientation = c("horizontal", 9 | "vertical"), correct = "Correct!", incorrect = "Incorrect", 10 | try_again = incorrect, message = NULL, post_message = NULL, 11 | loading = c("Loading: "), submit_button = "Give feedback", 12 | try_again_button = "Try Again", allow_retry = TRUE, 13 | random_answer_order = TRUE, options = sortable_options()) 14 | } 15 | \arguments{ 16 | \item{initial}{Initial value of answer options. This must be a character vector.} 17 | 18 | \item{...}{One or more answers. Passed to \code{\link[learnr:question]{learnr::question()}}.} 19 | 20 | \item{text}{Question or option text} 21 | 22 | \item{orientation}{Either \code{horizontal} or \code{vertical}, and specifies the 23 | layout of the components on the page.} 24 | 25 | \item{correct}{Text to print for a correct answer (defaults to "Correct!")} 26 | 27 | \item{incorrect}{Text to print for an incorrect answer (defaults to "Incorrect") 28 | when \code{allow_retry} is \code{FALSE}.} 29 | 30 | \item{try_again}{Text to print for an incorrect answer (defaults to "Incorrect") 31 | when \code{allow_retry} is \code{TRUE}.} 32 | 33 | \item{message}{Additional message to display along with correct/incorrect feedback. 34 | This message is always displayed after a question submission.} 35 | 36 | \item{post_message}{Additional message to display along with correct/incorrect feedback. 37 | If \code{allow_retry} is \code{TRUE}, this message will only be displayed after the 38 | correct submission. If \code{allow_retry} is \code{FALSE}, it will produce a second 39 | message alongside the \code{message} message value.} 40 | 41 | \item{loading}{Loading text to display as a placeholder while the question is loaded} 42 | 43 | \item{submit_button}{Label for the submit button. Defaults to \code{"Submit Answer"}} 44 | 45 | \item{try_again_button}{Label for the try again button. Defaults to \code{"Submit Answer"}} 46 | 47 | \item{allow_retry}{Allow retry for incorrect answers. Defaults to \code{FALSE}.} 48 | 49 | \item{random_answer_order}{Display answers in a random order.} 50 | 51 | \item{options}{Extra options to be stored in the question object.} 52 | } 53 | \description{ 54 | bucket problem question for learnr tutorials (experimental). 55 | } 56 | \examples{ 57 | ## Example of bucket problem inside a learn tutorial 58 | if (interactive()) { 59 | learnr::run_tutorial("bucket", package = "parsons") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /man/question_parsons.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/question_parsons.R 3 | \name{question_parsons} 4 | \alias{question_parsons} 5 | \title{Parsons problem question for learnr tutorials (experimental).} 6 | \usage{ 7 | question_parsons(initial, ..., problem_type = c("base", "ggplot2", 8 | "tidyverse"), orientation = c("horizontal", "vertical"), 9 | correct = "Correct!", incorrect = "Incorrect", 10 | try_again = incorrect, message = NULL, post_message = NULL, 11 | loading = c("Loading: "), submit_button = "Give feedback", 12 | try_again_button = "Try Again", allow_retry = TRUE, 13 | random_answer_order = TRUE, options = sortable_options()) 14 | } 15 | \arguments{ 16 | \item{initial}{Initial value of answer options. This must be a character vector.} 17 | 18 | \item{...}{One or more answers. Passed to \code{\link[learnr:question]{learnr::question()}}.} 19 | 20 | \item{problem_type}{One of \code{base}, \code{ggplot2} or \code{tidyverse}, indicating the type of 21 | problem statement. For \code{tidyverse}, the resulting answer will 22 | automatically append a \code{\%>\%} at the end of each answer, and for \code{ggplot2} 23 | every line will be followed by a \code{+}.} 24 | 25 | \item{orientation}{Either \code{horizontal} or \code{vertical}, and specifies the 26 | layout of the components on the page.} 27 | 28 | \item{correct}{Text to print for a correct answer (defaults to "Correct!")} 29 | 30 | \item{incorrect}{Text to print for an incorrect answer (defaults to "Incorrect") 31 | when \code{allow_retry} is \code{FALSE}.} 32 | 33 | \item{try_again}{Text to print for an incorrect answer (defaults to "Incorrect") 34 | when \code{allow_retry} is \code{TRUE}.} 35 | 36 | \item{message}{Additional message to display along with correct/incorrect feedback. 37 | This message is always displayed after a question submission.} 38 | 39 | \item{post_message}{Additional message to display along with correct/incorrect feedback. 40 | If \code{allow_retry} is \code{TRUE}, this message will only be displayed after the 41 | correct submission. If \code{allow_retry} is \code{FALSE}, it will produce a second 42 | message alongside the \code{message} message value.} 43 | 44 | \item{loading}{Loading text to display as a placeholder while the question is loaded} 45 | 46 | \item{submit_button}{Label for the submit button. Defaults to \code{"Submit Answer"}} 47 | 48 | \item{try_again_button}{Label for the try again button. Defaults to \code{"Submit Answer"}} 49 | 50 | \item{allow_retry}{Allow retry for incorrect answers. Defaults to \code{FALSE}.} 51 | 52 | \item{random_answer_order}{Display answers in a random order.} 53 | 54 | \item{options}{Extra options to be stored in the question object.} 55 | } 56 | \description{ 57 | Add Parsons Problem tasks to your \code{learnr} tutorials. The 58 | student can drag-and-drop the answer options into the desired order. 59 | 60 | This is a highly experimental, initial attempt at making Parsons problems in 61 | \code{learnr} tutorials. Parsons problems is a type of programming assignment 62 | where the student must order statements in the correct order. 63 | 64 | If the task also includes indentation of the statements, it's called a 65 | second-order Parsons problem. Note that second order problems have not yet 66 | been implemented. 67 | 68 | \if{html}{\figure{parsons_app_submit.png}{options: width="60\%" max-width="500px" alt="Figure: Parsons submit state"}} 69 | 70 | Features (design choices): 71 | \itemize{ 72 | \item Items (except the last) in the right hand column will have a \code{\%>\%} 73 | appended. 74 | \item Items (except the first) in the right hand column will automatically be 75 | indented. 76 | \item The initial values are shuffled into random answer order. 77 | } 78 | 79 | Limitations: 80 | \itemize{ 81 | \item It does not do any code evaluation 82 | \item It does not support indentation 83 | \item It assumes code is from the \code{tidyverse} and only supports the \code{magrittr} 84 | pipe \code{\%>\%} operator 85 | } 86 | } 87 | \section{Creating a parsons question}{ 88 | 89 | 90 | Use \code{question_parsons} inside a \code{learnr} tutorial chunk 91 | 92 | For example:\preformatted{```{r iris} 93 | question_parsons( 94 | initial = c( 95 | "iris", 96 | "mutate(...)", 97 | "summarize(...)", 98 | "print()" 99 | ), 100 | answer(c( 101 | "iris", 102 | "mutate(...)", 103 | "summarize(...)" 104 | ), correct = TRUE) 105 | ) 106 | ``` 107 | } 108 | 109 | On initialization, the initial values are randomized: 110 | 111 | \if{html}{\figure{parsons_app_initial.png}{options: width="60\%" max-width="500px" alt="Figure: Parsons initial state"}} 112 | 113 | As the student drags values to the right column, the \code{magrittr} gets 114 | appended, and items are automatically indented: 115 | 116 | \if{html}{\figure{parsons_app_submit.png}{options: width="60\%" max-width="500px" alt="Figure: Parsons submit state"}} 117 | } 118 | 119 | \examples{ 120 | ## Example of parsons problem inside a learn tutorial 121 | if (interactive()) { 122 | learnr::run_tutorial("parsons", package = "parsons") 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /man/question_ui_try_again.bucket_question.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/question_bucket.R 3 | \name{question_ui_try_again.bucket_question} 4 | \alias{question_ui_try_again.bucket_question} 5 | \title{Disable input after student submitted answer.} 6 | \usage{ 7 | \method{question_ui_try_again}{bucket_question}(question, answer_input, 8 | ...) 9 | } 10 | \arguments{ 11 | \item{question}{Question object} 12 | 13 | \item{answer_input}{user input value} 14 | 15 | \item{...}{not used} 16 | } 17 | \description{ 18 | Disable input after student submitted answer. 19 | } 20 | -------------------------------------------------------------------------------- /man/question_ui_try_again.parsons_question.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/question_parsons.R 3 | \name{question_ui_try_again.parsons_question} 4 | \alias{question_ui_try_again.parsons_question} 5 | \title{Disable input after student submitted answer.} 6 | \usage{ 7 | \method{question_ui_try_again}{parsons_question}(question, answer_input, 8 | ...) 9 | } 10 | \arguments{ 11 | \item{question}{Question object} 12 | 13 | \item{answer_input}{user input value} 14 | 15 | \item{...}{not used} 16 | } 17 | \description{ 18 | Disable input after student submitted answer. 19 | } 20 | -------------------------------------------------------------------------------- /parsons.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: XeLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace,vignette 22 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstudio/parsons/42d0bfb8f23a3c8048cc4c1fbca95a0ef1e0bca3/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /scripts/compile_css.R: -------------------------------------------------------------------------------- 1 | 2 | if (!require(sass)) { 3 | install.packages("sass") 4 | } 5 | library(sass) 6 | 7 | scss_files <- dir( 8 | file.path("inst", "htmlwidgets", "plugins", "parsons"), 9 | pattern = "^[^_].*?\\.scss", 10 | full.names = TRUE 11 | ) 12 | 13 | for (scss_file in scss_files) { 14 | message("Translating: ", basename(scss_file)) 15 | sass::sass( 16 | input = sass::sass_file(scss_file), 17 | output = sub("\\.scss", ".css", scss_file) 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /scripts/deploy_apps.R: -------------------------------------------------------------------------------- 1 | 2 | if (!requireNamespace("remotes")) install.packages("remotes") 3 | 4 | # install rsconnect and glue 5 | remotes::install_cran("rsconnect") 6 | remotes::install_cran("glue") 7 | 8 | # install the latest 9 | remotes::install_github("rstudio/sortable", upgrade = "always") 10 | 11 | 12 | 13 | deploy_app <- function( 14 | app_dir, 15 | name = glue::glue("sortable_{basename(app_dir)}_app"), 16 | ... 17 | ) { 18 | cat("\n\n\n") 19 | message("Deploying: ", name) 20 | cat("\n") 21 | rsconnect::deployApp( 22 | appDir = app_dir, 23 | appName = name, 24 | server = "shinyapps.io", 25 | account = "andrie-de-vries", 26 | forceUpdate = TRUE, 27 | ... 28 | ) 29 | } 30 | 31 | deploy_tutorial <- function( 32 | app_dir, 33 | doc = dir(app_dir, pattern = "\\.Rmd$")[1], 34 | name = glue::glue("sortable_tutorial_{basename(app_dir)}") 35 | ) { 36 | deploy_app( 37 | app_dir = app_dir, 38 | name = name, 39 | appPrimaryDoc = dir(app_dir, pattern = "\\.Rmd$")[1] 40 | ) 41 | } 42 | 43 | 44 | deploy_folder <- function(path, fn) { 45 | lapply( 46 | dir(path, full.names = TRUE), 47 | function(path) { 48 | if (dir.exists(path)) { 49 | fn(path) 50 | } 51 | } 52 | ) 53 | } 54 | 55 | deploy_folder("inst/shiny-examples", deploy_app) 56 | deploy_folder("inst/tutorials", deploy_tutorial) 57 | 58 | message("done") 59 | -------------------------------------------------------------------------------- /scripts/deploy_on_travis.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | if (!requireNamespace("remotes")) install.packages("remotes") 4 | remotes::install_cran("rsconnect") 5 | 6 | # Set the account info for deployment. 7 | rsconnect::setAccountInfo( 8 | name = Sys.getenv("shinyapps_name"), 9 | token = Sys.getenv("shinyapps_token"), 10 | secret = Sys.getenv("shinyapps_secret") 11 | ) 12 | 13 | # deploy all apps 14 | source("scripts/deploy_apps.R") 15 | -------------------------------------------------------------------------------- /scripts/load_all_shim.R: -------------------------------------------------------------------------------- 1 | # Makes it easy to test the package in development by shimming `htmlwidgets` 2 | # and `htmltools`, before `load_all()`. 3 | # 4 | # Solution by Winston Chang (https://gist.github.com/wch/c942335660dc6c96322f) 5 | 6 | local({ 7 | shim_system_file <- function(package) { 8 | imports <- parent.env(asNamespace(package)) 9 | pkgload:::unlock_environment(imports) 10 | imports$system.file <- pkgload:::shim_system.file 11 | } 12 | 13 | shim_system_file("htmlwidgets") 14 | shim_system_file("htmltools") 15 | }) 16 | 17 | devtools::load_all() 18 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(parsons) 3 | 4 | test_check("parsons") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-expectations.R: -------------------------------------------------------------------------------- 1 | context("expectations") 2 | 3 | test_that("pass_if works", { 4 | 5 | expect_is( 6 | pass_if("x"), 7 | "tutorial_question_answer" 8 | ) 9 | 10 | expect_is( 11 | pass_if(~x == 1, "1"), 12 | "parsons_expectation_pass" 13 | ) 14 | 15 | expect_is( 16 | pass_if(function(x)x == 1, "1"), 17 | "parsons_expectation_pass" 18 | ) 19 | 20 | }) 21 | 22 | test_that("fail_if works", { 23 | 24 | expect_is( 25 | fail_if("x"), 26 | "tutorial_question_answer" 27 | ) 28 | 29 | expect_is( 30 | fail_if(~x == 1, "1"), 31 | "parsons_expectation_fail" 32 | ) 33 | 34 | expect_is( 35 | fail_if(function(x)x == 1, "1"), 36 | "parsons_expectation_fail" 37 | ) 38 | 39 | expect_is( 40 | fail_if(function(x){!"print()" %in% x}, "1"), 41 | "parsons_expectation_fail" 42 | ) 43 | 44 | }) 45 | -------------------------------------------------------------------------------- /tests/testthat/test-learnr-question_parsons.R: -------------------------------------------------------------------------------- 1 | context("learnr question_parsons") 2 | 3 | test_that( "init display validates", { 4 | 5 | question <- question_parsons( 6 | # "Sort the first 5 letters", 7 | initial = LETTERS[1:5], 8 | learnr::answer(LETTERS[1:5], correct = TRUE), 9 | learnr::answer(rev(LETTERS[1:5]), correct = FALSE, "Other direction!") 10 | ) 11 | expect_is(question, "parsons_question") 12 | 13 | expect_silent({ 14 | question_ui_initialize(question, "ignored") 15 | }) 16 | 17 | expect_silent({ 18 | question_ui_completed(question, LETTERS[5:1]) 19 | }) 20 | expect_silent( 21 | question_ui_try_again(question, rev(LETTERS[1:5])) 22 | ) 23 | 24 | expect_true( 25 | question_is_valid(question, letters[1:5]) 26 | ) 27 | expect_false( 28 | question_is_valid(question, NULL) 29 | ) 30 | 31 | # expect_identical( 32 | # question_is_correct(question, LETTERS[1:5]), 33 | # learnr::question_is_correct(TRUE, NULL) 34 | # ) 35 | tmp_answer <- learnr::answer("ignored", FALSE, "Other direction!") 36 | # expect_identical( 37 | # question_is_correct(question, LETTERS[5:1]), 38 | # learnr::question_is_correct_value(FALSE, tmp_answer$message) 39 | # ) 40 | # expect_identical( 41 | # question_is_correct(question, letters[1:5]), 42 | # learnr::question_is_correct_value(FALSE, NULL) 43 | # ) 44 | 45 | }) 46 | -------------------------------------------------------------------------------- /tests/testthat/test-parsons.R: -------------------------------------------------------------------------------- 1 | context("parsons") 2 | 3 | test_that("Can create parsons", { 4 | library(learnr) 5 | z <- parsons_problem( 6 | header = "This is an example of a Parsons problem", 7 | initial = c( 8 | "iris", 9 | "mutate(...)", 10 | "summarize(...)", 11 | "print()" 12 | ), 13 | answer(c( 14 | "iris", 15 | "mutate(...)", 16 | "summarize(...)", 17 | "print()" 18 | )), 19 | input_id = "test-parsons" 20 | ) 21 | 22 | expect_is(z, "parsons_problem") 23 | }) 24 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/introduction.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "introduction" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{introduction} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | ```{r setup} 18 | library(parsons) 19 | ``` 20 | --------------------------------------------------------------------------------