├── .github
├── .gitignore
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ ├── question.md
│ └── bug_report.md
├── CONTRIBUTING.md
├── pkgdown-pr-preview-build.R
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── pr-commands.yaml
├── pkgdown
├── assets
│ ├── snippets
│ │ ├── ace
│ │ │ ├── mode-text.js
│ │ │ ├── mode-plain_text.js
│ │ │ └── theme-textmate.js
│ │ ├── addition.md
│ │ ├── tutorialyaml-allowskip-topic.md
│ │ ├── tutorialyaml-progressive-topic.md
│ │ ├── exercisecaption.md
│ │ ├── runtutorial.md
│ │ ├── videos-size.md
│ │ ├── tutorialyaml-progressive.md
│ │ ├── tutorialyaml.md
│ │ ├── exerciselines.md
│ │ ├── tutorialtoccollapsed.md
│ │ ├── tutorialyaml-allowskip.md
│ │ ├── tutorvideos.md
│ │ ├── exerciseoptions.md
│ │ ├── tutoryamlids.md
│ │ ├── tutorids.md
│ │ ├── exercisecompletion.md
│ │ ├── question.md
│ │ ├── exercisetimelimit.md
│ │ ├── exerciseblanks.md
│ │ ├── exercisesetupsuffix.md
│ │ ├── questionretry.md
│ │ ├── exercisehintdiv.md
│ │ ├── questionrandom.md
│ │ ├── exercisesolution.md
│ │ ├── exerciseeval.md
│ │ ├── questionmath.md
│ │ ├── questionmessages.md
│ │ ├── tutorrecorder.md
│ │ ├── exercisefeedback.md
│ │ ├── exercisecheck.md
│ │ ├── exercise-tests.md
│ │ ├── exercisecodecheck.md
│ │ ├── exercisedata.md
│ │ ├── hello-learnr.md
│ │ ├── exercisesetup.md
│ │ ├── questionmultiple.md
│ │ ├── exercisehints.md
│ │ ├── shiny.md
│ │ ├── exercisesolutionhidden.md
│ │ ├── exercisesetupshared.md
│ │ ├── exercisechained.md
│ │ ├── exercisesetupchained.md
│ │ ├── questionquiz.md
│ │ ├── tutorstorage.md
│ │ ├── customchecker.md
│ │ ├── codefeedback.md
│ │ ├── exerciseevaluator.md
│ │ └── snippets.js
│ ├── images
│ │ └── hello.png
│ └── learnr-social.png
├── favicon
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-76x76.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-152x152.png
│ └── apple-touch-icon-180x180.png
├── extra.css
├── templates
│ ├── in-header.html
│ └── content-article.html
└── index.Rmd
├── revdep
├── failures.md
├── problems.md
├── .gitignore
├── email.yml
├── cran.md
└── README.md
├── tests
├── manual
│ ├── .gitignore
│ ├── tests-chunk.Rmd
│ ├── hint-popover.Rmd
│ └── ace-mode
│ │ └── ace-mode.Rmd
├── testthat
│ ├── tutorials
│ │ ├── .gitignore
│ │ ├── hint-div
│ │ │ └── hint-div.Rmd
│ │ ├── missing-exercise-checker.Rmd
│ │ ├── knitr-hooks_empty-exercise
│ │ │ ├── empty-exercise.Rmd
│ │ │ ├── full-exercise.Rmd
│ │ │ └── duplicate-label.Rmd
│ │ ├── exercise-option-is-symbol.Rmd
│ │ ├── hint-next
│ │ │ └── hint-next.Rmd
│ │ ├── hint-copy
│ │ │ └── hint-copy.Rmd
│ │ ├── basic.Rmd
│ │ └── optional-show-solution.Rmd
│ ├── test-tutorial-format.R
│ ├── test-options.R
│ ├── setup-chunks
│ │ ├── setup-cycle-self.Rmd
│ │ ├── exercise-cycle-self.Rmd
│ │ ├── error-check-chunk_bad.Rmd
│ │ ├── error-check-chunk_good.Rmd
│ │ ├── setup-cycle-two.Rmd
│ │ ├── exercise-cycle-two.Rmd
│ │ ├── setup-cycle.Rmd
│ │ ├── exercise-global-setup.Rmd
│ │ ├── exercise-cycle.Rmd
│ │ └── exercise-cycle-default-setup.Rmd
│ ├── test-storage.R
│ ├── test-dependency.R
│ ├── test-shinytest2-aaa.R
│ ├── test-cookies.R
│ ├── _snaps
│ │ └── available-tutorials.md
│ ├── test-duplicate_env.R
│ ├── test-available-tutorials.R
│ ├── test-question_radio.R
│ ├── test-praise.R
│ ├── test-mark_as.R
│ ├── test-mock_exercise.R
│ ├── helpers.R
│ ├── test-feedback.R
│ ├── test-install-dependencies.R
│ ├── test-events.R
│ └── test-mutate_tags.R
└── testthat.R
├── vignettes
├── .gitignore
├── articles
│ ├── images
│ │ ├── slidy.png
│ │ ├── question.png
│ │ ├── solution.png
│ │ ├── exercises.png
│ │ ├── questions.png
│ │ ├── exercise-code.png
│ │ ├── question-math.png
│ │ ├── tutorial-sql.png
│ │ ├── exercise-blanks.png
│ │ ├── exercise-error.png
│ │ ├── exercise-result.png
│ │ ├── format-tutorial.png
│ │ ├── tutorial-hello.png
│ │ ├── tutorial-slidy.png
│ │ ├── questions-message.png
│ │ ├── create-new-tutorial.png
│ │ ├── exercise-completion.png
│ │ ├── tutorial-ex-setup-r.png
│ │ ├── tutorial-ex-data-basics.png
│ │ ├── tutorial-ex-data-filter.png
│ │ ├── tutorial-ex-data-mutate.png
│ │ ├── tutorial-quiz_question.png
│ │ ├── example-community-rtledu.png
│ │ ├── shinyapps-deploy-faithful.png
│ │ ├── example-community-afrilearnr.png
│ │ ├── example-community-qsslearnr.png
│ │ ├── tutorial-ex-data-summarise.png
│ │ ├── tutorial-external-sortable.png
│ │ ├── example-community-dosstoolkit.png
│ │ └── example-community-learntidymodels.png
│ ├── releases
│ │ └── images
│ │ │ ├── blanks-warning.png
│ │ │ ├── error-checker.png
│ │ │ ├── syntax-warning.png
│ │ │ ├── question-numeric.png
│ │ │ ├── r-python-exercise.png
│ │ │ ├── blanks-warning-custom.png
│ │ │ ├── exercise-tab-example.gif
│ │ │ ├── exercise-tab-example.mp4
│ │ │ ├── fancy-quotes-warning.png
│ │ │ └── janko-ferlic-sfL_QOnmy00-unsplash.jpg
│ ├── snippets.R
│ ├── example_cards.R
│ └── examples.Rmd
└── img
│ ├── shinyapps-publishing-advanced.png
│ ├── shinyapps-publishing-instances.png
│ ├── shinyapps-publishing-metrics.png
│ └── shinyapps-publishing-instance-size.png
├── sandbox
├── .gitignore
└── autocompletion.Rmd
├── inst
├── rmarkdown
│ └── templates
│ │ └── tutorial
│ │ ├── skeleton
│ │ ├── .gitignore
│ │ └── skeleton.Rmd
│ │ ├── resources
│ │ └── images
│ │ │ ├── topicProgress.png
│ │ │ ├── close.svg
│ │ │ └── exerciseDone.svg
│ │ └── template.yaml
├── internals
│ ├── i18n_random_phrases.rds
│ ├── i18n_translations.rds
│ ├── templates
│ │ └── exercise-setup.Rmd
│ └── learnr.py
├── tutorials
│ ├── polyglot
│ │ ├── portal_mammals.sqlite
│ │ └── polyglot.Rmd
│ ├── ex-data-basics
│ │ └── images
│ │ │ └── flights.png
│ ├── ex-data-filter
│ │ └── images
│ │ │ └── transform-logical.png
│ ├── hello
│ │ └── hello.Rmd
│ ├── slidy
│ │ └── slidy.Rmd
│ └── setup-chunks
│ │ └── setup-chunks.Rmd
├── lib
│ └── idb-keyval
│ │ ├── note.md
│ │ ├── LICENSE
│ │ └── idb-keyval-iife-compat.min.js
├── staticexports
│ ├── assertions.R
│ ├── strings.R
│ └── knitr_engine_caption.R
└── examples
│ └── apparmor
│ └── apparmor_evaluator.R
├── .eslintignore
├── man
├── figures
│ ├── logo.png
│ ├── lifecycle-stable.svg
│ ├── lifecycle-defunct.svg
│ ├── lifecycle-archived.svg
│ ├── lifecycle-maturing.svg
│ ├── lifecycle-deprecated.svg
│ ├── lifecycle-superseded.svg
│ ├── lifecycle-experimental.svg
│ ├── lifecycle-questioning.svg
│ └── lifecycle-soft-deprecated.svg
├── rmd-fragments
│ ├── learnr-install.Rmd
│ ├── learnr-overview.Rmd
│ ├── hello-tutorial.Rmd
│ ├── badges.Rmd
│ └── learnr-examples-showcase.Rmd
├── tutorial_html_dependency.Rd
├── initialize_tutorial.Rd
├── safe_env.Rd
├── disable_all_tags.Rd
├── knit_print.Rd
├── filesystem_storage.Rd
├── available_tutorials.Rd
├── external_evaluator.Rd
├── random_praise.Rd
├── tutorial_package_dependencies.Rd
├── finalize_question.Rd
├── duplicate_env.Rd
├── event_register_handler.Rd
├── debug_exercise_checker.Rd
├── safe.Rd
├── format_quiz.Rd
├── mark_as_correct_incorrect.Rd
├── learnr-package.Rd
├── question_radio.Rd
├── random_phrases_add.Rd
├── one_time.Rd
├── run_tutorial.Rd
├── tutorial_options.Rd
└── get_tutorial_state.Rd
├── .browserslistrc
├── .gitignore
├── tools
├── deploy_tutorials_on_local.R
├── deploy_tutorials_on_ci.R
├── update-ace.R
└── deploy_tutorials.R
├── babel.config.json
├── cran-comments.md
├── learnr.Rproj
├── README.Rmd
├── R
├── shinytest.R
├── ace.R
├── events_record.R
├── zzz.R
├── data_dir.R
├── learnr-package.R
├── html_selector.R
├── initialize.R
├── tutorial_package_dependencies.R
├── learnr_messages.R
├── html-dependencies.R
└── feedback.R
├── .Rbuildignore
├── package.json
├── learnr-js
└── build.js
├── README.md
└── DESCRIPTION
/.github/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/ace/mode-text.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/revdep/failures.md:
--------------------------------------------------------------------------------
1 | *Wow, no problems at all. :)*
--------------------------------------------------------------------------------
/revdep/problems.md:
--------------------------------------------------------------------------------
1 | *Wow, no problems at all. :)*
--------------------------------------------------------------------------------
/tests/manual/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 | rsconnect/*
--------------------------------------------------------------------------------
/vignettes/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 | ./*.R
3 | *.R
4 |
--------------------------------------------------------------------------------
/tests/testthat/tutorials/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 | rsconnect/*
--------------------------------------------------------------------------------
/sandbox/.gitignore:
--------------------------------------------------------------------------------
1 | *_files/
2 | *.html
3 | programming-basics*
4 |
--------------------------------------------------------------------------------
/inst/rmarkdown/templates/tutorial/skeleton/.gitignore:
--------------------------------------------------------------------------------
1 | skeleton.html
2 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/addition.md:
--------------------------------------------------------------------------------
1 | ```{r addition, exercise=TRUE}
2 | 1 + 1
3 | ```
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | inst/lib/*
2 | inst/rmarkdown/templates/tutorial/resources/tutorial-format.js
3 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutorialyaml-allowskip-topic.md:
--------------------------------------------------------------------------------
1 |
2 | ### Exercise 2 {data-allow-skip=TRUE}
3 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutorialyaml-progressive-topic.md:
--------------------------------------------------------------------------------
1 |
2 | ## Topic 1 {data-progressive=TRUE}
3 |
--------------------------------------------------------------------------------
/man/figures/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/man/figures/logo.png
--------------------------------------------------------------------------------
/revdep/.gitignore:
--------------------------------------------------------------------------------
1 | checks
2 | library
3 | checks.noindex
4 | library.noindex
5 | data.sqlite
6 | *.html
7 |
--------------------------------------------------------------------------------
/pkgdown/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/favicon/favicon.ico
--------------------------------------------------------------------------------
/pkgdown/assets/images/hello.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/assets/images/hello.png
--------------------------------------------------------------------------------
/pkgdown/assets/learnr-social.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/assets/learnr-social.png
--------------------------------------------------------------------------------
/pkgdown/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/pkgdown/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/revdep/email.yml:
--------------------------------------------------------------------------------
1 | release_date: ???
2 | rel_release_date: ???
3 | my_news_url: ???
4 | release_version: ???
5 | release_details: ???
6 |
--------------------------------------------------------------------------------
/vignettes/articles/images/slidy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/slidy.png
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # Browsers that we support
2 | last 2 versions
3 | not dead
4 | > 0.2%
5 | Firefox ESR
6 | phantomjs 2.1
7 | IE 11 # kinda
8 |
--------------------------------------------------------------------------------
/inst/internals/i18n_random_phrases.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/inst/internals/i18n_random_phrases.rds
--------------------------------------------------------------------------------
/inst/internals/i18n_translations.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/inst/internals/i18n_translations.rds
--------------------------------------------------------------------------------
/pkgdown/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/vignettes/articles/images/question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/question.png
--------------------------------------------------------------------------------
/vignettes/articles/images/solution.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/solution.png
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisecaption.md:
--------------------------------------------------------------------------------
1 | ```{r setup, include=FALSE}
2 | library(learnr)
3 | tutorial_options(exercise.cap = "Sandbox")
4 | ```
--------------------------------------------------------------------------------
/tests/testthat.R:
--------------------------------------------------------------------------------
1 |
2 | if (requireNamespace("testthat")) {
3 | library(testthat)
4 | library(learnr)
5 |
6 | test_check("learnr")
7 | }
8 |
--------------------------------------------------------------------------------
/vignettes/articles/images/exercises.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/exercises.png
--------------------------------------------------------------------------------
/vignettes/articles/images/questions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/questions.png
--------------------------------------------------------------------------------
/pkgdown/favicon/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/favicon/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/pkgdown/favicon/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/favicon/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/vignettes/articles/images/exercise-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/exercise-code.png
--------------------------------------------------------------------------------
/vignettes/articles/images/question-math.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/question-math.png
--------------------------------------------------------------------------------
/vignettes/articles/images/tutorial-sql.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/tutorial-sql.png
--------------------------------------------------------------------------------
/inst/tutorials/polyglot/portal_mammals.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/inst/tutorials/polyglot/portal_mammals.sqlite
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/runtutorial.md:
--------------------------------------------------------------------------------
1 | ```r
2 | learnr::run_tutorial("hello", package = "learnr")
3 | learnr::run_tutorial("slidy", package = "learnr")
4 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/videos-size.md:
--------------------------------------------------------------------------------
1 | {width="90%"}
2 |
3 | {width="560" height="315"}
4 |
--------------------------------------------------------------------------------
/pkgdown/favicon/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/favicon/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/pkgdown/favicon/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/favicon/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/pkgdown/favicon/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/pkgdown/favicon/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/vignettes/articles/images/exercise-blanks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/exercise-blanks.png
--------------------------------------------------------------------------------
/vignettes/articles/images/exercise-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/exercise-error.png
--------------------------------------------------------------------------------
/vignettes/articles/images/exercise-result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/exercise-result.png
--------------------------------------------------------------------------------
/vignettes/articles/images/format-tutorial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/format-tutorial.png
--------------------------------------------------------------------------------
/vignettes/articles/images/tutorial-hello.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/tutorial-hello.png
--------------------------------------------------------------------------------
/vignettes/articles/images/tutorial-slidy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/tutorial-slidy.png
--------------------------------------------------------------------------------
/inst/tutorials/ex-data-basics/images/flights.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/inst/tutorials/ex-data-basics/images/flights.png
--------------------------------------------------------------------------------
/vignettes/articles/images/questions-message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/questions-message.png
--------------------------------------------------------------------------------
/vignettes/img/shinyapps-publishing-advanced.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/img/shinyapps-publishing-advanced.png
--------------------------------------------------------------------------------
/vignettes/img/shinyapps-publishing-instances.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/img/shinyapps-publishing-instances.png
--------------------------------------------------------------------------------
/vignettes/img/shinyapps-publishing-metrics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/img/shinyapps-publishing-metrics.png
--------------------------------------------------------------------------------
/vignettes/articles/images/create-new-tutorial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/create-new-tutorial.png
--------------------------------------------------------------------------------
/vignettes/articles/images/exercise-completion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/exercise-completion.png
--------------------------------------------------------------------------------
/vignettes/articles/images/tutorial-ex-setup-r.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/tutorial-ex-setup-r.png
--------------------------------------------------------------------------------
/vignettes/articles/images/tutorial-ex-data-basics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/tutorial-ex-data-basics.png
--------------------------------------------------------------------------------
/vignettes/articles/images/tutorial-ex-data-filter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/tutorial-ex-data-filter.png
--------------------------------------------------------------------------------
/vignettes/articles/images/tutorial-ex-data-mutate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/tutorial-ex-data-mutate.png
--------------------------------------------------------------------------------
/vignettes/articles/images/tutorial-quiz_question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/tutorial-quiz_question.png
--------------------------------------------------------------------------------
/vignettes/articles/releases/images/blanks-warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/releases/images/blanks-warning.png
--------------------------------------------------------------------------------
/vignettes/articles/releases/images/error-checker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/releases/images/error-checker.png
--------------------------------------------------------------------------------
/vignettes/articles/releases/images/syntax-warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/releases/images/syntax-warning.png
--------------------------------------------------------------------------------
/vignettes/img/shinyapps-publishing-instance-size.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/img/shinyapps-publishing-instance-size.png
--------------------------------------------------------------------------------
/vignettes/articles/images/example-community-rtledu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/example-community-rtledu.png
--------------------------------------------------------------------------------
/vignettes/articles/images/shinyapps-deploy-faithful.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/shinyapps-deploy-faithful.png
--------------------------------------------------------------------------------
/vignettes/articles/releases/images/question-numeric.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/releases/images/question-numeric.png
--------------------------------------------------------------------------------
/inst/tutorials/ex-data-filter/images/transform-logical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/inst/tutorials/ex-data-filter/images/transform-logical.png
--------------------------------------------------------------------------------
/vignettes/articles/images/example-community-afrilearnr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/example-community-afrilearnr.png
--------------------------------------------------------------------------------
/vignettes/articles/images/example-community-qsslearnr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/example-community-qsslearnr.png
--------------------------------------------------------------------------------
/vignettes/articles/images/tutorial-ex-data-summarise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/tutorial-ex-data-summarise.png
--------------------------------------------------------------------------------
/vignettes/articles/images/tutorial-external-sortable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/tutorial-external-sortable.png
--------------------------------------------------------------------------------
/vignettes/articles/releases/images/r-python-exercise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/releases/images/r-python-exercise.png
--------------------------------------------------------------------------------
/vignettes/articles/images/example-community-dosstoolkit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/example-community-dosstoolkit.png
--------------------------------------------------------------------------------
/vignettes/articles/releases/images/blanks-warning-custom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/releases/images/blanks-warning-custom.png
--------------------------------------------------------------------------------
/vignettes/articles/releases/images/exercise-tab-example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/releases/images/exercise-tab-example.gif
--------------------------------------------------------------------------------
/vignettes/articles/releases/images/exercise-tab-example.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/releases/images/exercise-tab-example.mp4
--------------------------------------------------------------------------------
/vignettes/articles/releases/images/fancy-quotes-warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/releases/images/fancy-quotes-warning.png
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutorialyaml-progressive.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hello, Tutorial!"
3 | output:
4 | learnr::tutorial:
5 | progressive: true
6 | runtime: shiny_prerendered
7 | ---
8 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutorialyaml.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hello, Tutorial!"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | description: "Welcome to learnr tutorials!"
6 | ---
7 |
--------------------------------------------------------------------------------
/vignettes/articles/images/example-community-learntidymodels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/images/example-community-learntidymodels.png
--------------------------------------------------------------------------------
/inst/rmarkdown/templates/tutorial/resources/images/topicProgress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/inst/rmarkdown/templates/tutorial/resources/images/topicProgress.png
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exerciselines.md:
--------------------------------------------------------------------------------
1 | ```{r add-function, exercise=TRUE, exercise.lines=15}
2 | # Write a function to add two numbers together
3 | add_numbers <- function(a, b) {
4 |
5 | }
6 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutorialtoccollapsed.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hello, Tutorial!"
3 | output:
4 | learnr::tutorial:
5 | toc_float:
6 | collapsed: true
7 | runtime: shiny_prerendered
8 | ---
--------------------------------------------------------------------------------
/inst/rmarkdown/templates/tutorial/template.yaml:
--------------------------------------------------------------------------------
1 | name: Interactive Tutorial
2 | description: >
3 | Interactive tutorial containing figures, videos, R exercises and quiz questions.
4 | create_dir: true
5 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutorialyaml-allowskip.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hello, Tutorial!"
3 | output:
4 | learnr::tutorial:
5 | progressive: true
6 | allow_skip: true
7 | runtime: shiny_prerendered
8 | ---
--------------------------------------------------------------------------------
/vignettes/articles/releases/images/janko-ferlic-sfL_QOnmy00-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/department-for-transport/learnr/main/vignettes/articles/releases/images/janko-ferlic-sfL_QOnmy00-unsplash.jpg
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutorvideos.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |
4 | 
5 | 
6 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exerciseoptions.md:
--------------------------------------------------------------------------------
1 | ```{r setup, include=FALSE}
2 | library(learnr)
3 | tutorial_options(exercise.timelimit = 60)
4 | ```
5 |
6 | ```{r addition, exercise=TRUE, exercise.timelimit = 60}
7 | 1 + 1
8 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutoryamlids.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hello, Tutorial!"
3 | tutorial:
4 | id: "com.example.tutorials.my-first-tutorial"
5 | version: 2.1
6 | output: learnr::tutorial
7 | runtime: shiny_prerendered
8 | ---
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutorids.md:
--------------------------------------------------------------------------------
1 | options(
2 | tutorial.http_header_tutorial_id = "X-Tutorial-ID",
3 | tutorial.http_header_tutorial_version = "X-Tutorial-Version",
4 | tutorial.http_header_user_id = "X-Tutorial-UserID"
5 | })
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisecompletion.md:
--------------------------------------------------------------------------------
1 | ```{r setup, include=FALSE}
2 | library(learnr)
3 | tutorial_options(exercise.completion = FALSE)
4 | ```
5 |
6 | ```{r histogram-plot, exercise=TRUE, exercise.completion=TRUE}
7 |
8 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/question.md:
--------------------------------------------------------------------------------
1 | ```{r letter-a, echo=FALSE}
2 | question("What number is the letter A in the English alphabet?",
3 | answer("8"),
4 | answer("14"),
5 | answer("1", correct = TRUE),
6 | answer("23")
7 | )
8 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisetimelimit.md:
--------------------------------------------------------------------------------
1 | options(tutorial.exercise.timelimit = 10)
2 |
3 | ```{r setup, include=FALSE}
4 | tutorial_options(exercise.timelimit = 10)
5 | ```
6 |
7 | ```{r exercise1, exercise=TRUE, exercise.timelimit=10}
8 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exerciseblanks.md:
--------------------------------------------------------------------------------
1 | Fill in the blank to create an expression that adds up to **4**.
2 |
3 | ```{r blank, exercise = TRUE, exercise.blanks = "___+"}
4 | 1 + ____
5 | ```
6 |
7 | ```{r blank-solution}
8 | 1 + 3
9 | ```
10 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisesetupsuffix.md:
--------------------------------------------------------------------------------
1 | ```{r filter-setup}
2 | nycflights <- nycflights13::flights
3 | ```
4 |
5 | ```{r filter, exercise=TRUE}
6 | # Change the filter to select February rather than January
7 | nycflights <- filter(nycflights, month == 1)
8 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/questionretry.md:
--------------------------------------------------------------------------------
1 | ```{r letter-a, echo=FALSE}
2 | question("What number is the letter A in the English alphabet?",
3 | answer("8"),
4 | answer("14"),
5 | answer("1", correct = TRUE),
6 | answer("23"),
7 | allow_retry = TRUE
8 | )
9 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisehintdiv.md:
--------------------------------------------------------------------------------
1 | ```{r filter, exercise=TRUE}
2 | # filter the flights table to include only United and American flights
3 | flights
4 | ```
5 |
6 |
7 | **Hint:** You may want to use the dplyr `filter` function.
8 |
9 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/questionrandom.md:
--------------------------------------------------------------------------------
1 | ```{r letter-a, echo=FALSE}
2 | question("What number is the letter A in the English alphabet?",
3 | answer("8"),
4 | answer("14"),
5 | answer("1", correct = TRUE),
6 | answer("23"),
7 | random_answer_order = TRUE
8 | )
9 | ```
--------------------------------------------------------------------------------
/revdep/cran.md:
--------------------------------------------------------------------------------
1 | ## revdepcheck results
2 |
3 | We checked 14 reverse dependencies (13 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
4 |
5 | * We saw 0 new problems
6 | * We failed to check 0 packages
7 |
8 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisesolution.md:
--------------------------------------------------------------------------------
1 | ```{r filter, exercise=TRUE}
2 | # Change the filter to select February rather than January
3 | nycflights <- filter(nycflights, month == 1)
4 | ```
5 |
6 | ```{r filter-solution}
7 | nycflights <- filter(nycflights, month == 2)
8 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exerciseeval.md:
--------------------------------------------------------------------------------
1 | ```{r setup, include=FALSE}
2 | library(learnr)
3 | tutorial_options(exercise.eval = TRUE)
4 | ```
5 |
6 | ```{r filter, exercise=TRUE, exercise.eval=FALSE}
7 | # Change the filter to select February rather than January
8 | filter(nycflights, month == 1)
9 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/questionmath.md:
--------------------------------------------------------------------------------
1 | ```{r math, echo=FALSE}
2 | x <- 42
3 | question(sprintf("Suppose $x = %s$. Choose the correct statement:", x),
4 | answer(sprintf("$\\sqrt{x} = %d$", x + 1)),
5 | answer(sprintf("$x ^ 2 = %d$", x^2), correct = TRUE),
6 | answer("$\\sin x = 1$")
7 | )
8 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/questionmessages.md:
--------------------------------------------------------------------------------
1 | ```{r letter-a, echo=FALSE}
2 | question("What number is the letter A in the *English* alphabet?",
3 | answer("8"),
4 | answer("1", correct = TRUE),
5 | answer("2", message = "2 is close but it's the letter B rather than A."),
6 | answer("26")
7 | )
8 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutorrecorder.md:
--------------------------------------------------------------------------------
1 | tutorial_event_recorder <- function(tutorial_id, tutorial_version, user_id,
2 | event, data) {
3 | cat(tutorial_id, " (", tutorial_version, "): ", user_id , "\n", sep = "")
4 | cat("event: ", event, "\n", sep = "")
5 | }
6 |
--------------------------------------------------------------------------------
/inst/lib/idb-keyval/note.md:
--------------------------------------------------------------------------------
1 | [Original File Location](https://github.com/jakearchibald/idb-keyval/blob/master/dist/idb-keyval-iife-compat.min.js)
2 | [New File Location](https://github.com/schloerke/idb-keyval/blob/idb-keyval/dist/idb-keyval-iife-compat.min.js)
3 | [PR](https://github.com/jakearchibald/idb-keyval/pull/73)
4 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisefeedback.md:
--------------------------------------------------------------------------------
1 | ```r
2 | checker <- function(
3 | label,
4 | user_code,
5 | solution_code,
6 | check_code,
7 | envir_result,
8 | evaluate_result,
9 | envir_prep,
10 | ...
11 | ) {
12 | list(message = "Great job!", correct = TRUE, location = "append")
13 | }
14 | ```
15 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisecheck.md:
--------------------------------------------------------------------------------
1 | ```{r setup, include=FALSE}
2 | library(learnr)
3 | gradethis::gradethis_setup()
4 | ```
5 |
6 | * Submit `1+1` to receive a correct grade.
7 |
8 | ```{r exercise1, exercise = TRUE}
9 |
10 | ```
11 |
12 | ```{r exercise1-check}
13 | grade_result(
14 | pass_if(~identical(.result, 2))
15 | )
16 | ```
17 |
--------------------------------------------------------------------------------
/inst/staticexports/assertions.R:
--------------------------------------------------------------------------------
1 | is_AsIs <- function(x) {
2 | inherits(x, "AsIs")
3 | }
4 |
5 | is_html_tag <- function(x) {
6 | inherits(x, c("shiny.tag", "shiny.tag.list"))
7 | }
8 |
9 | is_html_chr <- function(x) {
10 | is.character(x) && inherits(x, "html")
11 | }
12 |
13 | is_html_any <- function(x) {
14 | is_html_tag(x) || is_html_chr(x)
15 | }
16 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercise-tests.md:
--------------------------------------------------------------------------------
1 | ```{r addition, exercise = TRUE}
2 |
3 | ```
4 |
5 | ```{r addition-solution}
6 | 1 + 1
7 | ```
8 |
9 | ```{r addition-tests}
10 | 1 + 1
11 |
12 | # one plus two ----
13 | 1 + 2
14 |
15 | # one plus three ----
16 | 1 + 3
17 |
18 | # one equals three ----
19 | 1 = 3
20 |
21 | # 2 minus one ----
22 | 2 - 1
23 | ```
24 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisecodecheck.md:
--------------------------------------------------------------------------------
1 | ```{r setup, include=FALSE}
2 | library(learnr)
3 | gradethis::gradethis_setup()
4 | ```
5 |
6 | * Submit `1+1` to receive a correct grade.
7 |
8 | ```{r exercise1, exercise = TRUE}
9 |
10 | ```
11 |
12 | ```{r exercise1-solution}
13 | 1+1
14 | ```
15 |
16 | ```{r exercise1-code-check}
17 | grade_code()
18 | ```
19 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisedata.md:
--------------------------------------------------------------------------------
1 | ```{r setup}
2 | library(tidyverse)
3 |
4 | flights_jan <-
5 | nycflights13::flights %>%
6 | filter(month == 2)
7 |
8 | dir.create("data", showWarnings = FALSE)
9 | write_csv(flights_jan, "data/flights_jan.csv")
10 | ```
11 |
12 | ```{r read-flights, exercise=TRUE}
13 | read_csv("data/flights_jan.csv")
14 | ```
15 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/hello-learnr.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hello, Tutorial!"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ```{r setup, include=FALSE}
8 | library(learnr)
9 | ```
10 |
11 | This code computes the answer to one plus one,
12 | change it so it computes two plus two:
13 |
14 | ```{r addition, exercise=TRUE}
15 | 1 + 1
16 | ```
--------------------------------------------------------------------------------
/inst/staticexports/strings.R:
--------------------------------------------------------------------------------
1 |
2 | str_trim <- function(x, side = "both", character = "\\s") {
3 | if (side %in% c("both", "left", "start")) {
4 | rgx <- sprintf("^%s+", character)
5 | x <- sub(rgx, "", x)
6 | }
7 | if (side %in% c("both", "right", "end")) {
8 | rgx <- sprintf("%s+$", character)
9 | x <- sub(rgx, "", x)
10 | }
11 | x
12 | }
13 |
--------------------------------------------------------------------------------
/tests/testthat/test-tutorial-format.R:
--------------------------------------------------------------------------------
1 | test_that("tutorial() returns an rmarkdown format", {
2 | expect_true(inherits(tutorial(), "rmarkdown_output_format"))
3 | })
4 |
5 | test_that("tutorial() does not support anchor_sections", {
6 | expect_error(tutorial(anchor_sections = TRUE), "do not support")
7 | expect_error(tutorial(anchor_sections = FALSE), "do not support")
8 | })
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .Ruserdata
5 | .DS_Store
6 | .vscode
7 | __pycache__
8 | Untitled
9 | issues
10 | node_modules
11 |
12 | # atom config folder
13 | .atom/
14 |
15 | # html tutorial output
16 | inst/tutorials/*/*.html
17 | inst/tutorials/*/*.sqlite
18 |
19 | # deployment output
20 | rsconnect/
21 | inst/doc
22 | doc
23 | Meta
24 | reference/
25 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisesetup.md:
--------------------------------------------------------------------------------
1 | ```{r setup, include=FALSE}
2 | nycflights <- nycflights13::flights
3 | ```
4 |
5 | ```{r filter, exercise=TRUE}
6 | # Change the filter to select February rather than January
7 | filter(nycflights, month == 1)
8 | ```
9 |
10 | ```{r arrange, exercise=TRUE}
11 | # Change the sort order to Ascending
12 | arrange(nycflights, desc(arr_delay))
13 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/questionmultiple.md:
--------------------------------------------------------------------------------
1 | ```{r where-am-i, echo=FALSE}
2 | question("Where are you right now? (select ALL that apply)",
3 | answer("Planet Earth", correct = TRUE),
4 | answer("Pluto"),
5 | answer("At a computing device", correct = TRUE),
6 | answer("In the Milky Way", correct = TRUE),
7 | incorrect = "Incorrect. You're on Earth, in the Milky Way, at a computer.")
8 | )
9 | ```
--------------------------------------------------------------------------------
/tools/deploy_tutorials_on_local.R:
--------------------------------------------------------------------------------
1 |
2 |
3 | if (!requireNamespace("remotes")) {
4 | install.packages("remotes")
5 | }
6 | remotes::install_cran("callr")
7 |
8 | # call in separate / non-interactive process
9 | # to avoid local dev version to be loaded and confuse packrat
10 | callr::r(
11 | function() {
12 | source("tools/deploy_tutorials.R")
13 | },
14 | show = TRUE
15 | )
16 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": "auto",
7 | "spec": true,
8 | "useBuiltIns": "usage",
9 | "forceAllTransforms": true,
10 | "corejs": {
11 | "version": "3.21",
12 | "proposals": false
13 | }
14 | }
15 | ]
16 | ],
17 | "comments": false
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisehints.md:
--------------------------------------------------------------------------------
1 | ```{r filter, exercise=TRUE}
2 | # filter the flights table to include only United and American flights
3 | flights
4 | ```
5 |
6 | ```{r filter-hint-1}
7 | filter(flights, ...)
8 | ```
9 |
10 | ```{r filter-hint-2}
11 | filter(flights, UniqueCarrier=="AA")
12 | ```
13 |
14 | ```{r filter-hint-3}
15 | filter(flights, UniqueCarrier=="AA" | UniqueCarrier=="UA")
16 | ```
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/shiny.md:
--------------------------------------------------------------------------------
1 | ```{r, echo=FALSE}
2 | sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 30)
3 | plotOutput("distPlot")
4 | ```
5 |
6 | ```{r, context="server"}
7 | output$distPlot <- renderPlot({
8 | x <- faithful[, 2] # Old Faithful Geyser data
9 | bins <- seq(min(x), max(x), length.out = input$bins + 1)
10 | hist(x, breaks = bins, col = 'darkgray', border = 'white')
11 | })
12 | ```
--------------------------------------------------------------------------------
/cran-comments.md:
--------------------------------------------------------------------------------
1 | This is a patch release containing a few small bug fixes and minor features.
2 |
3 | ## R CMD check results
4 |
5 | 0 errors | 0 warnings | 0 notes
6 |
7 | ## revdepcheck results
8 |
9 | We checked 14 reverse dependencies (13 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
10 |
11 | * We saw 0 new problems
12 | * We failed to check 0 packages
13 |
--------------------------------------------------------------------------------
/inst/internals/templates/exercise-setup.Rmd:
--------------------------------------------------------------------------------
1 | ```{r learnr-setup, include=FALSE}
2 | # hack the pager function so that we can print help with custom pager function
3 | # http://stackoverflow.com/questions/24146843/including-r-help-in-knitr-output
4 | options(pager = function(files, header, title, delete.file) {
5 | cat(do.call('c', lapply(files, readLines)), sep = '\n')
6 | })
7 | knitr::opts_chunk$set(echo = FALSE, comment = NA, error = FALSE)
8 | ```
9 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisesolutionhidden.md:
--------------------------------------------------------------------------------
1 | ```{r filter, exercise=TRUE}
2 | # filter the flights table to include only United and American flights
3 | flights
4 | ```
5 |
6 | ```{r filter-hint-1}
7 | filter(flights, ...)
8 | ```
9 |
10 | ```{r filter-hint-2}
11 | filter(flights, UniqueCarrier=="AA")
12 | ```
13 |
14 | ```{r filter-solution, exercise.reveal_solution = FALSE}
15 | filter(flights, UniqueCarrier=="AA" | UniqueCarrier=="UA")
16 | ```
--------------------------------------------------------------------------------
/tests/testthat/test-options.R:
--------------------------------------------------------------------------------
1 |
2 | context("options")
3 |
4 | test_that("tutor options set knitr options", {
5 | tutorial_options(exercise.cap = "Caption")
6 | expect_equal(knitr::opts_chunk$get("exercise.cap"), "Caption")
7 | })
8 |
9 | test_that("tutor options don't set knitr options when excluded from the call", {
10 | tutorial_options(exercise.cap = "Caption")
11 | expect_equal(knitr::opts_chunk$get("exercise.eval"), NULL)
12 | })
13 |
14 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisesetupshared.md:
--------------------------------------------------------------------------------
1 | ```{r prepare-flights}
2 | nycflights <- nycflights13::flights
3 | ```
4 |
5 | ```{r filter, exercise=TRUE, exercise.setup = "prepare-flights"}
6 | # Change the filter to select February rather than January
7 | filter(nycflights, month == 1)
8 | ```
9 |
10 | ```{r arrange, exercise=TRUE, exercise.setup = "prepare-flights"}
11 | # Change the sort order to Ascending
12 | arrange(nycflights, desc(arr_delay))
13 | ```
--------------------------------------------------------------------------------
/tests/testthat/setup-chunks/setup-cycle-self.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Chained setup chunks: Cycles"
3 | author: "Nischal Shrestha"
4 | output: learnr::tutorial
5 | runtime: shiny_prerendered
6 | description: >
7 | This is a demo of catching cyclic dependencies with chained setup chunks starting from a setup chunk.
8 | ---
9 |
10 | ```{r setup, include = FALSE}
11 | library(learnr)
12 | ```
13 |
14 | ```{r dataA, exercise.setup = "dataA"}
15 | 1
16 | ```
17 |
18 |
--------------------------------------------------------------------------------
/inst/internals/learnr.py:
--------------------------------------------------------------------------------
1 |
2 | class __learnr__:
3 | '''An internal class to provide Python utility functions'''
4 |
5 | @staticmethod
6 | def deep_copy(dict, deep=True):
7 | import copy
8 | from types import ModuleType
9 | new_dict = {}
10 | for k, v in dict.items():
11 | if (k == "r" or isinstance(v, ModuleType)):
12 | new_dict[k] = v
13 | else:
14 | new_dict[k] = copy.deepcopy(v) if deep else v
15 | return new_dict
16 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisechained.md:
--------------------------------------------------------------------------------
1 | ```{r prepare-flights}
2 | nycflights <- nycflights13::flights
3 | ```
4 |
5 | ```{r filtered-flights, exercise=TRUE, exercise.setup = "prepare-flights"}
6 | # Filter to select February rather than January
7 | flights_february <- filter(nycflights, month == 2)
8 | ```
9 |
10 | ```{r arrange, exercise=TRUE, exercise.setup = "filtered-flights"}
11 | # Change the sort order to Ascending
12 | arrange(flights_february, desc(arr_delay))
13 | ```
14 |
--------------------------------------------------------------------------------
/tests/testthat/setup-chunks/exercise-cycle-self.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Chained setup chunks: Cycles"
3 | author: "Nischal Shrestha"
4 | output: learnr::tutorial
5 | runtime: shiny_prerendered
6 | description: >
7 | This is a demo of catching cyclic dependencies with chained setup chunks starting from an exercise.
8 | ---
9 |
10 | ```{r setup, include = FALSE}
11 | library(learnr)
12 | ```
13 |
14 | ```{r data1, exercise=TRUE, exercise.setup = "data1"}
15 | 1
16 |
17 | ```
18 |
19 |
--------------------------------------------------------------------------------
/pkgdown/extra.css:
--------------------------------------------------------------------------------
1 | /* examples gallery */
2 | a.thumbnail.active,
3 | a.thumbnail:focus,
4 | a.thumbnail:hover {
5 | border-color: #FEDB00;
6 | }
7 |
8 | .contents.gallery {
9 | max-width: min(100%, 72rem);
10 | }
11 |
12 | #banner {
13 | margin-top: 90px;
14 | height: 300px;
15 | width: 100%;
16 | z-index: 0;
17 | background-size: cover;
18 | background-position: 50%;
19 | background-repeat: no-repeat;
20 | }
21 |
22 | #banner + .row > main {
23 | margin-top: 0;
24 | }
25 |
--------------------------------------------------------------------------------
/tests/testthat/tutorials/hint-div/hint-div.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Next hint tests"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ## Tests
8 |
9 | ### HTML hint {#html}
10 |
11 | ```{r html, exercise=TRUE}
12 | # html
13 | ```
14 |
15 |
16 | This is the HTML hint .
17 |
18 |
19 | ### Markdown hint {#md}
20 |
21 | ```{r md, exercise=TRUE}
22 | # md
23 | ```
24 |
25 | ::: {#md-hint}
26 | This is the **md hint**.
27 | :::
28 |
--------------------------------------------------------------------------------
/tests/testthat/tutorials/missing-exercise-checker.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Missing Exercise Checker"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ```{r setup, include=FALSE}
8 | library(learnr)
9 | ```
10 |
11 | ## Topic 1
12 |
13 |
14 |
15 | ```{r two-plus-two, exercise=TRUE}
16 |
17 | ```
18 |
19 | ```{r two-plus-two-check}
20 | # presence of a check chunk implies exercise.checker is required
21 | ```
22 |
23 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exercisesetupchained.md:
--------------------------------------------------------------------------------
1 | ```{r prepare-flights}
2 | nycflights <- nycflights13::flights
3 | ```
4 |
5 | ```{r filtered-flights, exercise.setup = "prepare-flights"}
6 | # Prepare a filtered nycflights to select February rather than January
7 | flights_february <- filter(nycflights, month == 2)
8 | ```
9 |
10 | ```{r arrange, exercise=TRUE, exercise.setup = "filtered-flights"}
11 | # Change the sort order to Ascending
12 | arrange(flights_february, desc(arr_delay))
13 | ```
14 |
--------------------------------------------------------------------------------
/learnr.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 | AutoAppendNewline: Yes
16 | StripTrailingWhitespace: Yes
17 |
18 | BuildType: Package
19 | PackageUseDevtools: Yes
20 | PackageInstallArgs: --no-multiarch --with-keep.source --no-byte-compile
21 | PackageRoxygenize: rd,collate,namespace
22 |
--------------------------------------------------------------------------------
/pkgdown/templates/in-header.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 | {{#includes}}{{{in_header}}}{{/includes}}
13 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/ace/mode-plain_text.js:
--------------------------------------------------------------------------------
1 | ace.define("ace/mode/plain_text",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/text_highlight_rules","ace/mode/behaviour"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./text_highlight_rules").TextHighlightRules,o=e("./behaviour").Behaviour,u=function(){this.HighlightRules=s,this.$behaviour=new o};r.inherits(u,i),function(){this.type="text",this.getNextLineIndent=function(e,t,n){return""},this.$id="ace/mode/plain_text"}.call(u.prototype),t.Mode=u})
--------------------------------------------------------------------------------
/tests/testthat/setup-chunks/error-check-chunk_bad.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Error checking chunks require standard check chunks"
3 | author: "Garrick Aden-Buie"
4 | output: learnr::tutorial
5 | runtime: shiny_prerendered
6 | description: >
7 | Error check chunks, -error-check, require a standard -check chunk
8 | ---
9 |
10 | ```{r setup, include = FALSE}
11 | library(learnr)
12 | tutorial_options(exercise.checker = identity)
13 | ```
14 |
15 | ```{r ex, exercise = TRUE}
16 | 1
17 | ```
18 |
19 | ```{r ex-error-check}
20 | 2
21 | ```
22 |
--------------------------------------------------------------------------------
/tests/testthat/setup-chunks/error-check-chunk_good.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Error checking chunks require standard check chunks"
3 | author: "Garrick Aden-Buie"
4 | output: learnr::tutorial
5 | runtime: shiny_prerendered
6 | description: This example should render
7 | ---
8 |
9 | ```{r setup, include = FALSE}
10 | library(learnr)
11 | tutorial_options(exercise.checker = identity)
12 | ```
13 |
14 | ```{r ex, exercise = TRUE}
15 | 1
16 | ```
17 |
18 | ```{r ex-error-check}
19 | 2
20 | ```
21 |
22 | ```{r ex-check}
23 | 3
24 | ```
25 |
--------------------------------------------------------------------------------
/tests/testthat/setup-chunks/setup-cycle-two.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Chained setup chunks: Cycles"
3 | author: "Nischal Shrestha"
4 | output: learnr::tutorial
5 | runtime: shiny_prerendered
6 | description: >
7 | This is a demo of catching cyclic dependencies with chained setup chunks starting from a setup chunk.
8 | ---
9 |
10 | ```{r setup, include = FALSE}
11 | library(learnr)
12 | ```
13 |
14 | ```{r dataA, exercise.setup = "dataB"}
15 | 1
16 | ```
17 |
18 | ```{r dataB, exercise.setup = "dataA"}
19 | 2
20 | ```
21 |
22 |
23 |
--------------------------------------------------------------------------------
/inst/tutorials/hello/hello.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hello, Tutorial!"
3 | author: "J.J. Allaire"
4 | date: "March 1st, 2017"
5 | output: learnr::tutorial
6 | runtime: shiny_prerendered
7 | # Do not index/display tutorial by setting `private: true`
8 | # private: true
9 | description: >
10 | This is a demo tutorial.
11 | ---
12 |
13 | ```{r setup, include=FALSE}
14 | library(learnr)
15 | ```
16 |
17 | The following code computes the answer to 1+1. Change it so it computes 2 + 2:
18 |
19 | ```{r addition, exercise=TRUE}
20 | 1 + 1
21 | ```
22 |
--------------------------------------------------------------------------------
/README.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "learnr"
3 | output: md_document
4 | ---
5 |
6 | # learnr
7 |
8 | ```{r child="man/rmd-fragments/badges.Rmd"}
9 | ```
10 |
11 | ```{r child="man/rmd-fragments/learnr-overview.Rmd"}
12 | ```
13 |
14 | Learn more about the **learnr** package and try example tutorials online at .
15 |
16 | ## Installation
17 |
18 | ```{r child="man/rmd-fragments/learnr-install.Rmd"}
19 | ```
20 |
--------------------------------------------------------------------------------
/man/rmd-fragments/learnr-install.Rmd:
--------------------------------------------------------------------------------
1 | Install the latest official learnr release from CRAN:
2 |
3 | ``` r
4 | install.packages("learnr")
5 | ```
6 |
7 | Or you can install the most recent version in-development from GitHub with the [remotes package](https://remotes.r-lib.org):
8 |
9 | ``` r
10 | # install.packages("remotes")
11 | remotes::install_github("rstudio/learnr")
12 | ```
13 |
14 | learnr works best with a recent [version of RStudio](https://posit.co/download/rstudio-desktop/) (v1.0.136 or later) which includes tools for easily running and previewing tutorials.
15 |
--------------------------------------------------------------------------------
/tests/testthat/setup-chunks/exercise-cycle-two.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Chained setup chunks: Cycles"
3 | author: "Nischal Shrestha"
4 | output: learnr::tutorial
5 | runtime: shiny_prerendered
6 | description: >
7 | This is a demo of catching cyclic dependencies with chained setup chunks starting from an exercise chunk.
8 | ---
9 |
10 | ```{r setup, include = FALSE}
11 | library(learnr)
12 | ```
13 |
14 | ```{r data1, exercise=TRUE, exercise.setup = "data2"}
15 | 1
16 |
17 | ```
18 |
19 | ```{r data2, exercise=TRUE, exercise.setup = "data1"}
20 | 2
21 | ```
22 |
23 |
24 |
--------------------------------------------------------------------------------
/tests/testthat/setup-chunks/setup-cycle.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Chained setup chunks: Cycles"
3 | author: "Nischal Shrestha"
4 | output: learnr::tutorial
5 | runtime: shiny_prerendered
6 | description: >
7 | This is a demo of catching cyclic dependencies with chained setup chunks starting from a setup chunk.
8 | ---
9 |
10 | ```{r setup, include = FALSE}
11 | library(learnr)
12 | ```
13 |
14 | ```{r dataA, exercise.setup = "dataC"}
15 | 1
16 | ```
17 |
18 | ```{r dataB, exercise.setup = "dataA"}
19 | 2
20 | ```
21 |
22 | ```{r dataC, exercise.setup = "dataB"}
23 | 3
24 | ```
25 |
--------------------------------------------------------------------------------
/tools/deploy_tutorials_on_ci.R:
--------------------------------------------------------------------------------
1 |
2 | if (!requireNamespace("remotes")) {
3 | install.packages("remotes")
4 | }
5 | remotes::install_cran("rsconnect")
6 |
7 | # Set the account info for deployment.
8 | rsconnect::setAccountInfo(
9 | name = Sys.getenv("SHINYAPPS_NAME"), # learnr-examples
10 | token = Sys.getenv("SHINYAPPS_TOKEN"),
11 | secret = Sys.getenv("SHINYAPPS_SECRET")
12 | )
13 |
14 | # deploy all tutorials
15 | # deploy using callr with `show = TRUE`
16 | # to avoid "no output to travis console for 10 mins" error
17 | source("tools/deploy_tutorials_on_local.R")
18 |
--------------------------------------------------------------------------------
/tests/testthat/test-storage.R:
--------------------------------------------------------------------------------
1 |
2 | context("storage")
3 |
4 | test_that("filesystem storage can be created", {
5 | fs <- filesystem_storage(tempfile())
6 | expect_equal(fs$type, "local")
7 | })
8 |
9 | test_that("objects cna be saved into filesystem storage", {
10 | fs <- filesystem_storage(tempfile())
11 | fs$save_object("tutorial_id", "tutorial_version", "user_id", "object_id", "data")
12 | obj <- fs$get_object("tutorial_id", "tutorial_version", "user_id", "object_id")
13 | expect_equal(obj, "data")
14 | fs$remove_all_objects("tutorial_id", "tutorial_version", "user_id")
15 | })
--------------------------------------------------------------------------------
/R/shinytest.R:
--------------------------------------------------------------------------------
1 | # Input processor, for generating code in shinytest Recorder app
2 | register_shinytest_inputprocessor <- function() {
3 | if (is_installed("shinytest2", "0.1.0")) {
4 | shinytest2::register_input_processor("learnr.exercise", function(value) {
5 | # Drop all information from `value` except `code`.
6 | value <- value["code"]
7 | dput_to_string(value)
8 | })
9 | }
10 | }
11 |
12 | # Snapshot preprocessor, for massaging input value before taking snapshot.
13 | snapshotPreprocessorLearnrExercise <- function(value) {
14 | value$timestamp <- NULL
15 | value
16 | }
17 |
--------------------------------------------------------------------------------
/tests/testthat/setup-chunks/exercise-global-setup.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Exercise-specific global setup chunk"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | description: >
6 | A demo tutorial with a separate global exercise setup chunk
7 | ---
8 |
9 | ```{r setup, include = FALSE}
10 | library(learnr)
11 | ```
12 |
13 | ```{r setup-global-exercise}
14 | global <- 0
15 | ```
16 |
17 |
18 |
19 | ```{r data1, exercise = TRUE}
20 | 1
21 | ```
22 |
23 | ```{r data1-setup}
24 | 2
25 | ```
26 |
27 |
--------------------------------------------------------------------------------
/tests/testthat/test-dependency.R:
--------------------------------------------------------------------------------
1 |
2 | context("dependency")
3 |
4 | test_that("tutor html dependencies can be retreived", {
5 | dep <- tutorial_html_dependency()
6 | expect_equal(dep$name, "tutorial")
7 | })
8 |
9 | test_that("tutorial package dependencies can be enumerated", {
10 | packages <- tutorial_package_dependencies("ex-data-summarise", "learnr")
11 | expect_true("tidyverse" %in% packages)
12 | })
13 | test_that("Per package, tutorial package dependencies can be enumerated", {
14 | packages <- tutorial_package_dependencies(package = "learnr")
15 | expect_true("tidyverse" %in% packages)
16 | })
17 |
--------------------------------------------------------------------------------
/man/tutorial_html_dependency.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/html-dependencies.R
3 | \name{tutorial_html_dependency}
4 | \alias{tutorial_html_dependency}
5 | \title{Tutorial HTML dependency}
6 | \usage{
7 | tutorial_html_dependency()
8 | }
9 | \value{
10 | \pkg{learnr}'s HTML dependencies
11 | }
12 | \description{
13 | HTML dependency for core tutorial JS and CSS. This should be included as a
14 | dependency for custom tutorial formats that wish to ensure that that
15 | \code{tutorial.js} and \code{tutorial.css} are loaded prior their own scripts and
16 | stylesheets.
17 | }
18 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/questionquiz.md:
--------------------------------------------------------------------------------
1 | ```{r quiz1, echo=FALSE}
2 | quiz(caption = "Quiz 1",
3 | question("What number is the letter A in the *English* alphabet?",
4 | answer("8"),
5 | answer("14"),
6 | answer("1", correct = TRUE),
7 | answer("23")
8 | ),
9 | question("Where are you right now? (select ALL that apply)",
10 | answer("Planet Earth", correct = TRUE),
11 | answer("Pluto"),
12 | answer("At a computing device", correct = TRUE),
13 | answer("In the Milky Way", correct = TRUE),
14 | incorrect = "Incorrect. You're on Earth, in the Milky Way, at a computer."
15 | )
16 | )
17 | ```
--------------------------------------------------------------------------------
/inst/lib/idb-keyval/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016, Jake Archibald
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/inst/tutorials/slidy/slidy.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | output: slidy_presentation
3 | runtime: shiny_prerendered
4 | title: "Slidy demo"
5 | description: >
6 | This tutorial demonstrates how one can use dygraphs (with the dygraph package)
7 | as part of a learnr tutorial.
8 | ---
9 |
10 | ```{r setup, include=FALSE}
11 | library(learnr)
12 | library(dygraphs)
13 | ```
14 |
15 | ## Time Series with dygraphs
16 |
17 | Modify the dyOptions to customize the graph's appearance:
18 |
19 | ```{r dygraph-options, exercise=TRUE, exercise.eval=TRUE, fig.height=5.5}
20 | dygraph(ldeaths) %>%
21 | dyOptions(fillGraph = TRUE, drawGrid = TRUE)
22 | ```
23 |
--------------------------------------------------------------------------------
/tests/testthat/setup-chunks/exercise-cycle.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Chained setup chunks: Cycles"
3 | author: "Nischal Shrestha"
4 | output: learnr::tutorial
5 | runtime: shiny_prerendered
6 | description: >
7 | This is a demo of catching cyclic dependencies with chained setup chunks starting from an exercise chunk.
8 | ---
9 |
10 | ```{r setup, include = FALSE}
11 | library(learnr)
12 | ```
13 |
14 | ```{r data1, exercise=TRUE, exercise.setup = "data3"}
15 | 1
16 |
17 | ```
18 |
19 | ```{r data2, exercise=TRUE, exercise.setup = "data1"}
20 | 2
21 | ```
22 |
23 | ```{r data3, exercise=as.logical(1), exercise.setup = "data2"}
24 | 3
25 |
26 | ```
27 |
--------------------------------------------------------------------------------
/tests/testthat/tutorials/knitr-hooks_empty-exercise/empty-exercise.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: Empty Exercise Code
3 | output: learnr::tutorial
4 | runtime: shinyrmd
5 | ---
6 |
7 | ```{r setup, include = FALSE}
8 | library(learnr)
9 | tutorial_options(exercise.lines = 4L)
10 |
11 | knitr::opts_chunk$set(
12 | echo = FALSE,
13 | custom_chunk_opt = "default",
14 | fig.path = "figures",
15 | cache.path = "cache"
16 | )
17 | ```
18 |
19 | ## Test
20 |
21 | ### Empty
22 |
23 | ```{r empty, exercise = TRUE, custom_chunk_opt = "custom"}
24 | ```
25 |
26 | ```{r empty-hint}
27 | # hint code
28 | ```
29 |
30 | ```{r empty-solution}
31 | mtcars
32 | ```
33 |
--------------------------------------------------------------------------------
/tests/testthat/setup-chunks/exercise-cycle-default-setup.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Chained setup chunks: Cycles"
3 | author: "Nischal Shrestha"
4 | output: learnr::tutorial
5 | runtime: shiny_prerendered
6 | description: >
7 | This is a demo of catching cyclic dependencies with setup chunks starting from an exercise chunk with no exercise.setup set.
8 | ---
9 |
10 | ```{r setup, include = FALSE}
11 | library(learnr)
12 | ```
13 |
14 |
15 |
16 | ```{r data1, exercise = TRUE}
17 | 1
18 | ```
19 |
20 | ```{r data1-setup, exercise.setup = "data1"}
21 | 2
22 | ```
23 |
24 |
--------------------------------------------------------------------------------
/tests/testthat/test-shinytest2-aaa.R:
--------------------------------------------------------------------------------
1 | # https://github.com/rstudio/shinytest2/blob/c29b78e9/tests/testthat/test-aaa.R
2 | skip_on_cran() # Uses chromote
3 | skip_on_ci_if_not_pr()
4 |
5 | # Try to warm up chromote. IDK why it fails on older versions of R.
6 | test_that("Chromote loads", {
7 | on_ci <- isTRUE(as.logical(Sys.getenv("CI")))
8 | skip_if(!on_ci, "Not on CI")
9 |
10 | # Wrap in a `try()` as the test doesn't matter
11 | # Only the action of trying to open chromote matters
12 | try({
13 | chromote <- utils::getFromNamespace("default_chromote_object", "chromote")()
14 | chromote$new_session()
15 | })
16 |
17 | expect_true(TRUE)
18 | })
19 |
--------------------------------------------------------------------------------
/tests/testthat/tutorials/knitr-hooks_empty-exercise/full-exercise.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: Empty Exercise Code
3 | output: learnr::tutorial
4 | runtime: shinyrmd
5 | ---
6 |
7 | ```{r setup, include = FALSE}
8 | library(learnr)
9 | tutorial_options(exercise.lines = 4L)
10 |
11 | knitr::opts_chunk$set(
12 | echo = FALSE,
13 | custom_chunk_opt = "default",
14 | fig.path = "figures",
15 | cache.path = "cache"
16 | )
17 | ```
18 |
19 | ## Test
20 |
21 | ### Empty
22 |
23 | ```{r empty, exercise = TRUE, custom_chunk_opt = "custom"}
24 |
25 |
26 |
27 |
28 | ```
29 |
30 | ```{r empty-hint}
31 | # hint code
32 | ```
33 |
34 | ```{r empty-solution}
35 | mtcars
36 | ```
37 |
--------------------------------------------------------------------------------
/inst/rmarkdown/templates/tutorial/resources/images/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/testthat/tutorials/exercise-option-is-symbol.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Test: Exercise Option is a Symbol"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ```{r setup, include=FALSE}
8 | library(learnr)
9 | do_exercise <- TRUE
10 | knitr::opts_chunk$set(echo = FALSE)
11 | ```
12 |
13 |
14 | ## Topic
15 |
16 | ### Exercise T
17 |
18 | *Here's a simple exercise with an empty code chunk provided for entering the answer.*
19 |
20 | Write the R code required to add two plus two:
21 |
22 | ```{r symbol, exercise = T}
23 | "symbol"
24 | ```
25 |
26 | ### Exercise Variable
27 |
28 | ```{r variable, exercise = do_exercise}
29 | "variable (also a symbol)"
30 | ```
31 |
--------------------------------------------------------------------------------
/inst/rmarkdown/templates/tutorial/resources/images/exerciseDone.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/tutorstorage.md:
--------------------------------------------------------------------------------
1 | options(tutorial.storage = list(
2 |
3 | # save an arbitrary R object "data" to storage
4 | save_object = function(tutorial_id, tutorial_version, user_id, object_id, data) {
5 |
6 | },
7 |
8 | # retreive a single R object from storage
9 | get_object = function(tutorial_id, tutorial_version, user_id, object_id) {
10 | NULL
11 | },
12 |
13 | # retreive a list of all R objects stored
14 | get_objects = function(tutorial_id, tutorial_version, user_id) {
15 | list()
16 | },
17 |
18 | # remove all stored R objects
19 | remove_all_objects = function(tutorial_id, tutorial_version, user_id) {
20 |
21 | }
22 | )
23 |
24 |
--------------------------------------------------------------------------------
/man/initialize_tutorial.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/initialize.R
3 | \name{initialize_tutorial}
4 | \alias{initialize_tutorial}
5 | \title{Initialize tutorial R Markdown extensions}
6 | \usage{
7 | initialize_tutorial()
8 | }
9 | \value{
10 | If not previously run, initializes knitr hooks and provides the
11 | required \code{\link[rmarkdown:shiny_prerendered_chunk]{rmarkdown::shiny_prerendered_chunk()}}s to initialize \pkg{learnr}.
12 | }
13 | \description{
14 | One time initialization of R Markdown extensions required by the
15 | \pkg{learnr} package. This function is typically called automatically
16 | as a result of using exercises or questions.
17 | }
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name : Feature request
3 | about : Request a new feature.
4 | ---
5 |
6 |
15 |
--------------------------------------------------------------------------------
/man/safe_env.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/run.R
3 | \name{safe_env}
4 | \alias{safe_env}
5 | \title{Safe R CMD environment}
6 | \usage{
7 | safe_env()
8 | }
9 | \value{
10 | A list of envvars, modified from \code{\link[callr:rcmd_safe_env]{callr::rcmd_safe_env()}}.
11 | }
12 | \description{
13 | By default, \code{callr::\link[callr]{rcmd_safe_env}} suppresses the ability
14 | to open a browser window. This is the default execution environment within
15 | \code{callr::\link[callr]{r}}. However, opening a browser is expected
16 | behavior within the learnr package and should not be suppressed.
17 | }
18 | \examples{
19 | safe_env()
20 |
21 | }
22 | \keyword{internal}
23 |
--------------------------------------------------------------------------------
/vignettes/articles/snippets.R:
--------------------------------------------------------------------------------
1 | insert_snippet <- function(key) {
2 | #
3 | #
4 | snippet <- readLines(fs::path("../../pkgdown/assets/snippets", key, ext = "md"), warn = FALSE)
5 | snippet <- paste(snippet, collapse = "\n")
6 | snippet <- gsub("`", "`", snippet)
7 | snippet <- htmltools::HTML(snippet)
8 |
9 | htmltools::withTags(
10 | htmltools::tagList(
11 | div(
12 | id = key,
13 | class = "sourceCode",
14 | pre(class = "markdown", code(snippet)),
15 | ),
16 | script(type = "text/javascript", htmltools::HTML(sprintf("loadSnippet('%s')", key)))
17 | )
18 | )
19 | }
--------------------------------------------------------------------------------
/R/ace.R:
--------------------------------------------------------------------------------
1 | # This file was autogenerated by 'tools/update-ace.R'
2 | ACE_VERSION <- "1.10.1"
3 | ACE_THEMES <- c("ambiance", "chaos", "chrome", "cloud9_day", "cloud9_night_low_color", "cloud9_night", "clouds_midnight", "clouds", "cobalt", "crimson_editor", "dawn", "dracula", "dreamweaver", "eclipse", "github", "gob", "gruvbox_dark_hard", "gruvbox_light_hard", "gruvbox", "idle_fingers", "iplastic", "katzenmilch", "kr_theme", "kuroir", "merbivore_soft", "merbivore", "mono_industrial", "monokai", "nord_dark", "one_dark", "pastel_on_dark", "solarized_dark", "solarized_light", "sqlserver", "terminal", "textmate", "tomorrow_night_blue", "tomorrow_night_bright", "tomorrow_night_eighties", "tomorrow_night", "tomorrow", "twilight", "vibrant_ink", "xcode")
4 |
--------------------------------------------------------------------------------
/man/disable_all_tags.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/mutate_tags.R
3 | \name{disable_all_tags}
4 | \alias{disable_all_tags}
5 | \title{Disable all html tags}
6 | \usage{
7 | disable_all_tags(ele)
8 | }
9 | \arguments{
10 | \item{ele}{html tag element}
11 | }
12 | \value{
13 | An \pkg{htmltools} HTML object with appended \code{class = "disabled"} and
14 | \code{disabled} attributes on all tags.
15 | }
16 | \description{
17 | Method to disable all html tags to not allow users to interact with the html.
18 | }
19 | \examples{
20 | # add an href to all a tags
21 | disable_all_tags(
22 | htmltools::tagList(
23 | htmltools::a(),
24 | htmltools::a()
25 | )
26 | )
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name : Ask a Question
3 | about : The issue tracker is not for general help questions -- please ask general learnr help questions at https://community.rstudio.com/c/shiny.
4 | ---
5 |
6 | The issue tracker is not for questions. If you have a question, please feel free to ask learnr help questions on our community site under the teaching category, at https://community.rstudio.com/c/teaching. Please tag your post with a learnr tag to increase visibility.
7 |
8 | Open a new post [here (teaching `category` with `learnr` tag)](https://community.rstudio.com/new-topic?title=&category_id=13&tags=learnr&body=%0A%0A%0A%20%20--------%0A%20%20%0A%20%20%3Csup%3EReferred%20here%20by%20%60learnr%60%27s%20GitHub%3C/sup%3E%0A&u=barret).
9 |
--------------------------------------------------------------------------------
/tests/testthat/tutorials/hint-next/hint-next.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Next hint tests"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ## Tests
8 |
9 | ### One hint
10 |
11 | ```{r one, exercise = TRUE}
12 | 1+1
13 | ```
14 |
15 | ```{r one-hint}
16 | # one hint
17 | ```
18 |
19 | ### Two hints
20 |
21 | ```{r two, exercise = TRUE}
22 | 1+1
23 | ```
24 |
25 | ```{r two-hint-1}
26 | # first hint
27 | ```
28 |
29 | ```{r two-hint-2}
30 | # second hint
31 | ```
32 |
33 | ### Two hints plus solution
34 |
35 | ```{r three, exercise = TRUE}
36 | 1+1
37 | ```
38 |
39 | ```{r three-hint-1}
40 | # 3 - first hint
41 | ```
42 |
43 | ```{r three-hint-2}
44 | # 3 - second hint
45 | ```
46 |
47 | ```{r three-solution}
48 | 2 + 2
49 | ```
50 |
--------------------------------------------------------------------------------
/man/knit_print.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/quiz.R
3 | \name{knit_print.tutorial_question}
4 | \alias{knit_print.tutorial_question}
5 | \alias{knit_print.tutorial_quiz}
6 | \title{Knitr quiz print methods}
7 | \usage{
8 | \method{knit_print}{tutorial_question}(x, ...)
9 |
10 | \method{knit_print}{tutorial_quiz}(x, ...)
11 | }
12 | \arguments{
13 | \item{x}{An R object to be printed}
14 |
15 | \item{...}{Additional arguments passed to the S3 method. Currently ignored,
16 | except two optional arguments \code{options} and \code{inline}; see
17 | the references below.}
18 | }
19 | \description{
20 | \code{knitr::\link[knitr]{knit_print}} methods for \code{\link{question}} and
21 | \code{\link{quiz}}
22 | }
23 |
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^.*\.Rproj$
2 | ^\.Rproj\.user$
3 | ^README.R?md$
4 | ^dependencies$
5 | ^design$
6 | ^docs$
7 | ^examples$
8 | ^sandbox$
9 | ^tools$
10 | ^issues$
11 | ^.*/skeleton/skeleton.html
12 |
13 | ^\.travis\.yml$
14 | ^\.atom
15 |
16 | rsconnect/
17 | ^\.github/
18 | ^inst/tutorials/.*\.html$
19 | ^inst/tutorials/.*\.sqlite$
20 | ^revdep$
21 | ^LICENSE\.md$
22 | ^cran-comments\.md$
23 | ^CRAN-RELEASE$
24 | ^doc$
25 | ^Meta$
26 | ^\.github
27 | ^data-raw$
28 | ^\.github$
29 | ^vignettes/articles$
30 | ^reference$
31 | ^pkgdown$
32 | ^_dev$
33 | ^learnr-js$
34 | ^node_modules$
35 | ^package\.json$
36 | ^package-lock\.json$
37 | ^\.browserslistrc$
38 | ^babel\.config\.json$
39 | ^\.eslintignore$
40 | ^\.vscode$
41 | ^inst/tutorials/*/*_files$
42 | ^tests/manual$
43 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/customchecker.md:
--------------------------------------------------------------------------------
1 | ```r
2 | custom_checker <- function(label, user_code, check_code, envir_result, evaluate_result, last_value, stage, ...) {
3 | # this is a code check
4 | if (stage == "code_check") {
5 | if (is_bad_code(user_code, check_code)) {
6 | return(list(message = "I wasn't expecting that code", correct = FALSE))
7 | }
8 | return(list(message = "Nice code!", correct = TRUE))
9 | }
10 | # this is a fully evaluated chunk check
11 | if (is_bad_result(last_value, check_code)) {
12 | return(list(message = "I wasn't expecting that result", correct = FALSE))
13 | }
14 | list(message = "Great job!", correct = TRUE, location = "append")
15 | }
16 |
17 | tutorial_options(exercise.checker = custom_checker)
18 | ```
19 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/codefeedback.md:
--------------------------------------------------------------------------------
1 | ```r
2 | custom_checker <- function(label, user_code, check_code, envir_result, evaluate_result, last_value, ...) {
3 | if (is.null(envir_result)) {
4 | # check_code contains `*-code-check` code
5 | if (is_bad_code(user_code, check_code)) {
6 | return(list(message = "I wasn't expecting that code", correct = FALSE))
7 | }
8 | return(list(message = "Nice code!", correct = TRUE))
9 | }
10 |
11 | # check_code contains `*-check` code
12 | if (is_bad_result(last_value, check_code)) {
13 | return(list(message = "I wasn't expecting that result", correct = FALSE))
14 | }
15 | list(message = "Great job!", correct = TRUE, location = "append")
16 | }
17 |
18 | tutorial_options(exercise.checker = custom_checker)
19 | ```
--------------------------------------------------------------------------------
/tests/testthat/tutorials/knitr-hooks_empty-exercise/duplicate-label.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: Empty Exercise Code with Duplicate
3 | output: learnr::tutorial
4 | runtime: shinyrmd
5 | ---
6 |
7 | ```{r setup, include = FALSE}
8 | library(learnr)
9 | tutorial_options(exercise.lines = 4L)
10 |
11 | knitr::opts_chunk$set(
12 | echo = FALSE,
13 | custom_chunk_opt = "default",
14 | fig.path = "figures",
15 | cache.path = "cache"
16 | )
17 | ```
18 |
19 | ## Test
20 |
21 | ### First empty
22 |
23 | This tutorial should fail to parse.
24 |
25 | ```{r empty}
26 |
27 |
28 | ```
29 |
30 | ### Empty
31 |
32 | ```{r empty, exercise = TRUE, custom_chunk_opt = "custom"}
33 | ```
34 |
35 | ```{r empty-hint}
36 | # hint code
37 | ```
38 |
39 | ```{r empty-solution}
40 | mtcars
41 | ```
42 |
--------------------------------------------------------------------------------
/man/filesystem_storage.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/storage.R
3 | \name{filesystem_storage}
4 | \alias{filesystem_storage}
5 | \title{Filesystem-based storage for tutor state data}
6 | \usage{
7 | filesystem_storage(dir, compress = TRUE)
8 | }
9 | \arguments{
10 | \item{dir}{Directory to store state data within}
11 |
12 | \item{compress}{Should \code{.rds} files be compressed?}
13 | }
14 | \value{
15 | Storage handler suitable for \code{options(tutorial.storage = ...)}
16 | }
17 | \description{
18 | Tutorial state storage handler that uses the filesystem as a backing store.
19 | The directory will contain tutorial state data partitioned by \code{user_id},
20 | \code{tutorial_id}, and \code{tutorial_version} (in that order)
21 | }
22 |
--------------------------------------------------------------------------------
/tests/testthat/test-cookies.R:
--------------------------------------------------------------------------------
1 | context("cookies")
2 |
3 | test_that("cookies are properly serialized", {
4 | cookies <- structure(list(
5 | domain = c("httpbin.org", "httpbin.org"),
6 | flag = c(FALSE, FALSE),
7 | path = c("/", "/"),
8 | secure = c(FALSE, FALSE),
9 | expiration = c(1587586247L, 0L),
10 | name = c("foo", "bar"),
11 | value = c("123", "ftw")
12 | ),
13 | row.names = c(NA, -2L),
14 | class = "data.frame")
15 |
16 | f <- tempfile()
17 | on.exit({unlink(f)})
18 |
19 | write_cookies(cookies, f)
20 | txt <- readLines(f)
21 | expect_equal(length(txt), 2)
22 | expect_equal(txt[1], "httpbin.org\tFALSE\t/\tFALSE\t1587586247\tfoo\t123")
23 | expect_equal(txt[2], "httpbin.org\tFALSE\t/\tFALSE\t0\tbar\tftw")
24 | })
25 |
26 |
--------------------------------------------------------------------------------
/man/available_tutorials.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/available_tutorials.R
3 | \name{available_tutorials}
4 | \alias{available_tutorials}
5 | \title{List available tutorials}
6 | \usage{
7 | available_tutorials(package = NULL)
8 | }
9 | \arguments{
10 | \item{package}{Name of package}
11 | }
12 | \value{
13 | \code{available_tutorials()} returns a \code{data.frame} containing "package",
14 | "name", "title", "description", "package_dependencies", "private", and
15 | "yaml_front_matter".
16 | }
17 | \description{
18 | List the tutorials that are currently available via installed R packages.
19 | Or list the specific tutorials that are contained within a given R package.
20 | }
21 | \examples{
22 | available_tutorials(package = "learnr")
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/man/rmd-fragments/learnr-overview.Rmd:
--------------------------------------------------------------------------------
1 | The **learnr** package makes it easy to turn any [R Markdown](https://rmarkdown.rstudio.com/) document into an interactive tutorial. Tutorials consist of content along with interactive components for checking and reinforcing understanding. Tutorials can include any or all of the following:
2 |
3 | 1. Narrative, figures, illustrations, and equations.
4 |
5 | 2. Code exercises (R code chunks that users can edit and execute directly).
6 |
7 | 3. Quiz questions.
8 |
9 | 4. Videos (supported services include YouTube and Vimeo).
10 |
11 | 5. Interactive Shiny components.
12 |
13 | Tutorials automatically preserve work done within them, so if a user works on a few exercises or questions and returns to the tutorial later they can pick up right where they left off.
14 |
--------------------------------------------------------------------------------
/tests/testthat/_snaps/available-tutorials.md:
--------------------------------------------------------------------------------
1 | # Tutorial names are retrieved
2 |
3 | Code
4 | available_tutorials("learnr")
5 | Output
6 | Available tutorials:
7 | * learnr
8 | - ex-data-basics : "Data basics"
9 | - ex-data-filter : "Filter observations"
10 | - ex-data-mutate : "Create new variables"
11 | - ex-data-summarise : "Summarise Tables"
12 | - ex-setup-r : "Set Up"
13 | - hello : "Hello, Tutorial!"
14 | - polyglot : "Multi-language exercises"
15 | - quiz_question : "Tutorial Quiz Questions in `learnr`"
16 | - setup-chunks : "Chained setup chunks"
17 | - slidy : "Slidy demo"
18 | - sql-exercise : "Interactive SQL Exercises"
19 |
20 |
--------------------------------------------------------------------------------
/tests/testthat/test-duplicate_env.R:
--------------------------------------------------------------------------------
1 |
2 | context("duplicate_env")
3 |
4 | test_that("it duplicates", {
5 |
6 | e <- new.env(parent = baseenv())
7 | e$x <- 1
8 | e$.key <- "value"
9 |
10 | new_envir <- duplicate_env(e)
11 |
12 | # check parent is equivalent
13 | expect_true(identical(parent.env(new_envir), baseenv()))
14 |
15 | # check keys exist
16 | test_names <- c("x", ".key")
17 | envir_names <- ls(envir = new_envir, all.names = TRUE)
18 | expect_equal(sort(test_names), sort(envir_names))
19 | })
20 |
21 | test_that("does not fail when the envir has a class", {
22 | e <- new.env()
23 | e$x <- 1
24 | class(e) <- "foo"
25 |
26 | expect_error({
27 | as.list(e)
28 | })
29 |
30 | expect_silent({
31 | new_envir <- duplicate_env(e)
32 | })
33 |
34 | expect_equal(new_envir$x, 1)
35 | })
36 |
--------------------------------------------------------------------------------
/tests/testthat/test-available-tutorials.R:
--------------------------------------------------------------------------------
1 |
2 | context("available tutorials")
3 |
4 | test_that("Tutorial names are retrieved", {
5 | local_edition(3)
6 |
7 | expect_error(available_tutorials("not a package"), "No package found")
8 | expect_error(available_tutorials("base"), "No tutorials found")
9 | expect_true("hello" %in% available_tutorials("learnr")$name)
10 | expect_true("hello" %in% suppressMessages(run_tutorial(package = "learnr")$name))
11 | expect_s3_class(available_tutorials("learnr"), "learnr_available_tutorials")
12 |
13 | expect_error(run_tutorial("helloo", package = "learnr"), "\"hello\"")
14 | expect_error(run_tutorial("doesn't exist", package = "learnr"), "Available ")
15 | expect_message(run_tutorial(package = "learnr"), "Available ")
16 |
17 | expect_snapshot(available_tutorials("learnr"))
18 | })
19 |
--------------------------------------------------------------------------------
/man/external_evaluator.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/evaluators.R
3 | \name{external_evaluator}
4 | \alias{external_evaluator}
5 | \title{External execution evaluator}
6 | \usage{
7 | external_evaluator(
8 | endpoint = getOption("tutorial.external.host",
9 | Sys.getenv("TUTORIAL_EXTERNAL_EVALUATOR_HOST", NA)),
10 | max_curl_conns = 50
11 | )
12 | }
13 | \arguments{
14 | \item{endpoint}{The HTTP(S) endpoint to POST the exercises to}
15 |
16 | \item{max_curl_conns}{The maximum number of simultaneous HTTP requests to the
17 | endpoint.}
18 | }
19 | \value{
20 | A function that takes an expression (\code{expr}), \code{timelimit}, \code{exercise}
21 | and \code{session}.
22 | }
23 | \description{
24 | \href{https://lifecycle.r-lib.org/articles/stages.html}{Lifecycle: experimental}
25 | }
26 |
--------------------------------------------------------------------------------
/tests/manual/tests-chunk.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Test Cases"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ```{r setup, include=FALSE}
8 | library(learnr)
9 | tutorial_options(exercise.checker = function(user_code, ...) return(user_code))
10 | knitr::opts_chunk$set(echo = FALSE)
11 | ```
12 |
13 | ## Test Cases
14 |
15 | ### Exercise
16 |
17 | ```{r addition-setup}
18 | x <- 1 + 1
19 | ```
20 |
21 | ```{r addition, exercise = TRUE}
22 |
23 | ```
24 |
25 | ```{r addition-solution}
26 | 1 + 1
27 | ```
28 |
29 | ```{r addition-check}
30 | check_this_exercise(user_code, solution_code)
31 | ```
32 |
33 | ```{r addition-tests}
34 | 1 + 1
35 |
36 | # one plus two ----
37 | 1 + 2
38 |
39 | # one plus three ----
40 | 1 + 3
41 |
42 | # one equals three ----
43 | 1 = 3
44 |
45 | # 2 minus one ----
46 | 2 - 1
47 | ```
48 |
--------------------------------------------------------------------------------
/R/events_record.R:
--------------------------------------------------------------------------------
1 | record_event <- function(session, event, data) {
2 | recorder <- getOption("tutorial.event_recorder", default = NULL)
3 | if (!is.null(recorder)) {
4 | recorder(tutorial_id = read_request(session, "tutorial.tutorial_id"),
5 | tutorial_version = read_request(session, "tutorial.tutorial_version"),
6 | user_id = read_request(session, "tutorial.user_id"),
7 | event = event,
8 | data = data)
9 | }
10 | invisible(NULL)
11 | }
12 |
13 |
14 | debug_event_recorder <- function(
15 | tutorial_id,
16 | tutorial_version,
17 | user_id,
18 | event,
19 | data
20 | ) {
21 | cat(tutorial_id, " (", tutorial_version, "): ", user_id, "\n", sep = "")
22 | cat("event: ", event, "\n", sep = "")
23 | if (is.character(data)) cat(data) else utils::str(data)
24 | cat("\n")
25 | }
26 |
--------------------------------------------------------------------------------
/man/rmd-fragments/hello-tutorial.Rmd:
--------------------------------------------------------------------------------
1 | To create a tutorial, set `runtime: shiny_prerendered` in the YAML frontmatter of your `.Rmd` file to turn your R Markdown document into an [interactive app](https://rmarkdown.rstudio.com/lesson-14.html).
2 |
3 | Then, call `library(learnr)` within your Rmd file to activate tutorial mode, and use the `exercise = TRUE` chunk option to turn code chunks into exercises. Users can edit and execute the R code and see the results right within their browser.
4 |
5 | For example, here's a very simple tutorial:
6 |
7 | ```{r snippet-hello-learnr, echo = FALSE}
8 | source("../../vignettes/articles/snippets.R")
9 | insert_snippet("hello-learnr")
10 | ```
11 |
12 | This is what the running tutorial document looks like after the user has entered their answer:
13 |
14 |
15 |
--------------------------------------------------------------------------------
/man/random_praise.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/praise.R
3 | \name{random_praise}
4 | \alias{random_praise}
5 | \alias{random_encouragement}
6 | \title{Random praise and encouragement}
7 | \usage{
8 | random_praise(language = NULL)
9 |
10 | random_encouragement(language = NULL)
11 | }
12 | \arguments{
13 | \item{language}{The language for the random phrase. The currently supported
14 | languages include: \code{en}, \code{es}, \code{pt}, \code{pl}, \code{tr}, \code{de}, \code{emo}, and \code{testing}
15 | (static phrases).}
16 | }
17 | \value{
18 | Character string with a random saying
19 | }
20 | \description{
21 | Random praises and encouragements sayings to compliment your question and
22 | quiz experience.
23 | }
24 | \examples{
25 | random_praise()
26 | random_praise()
27 |
28 | random_encouragement()
29 | random_encouragement()
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/man/tutorial_package_dependencies.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/tutorial_package_dependencies.R
3 | \name{tutorial_package_dependencies}
4 | \alias{tutorial_package_dependencies}
5 | \title{List tutorial dependencies}
6 | \usage{
7 | tutorial_package_dependencies(name = NULL, package = NULL)
8 | }
9 | \arguments{
10 | \item{name}{The tutorial name. If \code{name} is \code{NULL}, then all
11 | tutorials within \code{package} will be searched.}
12 |
13 | \item{package}{The \R package providing the tutorial. If \code{package} is
14 | \code{NULL}, then all tutorials will be searched.}
15 | }
16 | \value{
17 | A character vector of package names that are required for execution.
18 | }
19 | \description{
20 | List the \R packages required to run a particular tutorial.
21 | }
22 | \examples{
23 | tutorial_package_dependencies(package = "learnr")
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/inst/staticexports/knitr_engine_caption.R:
--------------------------------------------------------------------------------
1 |
2 | knitr_engine_caption <- function(engine = NULL) {
3 | if (is.null(engine)) {
4 | engine <- "r"
5 | }
6 |
7 | switch(
8 | tolower(engine),
9 | "bash" = "Bash",
10 | "c" = "C",
11 | "coffee" = "CoffeeScript",
12 | "cc" = "C++",
13 | "css" = "CSS",
14 | "go" = "Go",
15 | "groovy" = "Groovy",
16 | "haskell" = "Haskell",
17 | "js" = "JavaScript",
18 | "mysql" = "MySQL",
19 | "node" = "Node.js",
20 | "octave" = "Octave",
21 | "psql" = "PostgreSQL",
22 | "python" = "Python",
23 | "r" = "R",
24 | "rcpp" = "Rcpp",
25 | "cpp11" = "cpp11",
26 | "rscript" = "Rscript",
27 | "ruby" = "Ruby",
28 | "perl" = "Perl",
29 | "sass" = "Sass",
30 | "scala" = "Scala",
31 | "scss" = "SCSS",
32 | "sql" = "SQL",
33 | # else, return as the user provided
34 | engine
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We welcome contributions to the **learnr** package!
4 |
5 | To submit a contribution:
6 |
7 | 1. [Fork](https://github.com/rstudio/learnr/fork) the repository and make your changes.
8 |
9 | 2. Ensure that you have signed the [individual](https://www.rstudio.com/wp-content/uploads/2014/06/rstudioindividualcontributoragreement.pdf) or [corporate](https://www.rstudio.com/wp-content/uploads/2014/06/rstudiocorporatecontributoragreement.pdf) contributor agreement as appropriate. You can send the signed copy to contribute@rstudio.com, cc'ing barret@rstudio.com.
10 |
11 | 3. Submit a [pull request](https://help.github.com/articles/using-pull-requests).
12 |
13 | We generally do not merge pull requests that update included web libraries (such as Bootstrap or jQuery) because it is difficult for us to verify that the update is done correctly; we prefer to update these libraries ourselves.
14 |
--------------------------------------------------------------------------------
/man/finalize_question.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/mutate_tags.R
3 | \name{finalize_question}
4 | \alias{finalize_question}
5 | \title{Finalize a question}
6 | \usage{
7 | finalize_question(ele)
8 | }
9 | \arguments{
10 | \item{ele}{html tag element}
11 | }
12 | \value{
13 | An \pkg{htmltools} HTML object with appropriately appended classes
14 | such that a tutorial question is marked as the final answer.
15 | }
16 | \description{
17 | Mark a question as finalized by adding a \code{question-final} class to the HTML
18 | output at the top level, in addition to disabling all tags with
19 | \code{\link[=disable_all_tags]{disable_all_tags()}}.
20 | }
21 | \examples{
22 | # finalize the question UI
23 | finalize_question(
24 | htmltools::div(
25 | class = "custom-question",
26 | htmltools::div("answer 1"),
27 | htmltools::div("answer 2")
28 | )
29 | )
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/pkgdown/index.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "learnr"
3 | output: md_document
4 | ---
5 |
6 | # learnr
7 |
8 | ```{r child="../man/rmd-fragments/badges.Rmd"}
9 | ```
10 |
11 | ```{r child="../man/rmd-fragments/learnr-overview.Rmd"}
12 | ```
13 |
14 | ## Examples
15 |
16 | Here are some examples of tutorials created with the **learnr** package.
17 |
18 | ```{r example-showcase, echo=FALSE}
19 | source("../vignettes/articles/example_cards.R")
20 | ex <- yaml::read_yaml("../vignettes/articles/examples.yml")
21 | ex <- lapply(ex, function(x) {
22 | x$image <- paste0("articles/", x$image)
23 | x
24 | })
25 | example_cards(ex, "showcase")
26 | ```
27 |
28 | ## Installation
29 |
30 | ```{r child="../man/rmd-fragments/learnr-install.Rmd"}
31 | ```
32 |
33 | ## Hello, Tutorial!
34 |
35 | ```{r child="../man/rmd-fragments/hello-tutorial.Rmd"}
36 | ```
37 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-stable.svg:
--------------------------------------------------------------------------------
1 | lifecycle lifecycle stable stable
--------------------------------------------------------------------------------
/man/figures/lifecycle-defunct.svg:
--------------------------------------------------------------------------------
1 | lifecycle lifecycle defunct defunct
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/exerciseevaluator.md:
--------------------------------------------------------------------------------
1 | options(tutorial.exercise.evaluator.onstart = function(pid) {
2 |
3 | # import RAppArmor
4 | require(RAppArmor, quietly = TRUE)
5 |
6 | # set process group to pid (allows kill of entire subtree in cleanup)
7 | setpgid();
8 |
9 | # set nice priority
10 | setpriority(10)
11 |
12 | # set rlimits as appropriate
13 | rlimit_nproc(1000)
14 | rlimit_as(1024*1024*1024)
15 |
16 | # change to r-user profile (see note above on required edit to r-user)
17 | aa_change_profile("r-user")
18 | })
19 |
20 | options(tutorial.exercise.evaluator.oncleanup = function(pid) {
21 |
22 | # import RAppArmor
23 | require(RAppArmor, quietly = TRUE)
24 |
25 | # kill entire process subtree. note that the second call works
26 | # because the call to setpgid above sets our pgid (process group id)
27 | # to our pid (process id)
28 | kill(pid, tools::SIGKILL)
29 | kill(-1 * pid, tools::SIGKILL)
30 | })
31 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-archived.svg:
--------------------------------------------------------------------------------
1 | lifecycle lifecycle archived archived
--------------------------------------------------------------------------------
/man/figures/lifecycle-maturing.svg:
--------------------------------------------------------------------------------
1 | lifecycle lifecycle maturing maturing
--------------------------------------------------------------------------------
/man/figures/lifecycle-deprecated.svg:
--------------------------------------------------------------------------------
1 | lifecycle lifecycle deprecated deprecated
--------------------------------------------------------------------------------
/man/figures/lifecycle-superseded.svg:
--------------------------------------------------------------------------------
1 | lifecycle lifecycle superseded superseded
--------------------------------------------------------------------------------
/man/figures/lifecycle-experimental.svg:
--------------------------------------------------------------------------------
1 | lifecycle lifecycle experimental experimental
--------------------------------------------------------------------------------
/man/figures/lifecycle-questioning.svg:
--------------------------------------------------------------------------------
1 | lifecycle lifecycle questioning questioning
--------------------------------------------------------------------------------
/man/figures/lifecycle-soft-deprecated.svg:
--------------------------------------------------------------------------------
1 | lifecycle lifecycle soft-deprecated soft-deprecated
--------------------------------------------------------------------------------
/R/zzz.R:
--------------------------------------------------------------------------------
1 |
2 | # install knitr hooks when package is attached to search path
3 | .onAttach <- function(libname, pkgname) {
4 | install_knitr_hooks()
5 | initialize_tutorial()
6 | }
7 |
8 | # remove knitr hooks when package is detached from search path
9 | .onDetach <- function(libpath) {
10 | remove_knitr_hooks()
11 | }
12 |
13 | .onLoad <- function(libname, pkgname) {
14 | register_default_event_handlers()
15 |
16 | # We need an input handler for learnr.exercise, only so that we can call
17 | # shinytest::registerInputProcessor(), below.
18 | removeInputHandler("learnr.exercise")
19 | registerInputHandler("learnr.exercise", function(x, shinysession, name) {
20 | snapshotPreprocessInput(name, snapshotPreprocessorLearnrExercise)
21 | x
22 | })
23 |
24 |
25 | if ("shinytest2" %in% loadedNamespaces()) {
26 | register_shinytest_inputprocessor()
27 | }
28 | setHook(
29 | packageEvent("shinytest2", "onLoad"),
30 | function(...) register_shinytest_inputprocessor()
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/man/duplicate_env.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/utils.R
3 | \name{duplicate_env}
4 | \alias{duplicate_env}
5 | \title{Create a duplicate of an environment}
6 | \usage{
7 | duplicate_env(envir, parent = parent.env(envir))
8 | }
9 | \arguments{
10 | \item{envir}{environment to duplicate}
11 |
12 | \item{parent}{parent environment to set for the new environment. Defaults to
13 | the parent environment of \code{envir}.}
14 | }
15 | \value{
16 | A duplicated copy of \code{envir} whose parent env is \code{parent}.
17 | }
18 | \description{
19 | Copy all items from the environment to a new environment. By default, the new
20 | environment will share the same parent environment.
21 | }
22 | \examples{
23 | # Make a new environment with the object 'key'
24 | envir <- new.env()
25 | envir$key <- "value"
26 | "key" \%in\% ls() # FALSE
27 | "key" \%in\% ls(envir = envir) # TRUE
28 |
29 | # Duplicate the envir and show it contains 'key'
30 | new_envir <- duplicate_env(envir)
31 | "key" \%in\% ls(envir = new_envir) # TRUE
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/R/data_dir.R:
--------------------------------------------------------------------------------
1 | copy_data_dir <- function(source_dir, exercise_dir) {
2 | if (is.null(source_dir)) {
3 | # First check options(), then environment variable, then default to "data/"
4 | source_dir <- getOption(
5 | "tutorial.data_dir",
6 | Sys.getenv("TUTORIAL_DATA_DIR", if (dir.exists("data")) "data" else "")
7 | )
8 | }
9 |
10 | if (identical(source_dir, "")) {
11 | return(invisible(NULL))
12 | }
13 |
14 | if (!dir.exists(source_dir)) {
15 | rlang::abort(
16 | "An error occurred with the tutorial: the data directory does not exist.",
17 | class = "learnr_missing_source_data_dir"
18 | )
19 | }
20 |
21 | dest_dir <- file.path(exercise_dir, "data")
22 | dir.create(dest_dir)
23 |
24 | if (!dir.exists(dest_dir)) {
25 | rlang::abort(
26 | "An error occurred with the tutorial: we weren't able to create the data directory for this exercise.",
27 | class = "learnr_missing_dest_data_dir"
28 | )
29 | }
30 |
31 | file.copy(dir(source_dir, full.names = TRUE), dest_dir, recursive = TRUE)
32 |
33 | return(invisible(dest_dir))
34 | }
35 |
--------------------------------------------------------------------------------
/inst/examples/apparmor/apparmor_evaluator.R:
--------------------------------------------------------------------------------
1 |
2 | # Note: To use the "r-user" AppArmor profile you should add the following line
3 | # to /etc/apparmor.d/rapparmor.d/r-user:
4 | #
5 | # /usr/lib/rstudio/bin/pandoc/* rix,
6 | #
7 |
8 | options(tutorial.exercise.evaluator.onstart = function(pid) {
9 |
10 | # import RAppArmor
11 | require(RAppArmor, quietly = TRUE)
12 |
13 | # set process group to pid (allows kill of entire subtree in cleanup)
14 | setpgid();
15 |
16 | # set nice priority
17 | setpriority(10)
18 |
19 | # set rlimits as appropriate
20 | rlimit_nproc(1000)
21 | rlimit_as(1024*1024*1024)
22 |
23 | # change to r-user profile (see note above on required edit to r-user)
24 | aa_change_profile("r-user")
25 | })
26 |
27 | options(tutorial.exercise.evaluator.oncleanup = function(pid) {
28 |
29 | # import RAppArmor
30 | require(RAppArmor, quietly = TRUE)
31 |
32 | # kill entire process subtree. note that the second call works
33 | # because the call to setpgid above sets our pgid (process group id)
34 | # to our pid (process id)
35 | kill(pid, tools::SIGKILL)
36 | kill(-1 * pid, tools::SIGKILL)
37 | })
38 |
39 |
--------------------------------------------------------------------------------
/tests/manual/hint-popover.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hint and Solution Popovers"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ## Test Cases
8 |
9 | ### Hint Only
10 |
11 | The hint in this exercise should have three lines.
12 |
13 | ```{r ex1, exercise = TRUE}
14 | 1+1
15 | ```
16 |
17 | ```{r ex1-hint}
18 | line1
19 | line2
20 | line3
21 | ```
22 |
23 | ### Solution Only
24 |
25 | The hint in this exercise should have six lines.
26 |
27 | ```{r ex2, exercise = TRUE}
28 | 1+1
29 | ```
30 |
31 | ```{r ex2-solution}
32 | line1
33 | line2
34 | line3
35 | line4
36 | line5
37 | line6
38 | ```
39 |
40 | ### Solution and Hints
41 |
42 | 1. Hint with 5 lines
43 | 2. Hint with 15 lines
44 | 3. Solution with 3 lines
45 |
46 | ```{r ex3, exercise = TRUE}
47 | 1+1
48 | ```
49 |
50 | ```{r ex3-hint-1}
51 | line1
52 | line2
53 | line3
54 | line4
55 | line5
56 | ```
57 |
58 | ```{r ex3-hint-2}
59 | line1
60 | line2
61 | line3
62 | line4
63 | line5
64 | line6
65 | line7
66 | line8
67 | line9
68 | line10
69 | line11
70 | line12
71 | line13
72 | line14
73 | line15
74 | ```
75 |
76 | ```{r ex3-solution}
77 | line1
78 | line2
79 | line3
80 | ```
81 |
--------------------------------------------------------------------------------
/man/rmd-fragments/badges.Rmd:
--------------------------------------------------------------------------------
1 |
2 | [](https://github.com/rstudio/learnr)
3 | [](https://CRAN.R-project.org/package=learnr)
4 | [](http://www.rpackages.io/package/learnr)
5 | [](https://zenodo.org/badge/latestdoi/71377580)
6 |
7 | [](https://github.com/rstudio/learnr/discussions)
8 | []( https://community.rstudio.com/c/teaching/13)
9 | [](https://community.rstudio.com/new-topic?title=&category_id=13&tags=learnr&body=%0A%0A%0A%20%20--------%0A%20%20%0A%20%20%3Csup%3EReferred%20here%20by%20%60learnr%60%27s%20GitHub%3C/sup%3E%0A&u=barret)
10 |
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "learnr",
3 | "private": true,
4 | "engines": {
5 | "node": ">= 14"
6 | },
7 | "scripts": {
8 | "build": "npm run lint && npm run build:esbuild",
9 | "build:esbuild": "node learnr-js/build.js",
10 | "lint": "standard --fix learnr-js",
11 | "copy": "npm run copy:clipboard && npm run copy:bootbox && npm run copy:i18next",
12 | "copy:clipboard": "cpy 'clipboard.min.js' ../../../inst/lib/clipboardjs --cwd node_modules/clipboard/dist/",
13 | "copy:bootbox": "cpy 'bootbox.min.js' ../../../inst/lib/bootbox --cwd node_modules/bootbox/dist/",
14 | "copy:i18next": "cpy 'i18next.min.js' ../../inst/lib/i18next --cwd node_modules/i18next/"
15 | },
16 | "devDependencies": {
17 | "@babel/cli": "^7.16.8",
18 | "@babel/core": "^7.16.12",
19 | "@babel/preset-env": "^7.16.11",
20 | "bootbox": "^5.5.2",
21 | "browserslist": "^4.19.1",
22 | "clipboard": "^2.0.10",
23 | "core-js": "^3.21.0",
24 | "cpy-cli": "^4.1.0",
25 | "esbuild": "^0.14.18",
26 | "esbuild-plugin-babel": "https://github.com/schloerke/esbuild-plugin-babel#patch-2",
27 | "i18next": "^21.6.10",
28 | "standard": "^16.0.4"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/R/learnr-package.R:
--------------------------------------------------------------------------------
1 | #' @keywords internal
2 | "_PACKAGE"
3 |
4 | ## usethis namespace: start
5 | #' @importFrom evaluate evaluate
6 | #' @importFrom htmltools attachDependencies
7 | #' @importFrom htmltools div
8 | #' @importFrom htmltools HTML
9 | #' @importFrom htmltools htmlDependency
10 | #' @importFrom htmltools tags
11 | #' @importFrom htmlwidgets createWidget
12 | #' @importFrom jsonlite base64_dec
13 | #' @importFrom jsonlite base64_enc
14 | #' @importFrom knitr all_labels
15 | #' @importFrom knitr knit_hooks
16 | #' @importFrom knitr knit_meta_add
17 | #' @importFrom knitr opts_chunk
18 | #' @importFrom knitr opts_hooks
19 | #' @importFrom knitr opts_knit
20 | #' @importFrom knitr spin
21 | #' @importFrom lifecycle deprecated
22 | #' @importFrom rprojroot find_root
23 | #' @importFrom rprojroot is_r_package
24 | #' @importFrom shiny invalidateLater
25 | #' @importFrom shiny isolate
26 | #' @importFrom shiny observe
27 | #' @importFrom shiny observeEvent
28 | #' @importFrom shiny reactive
29 | #' @importFrom shiny reactiveValues
30 | #' @importFrom shiny req
31 | #' @importFrom stats runif
32 | #' @importFrom withr with_envvar
33 | ## usethis namespace: end
34 | NULL
35 |
36 | # @staticimports pkg:staticimports
37 | # compact
38 | # imap_lgl
39 |
--------------------------------------------------------------------------------
/tests/testthat/test-question_radio.R:
--------------------------------------------------------------------------------
1 | test_that("question_radio() throws an error when using only answer_fn() answers", {
2 | expect_error(
3 | question_radio(
4 | "test",
5 | answer_fn(~ "one"),
6 | answer_fn(~ "two")
7 | )
8 | )
9 | })
10 |
11 | test_that("question_radio() throws an error if it doesn't include a correct answer, even with answer_fn()", {
12 | expect_error(
13 | question_radio(
14 | "test",
15 | answer_fn(~ "one"),
16 | answer("two", correct = FALSE)
17 | )
18 | )
19 |
20 | expect_error(
21 | question_radio(
22 | "test",
23 | answer("two", correct = FALSE)
24 | )
25 | )
26 | })
27 |
28 | test_that("question_radio() warns when using answer_fn() answers", {
29 | expect_warning(
30 | q_fn <- question_radio(
31 | "test",
32 | answer_fn(~ "one"),
33 | answer("two", correct = TRUE)
34 | )
35 | )
36 |
37 | q_no_fn <- question_radio(
38 | "test",
39 | answer("two", correct = TRUE)
40 | )
41 |
42 | clean_question <- function(q) {
43 | # strip random bits
44 | q$ids <- NULL
45 | q$seed <- 42L
46 | q$answers[[1]]$id <- "answer-id"
47 | q
48 | }
49 |
50 | expect_equal(clean_question(q_fn), clean_question(q_no_fn))
51 | })
52 |
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/snippets.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | function loadSnippet(snippet, mode) {
4 | mode = mode || "r";
5 | $("#" + snippet).addClass("snippet").removeClass('sourceCode');
6 | const editor = ace.edit(snippet);
7 | editor.setHighlightActiveLine(false);
8 | editor.setShowPrintMargin(false);
9 | editor.setReadOnly(true);
10 | editor.setShowFoldWidgets(false);
11 | editor.renderer.setDisplayIndentGuides(false);
12 | editor.renderer.setShowGutter(true);
13 | editor.renderer.setOption('showLineNumbers', true);
14 | editor.setTheme("ace/theme/textmate");
15 | editor.$blockScrolling = Infinity;
16 | editor.session.setMode("ace/mode/" + mode);
17 | editor.session.getSelection().clearSelection();
18 |
19 | const root = document.querySelector('meta[name="pkgdown-site-root"]').content;
20 | $.get(root + "snippets/" + snippet + ".md", function(data) {
21 | // Write the snippet into the editor
22 | editor.setValue(data, -1);
23 | editor.setOptions({
24 | maxLines: editor.session.getLength()
25 | });
26 |
27 | // and write the snippet into an element for screen readers
28 | const pre = $(' ')
29 | .append($('').text(data));
30 | $(editor.container).prepend(pre);
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/sandbox/autocompletion.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Autocompletion"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ```{r setup, include=FALSE}
8 | library(learnr)
9 | library(nycflights13)
10 | options(tutorial.event_recorder = learnr:::debug_event_recorder)
11 | tutorial_options(
12 | exercise.eval = FALSE,
13 | exercise.checker = function(label, user_code, envir_result, ...) {
14 | if (is.null(envir_result))
15 | list(message = "Bad code!",
16 | correct = FALSE)
17 | else
18 | list(message = "Nice job!",
19 | correct = TRUE,
20 | location = "append")
21 | },
22 | exercise.completion = TRUE,
23 | exercise.diagnostics = TRUE
24 | )
25 | ```
26 |
27 | ## Autocompletion Sandbox
28 |
29 | ```{r autocompletion-setup}
30 | autocompletion <- list(apple = 1, banana = 2, cherry = 3)
31 | ```
32 |
33 | ```{r autocompletion, exercise=TRUE, exercise.completion = TRUE, exercise.setup="autocompletion-setup"}
34 | # Try typing here
35 | ```
36 |
37 | ```{r autocompletion2, exercise=TRUE}
38 | # Try typing here
39 | ```
40 |
41 | ```{r no-diagnostics, exercise=TRUE, exercise.diagnostics=FALSE}
42 | # no diagnostics here
43 | ```
44 |
45 | ```{r diagnostics, exercise=TRUE}
46 | # diagnostics here
47 | ```
48 |
49 |
--------------------------------------------------------------------------------
/tests/testthat/test-praise.R:
--------------------------------------------------------------------------------
1 | test_that("random_phrases()", {
2 | expect_error(random_phrases("foo"), "should be one of")
3 | expect_warning(
4 | expect_equal(random_phrases("praise", "foo"), random_phrases("praise", "en"))
5 | )
6 |
7 | knitr::opts_knit$set("tutorial.language" = "en")
8 | expect_equal(random_phrases("praise"), random_phrases("praise", "en"))
9 | expect_equal(random_phrases("encouragement"), random_phrases("encouragement", "en"))
10 | knitr::opts_knit$set("tutorial.language" = NULL)
11 |
12 | expect_equal(random_phrases("praise", "testing"), "RANDOM PRAISE.")
13 | expect_equal(random_phrases("encouragement", "testing"), "RANDOM ENCOURAGEMENT.")
14 | })
15 |
16 | test_that("random_phrases_add()", {
17 | random_phrases_add(
18 | language = "bogus",
19 | praise = "Praise here!",
20 | encouragement = c("Go 1", "Go 2")
21 | )
22 |
23 | expect_equal(random_phrases("praise", "bogus"), "Praise here!")
24 | expect_equal(random_phrases("encouragement", "bogus"), c("Go 1", "Go 2"))
25 |
26 | random_phrases_add("bogus", encouragement = "Go 3")
27 | expect_equal(random_phrases("encouragement", "bogus"), c("Go 1", "Go 2", "Go 3"))
28 |
29 | expect_error(random_phrases_add("bogus", list("bad")))
30 | expect_error(random_phrases_add("bogus", 1:4))
31 | })
32 |
--------------------------------------------------------------------------------
/tests/testthat/tutorials/hint-copy/hint-copy.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hint and Solution Copy and Paste"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ```{r setup, include=FALSE}
8 | knitr::opts_chunk$set(comment = "")
9 | report_code_event <- function(tutorial_id, tutorial_version, user_id, event, data) {
10 | if (is.null(data$code)) return()
11 | rlang::inform(sprintf("---- %s ----\n%s", event, capture.output(dput(data$code))))
12 | }
13 | options(tutorial.event_recorder = report_code_event)
14 | ```
15 |
16 | ## Test Cases
17 |
18 |
19 |
20 | ### Hint
21 |
22 | Reveal the hint, copy and paste into the exercise, and run.
23 |
24 | ```{r ex1, exercise = TRUE}
25 |
26 | ```
27 |
28 | ```{r ex1-hint}
29 | c(
30 | 1,
31 | 2,
32 | 3
33 | )
34 | ```
35 |
36 | Expected output:
37 |
38 | ::: {#ex1-expected-output}
39 | ```{r ref.label="ex1-hint", echo=FALSE}
40 | ```
41 | :::
42 |
43 | ### Solution
44 |
45 | Reveal the solution, copy and paste into the exercise, and run.
46 |
47 | ```{r ex2, exercise = TRUE}
48 |
49 | ```
50 |
51 | ```{r ex2-solution}
52 | c(
53 | "apple",
54 | "banana",
55 | "coconut"
56 | )
57 | ```
58 |
59 | Expected output:
60 |
61 | ::: {#ex2-expected-output}
62 | ```{r ref.label="ex2-solution", echo=FALSE}
63 | ```
64 | :::
65 |
--------------------------------------------------------------------------------
/tests/testthat/test-mark_as.R:
--------------------------------------------------------------------------------
1 | context("mark as")
2 |
3 | test_that("correct", {
4 | ans <- correct()
5 | expect_s3_class(ans, "learnr_mark_as")
6 | expect_equal(ans$correct, TRUE)
7 | expect_equal(ans$messages, NULL)
8 | })
9 |
10 | test_that("correct passes messages", {
11 | msg <- quiz_text("**msg**")
12 | ans <- correct(msg)
13 | expect_s3_class(ans, "learnr_mark_as")
14 | expect_equal(ans$correct, TRUE)
15 | expect_equal(ans$messages, msg)
16 |
17 | msgs <- htmltools::tagList(
18 | quiz_text("**msg 1**"),
19 | quiz_text("_msg 2_")
20 | )
21 | ans <- correct(msgs)
22 | expect_s3_class(ans, "learnr_mark_as")
23 | expect_equal(ans$correct, TRUE)
24 | expect_equal(ans$messages, msgs)
25 | })
26 |
27 |
28 | test_that("incorrect", {
29 | ans <- incorrect()
30 | expect_s3_class(ans, "learnr_mark_as")
31 | expect_equal(ans$correct, FALSE)
32 | expect_equal(ans$messages, NULL)
33 | })
34 |
35 | test_that("incorrect passes messages", {
36 | msg <- quiz_text("**msg**")
37 | ans <- incorrect(msg)
38 | expect_s3_class(ans, "learnr_mark_as")
39 | expect_equal(ans$correct, FALSE)
40 | expect_equal(ans$messages, msg)
41 |
42 | msgs <- htmltools::tagList(
43 | quiz_text("**msg 1**"),
44 | quiz_text("_msg 2_")
45 | )
46 | ans <- incorrect(msgs)
47 | expect_s3_class(ans, "learnr_mark_as")
48 | expect_equal(ans$correct, FALSE)
49 | expect_equal(ans$messages, msgs)
50 | })
51 |
--------------------------------------------------------------------------------
/man/event_register_handler.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/events.R
3 | \name{event_register_handler}
4 | \alias{event_register_handler}
5 | \title{Register an event handler callback}
6 | \usage{
7 | event_register_handler(event, callback)
8 | }
9 | \arguments{
10 | \item{event}{The name of an event.}
11 |
12 | \item{callback}{A function to be invoked when an event with a specified name
13 | occurs. The callback must take parameters \code{session}, \code{event}, and \code{data}.}
14 | }
15 | \value{
16 | A function which, if invoked, will remove the callback.
17 | }
18 | \description{
19 | Register an event handler on a per-tutorial basis. Handlers for an event will
20 | be fired in the order that they were registered.
21 | }
22 | \details{
23 | In most cases, this will be called within a learnr document. If that is the
24 | case, then the handler will exist as long as the document (that is, the Shiny
25 | application) is running.
26 |
27 | If this function is called in a learnr .Rmd document, it should be in a chunk
28 | with \code{context="server-start"}. If it is called with \code{context="server"}, the
29 | handler will be registered at least two times (once for the application as a
30 | whole, and once per user session).
31 |
32 | If this function is called outside of a learnr document, then the handler
33 | will persist until the learnr package is unloaded, typically when the R
34 | session is stopped.
35 | }
36 |
--------------------------------------------------------------------------------
/inst/tutorials/polyglot/polyglot.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Multi-language exercises"
3 | author: "Cass (Z.) Wilkinson Saldaña"
4 | output: learnr::tutorial
5 | runtime: shiny_prerendered
6 | ---
7 |
8 | ```{r setup, include = FALSE}
9 | library(learnr)
10 |
11 | # altered from https://datacarpentry.org/R-ecology-lesson/05-r-and-databases.html
12 | if (!file.exists("portal_mammals.sqlite")) {
13 | download.file(url = "https://ndownloader.figshare.com/files/2292171",
14 | destfile = "portal_mammals.sqlite", mode = "wb")
15 | }
16 | mammals <- DBI::dbConnect(RSQLite::SQLite(), "portal_mammals.sqlite")
17 | ```
18 |
19 | ### Python or R?
20 |
21 | The following two code exercises run language agnostic code. It will finally evaluate to either 5 or 10.
22 |
23 | ```{r r-not-python, exercise=TRUE, exercise.lines = 5}
24 | # if running in R, this should return 10
25 | # if running in Python, this should return FALSE and 5
26 | x = 5
27 | x <-10
28 | print(x)
29 | ```
30 |
31 | ```{python python-not-r, exercise=TRUE, exercise.lines = 5}
32 | # if running in R, this should return 10
33 | # if running in Python, this should return FALSE and 5
34 | x = 5
35 | x <-10
36 | print(x)
37 | ```
38 |
39 | ### Bash
40 |
41 | Execute exercises in bash
42 |
43 | ```{bash bash-not-r, exercise = TRUE}
44 | echo "hello world"
45 | date
46 | ```
47 |
48 | ### SQL
49 |
50 | Use a SQL connection defined in a setup chunk.
51 |
52 | ```{sql sql-not-r, exercise = TRUE, connection="mammals"}
53 | SELECT *
54 | FROM `surveys`
55 | LIMIT 10
56 | ```
57 |
--------------------------------------------------------------------------------
/tests/manual/ace-mode/ace-mode.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Ace Language Mode"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ```{r setup, include=FALSE}
8 | library(learnr)
9 | ```
10 |
11 | ## Test Cases
12 |
13 | These exercises don't have checks.
14 |
15 | ### R
16 |
17 | An exercise using R.
18 |
19 | ```{r ex1, exercise = TRUE}
20 | stem(log(rivers))
21 | ```
22 |
23 | ```{r ex1-hint}
24 | stem(rivers)
25 | ```
26 |
27 | ### Python
28 |
29 | ```{python ex-python, exercise = TRUE}
30 | [x for x in [3, 4, 5, 6, 7] if x > 5]
31 | ```
32 |
33 | ```{python ex-python-hint}
34 | [x for x in [3, 4, 5, 6, 7] if x > 2]
35 | ```
36 |
37 | ### JavaScript
38 |
39 | *Note: syntax highlighting works but no output will be shown unless you run the code in an IIFE and use `console.log()` or `window.alert()`.*
40 |
41 | ```{js ex-js, exercise = TRUE, results = "asis"}
42 | (function() {
43 | const value = Math.min.apply(Math, [42, 6, 27])
44 | window.alert(`The value is ${value}`)
45 | })()
46 | ```
47 |
48 | ```{js ex-js-hint}
49 | console.log(Math.min(42, 6, 27))
50 | ```
51 |
52 | ### Julia
53 |
54 | ```{julia ex-julia, exercise = TRUE}
55 | # While loops loop while a condition is true
56 | let x = 0
57 | while x < 4
58 | println(x)
59 | x += 1 # Shorthand for in place increment: x = x + 1
60 | end
61 | end
62 | ```
63 |
64 | ```{julia ex-julia-hint}
65 | for pair in Dict("dog" => "mammal", "cat" => "mammal", "mouse" => "mammal")
66 | from, to = pair
67 | println("$from is a $to")
68 | end
69 | ```
70 |
--------------------------------------------------------------------------------
/tests/testthat/test-mock_exercise.R:
--------------------------------------------------------------------------------
1 | test_that("exercise mocks: mock_prep_setup()", {
2 | chunks <- list(
3 | mock_chunk("setup-1", "x <- 1"),
4 | mock_chunk("setup-2", "y <- 2", exercise.setup = "setup-1"),
5 | mock_chunk("setup-3", "z <- 3", exercise.setup = "setup-2")
6 | )
7 | expect_equal(mock_prep_setup(chunks, "setup-3"), "x <- 1\ny <- 2\nz <- 3")
8 | expect_equal(mock_prep_setup(chunks, "setup-2"), "x <- 1\ny <- 2")
9 | expect_equal(mock_prep_setup(chunks, "setup-1"), "x <- 1")
10 |
11 | # random order
12 | expect_equal(mock_prep_setup(chunks[3:1], "setup-3"), "x <- 1\ny <- 2\nz <- 3")
13 | expect_equal(mock_prep_setup(chunks[c(1, 3, 2)], "setup-3"), "x <- 1\ny <- 2\nz <- 3")
14 | expect_equal(mock_prep_setup(chunks[c(2, 3, 1)], "setup-3"), "x <- 1\ny <- 2\nz <- 3")
15 | expect_equal(mock_prep_setup(chunks[c(2, 1, 3)], "setup-3"), "x <- 1\ny <- 2\nz <- 3")
16 |
17 | # checks that setup chunk is in chunks
18 | expect_error(mock_prep_setup(chunks, "setup-Z"), "setup-Z")
19 |
20 | # cycles
21 | chunks[[1]]$opts$exercise.setup = "setup-3"
22 | expect_error(mock_prep_setup(chunks, "setup-3"), "-> setup-3$")
23 |
24 | # duplicate labels
25 | expect_error(mock_prep_setup(chunks[c(1, 1)], "setup-1"), "Duplicated")
26 | })
27 |
28 | test_that("mock_exercise() errors for duplicated chunks", {
29 | expect_error(
30 | mock_exercise(
31 | chunks = list(
32 | mock_chunk("ex1", "1 + 1", exercise = TRUE),
33 | mock_chunk("ex1", "2 + 2", exercise = TRUE)
34 | ),
35 | label = "ex"
36 | )
37 | )
38 | })
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name : Bug report
3 | about : Report a bug in learnr.
4 | ---
5 |
6 |
17 |
18 | ### System details
19 |
20 | Output of `sessioninfo::session_info()()`:
21 |
22 | ```
23 | # sessioninfo::session_info() output goes here
24 | ```
25 |
26 | ### Example application *or* steps to reproduce the problem
27 |
28 |
29 |
30 |
31 | ```r
32 | library(learnr)
33 | # Minimal, self-contained example code or markdown file goes here
34 | ```
35 |
36 | ### Describe the problem in detail
37 |
--------------------------------------------------------------------------------
/.github/pkgdown-pr-preview-build.R:
--------------------------------------------------------------------------------
1 | # ---- pkgdown::deploy_site_github() ----
2 | #
3 | # Follows the steps of deploy_site_github() but renders into
4 | # `preview/pr#` of `gh-pages` branch.
5 |
6 | # Pull gh-pages branch
7 | callr::run("git", c("remote", "set-branches", "--add", "origin", "gh-pages"), echo_cmd = TRUE)
8 | callr::run("git", c("fetch", "origin", "gh-pages"), echo_cmd = TRUE)
9 |
10 | local({
11 | # Setup worktree in tempdir
12 | dest_dir <- fs::dir_create(fs::file_temp())
13 | on.exit(unlink(dest_dir, recursive = TRUE), add = TRUE)
14 |
15 | callr::run("git", c("worktree", "add", "--track", "-B", "gh-pages", dest_dir, "origin/gh-pages"), echo_cmd = TRUE)
16 | on.exit(add = TRUE, {
17 | callr::run("git", c("worktree", "remove", dest_dir), echo_cmd = TRUE)
18 | })
19 |
20 | # PR preview is in a preview/pr# subdirectory of gh-pages branch
21 | dest_preview <- file.path("preview", paste0("pr", Sys.getenv("PR_NUMBER")))
22 | dest_dir_preview <- fs::dir_create(fs::path(dest_dir, dest_preview))
23 |
24 | url_base <- yaml::read_yaml("pkgdown/_pkgdown.yml")$url
25 |
26 | # Build the preview site in the /preview/pr#/ directory
27 | pkgdown:::build_site_github_pages(
28 | dest_dir = dest_dir_preview,
29 | override = list(
30 | url = file.path(url_base, dest_preview)
31 | ),
32 | clean = TRUE
33 | )
34 |
35 | msg <- paste("[preview]", pkgdown:::construct_commit_message("."))
36 | pkgdown:::github_push(dest_dir, msg, "origin", "gh-pages")
37 |
38 | message(
39 | "::notice title=pkgdown preview::",
40 | file.path(url_base, dest_preview)
41 | )
42 | })
43 |
44 |
--------------------------------------------------------------------------------
/man/debug_exercise_checker.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/debug_exercise_checker.R
3 | \name{debug_exercise_checker}
4 | \alias{debug_exercise_checker}
5 | \title{An Exercise Checker for Debugging}
6 | \usage{
7 | debug_exercise_checker(
8 | label,
9 | user_code,
10 | solution_code,
11 | check_code,
12 | envir_result,
13 | evaluate_result,
14 | envir_prep,
15 | last_value,
16 | engine,
17 | ...
18 | )
19 | }
20 | \arguments{
21 | \item{label}{Exercise label}
22 |
23 | \item{user_code}{Submitted user code}
24 |
25 | \item{solution_code}{The code in the \verb{*-solution} chunk}
26 |
27 | \item{check_code}{The checking code that originates from the \verb{*-check} chunk,
28 | the \verb{*-code-check} chunk, or the \verb{*-error-check} chunk.}
29 |
30 | \item{evaluate_result}{The return value from \code{evaluate::evaluate()}, called
31 | on \code{user_code}}
32 |
33 | \item{envir_prep, envir_result}{The environment before running user code
34 | (\code{envir_prep}) and the environment just after running the user's code
35 | (\code{envir_result}).}
36 |
37 | \item{last_value}{The last value after evaluating \code{user_code}}
38 |
39 | \item{engine}{The engine of the exercise chunk}
40 |
41 | \item{...}{Not used (future compatibility)}
42 | }
43 | \value{
44 | Feedback for use in exercise debugging.
45 | }
46 | \description{
47 | An exercise checker for debugging that renders all of the expected arguments
48 | of the \code{exercise.checker} option into HTML. Additionally, this function is
49 | used in testing of \code{evaluate_exercise()}.
50 | }
51 | \keyword{internal}
52 |
--------------------------------------------------------------------------------
/man/safe.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/run.R
3 | \name{safe}
4 | \alias{safe}
5 | \title{Execute R code in a safe R environment}
6 | \usage{
7 | safe(expr, ..., show = TRUE, env = safe_env())
8 | }
9 | \arguments{
10 | \item{expr}{expression that contains all the necessary library calls to
11 | execute. Expressions within callr do not inherit the existing,
12 | loaded libraries.}
13 |
14 | \item{...}{parameters passed to \code{callr::\link[callr]{r}}}
15 |
16 | \item{show}{Logical that determines if output should be displayed}
17 |
18 | \item{env}{Environment to evaluate the document in}
19 | }
20 | \value{
21 | The result of \code{expr}.
22 | }
23 | \description{
24 | When rendering (or running) a document with R markdown, it inherits the
25 | current R Global environment. This will produce unexpected behaviors,
26 | such as poisoning the R Global environment with existing variables. By
27 | rendering the document in a new, safe R environment, a \emph{vanilla},
28 | rendered document is produced.
29 | }
30 | \details{
31 | The environment variable \code{LEARNR_INTERACTIVE} will be set to \code{"1"}
32 | or \code{"0"} depending on if the calling session is interactive or not.
33 |
34 | Using \code{safe} should only be necessary when locally deployed.
35 | }
36 | \examples{
37 | \dontrun{
38 | # Direct usage
39 | safe(run_tutorial("hello", package = "learnr"))
40 |
41 | # Programmatic usage
42 | library(rlang)
43 |
44 | expr <- quote(run_tutorial("hello", package = "learnr"))
45 | safe(!!expr)
46 |
47 | tutorial <- "hello"
48 | safe(run_tutorial(!!tutorial, package = "learnr"))
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/vignettes/articles/example_cards.R:
--------------------------------------------------------------------------------
1 | card <- function(
2 | ...,
3 | class_card = NULL,
4 | class_card_body = NULL,
5 | class_card_title = NULL,
6 | class_card_footer = "text-end"
7 | ) {
8 | card_data <- list(...)
9 | htmltools::withTags(
10 | div(
11 | class = "col",
12 | div(
13 | class = c("card h-100 shadow-sm", class_card),
14 | a(
15 | href = card_data$link,
16 | img(
17 | src = card_data$image,
18 | class = "card-img-top",
19 | alt = paste0("Preview image of ", card_data$title)
20 | )
21 | ),
22 | div(
23 | class = c("card-body", class_card_body),
24 | h5(class = c("card-title", class_card_title), a(href = card_data$link, card_data$title)),
25 | if (!is.null(card_data$text)) div(
26 | class = "card-text text-muted fs-6",
27 | htmltools::HTML(commonmark::markdown_html(card_data$text))
28 | ),
29 | ),
30 | if (!is.null(card_data$footer)) div(
31 | class = c("card-footer", class_card_footer),
32 | htmltools::HTML(card_data$footer)
33 | )
34 | )
35 | )
36 | )
37 | }
38 |
39 | example_cards <- function(yml, group = NULL, class_row = "row-cols-1 row-cols-md-2 row-cols-lg-3") {
40 | examples <- if (is.list(yml)) {
41 | yml
42 | } else {
43 | yaml::read_yaml(yml)
44 | }
45 |
46 | examples <- purrr::keep(examples, function(x) x[["group"]] %in% group)
47 | examples <- purrr::transpose(examples)
48 |
49 | htmltools::tags$div(
50 | class = paste("row g-4", class_row),
51 | purrr::pmap(examples, card)
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/man/format_quiz.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/quiz_print.R
3 | \name{format.tutorial_question_answer}
4 | \alias{format.tutorial_question_answer}
5 | \alias{format.tutorial_question}
6 | \alias{format.tutorial_quiz}
7 | \alias{print.tutorial_question}
8 | \alias{print.tutorial_question_answer}
9 | \alias{print.tutorial_quiz}
10 | \title{Formatting and printing quizzes, questions, and answers}
11 | \usage{
12 | \method{format}{tutorial_question_answer}(x, ..., spacing = "")
13 |
14 | \method{format}{tutorial_question}(x, ..., spacing = "")
15 |
16 | \method{format}{tutorial_quiz}(x, ...)
17 |
18 | \method{print}{tutorial_question}(x, ...)
19 |
20 | \method{print}{tutorial_question_answer}(x, ...)
21 |
22 | \method{print}{tutorial_quiz}(x, ...)
23 | }
24 | \arguments{
25 | \item{x}{object of interest}
26 |
27 | \item{...}{ignored}
28 |
29 | \item{spacing}{Text to be placed at the beginning of each new line}
30 | }
31 | \description{
32 | Notes:
33 | \itemize{
34 | \item If custom question types are created, custom s3 formating methods may be implemented as well.
35 | \item Due to the shiny runtime of questions, a text representation of quizzes, questions, and answers will be presented.
36 | }
37 | }
38 | \examples{
39 | ex_question <- question("What number is the letter A in the alphabet?",
40 | answer("8"),
41 | answer("14"),
42 | answer("1", correct = TRUE),
43 | answer("23"),
44 | incorrect = "See [here](https://en.wikipedia.org/wiki/English_alphabet) and try again.",
45 | allow_retry = TRUE
46 | )
47 | cat(format(ex_question), "\n")
48 | }
49 | \seealso{
50 | \code{\link{quiz}}, \code{\link{question}}, \code{\link{answer}}
51 | }
52 |
--------------------------------------------------------------------------------
/man/rmd-fragments/learnr-examples-showcase.Rmd:
--------------------------------------------------------------------------------
1 | Here are some simple examples of tutorials created with the **learnr** package:
2 |
3 | ```{r, eval=FALSE, echo=FALSE}
4 | ## Not included to avoid rcmdcheck warnings about dependency on {quillt}
5 | # cat(format(quillt::examples("examples.yml", showcaseOnly = TRUE)))
6 | ```
7 |
8 |
45 |
--------------------------------------------------------------------------------
/learnr-js/build.js:
--------------------------------------------------------------------------------
1 | const { readFileSync } = require('fs')
2 | const { build } = require('esbuild')
3 | const babelPlugin = require('esbuild-plugin-babel')
4 |
5 | const excludeVendorFromSourceMapPlugin = ({ filter }) => ({
6 | name: 'excludeVendorFromSourceMap',
7 | setup (build) {
8 | build.onLoad({ filter }, args => {
9 | if (args.path.endsWith('.js')) {
10 | return {
11 | contents:
12 | readFileSync(args.path, 'utf8') +
13 | '\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJtYXBwaW5ncyI6IkEifQ==',
14 | loader: 'default'
15 | }
16 | }
17 | })
18 | }
19 | })
20 |
21 | const excludeNodeModules = excludeVendorFromSourceMapPlugin({
22 | filter: /node_modules/
23 | })
24 |
25 | const buildConfig = {
26 | sourcemap: true,
27 | bundle: true,
28 | minify: true,
29 | allowOverwrite: true,
30 | logLevel: 'info',
31 | target: ['es5'],
32 | plugins: [excludeNodeModules, babelPlugin()]
33 | }
34 |
35 | const buildTutorial = {
36 | entryPoints: ['learnr-js/tutorial/tutorial.js'],
37 | outfile: 'inst/lib/tutorial/tutorial.js',
38 | ...buildConfig
39 | }
40 |
41 | const buildTutorialFormat = {
42 | entryPoints: ['learnr-js/format/tutorial-format.js'],
43 | outfile: 'inst/rmarkdown/templates/tutorial/resources/tutorial-format.js',
44 | ...buildConfig
45 | }
46 |
47 | const buildI18N = {
48 | entryPoints: ['learnr-js/i18n/tutorial-i18n-init.js'],
49 | outfile: 'inst/lib/i18n/tutorial-i18n-init.js',
50 | ...buildConfig
51 | }
52 |
53 | build(buildTutorial).catch(() => process.exit(1))
54 | build(buildTutorialFormat).catch(() => process.exit(1))
55 | build(buildI18N).catch(() => process.exit(1))
56 |
--------------------------------------------------------------------------------
/vignettes/articles/examples.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "learnr Tutorial Examples"
3 | description: >
4 | A collection of example interactive tutorials that use the learnr package.
5 | toc: false
6 | output:
7 | html_document:
8 | toc_float: false
9 | editor_options:
10 | chunk_output_type: console
11 | ---
12 |
13 | ```{r setup, include=FALSE}
14 | knitr::opts_chunk$set(echo = FALSE)
15 | source("example_cards.R", local = TRUE)
16 | ```
17 |
18 | ```{css echo=FALSE}
19 | .row > main { max-width: 100%; }
20 | ```
21 |
22 | ## Complete Tutorials
23 |
24 | These tutorials were designed to showcase the features of the learnr package --- and to teach learners how to use R!
25 | Try working through these tutorials to get a sense of the look and feel of a learnr tutorial.
26 | Then check out the source code to see how these tutorials were made!
27 |
28 | ```{r example-cards-showcase}
29 | example_cards("examples.yml", "showcase")
30 | ```
31 |
32 | ## Demo Tutorials
33 |
34 | There's a lot that learnr can do.
35 | These tutorials showcase specific learnr features or show you some possibilities that build on other packages designed to work with R Markdown and learnr.
36 |
37 | ```{r example-cards-demo}
38 | example_cards("examples.yml", "demo")
39 | ```
40 |
41 | ## Community Showcase
42 |
43 | learnr's community has built some excellent tutorials and resources
44 | for learning data science and programming.
45 | Here are just a few highlights.
46 | (Please [suggest your favorite learnr resources](https://github.com/rstudio/learnr/discussions/categories/show-and-tell).)
47 |
48 | ```{r example-cards-community}
49 | example_cards("examples.yml", "community")
50 | ```
51 |
52 | ```{js echo=FALSE}
53 | document.getElementById('main').classList = ''
54 | ```
55 |
--------------------------------------------------------------------------------
/tests/testthat/helpers.R:
--------------------------------------------------------------------------------
1 |
2 | # https://github.com/rstudio/rmarkdown/blob/2faee0040a39008a47bdf1ba840bf402cba15a65/tests/testthat/helpers.R
3 |
4 | skip_if_not_pandoc <- function(ver = NULL) {
5 | if (!rmarkdown::pandoc_available(ver)) {
6 | msg <- if (is.null(ver)) {
7 | "Pandoc is not available"
8 | } else {
9 | sprintf("Version of Pandoc is lower than %s.", ver)
10 | }
11 | skip(msg)
12 | }
13 | }
14 |
15 | skip_if_pandoc <- function(ver = NULL) {
16 | if (rmarkdown::pandoc_available(ver)) {
17 | msg <- if (is.null(ver)) {
18 | "Pandoc is available"
19 | } else {
20 | sprintf("Version of Pandoc is greater than %s.", ver)
21 | }
22 | skip(msg)
23 | }
24 | }
25 |
26 | skip_on_ci_if_not_pr <- function() {
27 | # Don't skip locally
28 | if (!nzchar(Sys.getenv("CI", ""))) return()
29 | # If on CI, don't skip if envvar set by workflow is present
30 | if (nzchar(Sys.getenv("CI_IN_PR", ""))) return()
31 | # If on CI and not in a PR branch workflow... skip these tests
32 | skip("Skipping on CI, tests run in PR checks only")
33 | }
34 |
35 | skip_if_not_py_available <- function() {
36 | skip_if_not(reticulate::py_available(initialize = TRUE), "Python not available on this system")
37 | }
38 |
39 | expect_marked_as <- function(object, correct, messages = NULL) {
40 | if (is.null(messages)) {
41 | expect_equal(object, mark_as(correct))
42 | return()
43 | }
44 |
45 | if (length(messages) > 1) {
46 | messages_orig <- messages
47 | messages <- quiz_text(messages_orig[[1]])
48 | for (i in seq_along(messages_orig)[-1]) {
49 | messages <- htmltools::tagList(messages, messages_orig[[i]])
50 | }
51 | }
52 |
53 | expect_equal(object, mark_as(correct, messages))
54 | }
55 |
--------------------------------------------------------------------------------
/pkgdown/templates/content-article.html:
--------------------------------------------------------------------------------
1 | {{^as_is}}$for(header-includes)$
2 | $header-includes$
3 | $endfor${{/as_is}}
4 |
5 | $if(banner)$
$endif$
6 |
7 |
8 |
9 |
37 |
38 | $for(include-before)$
39 | $include-before$
40 | $endfor$
41 |
42 | $if(abstract)$
43 |
44 |
Abstract
45 | $abstract$
46 |
47 | $endif$
48 |
49 | $body$
50 |
51 | {{#toc}}
52 |
53 |
54 | {{#translate}}{{on_this_page}}{{/translate}}
55 |
56 |
57 | {{/toc}}
58 |
59 |
60 |
61 | $for(include-after)$
62 | $include-after$
63 | $endfor$
64 |
--------------------------------------------------------------------------------
/man/mark_as_correct_incorrect.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/question_answers.R
3 | \name{correct}
4 | \alias{correct}
5 | \alias{incorrect}
6 | \alias{mark_as}
7 | \title{Mark submission as correct or incorrect}
8 | \usage{
9 | correct(messages = NULL)
10 |
11 | incorrect(messages = NULL)
12 |
13 | mark_as(correct, messages = NULL)
14 | }
15 | \arguments{
16 | \item{messages}{A vector of messages to be displayed. The type of message
17 | will be determined by the \code{correct} value. Note that markdown messages are
18 | not rendered into HTML, but you may provide HTML using \code{\link[htmltools:HTML]{htmltools::HTML()}}
19 | or \link[htmltools:builder]{htmltools::tags}.}
20 |
21 | \item{correct}{Logical: is the question answer is correct}
22 | }
23 | \value{
24 | Returns a list with class \code{learnr_mark_as} to be returned from the
25 | \code{\link[=question_is_correct]{question_is_correct()}} method for the learnr question type.
26 | }
27 | \description{
28 | Helper method to communicate that the user's submission was correct or
29 | incorrect. These functions were originally designed for developers to create
30 | \code{\link[=question_is_correct]{question_is_correct()}} methods for custom question types, but they can also
31 | be called inside the functions created by \code{\link[=answer_fn]{answer_fn()}} to dynamically
32 | determine the result and message provided to the user.
33 | }
34 | \examples{
35 | # Radio button question implementation of `question_is_correct`
36 | question_is_correct.radio <- function(question, value, ...) {
37 | for (ans in question$answers) {
38 | if (as.character(ans$option) == value) {
39 | return(mark_as(ans$correct, ans$message))
40 | }
41 | }
42 | mark_as(FALSE, NULL)
43 | }
44 |
45 | }
46 | \seealso{
47 | \code{\link[=answer_fn]{answer_fn()}}
48 | }
49 |
--------------------------------------------------------------------------------
/tools/update-ace.R:
--------------------------------------------------------------------------------
1 |
2 | (function() {
3 | owd <- getwd()
4 | on.exit({setwd(owd)})
5 |
6 |
7 | ROOT <- rprojroot::find_package_root_file()
8 | ACE_VERSION <- "1.10.1"
9 | ACE_FILES <- c(
10 | "ace.js",
11 | "ext-language_tools.js",
12 | "mode-css.js",
13 | "mode-html.js",
14 | "mode-javascript.js",
15 | "mode-julia.js",
16 | "mode-plain_text.js",
17 | "mode-python.js",
18 | "mode-r.js",
19 | "mode-rdoc.js",
20 | "mode-rhtml.js",
21 | "mode-sql.js",
22 | "mode-text.js",
23 | "mode-xml.js"
24 | )
25 | ACE_THEME_PREFIX <- "theme-"
26 |
27 | url <- sprintf(
28 | "https://github.com/ajaxorg/ace-builds/archive/v%s.tar.gz",
29 | ACE_VERSION
30 | )
31 |
32 | destfile <- tempfile("ace-tarball-")
33 | on.exit({unlink(destfile)}, add = TRUE)
34 | download.file(url, destfile = destfile)
35 |
36 |
37 | exdir <- tempfile("ace-")
38 | on.exit({unlink(exdir, recursive = TRUE)}, add = TRUE)
39 | dir.create(exdir)
40 | untar(tarfile = destfile, exdir = exdir)
41 |
42 | setwd(exdir)
43 | setwd(sprintf("ace-builds-%s", ACE_VERSION))
44 |
45 |
46 | source <- c(
47 | file.path("src-min", ACE_FILES),
48 | dir("src-min", pattern = ACE_THEME_PREFIX, full.names = TRUE)
49 | )
50 | contents <- paste(lapply(source, function(file) {
51 | readChar(file, file.info(file)$size, TRUE)
52 | }), collapse = "\n")
53 |
54 | target <- file.path(ROOT, "inst/lib/ace/ace.js")
55 | writeLines(contents, con = target, sep = "\n", useBytes = TRUE)
56 |
57 | themes <- sub("^theme-", "", sub("\\.js$", "", dir("src", "^theme-")))
58 |
59 | metadata <- c(
60 | "# This file was autogenerated by 'tools/update-ace.R'",
61 | paste0("ACE_VERSION <- ", shQuote(ACE_VERSION, "cmd")),
62 | paste0("ACE_THEMES <- c(", paste0(shQuote(themes, "cmd"), collapse = ", "), ")")
63 | )
64 |
65 | cat("Saving metadata:\n\n", paste0(metadata, collapse = '\n'), "\n")
66 | writeLines(metadata, con = file.path(ROOT, "R/ace.R"))
67 | })()
68 |
--------------------------------------------------------------------------------
/tests/testthat/test-feedback.R:
--------------------------------------------------------------------------------
1 | fdbck <- function(message = "a", correct = TRUE, type = NULL, location = NULL) {
2 | feedback(message, correct, type, location)
3 | }
4 |
5 | test_that("feedback_validated() doesn't validate length-0 objects", {
6 | expect_null(feedback_validated(NULL))
7 | expect_equal(feedback_validated(list()), list())
8 | })
9 |
10 | test_that("feedback must be a list with $message and $correct", {
11 | expect_error(feedback_validated("no"), "must be a list")
12 | expect_error(feedback_validated(list(correct = FALSE)), "message")
13 | expect_error(feedback_validated(list(message = "foo")), "correct")
14 | })
15 |
16 | test_that("feedback message must be character or tag or tagList", {
17 | expect_error(feedback_validated(fdbck(list())), "character")
18 | expect_error(feedback_validated(fdbck(2)), "character")
19 | expect_error(feedback_validated(fdbck(list(a = 1, b = 2))), "character")
20 |
21 | expect_silent(feedback_validated(fdbck("good")))
22 | expect_silent(feedback_validated(fdbck(htmltools::HTML("good"))))
23 | expect_silent(feedback_validated(fdbck(htmltools::p("good"))))
24 | expect_silent(feedback_validated(fdbck(htmltools::tagList(htmltools::p("good")))))
25 | })
26 |
27 | test_that("feedback type must be one of the acceptable values", {
28 | expect_error(feedback_validated(fdbck(type = "--bad--")), "type")
29 |
30 | expect_equal(feedback_validated(fdbck(correct = TRUE))$type, "success")
31 | expect_equal(feedback_validated(fdbck(correct = FALSE))$type, "error")
32 | expect_equal(feedback_validated(fdbck(type = c("info", "error")))$type, "info")
33 | })
34 |
35 | test_that("feedback location must be one of the acceptable values", {
36 | expect_error(feedback_validated(fdbck(location = "--bad--")), "location")
37 |
38 | expect_equal(feedback_validated(fdbck())$location, "append")
39 | expect_equal(feedback_validated(fdbck(location = c("replace", "prepend")))$location, "replace")
40 | })
41 |
--------------------------------------------------------------------------------
/inst/tutorials/setup-chunks/setup-chunks.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Chained setup chunks"
3 | author: "Nischal Shrestha"
4 | output:
5 | learnr::tutorial:
6 | progressive: true
7 | allow_skip: true
8 | runtime: shiny_prerendered
9 | description: >
10 | This is a demo of chained setup chunks and how it can be used to build up / share code.
11 | ---
12 |
13 | ```{r setup, include = FALSE}
14 | library(learnr)
15 | d <- 3
16 | ```
17 |
18 | ## Addition
19 |
20 | ###
21 |
22 | We'll start the setup chunk chain with a regular chunk called `setupA`:
23 |
24 | ````{verbatim}
25 | ```{r setupA}
26 | a <- 5
27 | ```
28 | ````
29 |
30 | ```{r setupA}
31 | a <- 5
32 | ```
33 |
34 | ###
35 |
36 | Use `exercise.setup` to chain setup chunks.
37 | Let's start with a simple, single, setup chunk called `setupB` that depends on `setupA`.
38 |
39 | ````{verbatim}
40 | ```{r setupB, exercise.setup = "setupA"}
41 | b <- a + d
42 | ```
43 | ````
44 |
45 | ```{r setupB, exercise.setup = "setupA"}
46 | b <- a + d
47 | ```
48 |
49 | ###
50 |
51 | Then we define an exercise, `ex1`,
52 | that uses `setupB` as its setup chunk,
53 | thereby also using `setupA` for the exercise.
54 |
55 | ````{verbatim}
56 | ```{r ex1, exercise = TRUE, exercise.setup = "setupB"}
57 | x = b + 1
58 | x
59 | ```
60 | ````
61 |
62 | ```{r ex1, exercise = TRUE, exercise.setup = "setupB"}
63 | x = b + 1
64 | x
65 | ```
66 |
67 | **Evaluate the code in the above exercise. The result of `x` should be `9`.**
68 |
69 | ###
70 |
71 | Now let's define another exercise, `ex2`,
72 | that uses the pre-filled code from `ex1` for its setup code,
73 | thereby also depending on `setupB` and `setupA`.
74 |
75 | ````{verbatim}
76 | ```{r ex2, exercise = TRUE, exercise.setup = "ex1"}
77 | y <- x + 1
78 | y
79 | ```
80 | ````
81 |
82 | ```{r ex2, exercise = TRUE, exercise.setup = "ex1"}
83 | y <- x + 1
84 | y
85 | ```
86 |
87 | **Evaluate the code in the above exercise. The result of `y` should be `10`.**
88 |
--------------------------------------------------------------------------------
/man/learnr-package.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/learnr-package.R
3 | \docType{package}
4 | \name{learnr-package}
5 | \alias{learnr}
6 | \alias{learnr-package}
7 | \title{learnr: Interactive Tutorials for R}
8 | \description{
9 | \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}}
10 |
11 | Create interactive tutorials using R Markdown. Use a combination of narrative, figures, videos, exercises, and quizzes to create self-paced tutorials for learning about R and R packages.
12 | }
13 | \seealso{
14 | Useful links:
15 | \itemize{
16 | \item \url{https://rstudio.github.io/learnr/}
17 | \item \url{https://github.com/rstudio/learnr}
18 | \item Report bugs at \url{https://github.com/rstudio/learnr/issues}
19 | }
20 |
21 | }
22 | \author{
23 | \strong{Maintainer}: Garrick Aden-Buie \email{garrick@posit.co} (\href{https://orcid.org/0000-0002-7111-0077}{ORCID})
24 |
25 | Authors:
26 | \itemize{
27 | \item Barret Schloerke \email{barret@posit.co} (\href{https://orcid.org/0000-0001-9986-114X}{ORCID})
28 | \item JJ Allaire \email{jj@posit.co} [conceptor]
29 | \item Alexander Rossell Hayes \email{alex.rossellhayes@posit.co} (\href{https://orcid.org/0000-0001-9412-0457}{ORCID})
30 | }
31 |
32 | Other contributors:
33 | \itemize{
34 | \item Nischal Shrestha \email{nischal@posit.co} (\href{https://orcid.org/0000-0003-3321-1712}{ORCID}) [contributor]
35 | \item Angela Li \email{angelali921@gmail.com} (vignette) [contributor]
36 | \item Posit, PBC [copyright holder, funder]
37 | \item Ajax.org B.V. (Ace library) [contributor, copyright holder]
38 | \item Zeno Rocha (clipboard.js library) [contributor, copyright holder]
39 | \item Nick Payne (Bootbox library) [contributor, copyright holder]
40 | \item Jake Archibald (idb-keyval library) [contributor, copyright holder]
41 | \item i18next authors (i18next library) [contributor, copyright holder]
42 | }
43 |
44 | }
45 | \keyword{internal}
46 |
--------------------------------------------------------------------------------
/tools/deploy_tutorials.R:
--------------------------------------------------------------------------------
1 |
2 | if (!requireNamespace("remotes")) install.packages("remotes")
3 |
4 | # install rsconnect
5 | remotes::install_cran("rsconnect")
6 |
7 | # install the latest from github
8 | # must install for packrat to work as expected
9 | remotes::install_github("rstudio/learnr", upgrade = "always", force = TRUE)
10 |
11 | # install missing tutorial deps
12 | remotes::install_cran("renv")
13 | remotes::install_cran(
14 | setdiff(
15 | unique(renv::dependencies("inst/tutorials/")$Package),
16 | unname(installed.packages()[,"Package"])
17 | )
18 | )
19 |
20 | server <- "shinyapps.io"
21 | account <- "learnr-examples"
22 |
23 | deploy_app <- function(
24 | app_dir,
25 | name = basename(app_dir),
26 | ...
27 | ) {
28 | cat("\n\n\n")
29 | message("Deploying: ", name)
30 | cat("\n")
31 | rsconnect::deployApp(
32 | appDir = app_dir,
33 | appName = name,
34 | server = server,
35 | account = account,
36 | forceUpdate = TRUE,
37 | ...
38 | )
39 | }
40 |
41 | deploy_tutorial <- function(
42 | app_dir,
43 | doc = dir(app_dir, pattern = "\\.Rmd$")[1],
44 | name = basename(app_dir)
45 | ) {
46 | deploy_app(
47 | app_dir = app_dir,
48 | name = name,
49 | appPrimaryDoc = doc
50 | )
51 | }
52 |
53 |
54 | deploy_folder <- function(path, fn) {
55 | lapply(
56 | dir(path, full.names = TRUE),
57 | function(path) {
58 | if (dir.exists(path)) {
59 | fn(path)
60 | }
61 | }
62 | )
63 | }
64 |
65 | deploy_vignettes <- function() {
66 | lapply(
67 | dir("vignettes", pattern = ".Rmd", full.names = TRUE),
68 | function(rmd) {
69 | rsconnect::deployDoc(
70 | doc = rmd,
71 | appName = sub(".Rmd", "", basename(rmd)),
72 | server = server,
73 | account = account,
74 | forceUpdate = TRUE
75 | )
76 | }
77 | )
78 | }
79 |
80 | deploy_vignettes()
81 | deploy_folder("inst/tutorials", deploy_tutorial)
82 |
83 | message("done")
84 |
--------------------------------------------------------------------------------
/tests/testthat/test-install-dependencies.R:
--------------------------------------------------------------------------------
1 | context("install tutorial dependencies")
2 |
3 | create_test_tutorial <- function(code) {
4 | tutorial_dir <- file.path(tempdir(), "tutorial-deps")
5 | dir.create(tutorial_dir)
6 | tutorial_path <- tempfile("tutorial-deps",
7 | tmpdir = tutorial_dir,
8 | fileext = ".R")
9 | writeLines(code, con = tutorial_path)
10 | invisible(tutorial_dir)
11 | }
12 |
13 | test_that("get_needed_pkgs returns appropriate packages", {
14 | tutorial_dir <- create_test_tutorial("library(pkg1)\npkg2::n()")
15 | on.exit(unlink(tutorial_dir, recursive = TRUE), add = TRUE)
16 | expect_equal(get_needed_pkgs(tutorial_dir), c("pkg1", "pkg2"))
17 | })
18 |
19 | test_that("get_needed_pkgs returns length 0 if no new packages", {
20 | tutorial_dir <- create_test_tutorial("sum()")
21 | on.exit(unlink(tutorial_dir, recursive = TRUE), add = TRUE)
22 | expect_equal(length(get_needed_pkgs(tutorial_dir)), 0)
23 | })
24 |
25 | test_that("tutorial dependency check returns NULL for no dependencies", {
26 | tutorial_dir <- create_test_tutorial("sum(1:3)")
27 | on.exit(unlink(tutorial_dir, recursive = TRUE), add = TRUE)
28 |
29 | expect_silent(install_tutorial_dependencies(tutorial_dir))
30 | })
31 |
32 | # test_that("tutorial dependency check works (interactive)", {
33 | # skip_if_not(interactive())
34 | #
35 | # tutorial_dir <- create_test_tutorial("library(pkg1)\npkg2::n()")
36 | # on.exit(unlink(tutorial_dir, recursive = TRUE), add = TRUE)
37 | #
38 | # expect_error(
39 | # with_mock(
40 | # ask_pkgs_install = function(x) 2,
41 | # install_tutorial_dependencies(tutorial_dir)
42 | # )
43 | # )
44 | # })
45 |
46 | test_that("tutorial dependency check works (not interactive)", {
47 | skip_if(interactive())
48 |
49 | tutorial_dir <- create_test_tutorial("library(pkg1)\npkg2::n()")
50 | on.exit(unlink(tutorial_dir, recursive = TRUE), add = TRUE)
51 |
52 | expect_error(install_tutorial_dependencies(tutorial_dir))
53 | })
54 |
--------------------------------------------------------------------------------
/man/question_radio.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/question_radio.R
3 | \name{question_radio}
4 | \alias{question_radio}
5 | \title{Radio question}
6 | \usage{
7 | question_radio(
8 | text,
9 | ...,
10 | correct = "Correct!",
11 | incorrect = "Incorrect",
12 | try_again = incorrect,
13 | allow_retry = FALSE,
14 | random_answer_order = FALSE
15 | )
16 | }
17 | \arguments{
18 | \item{text}{Question or option text}
19 |
20 | \item{...}{Answers created with \code{\link[=answer]{answer()}} or extra parameters passed onto
21 | \code{\link[=question]{question()}}. Function answers are ignored for radio questions because the
22 | user is required to select a single answer.}
23 |
24 | \item{correct}{For \code{question}, text to print for a correct answer (defaults
25 | to "Correct!"). For \code{answer}, a boolean indicating whether this answer is
26 | correct.}
27 |
28 | \item{incorrect}{Text to print for an incorrect answer (defaults to
29 | "Incorrect") when \code{allow_retry} is \code{FALSE}.}
30 |
31 | \item{try_again}{Text to print for an incorrect answer (defaults to
32 | "Incorrect") when \code{allow_retry} is \code{TRUE}.}
33 |
34 | \item{allow_retry}{Allow retry for incorrect answers. Defaults to \code{FALSE}.}
35 |
36 | \item{random_answer_order}{Display answers in a random order.}
37 | }
38 | \value{
39 | Returns a learnr question of type \code{"learnr_radio"}.
40 | }
41 | \description{
42 | Creates a radio button tutorial quiz question. The student can select only
43 | one radio button before submitting their answer. Note: Multiple correct
44 | answers are allowed.
45 | }
46 | \examples{
47 | question_radio(
48 | "Pick the letter B",
49 | answer("A"),
50 | answer("B", correct = TRUE),
51 | answer("C"),
52 | answer("D"),
53 | allow_retry = TRUE,
54 | random_answer_order = TRUE
55 | )
56 |
57 | }
58 | \seealso{
59 | Other Interactive Questions:
60 | \code{\link{question_checkbox}()},
61 | \code{\link{question_numeric}()},
62 | \code{\link{question_text}()},
63 | \code{\link{quiz}()}
64 | }
65 | \concept{Interactive Questions}
66 |
--------------------------------------------------------------------------------
/man/random_phrases_add.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/praise.R
3 | \name{random_phrases_add}
4 | \alias{random_phrases_add}
5 | \title{Add phrases to the bank of random phrases}
6 | \usage{
7 | random_phrases_add(language = "en", praise = NULL, encouragement = NULL)
8 | }
9 | \arguments{
10 | \item{language}{The language of the phrases to be added.}
11 |
12 | \item{praise, encouragement}{A vector of praising or encouraging phrases,
13 | including final punctuation.}
14 | }
15 | \value{
16 | Returns the previous custom phrases invisibly when called in the
17 | global setup chunk or interactively. Otherwise, it returns a shiny pre-
18 | rendered chunk.
19 | }
20 | \description{
21 | Augment the random phrases available in \code{\link[=random_praise]{random_praise()}} and
22 | \code{\link[=random_encouragement]{random_encouragement()}} with phrases of your own. Note that these phrases
23 | are added to the existing phrases, rather than overwriting them.
24 | }
25 | \section{Usage in learnr tutorials}{
26 |
27 |
28 | To add random phrases in a learnr tutorial, you can either include one or
29 | more calls to \code{random_phrases_add()} in your global setup chunk:
30 |
31 | \if{html}{\out{}}\preformatted{```\{r setup, include = FALSE\}`r ''`
32 | library(learnr)
33 | random_phrases_add(
34 | language = "en",
35 | praise = "Great work!",
36 | encouragement = "I believe in you."
37 | )
38 | ```
39 | }\if{html}{\out{
}}
40 |
41 | Alternatively, you can call \code{random_phrases_add()} in a separate, standard
42 | R chunk (with \code{echo = FALSE}):
43 |
44 | \if{html}{\out{}}\preformatted{```\{r setup-phrases, echo = FALSE\}`r ''`
45 | random_phrases_add(
46 | language = "en",
47 | praise = c("Great work!", "You're awesome!"),
48 | encouragement = c("I believe in you.", "Yes we can!")
49 | )
50 | ```
51 | }\if{html}{\out{
}}
52 | }
53 |
54 | \examples{
55 | random_phrases_add("demo", praise = "Great!", encouragement = "Try again.")
56 | random_praise(language = "demo")
57 | random_encouragement(language = "demo")
58 |
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/R/html_selector.R:
--------------------------------------------------------------------------------
1 |
2 | # only handles id and classes
3 | as_selector <- function(selector) {
4 | if (inherits(selector, "shiny_selector") || inherits(selector, "shiny_selector_list")) {
5 | return(selector)
6 | }
7 |
8 | # make sure it's a trimmed string
9 | selector <- str_trim(selector)
10 |
11 | # yell if there is a comma
12 | if (grepl(",", selector, fixed = TRUE)) {
13 | stop("Do not know how to handle comma separated selector values")
14 | }
15 |
16 | # if it contains multiple elements, recurse
17 | if (grepl(" ", selector)) {
18 | selector <- lapply(strsplit(selector, "\\s+"), as_selector)
19 | selector <- structure(class = "shiny_selector_list", selector)
20 | return(selector)
21 | }
22 |
23 | match_everything <- isTRUE(all.equal(selector, "*"))
24 |
25 | element <- str_match(selector, "^([^#.]+)")
26 | selector <- str_remove(selector, "^[^#.]+")
27 |
28 | id <- str_remove(str_match(selector, "^#([^.]+)"), "#")
29 | selector <- str_remove(selector, "^#[^.]+")
30 |
31 | classes <- str_remove(str_match_all(selector, "\\.([^.]+)"), "^\\.")
32 |
33 | structure(class = "shiny_selector", list(
34 | element = element,
35 | id = id,
36 | classes = classes,
37 | match_everything = match_everything
38 | ))
39 | }
40 |
41 | as_selector_list <- function(selector) {
42 | selector <- as_selector(selector)
43 | if (inherits(selector, "shiny_selector")) {
44 | selector <- structure(class = "shiny_selector_list", list(selector))
45 | }
46 | selector
47 | }
48 |
49 | format.shiny_selector <- function(x, ...) {
50 | if (x$match_everything) {
51 | paste0("* // match everything")
52 | } else {
53 | paste0(x$element, if (!is.null(x$id)) paste0("#", x$id), paste0(".", x$classes, collapse = ""))
54 | }
55 | }
56 | format.shiny_selector_list <- function(x, ...) {
57 | paste0(unlist(lapply(x, format, ...)), collapse = " ")
58 | }
59 |
60 | print.shiny_selector <- function(x, ...) {
61 | cat("// css selector\n")
62 | cat(format(x, ...), "\n")
63 | }
64 | print.shiny_selector_list <- function(x, ...) {
65 | cat("// css selector list\n")
66 | cat(format(x, ...), "\n")
67 | }
68 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Pull Request
2 |
3 | Before you submit a pull request, please do the following:
4 |
5 | * Add an entry to NEWS concisely describing what you changed.
6 |
7 | * Add unit tests in the tests/testthat directory.
8 |
9 | * Run Build->Check Package in the RStudio IDE, or `devtools::check()`, to make sure your change did not add any messages, warnings, or errors.
10 |
11 | Doing these things will make it easier for the learnr development team to evaluate your pull request. Even so, we may still decide to modify your code or even not merge it at all. Factors that may prevent us from merging the pull request include:
12 |
13 | * breaking backward compatibility
14 | * adding a feature that we do not consider relevant for learnr
15 | * is hard to understand
16 | * is hard to maintain in the future
17 | * is computationally expensive
18 | * is not intuitive for people to use
19 |
20 | We will try to be responsive and provide feedback in case we decide not to merge your pull request.
21 |
22 |
23 | ## Minimal reproducible example
24 |
25 | Finally, please include a minimal reprex. The goal of a reprex is to make it as easy as possible for me to recreate your problem so that I can fix it. If you've never heard of a reprex before, start by reading , and follow the advice further down the page. Do NOT include session info unless it's explicitly asked for, or you've used `reprex::reprex(..., si = TRUE)` to hide it away.
26 | ```r
27 | reprex::reprex({
28 | library(learnr)
29 | # insert minimal reprex here
30 | })
31 | ```
32 |
33 | Due to the nature of learnr being interactive, if a reprex is not possible, please include a minimal learnr tutorial RMarkdown file that can be called with `rmarkdown::run` to produce an error or display the error has been fixed.
34 |
35 | Delete these instructions once you have read them.
36 |
37 | ---
38 |
39 | Brief description of the original problem and the approach behind your solution.
40 |
41 | ```r
42 | # insert reprex here
43 | ```
44 |
45 | PR task list:
46 | - [ ] Update NEWS
47 | - [ ] Add tests (if possible)
48 | - [ ] Update documentation with `devtools::document()`
49 |
--------------------------------------------------------------------------------
/inst/rmarkdown/templates/tutorial/skeleton/skeleton.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Tutorial"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ```{r setup, include=FALSE}
8 | library(learnr)
9 | knitr::opts_chunk$set(echo = FALSE)
10 | ```
11 |
12 |
13 | ## Topic 1
14 |
15 | ### Exercise
16 |
17 | *Here's a simple exercise with an empty code chunk provided for entering the answer.*
18 |
19 | Write the R code required to add two plus two:
20 |
21 | ```{r two-plus-two, exercise=TRUE}
22 |
23 | ```
24 |
25 | ### Exercise with Code
26 |
27 | *Here's an exercise with some prepopulated code as well as `exercise.lines = 5` to provide a bit more initial room to work.*
28 |
29 | Now write a function that adds any two numbers and then call it:
30 |
31 | ```{r add-function, exercise=TRUE, exercise.lines = 5}
32 | add <- function() {
33 |
34 | }
35 | ```
36 |
37 | ## Topic 2
38 |
39 | ### Exercise with Hint
40 |
41 | *Here's an exercise where the chunk is pre-evaluated via the `exercise.eval` option (so the user can see the default output we'd like them to customize). We also add a "hint" to the correct solution via the chunk immediate below labeled `print-limit-hint`.*
42 |
43 | Modify the following code to limit the number of rows printed to 5:
44 |
45 | ```{r print-limit, exercise=TRUE, exercise.eval=TRUE}
46 | mtcars
47 | ```
48 |
49 | ```{r print-limit-hint}
50 | head(mtcars)
51 | ```
52 |
53 | ### Quiz
54 |
55 | *You can include any number of single or multiple choice questions as a quiz. Use the `question` function to define a question and the `quiz` function for grouping multiple questions together.*
56 |
57 | Some questions to verify that you understand the purposes of various base and recommended R packages:
58 |
59 | ```{r quiz}
60 | quiz(
61 | question("Which package contains functions for installing other R packages?",
62 | answer("base"),
63 | answer("tools"),
64 | answer("utils", correct = TRUE),
65 | answer("codetools")
66 | ),
67 | question("Which of the R packages listed below are used to create plots?",
68 | answer("lattice", correct = TRUE),
69 | answer("tools"),
70 | answer("stats"),
71 | answer("grid", correct = TRUE)
72 | )
73 | )
74 | ```
75 |
76 |
--------------------------------------------------------------------------------
/man/one_time.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/events.R
3 | \name{one_time}
4 | \alias{one_time}
5 | \title{Wrap an expression that will be executed one time in an event handler}
6 | \usage{
7 | one_time(session, cond, expr, label = deparse(substitute(cond)))
8 | }
9 | \arguments{
10 | \item{session}{A Shiny session object.}
11 |
12 | \item{cond}{A condition that is used as a filter. The first time the
13 | condition evaluates to true, \code{expr} will be evaluated; after that, \code{expr}
14 | will not be evaluated again.}
15 |
16 | \item{expr}{An expression that will be evaluated once, the first time that
17 | \code{cond} is true.}
18 |
19 | \item{label}{A unique identifier. This is used as an ID for the condition and
20 | expression; if two calls to \code{one_time()} uses the same label, there will be
21 | an ID collision and only one of them will execute. By default, \code{cond} is
22 | deparsed and used as the label.}
23 | }
24 | \value{
25 | The result of evaluating \code{expr} (\code{one_time()} is intended to be
26 | called within an event handler).
27 | }
28 | \description{
29 | This wraps an expression so that it will be executed one time for a tutorial,
30 | based on some condition. The first time the condition is true, the expression
31 | will be executed; after that, the expression will not be evaluated again.
32 |
33 | The execution state is stored so that if the expression is executed, then the
34 | user quits the tutorial and then returns to it, the expression will not be
35 | executed a second time.
36 |
37 | A common use for \code{one_time} is to execute an expression when a section is
38 | viewed for the first time.
39 | }
40 | \examples{
41 | \dontrun{
42 | # This goes in a {r context="server-start"} chunk
43 |
44 | # The expression with message() will be executed the first time the user
45 | # sees the section with ID "section-exercise-with-hint".
46 | event_register_handler("section_viewed",
47 | function(session, event, data) {
48 | one_time(
49 | session,
50 | data$sectionId == "section-exercise-with-hint",
51 | {
52 | message("Seeing ", data$sectionId, " for the first time.")
53 | }
54 | )
55 | }
56 | )
57 |
58 |
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/tests/testthat/test-events.R:
--------------------------------------------------------------------------------
1 | test_that("Event handlers", {
2 | # Check that session, event, data are passed to callback.
3 | result <- NULL
4 | cancel <- event_register_handler("foo",
5 | function(session, event, data) { result <<- list(session, event, data) }
6 | )
7 | on.exit(cancel(), add = TRUE)
8 | event_trigger("session_obj", "foo", "data")
9 | expect_identical(result, list("session_obj", "foo", "data"))
10 | cancel()
11 |
12 |
13 | # Testing multiple event handlers for same event, checking for order
14 | x <- numeric()
15 | cancel1 <- event_register_handler(
16 | "foo",
17 | function(session, event, data) { x <<- c(x, 1) }
18 | )
19 | on.exit(cancel1(), add = TRUE)
20 |
21 | cancel2 <- event_register_handler(
22 | "foo",
23 | function(session, event, data) { x <<- c(x, 2) }
24 | )
25 | on.exit(cancel2(), add = TRUE)
26 |
27 | event_trigger(NULL, "foo", NA)
28 | expect_identical(x, c(1, 2))
29 |
30 | event_trigger(NULL, "foo", NA)
31 | expect_identical(x, c(1, 2, 1, 2))
32 |
33 | # Cancel first handler
34 | expect_true(cancel1())
35 | expect_false(cancel1())
36 |
37 | event_trigger(NULL, "foo", NA)
38 | expect_identical(x, c(1, 2, 1, 2, 2))
39 | })
40 |
41 |
42 | test_that("Event handler input checking", {
43 | # Should error if callback has incorrect args (session, event, data)
44 | expect_error(
45 | event_register_handler("foo", function(session, data) NULL)
46 | )
47 | expect_error(
48 | event_register_handler("foo", function(session, data, event) NULL)
49 | )
50 |
51 | # Error for empty event name
52 | expect_error(
53 | event_register_handler("", function(session, event, data) NULL)
54 | )
55 | })
56 |
57 |
58 | test_that("Errors are converted to warnings", {
59 | n <- 0
60 | f <- function() g()
61 | g <- function() stop("error in g")
62 | cancel1 <- event_register_handler("foo", function(session, event, data) f())
63 | on.exit(cancel1(), add = TRUE)
64 | cancel2 <- event_register_handler("foo", function(session, event, data) n <<- n + 1)
65 | on.exit(cancel2(), add = TRUE)
66 |
67 | expect_warning(event_trigger(NULL, "foo", NA), "error in g")
68 | # Other callbacks should still have executed.
69 | expect_identical(n, 1)
70 | })
71 |
--------------------------------------------------------------------------------
/man/run_tutorial.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/run.R
3 | \name{run_tutorial}
4 | \alias{run_tutorial}
5 | \title{Run a tutorial}
6 | \usage{
7 | run_tutorial(
8 | name = NULL,
9 | package = NULL,
10 | ...,
11 | shiny_args = NULL,
12 | clean = FALSE,
13 | as_rstudio_job = NULL
14 | )
15 | }
16 | \arguments{
17 | \item{name}{Tutorial name (subdirectory within \code{tutorials/} directory of
18 | installed \code{package}). Alternatively, if \code{package} is not provided, \code{name}
19 | may be a path to a local tutorial R Markdown file or a local directory
20 | containing a learnr tutorial. If \code{package} is provided, \code{name} must be the
21 | tutorial name.}
22 |
23 | \item{package}{Name of package. If \code{name} is a path to the local directory
24 | containing a learnr tutorial, then \code{package} should not be provided.}
25 |
26 | \item{...}{Unused. Included for future expansion and to ensure named
27 | arguments are used.}
28 |
29 | \item{shiny_args}{Additional arguments to forward to
30 | \code{\link[shiny:runApp]{shiny::runApp}}.}
31 |
32 | \item{clean}{When \code{TRUE}, the shiny prerendered HTML files are removed and
33 | the tutorial is re-rendered prior to starting the tutorial.}
34 |
35 | \item{as_rstudio_job}{Runs the tutorial in the background as an RStudio job.
36 | This is the default behavior when \code{run_tutorial()} detects that RStudio is
37 | available and can run jobs. Set to \code{FALSE} to disable and to run the
38 | tutorial in the current R session.
39 |
40 | When running as an RStudio job, \code{run_tutorial()} sets or overrides the
41 | \code{launch.browser} option for \code{shiny_args}. You can instead use the
42 | \code{shiny.launch.browser} global option in your current R session to set
43 | the default behavior when the tutorial is run. See \link[shiny:shinyOptions]{the shiny options documentation} for more information.}
44 | }
45 | \value{
46 | Starts a Shiny server running the learnr tutorial.
47 | }
48 | \description{
49 | Run a tutorial provided by an installed R package.
50 | }
51 | \examples{
52 | # display all "learnr" tutorials
53 | available_tutorials("learnr")
54 |
55 | # run basic example within learnr
56 | \dontrun{
57 | run_tutorial("hello", "learnr")
58 | }
59 |
60 | }
61 | \seealso{
62 | \code{\link{safe}} and \code{\link{available_tutorials}}
63 | }
64 |
--------------------------------------------------------------------------------
/tests/testthat/tutorials/basic.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Tutorial"
3 | output: learnr::tutorial
4 | tutorial:
5 | id: test-basic
6 | version: 9.9.9
7 | runtime: shiny_prerendered
8 | ---
9 |
10 | ```{r setup, include=FALSE}
11 | library(learnr)
12 | knitr::opts_chunk$set(echo = FALSE)
13 | ```
14 |
15 |
16 | ## Topic 1
17 |
18 | ### Exercise
19 |
20 | *Here's a simple exercise with an empty code chunk provided for entering the answer.*
21 |
22 | Write the R code required to add two plus two:
23 |
24 | ```{r two-plus-two, exercise=TRUE}
25 |
26 | ```
27 |
28 | ### Exercise with Code
29 |
30 | *Here's an exercise with some prepopulated code as well as `exercise.lines = 5` to provide a bit more initial room to work.*
31 |
32 | Now write a function that adds any two numbers and then call it:
33 |
34 | ```{r add-function, exercise=TRUE, exercise.lines = 5}
35 | add <- function() {
36 |
37 | }
38 | ```
39 |
40 | ## Topic 2
41 |
42 | ### Exercise with Hint
43 |
44 | *Here's an exercise where the chunk is pre-evaluated via the `exercise.eval` option (so the user can see the default output we'd like them to customize). We also add a "hint" to the correct solution via the chunk immediate below labeled `print-limit-hint`.*
45 |
46 | Modify the following code to limit the number of rows printed to 5:
47 |
48 | ```{r print-limit, exercise=TRUE, exercise.eval=TRUE}
49 | mtcars
50 | ```
51 |
52 | ```{r print-limit-hint}
53 | head(mtcars)
54 | ```
55 |
56 | ### Quiz
57 |
58 | *You can include any number of single or multiple choice questions as a quiz. Use the `question` function to define a question and the `quiz` function for grouping multiple questions together.*
59 |
60 | Some questions to verify that you understand the purposes of various base and recommended R packages:
61 |
62 | ```{r quiz}
63 | # Quiz answers create a stochastic ID that changes when rendered from a clean state
64 | # so this chunk has been disabled.
65 | quiz(
66 | question("Which package contains functions for installing other R packages?",
67 | answer("base"),
68 | answer("tools"),
69 | answer("utils", correct = TRUE),
70 | answer("codetools")
71 | ),
72 | question("Which of the R packages listed below are used to create plots?",
73 | answer("lattice", correct = TRUE),
74 | answer("tools"),
75 | answer("stats"),
76 | answer("grid", correct = TRUE)
77 | )
78 | )
79 | ```
80 |
81 |
--------------------------------------------------------------------------------
/man/tutorial_options.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/options.R
3 | \name{tutorial_options}
4 | \alias{tutorial_options}
5 | \title{Set tutorial options}
6 | \usage{
7 | tutorial_options(
8 | exercise.cap = NULL,
9 | exercise.eval = FALSE,
10 | exercise.timelimit = 30,
11 | exercise.lines = NULL,
12 | exercise.blanks = NULL,
13 | exercise.checker = NULL,
14 | exercise.error.check.code = NULL,
15 | exercise.completion = TRUE,
16 | exercise.diagnostics = TRUE,
17 | exercise.startover = TRUE,
18 | exercise.reveal_solution = TRUE
19 | )
20 | }
21 | \arguments{
22 | \item{exercise.cap}{Caption for exercise chunk (defaults to the engine's icon or the combination of the engine and \code{" code"}).}
23 |
24 | \item{exercise.eval}{Whether to pre-evaluate the exercise so the reader can
25 | see some default output (defaults to \code{FALSE}).}
26 |
27 | \item{exercise.timelimit}{Number of seconds to limit execution time to
28 | (defaults to \code{30}).}
29 |
30 | \item{exercise.lines}{Lines of code for exercise editor (defaults to the
31 | number of lines in the code chunk).}
32 |
33 | \item{exercise.blanks}{A regular expression to be used to identify blanks in
34 | submitted code that the user should fill in. If \code{TRUE} (default), blanks
35 | are three or more underscores in a row. If \code{FALSE}, blank checking is not
36 | performed.}
37 |
38 | \item{exercise.checker}{Function used to check exercise answers
39 | (e.g., \code{gradethis::grade_learnr()}).}
40 |
41 | \item{exercise.error.check.code}{A string containing R code to use for checking
42 | code when an exercise evaluation error occurs (e.g., \code{"gradethis::grade_code()"}).}
43 |
44 | \item{exercise.completion}{Use code completion in exercise editors.}
45 |
46 | \item{exercise.diagnostics}{Show diagnostics in exercise editors.}
47 |
48 | \item{exercise.startover}{Show "Start Over" button on exercise.}
49 |
50 | \item{exercise.reveal_solution}{Whether to reveal the exercise solution if
51 | a solution chunk is provided.}
52 | }
53 | \value{
54 | Nothing. Invisibly sets \link[knitr:opts_chunk]{knitr::opts_chunk} settings.
55 | }
56 | \description{
57 | Set various tutorial options that control the display and evaluation of
58 | exercises.
59 | }
60 | \examples{
61 | if (interactive()) {
62 | tutorial_options(exercise.eval = TRUE, exercise.timelimt = 10)
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/tests/testthat/tutorials/optional-show-solution.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Optionally Reveal Exercise Solution"
3 | output: learnr::tutorial
4 | runtime: shiny_prerendered
5 | ---
6 |
7 | ```{r setup, include=FALSE}
8 | library(learnr)
9 |
10 | HIDE_SOLUTION <- FALSE
11 | SHOW_SOLUTION <- TRUE
12 |
13 | # Set global option reveal solution option
14 | # options(tutorial.exercise.reveal_solution = FALSE)
15 | # tutorial_options(exercise.reveal_solution = TRUE)
16 | #<>
17 | ```
18 |
19 | ## Intro
20 |
21 | ### Default
22 |
23 | ```{r default, exercise = TRUE}
24 | 1 + 1
25 | ```
26 |
27 | ```{r default-hint-1}
28 | # DEFAULT HINT 1
29 | ```
30 |
31 | ```{r default-hint-2}
32 | # DEFAULT HINT 2
33 | ```
34 |
35 | ```{r default-solution}
36 | # DEFAULT SOLUTION 4631b0
37 | ```
38 |
39 | ### Reveal Set on Exercise
40 |
41 | ```{r set-on-ex, exercise = TRUE, exercise.reveal_solution = TRUE}
42 | 1 + 1
43 | ```
44 |
45 | ```{r set-on-ex-hint-1}
46 | # EXERCISE OPT HINT 1
47 | ```
48 |
49 | ```{r set-on-ex-hint-2}
50 | # EXERCISE OPT HINT 2
51 | ```
52 |
53 | ```{r set-on-ex-solution}
54 | # EXERCISE OPT SOLUTION 15c861
55 | ```
56 |
57 | ### Always Hides Solution
58 |
59 | ```{r hide, exercise = TRUE, exercise.reveal_solution = TRUE}
60 | 1 + 1
61 | ```
62 |
63 | ```{r hide-hint-1}
64 | # HIDDEN HINT 1
65 | ```
66 |
67 | ```{r hide-hint-2}
68 | # HIDDEN HINT 2
69 | ```
70 |
71 | ```{r hide-solution, exercise.reveal_solution = FALSE}
72 | # HIDDEN SOLUTION 48da3c
73 | ```
74 |
75 | ### Always Shows Solution
76 |
77 | ```{r show, exercise = TRUE, exercise.reveal_solution = FALSE}
78 | 1 + 1
79 | ```
80 |
81 | ```{r show-hint-1}
82 | # SHOWN HINT 1
83 | ```
84 |
85 | ```{r show-hint-2}
86 | # SHOWN HINT 2
87 | ```
88 |
89 | ```{r show-solution, exercise.reveal_solution = TRUE}
90 | # SHOWN SOLUTION 781cbb
91 | ```
92 |
93 | ### Hidden Using Variable in Chunk Opt
94 |
95 | ```{r var-hide, exercise = TRUE, exercise.reveal_solution = HIDE_SOLUTION}
96 | 1 + 1
97 | ```
98 |
99 | ```{r var-hide-solution}
100 | # HIDDEN VAR SOLUTION 0b219b
101 | ```
102 |
103 | ### Shown Using Variable in Chunk Opt
104 |
105 | ```{r var-shown, exercise = TRUE, exercise.reveal_solution = SHOW_SOLUTION}
106 | 1 + 1
107 | ```
108 |
109 | ```{r var-shown-solution}
110 | # SHOWN VAR SOLUTION aba888
111 | ```
112 |
--------------------------------------------------------------------------------
/R/initialize.R:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #' Initialize tutorial R Markdown extensions
5 | #'
6 | #' One time initialization of R Markdown extensions required by the
7 | #' \pkg{learnr} package. This function is typically called automatically
8 | #' as a result of using exercises or questions.
9 | #'
10 | #' @return If not previously run, initializes knitr hooks and provides the
11 | #' required [rmarkdown::shiny_prerendered_chunk()]s to initialize \pkg{learnr}.
12 | #'
13 | #' @export
14 | initialize_tutorial <- function() {
15 |
16 | # helper function for one time initialization
17 | if (isTRUE(getOption("knitr.in.progress")) &&
18 | !isTRUE(knitr::opts_knit$get("tutorial.initialized"))) {
19 |
20 | # html dependencies
21 | knitr::knit_meta_add(list(
22 | rmarkdown::html_dependency_jquery(),
23 | rmarkdown::html_dependency_font_awesome(),
24 | bootbox_html_dependency(),
25 | idb_html_dependency(),
26 | tutorial_html_dependency()
27 | ))
28 |
29 | # session initialization (forward tutorial metadata)
30 | rmarkdown::shiny_prerendered_chunk(
31 | 'server',
32 | sprintf('learnr:::register_http_handlers(session, metadata = %s)',
33 | dput_to_string(rmarkdown::metadata$tutorial)),
34 | singleton = TRUE
35 | )
36 |
37 | # clear exercise/question and initialize user state reactive
38 | rmarkdown::shiny_prerendered_chunk(
39 | 'server',
40 | 'learnr:::prepare_tutorial_state(session)',
41 | singleton = TRUE
42 | )
43 |
44 | # record tutorial language in session object
45 | rmarkdown::shiny_prerendered_chunk(
46 | "server",
47 | "learnr:::i18n_observe_tutorial_language(input, session)"
48 | )
49 |
50 | # Register session stop handler
51 | rmarkdown::shiny_prerendered_chunk(
52 | 'server',
53 | sprintf('session$onSessionEnded(function() {
54 | learnr:::event_trigger(session, "session_stop")
55 | })'),
56 | singleton = TRUE
57 | )
58 |
59 | # set initialized flag to ensure single initialization
60 | knitr::opts_knit$set(tutorial.initialized = TRUE)
61 | }
62 | }
63 |
64 |
65 | dput_to_string <- function(x) {
66 | conn <- textConnection("dput_to_string", "w")
67 | on.exit({close(conn)})
68 | dput(x, file = conn)
69 | # Must use a `"\n"` if `dput()`ing a function
70 | paste0(textConnectionValue(conn), collapse = "\n")
71 | }
72 |
--------------------------------------------------------------------------------
/inst/lib/idb-keyval/idb-keyval-iife-compat.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | idb-keyval -- super-simple-small promise-based keyval store implemented with IndexedDB
3 | Version 3.2.0
4 | https: //github.com/jakearchibald/idb-keyval
5 | (c) 2016 Jake Archibald, Apache License 2.0
6 | */
7 | "use strict";var _createClass=function(){function e(e,n){for(var t=0;t0&&void 0!==arguments[0]?arguments[0]:"keyval-store",r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"keyval";_classCallCheck(this,e),this.storeName=r,this._dbp=n(indexedDB.open(t),r).then(function(e){return e.objectStoreNames.contains(r)?e:(e.close(),n(indexedDB.open(t,e.version+1),r))})}return _createClass(e,[{key:"_withIDBStore",value:function(e,n){var t=this;return this._dbp.then(function(r){return new Promise(function(o,i){var u=r.transaction(t.storeName,e);u.oncomplete=function(){return o()},u.onabort=u.onerror=function(){return i(u.error)},n(u.objectStore(t.storeName))})})}}]),e}(),r=void 0;function o(){return r||(r=new t),r}return e.Store=t,e.get=function(e){var n=void 0;return(arguments.length>1&&void 0!==arguments[1]?arguments[1]:o())._withIDBStore("readonly",function(t){n=t.get(e)}).then(function(){return n.result})},e.set=function(e,n){return(arguments.length>2&&void 0!==arguments[2]?arguments[2]:o())._withIDBStore("readwrite",function(t){t.put(n,e)})},e.del=function(e){return(arguments.length>1&&void 0!==arguments[1]?arguments[1]:o())._withIDBStore("readwrite",function(n){n.delete(e)})},e.clear=function(){return(arguments.length>0&&void 0!==arguments[0]?arguments[0]:o())._withIDBStore("readwrite",function(e){e.clear()})},e.keys=function(){var e=[];return(arguments.length>0&&void 0!==arguments[0]?arguments[0]:o())._withIDBStore("readonly",function(n){(n.openKeyCursor||n.openCursor).call(n).onsuccess=function(){this.result&&(e.push(this.result.key),this.result.continue())}}).then(function(){return e})},e}({});
8 |
--------------------------------------------------------------------------------
/R/tutorial_package_dependencies.R:
--------------------------------------------------------------------------------
1 | get_needed_pkgs <- function(dir) {
2 |
3 | pkgs <- tutorial_dir_package_dependencies(dir)
4 |
5 | pkgs[!pkgs %in% utils::installed.packages()]
6 | }
7 |
8 | format_needed_pkgs <- function(needed_pkgs) {
9 | paste(" -", needed_pkgs, collapse = "\n")
10 | }
11 |
12 | ask_pkgs_install <- function(needed_pkgs) {
13 | question <- sprintf("Would you like to install the following packages?\n%s",
14 | format_needed_pkgs(needed_pkgs))
15 |
16 | utils::menu(choices = c("yes", "no"),
17 | title = question)
18 | }
19 |
20 | install_tutorial_dependencies <- function(dir) {
21 | needed_pkgs <- get_needed_pkgs(dir)
22 |
23 | if(length(needed_pkgs) == 0) {
24 | return(invisible(NULL))
25 | }
26 |
27 | if(!interactive()) {
28 | stop("The following packages need to be installed:\n",
29 | format_needed_pkgs(needed_pkgs))
30 | }
31 |
32 | answer <- ask_pkgs_install(needed_pkgs)
33 |
34 | if(answer == 2) {
35 | stop("The tutorial is missing required packages and cannot be rendered.")
36 | }
37 |
38 | utils::install.packages(needed_pkgs)
39 | }
40 |
41 |
42 |
43 |
44 | #' List tutorial dependencies
45 | #'
46 | #' List the \R packages required to run a particular tutorial.
47 | #'
48 | #' @param name The tutorial name. If \code{name} is \code{NULL}, then all
49 | #' tutorials within \code{package} will be searched.
50 | #' @param package The \R package providing the tutorial. If \code{package} is
51 | #' \code{NULL}, then all tutorials will be searched.
52 | #'
53 | #' @return A character vector of package names that are required for execution.
54 | #'
55 | #' @examples
56 | #' tutorial_package_dependencies(package = "learnr")
57 | #'
58 | #' @export
59 | tutorial_package_dependencies <- function(name = NULL, package = NULL) {
60 | if (identical(name, NULL)) {
61 | info <- available_tutorials(package = package)
62 | return(
63 | sort(unique(unlist(info$package_dependencies)))
64 | )
65 | }
66 |
67 | tutorial_dir_package_dependencies(
68 | get_tutorial_path(name = name, package = package)
69 | )
70 | }
71 |
72 | tutorial_dir_package_dependencies <- function(dir) {
73 | # enumerate tutorial package dependencies
74 | deps <- renv::dependencies(dir, quiet = TRUE)
75 |
76 | # R <= 3.4 can not sort(NULL)
77 | # if no deps are found, renv::dependencies returns NULL
78 | if (is.null(deps)) {
79 | return(NULL)
80 | }
81 |
82 | sort(unique(deps$Package))
83 | }
84 |
--------------------------------------------------------------------------------
/.github/workflows/pr-commands.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | issue_comment:
3 | types: [created]
4 | name: Commands
5 | jobs:
6 | document:
7 | if: startsWith(github.event.comment.body, '/document')
8 | name: document
9 | runs-on: ubuntu-18.04
10 | env:
11 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: r-lib/actions/pr-fetch@v2
15 | with:
16 | repo-token: ${{ secrets.GITHUB_TOKEN }}
17 | - uses: r-lib/actions/setup-r@v2
18 | - name: Install dependencies
19 | run: |
20 | options(repos = c(CRAN = "https://demo.rstudiopm.com/all/__linux__/bionic/latest"))
21 | install.packages(c("remotes", "roxygen2"))
22 | remotes::install_deps(dependencies = TRUE)
23 | shell: Rscript {0}
24 | - name: Document
25 | run: Rscript -e 'roxygen2::roxygenise()'
26 | - name: commit
27 | run: |
28 | git config --local user.name "$GITHUB_ACTOR"
29 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com"
30 | git add man/\* NAMESPACE
31 | git commit -m 'Document'
32 | - uses: r-lib/actions/pr-push@v2
33 | with:
34 | repo-token: ${{ secrets.GITHUB_TOKEN }}
35 | i18n:
36 | if: startsWith(github.event.comment.body, '/i18nize')
37 | name: i18nize
38 | runs-on: ubuntu-18.04
39 | env:
40 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
41 | steps:
42 | - uses: actions/checkout@v2
43 | - uses: r-lib/actions/pr-fetch@v2
44 | with:
45 | repo-token: ${{ secrets.GITHUB_TOKEN }}
46 | - uses: r-lib/actions/setup-r@v2
47 | - name: Install dependencies
48 | run: |
49 | options(repos = c(CRAN = "https://demo.rstudiopm.com/all/__linux__/bionic/latest"))
50 | install.packages(c("cli", "here", "purrr", "stringi", "yaml"))
51 | shell: Rscript {0}
52 | - name: Internationalize
53 | run: Rscript data-raw/i18n_translations.R
54 | - name: commit
55 | run: |
56 | git config --local user.name "$GITHUB_ACTOR"
57 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com"
58 | git commit inst/internals/i18n* -m 'Re-build internal internationalization data' || echo "No changes to commit"
59 | git push origin || echo "No changes to commit"
60 | - uses: r-lib/actions/pr-push@v2
61 | with:
62 | repo-token: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/pkgdown/assets/snippets/ace/theme-textmate.js:
--------------------------------------------------------------------------------
1 | ace.define("ace/theme/textmate",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";t.isDark=!1,t.cssClass="ace-tm",t.cssText='.ace-tm .ace_gutter {background: #f0f0f0;color: #333;}.ace-tm .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-tm .ace_fold {background-color: #6B72E6;}.ace-tm {background-color: #FFFFFF;color: black;}.ace-tm .ace_cursor {color: black;}.ace-tm .ace_invisible {color: rgb(191, 191, 191);}.ace-tm .ace_storage,.ace-tm .ace_keyword {color: blue;}.ace-tm .ace_constant {color: rgb(197, 6, 11);}.ace-tm .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-tm .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-tm .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-tm .ace_invalid {background-color: rgba(255, 0, 0, 0.1);color: red;}.ace-tm .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-tm .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-tm .ace_support.ace_type,.ace-tm .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-tm .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-tm .ace_string {color: rgb(3, 106, 7);}.ace-tm .ace_comment {color: rgb(76, 136, 107);}.ace-tm .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-tm .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-tm .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-tm .ace_variable {color: rgb(49, 132, 149);}.ace-tm .ace_xml-pe {color: rgb(104, 104, 91);}.ace-tm .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-tm .ace_heading {color: rgb(12, 7, 255);}.ace-tm .ace_list {color:rgb(185, 6, 144);}.ace-tm .ace_meta.ace_tag {color:rgb(0, 22, 142);}.ace-tm .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-tm .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-tm.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;}.ace-tm .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-tm .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-tm .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-tm .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-tm .ace_gutter-active-line {background-color : #dcdcdc;}.ace-tm .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-tm .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
--------------------------------------------------------------------------------
/man/get_tutorial_state.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/tutorial-state.R
3 | \name{get_tutorial_state}
4 | \alias{get_tutorial_state}
5 | \title{Observe the user's progress in the tutorial}
6 | \usage{
7 | get_tutorial_state(label = NULL, session = getDefaultReactiveDomain())
8 | }
9 | \arguments{
10 | \item{label}{A length-1 character label of the exercise or question.}
11 |
12 | \item{session}{The \code{session} object passed to function given to
13 | \code{shinyServer.} Default is \code{\link[shiny:domains]{shiny::getDefaultReactiveDomain()}}.}
14 | }
15 | \value{
16 | A reactiveValues object or a single reactive value (if \code{label} is
17 | provided). The names of the full reactiveValues object correspond to the
18 | label of the question or exercise. Each item contains the following
19 | entries:
20 | \itemize{
21 | \item \code{type}: One of \code{"question"} or \code{"exercise"}.
22 | \item \code{answer}: A character vector containing the user's submitted answer(s).
23 | \item \code{correct}: A logical indicating whether the user's answer was correct,
24 | or a logical \code{NA} if the submission was not checked for correctness.
25 | \item \code{timestamp}: The time at which the user's submission was completed, as
26 | a character string in UTC, formatted as \code{"\%F \%H:\%M:\%OS3 \%Z"}.
27 | }
28 | }
29 | \description{
30 | As a student progresses through a \pkg{learnr} tutorial, their progress is
31 | stored in a Shiny reactive values list for their session (see
32 | \code{\link[shiny:reactiveValues]{shiny::reactiveValues()}}). Without arguments, \code{get_tutorial_state()} returns
33 | the full reactiveValues object that can be converted to a conventional list
34 | with \code{\link[shiny:reactiveValuesToList]{shiny::reactiveValuesToList()}}. If the \code{label} argument is provided,
35 | the state of an individual question or exercise with that label is returned.
36 |
37 | Calling \code{get_tutorial_state()} introduces a reactive dependency on the state
38 | of returned questions or exercises unless called within \code{isolate()}. Note
39 | that \code{get_tutorial_state()} will only work for the tutorial author and must
40 | be used in a reactive context, i.e. within \code{\link[shiny:observe]{shiny::observe()}},
41 | \code{\link[shiny:observeEvent]{shiny::observeEvent()}}, or \code{\link[shiny:reactive]{shiny::reactive()}}. Any logic observing the
42 | user's tutorial state must be written inside a \code{context="server"} chunk in
43 | the tutorial's R Markdown source.
44 | }
45 | \seealso{
46 | \code{\link[=get_tutorial_info]{get_tutorial_info()}}
47 | }
48 |
--------------------------------------------------------------------------------
/R/learnr_messages.R:
--------------------------------------------------------------------------------
1 | # {learnr} functions are intended to be written into R Markdown documents,
2 | # but there are certain times when we'd like to warn tutorial authors of
3 | # potential issues without the warning text appearing in the actual tutorial.
4 | # Since we can't ask users to set message = FALSE globally, we have to do our
5 | # own thing. Instead, we have a way to create messages that are automatically
6 | # added to a queue of items when knitting is in progress -- if we're not knitting
7 | # then we just emit the message immediately. Then we take advantage of the
8 | # `tutorial` knit hook that runs before and after each chunk in the tutorial.
9 | # In the after run, we flush the queue and re-signal the condition so that it
10 | # appears in the render console, thus avoiding writing to the tutorial HTML.
11 |
12 | .learnr_messages <- local({
13 | queue <- list()
14 | list(
15 | peek = function() {
16 | if (length(queue)) queue
17 | },
18 | flush = function() {
19 | while(length(queue)) {
20 | cnd <- queue[[1]]
21 | if (inherits(cnd, "error")) {
22 | # throw errors, they're immediate
23 | rlang::cnd_signal(cnd)
24 | } else {
25 | # otherwise report condition as a message, but re-signal warnings
26 | msg <- rlang::cnd_message(cnd)
27 |
28 | if (inherits(cnd, "warning")) {
29 | mgs <- paste0("Warning: ", msg)
30 | rlang::cnd_signal(cnd)
31 | }
32 |
33 | rlang::inform(msg, class = "learnr_render_message")
34 | }
35 | queue[[1]] <<- NULL
36 | }
37 | },
38 | add = function(cnd) {
39 | queue <<- c(queue, list(cnd))
40 | invisible(cnd)
41 | }
42 | )
43 | })
44 |
45 | learnr_render_message <- function(..., level = c("inform", "warn", "abort")) {
46 | create_cnd <- switch(
47 | tolower(level),
48 | inform = rlang::inform,
49 | warn = rlang::warn,
50 | abort = rlang::abort
51 | )
52 | cnd <- rlang::catch_cnd(create_cnd(paste0(..., "\n"), "learnr_render_message"))
53 |
54 | if (isTRUE(getOption('knitr.in.progress'))) {
55 | .learnr_messages$add(cnd)
56 | } else {
57 | rlang::cnd_signal(cnd)
58 | }
59 | }
60 |
61 | learnr_render_catch <- function(expr, env = rlang::caller_env()) {
62 | cnd <- tryCatch(
63 | rlang::eval_bare(expr, env),
64 | error = identity,
65 | warning = identity,
66 | message = identity
67 | )
68 |
69 | if (!inherits(cnd, "condition")) {
70 | return(invisible())
71 | }
72 |
73 | if (isTRUE(getOption('knitr.in.progress'))) {
74 | .learnr_messages$add(cnd)
75 | } else {
76 | rlang::cnd_signal(cnd)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/R/html-dependencies.R:
--------------------------------------------------------------------------------
1 |
2 | #' Tutorial HTML dependency
3 | #'
4 | #' HTML dependency for core tutorial JS and CSS. This should be included as a
5 | #' dependency for custom tutorial formats that wish to ensure that that
6 | #' `tutorial.js` and `tutorial.css` are loaded prior their own scripts and
7 | #' stylesheets.
8 | #'
9 | #' @return \pkg{learnr}'s HTML dependencies
10 | #'
11 | #' @export
12 | tutorial_html_dependency <- function() {
13 | htmltools::htmlDependency(
14 | name = "tutorial",
15 | version = utils::packageVersion("learnr"),
16 | src = html_dependency_src("lib", "tutorial"),
17 | script = "tutorial.js",
18 | stylesheet = "tutorial.css",
19 | all_files = TRUE
20 | )
21 | }
22 |
23 | html_dependency_src <- function(...) {
24 | if (nzchar(Sys.getenv("RMARKDOWN_SHINY_PRERENDERED_DEVMODE"))) {
25 | r_dir <- utils::getSrcDirectory(html_dependency_src, unique = TRUE)
26 | pkg_dir <- dirname(r_dir)
27 | file.path(pkg_dir, "inst", ...)
28 | }
29 | else {
30 | system.file(..., package = "learnr")
31 | }
32 | }
33 |
34 | idb_html_dependency <- function() {
35 | htmltools::htmlDependency(
36 | name = "idb-keyvalue",
37 | version = "3.2.0",
38 | src = system.file("lib/idb-keyval", package = "learnr"),
39 | script = "idb-keyval-iife-compat.min.js",
40 | all_files = FALSE
41 | )
42 | }
43 |
44 | bootbox_html_dependency <- function() {
45 | htmltools::htmlDependency(
46 | name = "bootbox",
47 | version = "5.5.2",
48 | src = system.file("lib/bootbox", package = "learnr"),
49 | script = "bootbox.min.js"
50 | )
51 | }
52 |
53 | clipboardjs_html_dependency <- function() {
54 | htmltools::htmlDependency(
55 | name = "clipboardjs",
56 | version = "2.0.10",
57 | src = system.file("lib/clipboardjs", package = "learnr"),
58 | script = "clipboard.min.js"
59 | )
60 | }
61 |
62 |
63 | ace_html_dependency <- function() {
64 | htmltools::htmlDependency(
65 | name = "ace",
66 | version = ACE_VERSION,
67 | src = system.file("lib/ace", package = "learnr"),
68 | script = "ace.js"
69 | )
70 | }
71 |
72 | tutorial_i18n_html_dependency <- function(language = NULL) {
73 | htmltools::htmlDependency(
74 | name = "i18n",
75 | version = "21.6.10",
76 | src = system.file("lib/i18n", package = "learnr"),
77 | script = c("i18next.min.js", "tutorial-i18n-init.js"),
78 | head = format(htmltools::tags$script(
79 | id = "i18n-cstm-trns",
80 | type = "application/json",
81 | htmltools::HTML(
82 | jsonlite::toJSON(
83 | i18n_process_language_options(language),
84 | auto_unbox = TRUE
85 | )
86 | )
87 | ))
88 | )
89 | }
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # learnr
2 |
3 |
4 |
5 | [](https://github.com/rstudio/learnr)
7 | [](https://CRAN.R-project.org/package=learnr)
9 | [](http://www.rpackages.io/package/learnr)
11 | [](https://zenodo.org/badge/latestdoi/71377580)
12 | [](https://github.com/rstudio/learnr/discussions)
14 | [](https://community.rstudio.com/c/teaching/13)
16 | [](https://community.rstudio.com/new-topic?title=&category_id=13&tags=learnr&body=%0A%0A%0A%20%20--------%0A%20%20%0A%20%20%3Csup%3EReferred%20here%20by%20%60learnr%60%27s%20GitHub%3C/sup%3E%0A&u=barret)
18 |
19 |
20 | The **learnr** package makes it easy to turn any [R
21 | Markdown](https://rmarkdown.rstudio.com/) document into an interactive
22 | tutorial. Tutorials consist of content along with interactive components
23 | for checking and reinforcing understanding. Tutorials can include any or
24 | all of the following:
25 |
26 | 1. Narrative, figures, illustrations, and equations.
27 |
28 | 2. Code exercises (R code chunks that users can edit and execute
29 | directly).
30 |
31 | 3. Quiz questions.
32 |
33 | 4. Videos (supported services include YouTube and Vimeo).
34 |
35 | 5. Interactive Shiny components.
36 |
37 | Tutorials automatically preserve work done within them, so if a user
38 | works on a few exercises or questions and returns to the tutorial later
39 | they can pick up right where they left off.
40 |
41 | Learn more about the **learnr** package and try example tutorials online
42 | at .
43 |
44 | ## Installation
45 |
46 | Install the latest official learnr release from CRAN:
47 |
48 | install.packages("learnr")
49 |
50 | Or you can install the most recent version in-development from GitHub
51 | with the [remotes package](https://remotes.r-lib.org):
52 |
53 | # install.packages("remotes")
54 | remotes::install_github("rstudio/learnr")
55 |
56 | learnr works best with a recent [version of
57 | RStudio](https://posit.co/download/rstudio-desktop/) (v1.0.136 or later)
58 | which includes tools for easily running and previewing tutorials.
59 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Type: Package
2 | Package: learnr
3 | Title: Interactive Tutorials for R
4 | Version: 0.11.3.9000
5 | Authors@R: c(
6 | person("Garrick", "Aden-Buie", , "garrick@posit.co", role = c("aut", "cre"),
7 | comment = c(ORCID = "0000-0002-7111-0077")),
8 | person("Barret", "Schloerke", , "barret@posit.co", role = "aut",
9 | comment = c(ORCID = "0000-0001-9986-114X")),
10 | person("JJ", "Allaire", , "jj@posit.co", role = c("aut", "ccp")),
11 | person("Alexander", "Rossell Hayes", , "alex.rossellhayes@posit.co", role = "aut",
12 | comment = c(ORCID = "0000-0001-9412-0457")),
13 | person("Nischal", "Shrestha", , "nischal@posit.co", role = "ctb",
14 | comment = c(ORCID = "0000-0003-3321-1712")),
15 | person("Angela", "Li", , "angelali921@gmail.com", role = "ctb",
16 | comment = "vignette"),
17 | person("Posit, PBC", role = c("cph", "fnd")),
18 | person(, "Ajax.org B.V.", role = c("ctb", "cph"),
19 | comment = "Ace library"),
20 | person("Zeno", "Rocha", role = c("ctb", "cph"),
21 | comment = "clipboard.js library"),
22 | person("Nick", "Payne", role = c("ctb", "cph"),
23 | comment = "Bootbox library"),
24 | person("Jake", "Archibald", role = c("ctb", "cph"),
25 | comment = "idb-keyval library"),
26 | person("i18next authors", role = c("ctb", "cph"),
27 | comment = "i18next library")
28 | )
29 | Description: Create interactive tutorials using R Markdown. Use a
30 | combination of narrative, figures, videos, exercises, and quizzes to
31 | create self-paced tutorials for learning about R and R packages.
32 | License: Apache License (>= 2.0)
33 | URL: https://rstudio.github.io/learnr/, https://github.com/rstudio/learnr
34 | BugReports: https://github.com/rstudio/learnr/issues
35 | Imports:
36 | checkmate,
37 | digest,
38 | ellipsis (>= 0.2.0.1),
39 | evaluate,
40 | htmltools (>= 0.3.5),
41 | htmlwidgets,
42 | jsonlite,
43 | knitr (>= 1.31),
44 | lifecycle,
45 | markdown (>= 1.3),
46 | parallel,
47 | promises,
48 | rappdirs,
49 | renv (>= 0.8.0),
50 | rlang,
51 | rmarkdown (>= 1.12.0),
52 | rprojroot,
53 | shiny (>= 1.0),
54 | stats,
55 | utils,
56 | withr
57 | Suggests:
58 | bslib,
59 | callr,
60 | curl,
61 | DBI (>= 0.4-1),
62 | httpuv,
63 | later,
64 | reticulate,
65 | RSQLite,
66 | rstudioapi (>= 0.11),
67 | shinytest2,
68 | sortable,
69 | testthat (>= 3.0.3)
70 | VignetteBuilder:
71 | knitr
72 | Config/Needs/connect: rsconnect
73 | Config/Needs/coverage: covr
74 | Config/Needs/website: pkgdown, tidyverse/tidytemplate
75 | Encoding: UTF-8
76 | Roxygen: list(markdown = TRUE)
77 | RoxygenNote: 7.2.3
78 | SystemRequirements: pandoc (>= 1.14) - http://pandoc.org
79 |
--------------------------------------------------------------------------------
/R/feedback.R:
--------------------------------------------------------------------------------
1 | # Provide exercise feedback
2 | feedback <- function(message, correct, type, location) {
3 | feedback_validated(list(
4 | message = message,
5 | correct = correct,
6 | type = type,
7 | location = location
8 | ))
9 | }
10 |
11 | # return feedback if it's valid (with defaults), otherwise throw an error
12 | feedback_validated <- function(feedback) {
13 | if (!length(feedback)) {
14 | return(feedback)
15 | }
16 | if (!(is.list(feedback) && all(c("message", "correct") %in% names(feedback)))) {
17 | stop("Feedback must be a list with 'message' and 'correct' fields", call. = FALSE)
18 | }
19 | if (!(is.character(feedback$message) || inherits(feedback$message, c("shiny.tag", "shiny.tag.list")))) {
20 | stop("The 'message' field of feedback must be a character vector or an htmltools tag or tagList", call. = FALSE)
21 | }
22 | if (!is.logical(feedback$correct)) {
23 | stop("The 'correct' field of feedback must be a logical (i.e., boolean) value", call. = FALSE)
24 | }
25 | # Fill in type/location defaults and check their value
26 | feedback$type <- feedback$type[1] %||% "auto"
27 | feedback$location <- feedback$location[1] %||% "append"
28 | feedback_types <- c("auto", "success", "info", "warning", "error", "custom")
29 | if (!feedback$type %in% feedback_types) {
30 | stop("Feedback 'type' field must be one of these values: ",
31 | paste(feedback_types, collapse = ", "), call. = FALSE)
32 | }
33 | feedback_locations <- c("append", "prepend", "replace")
34 | if (!feedback$location %in% feedback_locations) {
35 | stop("Feedback 'location' field must be one of these values: ",
36 | paste(feedback_locations, collapse = ", "), call. = FALSE)
37 | }
38 | if (feedback$type %in% "auto") {
39 | feedback$type <- if (feedback$correct) "success" else "error"
40 | }
41 | feedback
42 | }
43 |
44 | feedback_as_html <- function(feedback) {
45 | if (!length(feedback)) {
46 | return(feedback)
47 | }
48 | feedback <- feedback_validated(feedback)
49 | if (feedback$type %in% "custom") {
50 | return(div(feedback$message))
51 | }
52 | if (feedback$type %in% "error") {
53 | feedback$type <- "danger"
54 | }
55 | if (feedback$type %in% c("success", "info", "warning", "danger")) {
56 | return(div(
57 | role = "alert",
58 | class = paste0("alert alert-", feedback$type),
59 | feedback$message
60 | ))
61 | }
62 | stop("Invalid message type specified.", call. = FALSE)
63 | }
64 |
65 | # helper function to create tags for error message
66 | error_message_html <- function(message, style = "code") {
67 | switch(
68 | style,
69 | alert = div(class = "alert alert-danger", role = "alert", message),
70 | code = ,
71 | pre(
72 | code(class = "text-danger", message, .noWS = c("before", "after")),
73 | .noWS = c("before", "after")
74 | )
75 | )
76 | }
77 |
--------------------------------------------------------------------------------
/revdep/README.md:
--------------------------------------------------------------------------------
1 | # Platform
2 |
3 | |field |value |
4 | |:--------|:------------------------------|
5 | |version |R version 4.2.2 (2022-10-31) |
6 | |os |macOS Ventura 13.2 |
7 | |system |aarch64, darwin20 |
8 | |ui |X11 |
9 | |language |(EN) |
10 | |collate |en_US.UTF-8 |
11 | |ctype |en_US.UTF-8 |
12 | |tz |America/New_York |
13 | |date |2023-03-14 |
14 | |pandoc |3.1 @ /opt/homebrew/bin/pandoc |
15 |
16 | # Dependencies
17 |
18 | |package |old |new |Δ |
19 | |:-----------|:---------|:---------|:--|
20 | |learnr |0.11.2 |0.11.3 |* |
21 | |backports |1.4.1 |1.4.1 | |
22 | |base64enc |0.1-3 |0.1-3 | |
23 | |bslib |0.4.2 |0.4.2 | |
24 | |cachem |1.0.7 |1.0.7 | |
25 | |checkmate |2.1.0 |2.1.0 | |
26 | |cli |3.6.0 |3.6.0 | |
27 | |commonmark |1.8.1 |1.8.1 | |
28 | |crayon |1.5.2 |1.5.2 | |
29 | |curl |5.0.0 |5.0.0 | |
30 | |digest |0.6.31 |0.6.31 | |
31 | |ellipsis |0.3.2 |0.3.2 | |
32 | |evaluate |0.20 |0.20 | |
33 | |fastmap |1.1.1 |1.1.1 | |
34 | |fontawesome |0.5.0 |0.5.0 | |
35 | |fs |1.6.1 |1.6.1 | |
36 | |glue |1.6.2 |1.6.2 | |
37 | |highr |0.10 |0.10 | |
38 | |htmltools |0.5.4 |0.5.4 | |
39 | |htmlwidgets |1.6.1 |1.6.1 | |
40 | |httpuv |1.6.9 |1.6.9 | |
41 | |jquerylib |0.1.4 |0.1.4 | |
42 | |jsonlite |1.8.4 |1.8.4 | |
43 | |knitr |1.42 |1.42 | |
44 | |later |1.3.0 |1.3.0 | |
45 | |lifecycle |1.0.3 |1.0.3 | |
46 | |magrittr |2.0.3 |2.0.3 | |
47 | |markdown |1.5 |1.5 | |
48 | |memoise |2.0.1 |2.0.1 | |
49 | |mime |0.12 |0.12 | |
50 | |promises |1.2.0.1 |1.2.0.1 | |
51 | |R6 |2.5.1 |2.5.1 | |
52 | |rappdirs |0.3.3 |0.3.3 | |
53 | |Rcpp |1.0.10 |1.0.10 | |
54 | |renv |0.17.0-40 |0.17.0-49 |* |
55 | |rlang |1.0.6 |1.0.6 | |
56 | |rmarkdown |2.20 |2.20 | |
57 | |rprojroot |2.0.3 |2.0.3 | |
58 | |sass |0.4.5 |0.4.5 | |
59 | |shiny |1.7.4 |1.7.4 | |
60 | |sourcetools |0.1.7-1 |0.1.7-1 | |
61 | |stringi |1.7.12 |1.7.12 | |
62 | |stringr |1.5.0 |1.5.0 | |
63 | |tinytex |0.44 |0.44 | |
64 | |vctrs |0.5.2 |0.5.2 | |
65 | |withr |2.5.0 |2.5.0 | |
66 | |xfun |0.37 |0.37 | |
67 | |xtable |1.8-4 |1.8-4 | |
68 | |yaml |2.3.7 |2.3.7 | |
69 |
70 | # Revdeps
71 |
72 |
--------------------------------------------------------------------------------
/tests/testthat/test-mutate_tags.R:
--------------------------------------------------------------------------------
1 | has_class <- function(el, .class, ...) {
2 | class_idx <- which(names(el$attribs) == "class")
3 | if (!length(class_idx)) return(FALSE)
4 | el_class <- vapply(class_idx, function(i) el$attribs[[i]], FUN.VALUE = character(1))
5 | grepl(.class, paste(el_class, collapse = " "), ...)
6 | }
7 |
8 | test_that("finalize_tags() finalizes the question UI", {
9 | q_ui_final <- finalize_question(
10 | htmltools::div(
11 | class = "custom-question",
12 | htmltools::div("answer 1"),
13 | htmltools::div("answer 2")
14 | )
15 | )
16 |
17 | expect_true(has_class(q_ui_final, "question-final"))
18 | expect_true(has_class(q_ui_final, "disabled"))
19 | expect_true(has_class(q_ui_final$children[[1]], "disabled"))
20 | expect_true(has_class(q_ui_final$children[[2]], "disabled"))
21 | expect_true("disabled" %in% q_ui_final$attribs)
22 | expect_true("disabled" %in% names(q_ui_final$children[[1]]$attribs))
23 | expect_true("disabled" %in% names(q_ui_final$children[[2]]$attribs))
24 |
25 | q_ui_checkbox <-
26 | checkboxGroupInput(
27 | "q-checkbox",
28 | label = "check box question",
29 | choiceValues = letters,
30 | choiceNames = LETTERS,
31 | selected = "a"
32 | )
33 |
34 | q_ui_checkbox_final <- finalize_question(q_ui_checkbox)
35 | # before
36 | expect_false(has_class(q_ui_checkbox, "question-final"))
37 | expect_false(has_class(q_ui_checkbox, "disabled"))
38 | expect_false("disabled" %in% names(q_ui_checkbox$attribs))
39 | # after
40 | expect_true(has_class(q_ui_checkbox_final, "question-final"))
41 | expect_true(has_class(q_ui_checkbox_final, "disabled"))
42 | expect_true("disabled" %in% names(q_ui_checkbox_final$attribs))
43 |
44 | q_ui_radio <-
45 | radioButtons(
46 | "q-radio",
47 | label = "radio question",
48 | choiceValues = letters,
49 | choiceNames = LETTERS,
50 | selected = "b"
51 | )
52 |
53 | q_ui_radio_final <- finalize_question(q_ui_radio)
54 | # before
55 | expect_false(has_class(q_ui_radio, "question-final"))
56 | expect_false(has_class(q_ui_radio, "disabled"))
57 | expect_false("disabled" %in% names(q_ui_radio$attribs))
58 | # after
59 | expect_true(has_class(q_ui_radio_final, "question-final"))
60 | expect_true(has_class(q_ui_radio_final, "disabled"))
61 | expect_true("disabled" %in% names(q_ui_radio_final$attribs))
62 | })
63 |
64 | test_that("finalize_question() works with a shiny.tag.list, too", {
65 | q_ui_final <- finalize_question(
66 | htmltools::tagList(
67 | htmltools::div("thing 1"),
68 | htmltools::div("thing 2")
69 | )
70 | )
71 |
72 | expect_s3_class(q_ui_final, "shiny.tag.list")
73 | expect_true(has_class(q_ui_final[[1]], "question-final"))
74 | expect_true(has_class(q_ui_final[[1]], "disabled"))
75 | expect_true(has_class(q_ui_final[[2]], "question-final"))
76 | expect_true(has_class(q_ui_final[[2]], "disabled"))
77 | })
--------------------------------------------------------------------------------