From ac80ef80254e9b5bd18e3a20fb99222efdd31a98 Mon Sep 17 00:00:00 2001 From: Patrick Vossler Date: Thu, 4 Jan 2024 12:41:05 -0800 Subject: [PATCH 1/2] add new fix for formatting chunks --- create-litr/_book/404.html | 289 +++++ .../_book/adding-extras-to-an-r-package.html | 398 ++++++ create-litr/_book/combining-.r-files.html | 342 ++++++ create-litr/_book/document.html | 327 +++++ .../documenting-the-package-and-testing.html | 425 +++++++ .../functionality-to-facilitate-workflow.html | 352 ++++++ create-litr/_book/generating-package.html | 734 +++++++++++ create-litr/_book/hash.html | 364 ++++++ .../_book/including-extras-for-litr.html | 489 ++++++++ create-litr/_book/including-templates.html | 409 +++++++ create-litr/_book/index.html | 298 +++++ .../anchor-sections-hash.css | 2 + .../anchor-sections-1.1.0/anchor-sections.css | 4 + .../anchor-sections-1.1.0/anchor-sections.js | 11 + .../css/fontawesome/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes .../gitbook-2.6.7/css/plugin-bookdown.css | 105 ++ .../gitbook-2.6.7/css/plugin-clipboard.css | 18 + .../gitbook-2.6.7/css/plugin-fontsettings.css | 303 +++++ .../gitbook-2.6.7/css/plugin-highlight.css | 426 +++++++ .../libs/gitbook-2.6.7/css/plugin-search.css | 31 + .../libs/gitbook-2.6.7/css/plugin-table.css | 1 + .../_book/libs/gitbook-2.6.7/css/style.css | 13 + .../_book/libs/gitbook-2.6.7/js/app.min.js | 1 + .../libs/gitbook-2.6.7/js/clipboard.min.js | 7 + .../libs/gitbook-2.6.7/js/jquery.highlight.js | 86 ++ .../libs/gitbook-2.6.7/js/plugin-bookdown.js | 259 ++++ .../libs/gitbook-2.6.7/js/plugin-clipboard.js | 33 + .../gitbook-2.6.7/js/plugin-fontsettings.js | 152 +++ .../libs/gitbook-2.6.7/js/plugin-search.js | 270 ++++ .../libs/gitbook-2.6.7/js/plugin-sharing.js | 116 ++ .../libs/jquery-3.6.0/jquery-3.6.0.min.js | 2 + create-litr/_book/overview.html | 293 +++++ create-litr/_book/package-setup.html | 345 ++++++ create-litr/_book/reference-keys.txt | 35 + create-litr/_book/rendering.html | 1090 +++++++++++++++++ create-litr/_book/search_index.json | 1 + create-litr/_book/tests.html | 671 ++++++++++ create-litr/index.Rmd | 67 +- docs/404.html | 4 +- docs/LICENSE-text.html | 4 +- docs/LICENSE.html | 4 +- docs/apple-touch-icon-120x120.png | Bin 8745 -> 8745 bytes docs/apple-touch-icon-152x152.png | Bin 10612 -> 10612 bytes docs/apple-touch-icon-180x180.png | Bin 12774 -> 12774 bytes docs/apple-touch-icon-60x60.png | Bin 4080 -> 4080 bytes docs/apple-touch-icon-76x76.png | Bin 5179 -> 5179 bytes docs/apple-touch-icon.png | Bin 12774 -> 12774 bytes docs/articles/basic-example.html | 4 +- docs/articles/faqs.html | 4 +- docs/articles/index.html | 2 +- docs/articles/package-templates.html | 4 +- docs/articles/packages-in-the-wild.html | 4 +- docs/authors.html | 6 +- .../bootstrap-5.3.1/bootstrap.bundle.min.js | 7 + .../bootstrap.bundle.min.js.map | 1 + docs/deps/bootstrap-5.3.1/bootstrap.min.css | 5 + docs/deps/bootstrap-5.3.1/font.css | 21 + ...txg8zYS_SKggPN4iEgvnHyvveLxVs9pbCIPrc.woff | Bin 0 -> 27828 bytes ...txg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrc.woff | Bin 0 -> 27492 bytes .../1adeadb2fe618c5ed46221f15e12b9c8.woff | Bin 0 -> 46088 bytes .../fonts/4iCs6KVjbNBYlgo6ew.woff | Bin 0 -> 134032 bytes .../fonts/4iCs6KVjbNBYlgoKfw7w.woff | Bin 0 -> 39832 bytes .../fonts/4iCv6KVjbNBYlgoCxCvTtA.woff | Bin 0 -> 117140 bytes .../fonts/4iCv6KVjbNBYlgoCxCvjsGyL.woff | Bin 0 -> 34452 bytes .../6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7nsDQ.woff | Bin 0 -> 17760 bytes .../6xK1dSBYKcSV-LCoeQqfX1RYOo3qPa7j.woff | Bin 0 -> 49156 bytes .../fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3aPA.woff | Bin 0 -> 74684 bytes .../6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7j.woff | Bin 0 -> 18420 bytes .../6xKydSBYKcSV-LCoeQqfX1RYOo3i54rAkw.woff | Bin 0 -> 74348 bytes .../6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vAkw.woff | Bin 0 -> 74332 bytes .../6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdo.woff | Bin 0 -> 18388 bytes .../6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zAkw.woff | Bin 0 -> 74148 bytes .../6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdo.woff | Bin 0 -> 18340 bytes .../fonts/CSR54z1Qlv-GDxkbKVQ_dFsvWNRevw.woff | Bin 0 -> 16724 bytes .../fonts/CSR54z1Qlv-GDxkbKVQ_dFsvaNA.woff | Bin 0 -> 29672 bytes .../fonts/CSR64z1Qlv-GDxkbKVQ_TOQ.woff | Bin 0 -> 75128 bytes .../fonts/CSR64z1Qlv-GDxkbKVQ_fOAKSw.woff | Bin 0 -> 16516 bytes ...xRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DBKXhM0.woff | Bin 0 -> 55992 bytes ...xRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DMyQhM0.woff | Bin 0 -> 56004 bytes ...g3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTbI1rSg.woff | Bin 0 -> 47720 bytes ...g3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTsoprSg.woff | Bin 0 -> 47924 bytes ...HjIg1_i6t8kCHKm4532VJOt5-QNFgpCtZ6Ew9.woff | Bin 0 -> 50580 bytes ...HjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Ew9.woff | Bin 0 -> 50580 bytes ...HjIg1_i6t8kCHKm4532VJOt5-QNFgpCuM70w9.woff | Bin 0 -> 51108 bytes .../fonts/KFOlCnqEu92Fr1MmEU9fBBc-.woff | Bin 0 -> 20544 bytes .../fonts/KFOlCnqEu92Fr1MmEU9vAA.woff | Bin 0 -> 65756 bytes .../fonts/KFOlCnqEu92Fr1MmSU5fBBc-.woff | Bin 0 -> 20416 bytes .../fonts/KFOlCnqEu92Fr1MmSU5vAA.woff | Bin 0 -> 65164 bytes .../fonts/KFOlCnqEu92Fr1MmWUlfBBc-.woff | Bin 0 -> 20408 bytes .../fonts/KFOlCnqEu92Fr1MmWUlvAA.woff | Bin 0 -> 65556 bytes .../fonts/KFOmCnqEu92Fr1Me5g.woff | Bin 0 -> 65456 bytes .../fonts/KFOmCnqEu92Fr1Mu4mxM.woff | Bin 0 -> 20344 bytes .../fonts/QGYpz_kZZAGCONcK2A4bGOj8mNhL.woff | Bin 0 -> 89776 bytes .../fonts/S6u8w4BMUTPHjxsAXC-s.woff | Bin 0 -> 29864 bytes .../fonts/S6u8w4BMUTPHjxswWA.woff | Bin 0 -> 35436 bytes .../fonts/S6u9w4BMUTPHh6UVSwiPHw.woff | Bin 0 -> 28044 bytes .../fonts/S6u9w4BMUTPHh6UVeww.woff | Bin 0 -> 33296 bytes .../fonts/S6u9w4BMUTPHh7USSwiPHw.woff | Bin 0 -> 30016 bytes .../fonts/S6u9w4BMUTPHh7USeww.woff | Bin 0 -> 35168 bytes .../fonts/S6uyw4BMUTPHjx4wWA.woff | Bin 0 -> 28648 bytes .../fonts/S6uyw4BMUTPHvxo.woff | Bin 0 -> 34020 bytes ...HuS_fvQtMwCp50KnMw2boKoduKmMEVuFuYMZs.woff | Bin 0 -> 138900 bytes ...HuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fMZs.woff | Bin 0 -> 137508 bytes ...HuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZs.woff | Bin 0 -> 128192 bytes .../XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmRTA.woff | Bin 0 -> 53216 bytes .../XRXI3I6Li01BKofiOc5wtlZ2di8HDGUmRTA.woff | Bin 0 -> 54196 bytes .../XRXI3I6Li01BKofiOc5wtlZ2di8HDLshRTA.woff | Bin 0 -> 53856 bytes .../a98f7a7574819ba83bec6279a2cecd95.woff | Bin 0 -> 45884 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk0ZjaVQ.woff | Bin 0 -> 72136 bytes ...SCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWVAexg.woff | Bin 0 -> 23636 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkaVQ.woff | Bin 0 -> 74700 bytes ...SCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWVAexg.woff | Bin 0 -> 23576 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkaVQ.woff | Bin 0 -> 74564 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk_RkaVQ.woff | Bin 0 -> 74940 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjaVQ.woff | Bin 0 -> 74644 bytes ...SCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjWVAexg.woff | Bin 0 -> 22964 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjaVQ.woff | Bin 0 -> 71660 bytes ...X2vVnXBbObj2OVZyOOSr4dVJWUgsg-1x4gaVQ.woff | Bin 0 -> 22332 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1y4k.woff | Bin 0 -> 68664 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1y4k.woff | Bin 0 -> 70652 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgshZ1y4k.woff | Bin 0 -> 69392 bytes ...X2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4gaVQ.woff | Bin 0 -> 22940 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0C4k.woff | Bin 0 -> 70524 bytes ...X2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVQ.woff | Bin 0 -> 22908 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0C4k.woff | Bin 0 -> 70792 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjr0C4k.woff | Bin 0 -> 71144 bytes .../fonts/q5uGsou0JOdh94bfvQlr.woff | Bin 0 -> 31584 bytes docs/deps/data-deps.txt | 4 +- docs/favicon-16x16.png | Bin 1135 -> 1135 bytes docs/favicon-32x32.png | Bin 2097 -> 2097 bytes docs/index.html | 9 +- docs/pkgdown.yml | 4 +- .../reference/add_chunk_label_hyperlinks.html | 2 +- docs/reference/add_function_hyperlinks.html | 2 +- docs/reference/add_hex_sticker.html | 2 +- docs/reference/add_pkgdown.html | 2 +- docs/reference/add_readme.html | 2 +- docs/reference/add_text_to_file.html | 2 +- docs/reference/add_vignettes.html | 2 +- docs/reference/check_unedited.html | 2 +- .../description_litr_hash_field_name.html | 2 +- .../description_litr_version_field_name.html | 2 +- docs/reference/do_not_edit_message.html | 2 +- docs/reference/document.html | 2 +- docs/reference/find_labels.html | 2 +- docs/reference/get_package_directory.html | 2 +- docs/reference/get_params_used.html | 2 +- docs/reference/hash_package_directory.html | 2 +- docs/reference/index.html | 2 +- docs/reference/insert_hrefs.html | 2 +- docs/reference/litr-package.html | 2 +- docs/reference/litr_gitbook.html | 2 +- docs/reference/litr_html_document.html | 2 +- docs/reference/litr_pdf_document.html | 2 +- docs/reference/litrify_output_format.html | 2 +- docs/reference/load_all.html | 2 +- docs/reference/make_noticeable.html | 2 +- .../reference/read_hash_from_description.html | 2 +- docs/reference/remove_rstudio_extras.html | 2 +- docs/reference/render.html | 2 +- docs/reference/replace_ansi_sequences.html | 2 +- docs/reference/restore_knitr_objects.html | 2 +- docs/reference/send_to_package.html | 2 +- docs/reference/setup.html | 2 +- docs/reference/test_litr.html | 2 +- docs/reference/with_cleanup.html | 2 +- docs/reference/write_hash_to_description.html | 2 +- .../write_version_to_description.html | 2 +- docs/search.json | 2 +- .../_book/404.html | 4 +- .../_book/conclude.html | 126 +- .../generalization-to-other-greetings.html | 121 +- .../_book/index.html | 122 +- .../_book/package-setup.html | 161 +-- .../_book/search_index.json | 2 +- .../_book/the-basics-of-saying-hello.html | 147 +-- .../frombookdown/DESCRIPTION | 2 +- .../frombookdown/LICENSE | 2 +- .../frombookdown/LICENSE.md | 2 +- .../create-witharmadillo.html | 132 +- .../witharmadillo/DESCRIPTION | 2 +- .../witharmadillo/LICENSE | 2 +- .../witharmadillo/LICENSE.md | 2 +- .../create-rhasdata.html | 136 +- .../rhasdata/DESCRIPTION | 2 +- .../rhasdata/LICENSE | 2 +- .../rhasdata/LICENSE.md | 2 +- .../rhasdata/data/mydata.rda | Bin 2029 -> 2030 bytes .../create-withpkgdown.html | 220 ++-- .../docs/404.html | 4 +- .../docs/LICENSE-text.html | 4 +- .../docs/LICENSE.html | 4 +- .../docs/apple-touch-icon-120x120.png | Bin 8745 -> 8745 bytes .../docs/apple-touch-icon-152x152.png | Bin 10612 -> 10612 bytes .../docs/apple-touch-icon-180x180.png | Bin 12774 -> 12774 bytes .../docs/apple-touch-icon-60x60.png | Bin 4080 -> 4080 bytes .../docs/apple-touch-icon-76x76.png | Bin 5179 -> 5179 bytes .../docs/apple-touch-icon.png | Bin 12774 -> 12774 bytes .../docs/articles/index.html | 2 +- .../docs/articles/using-package.html | 6 +- .../docs/authors.html | 6 +- .../bootstrap-5.2.2/bootstrap.bundle.min.js | 7 - .../bootstrap.bundle.min.js.map | 1 - .../deps/bootstrap-5.2.2/bootstrap.min.css | 6 - .../bootstrap-5.3.1/bootstrap.bundle.min.js | 7 + .../bootstrap.bundle.min.js.map | 1 + .../deps/bootstrap-5.3.1/bootstrap.min.css | 5 + .../docs/deps/bootstrap-5.3.1/font.css | 124 ++ ...txg8zYS_SKggPN4iEgvnHyvveLxVs9pbCIPrc.woff | Bin 0 -> 27828 bytes ...txg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrc.woff | Bin 0 -> 27492 bytes .../1adeadb2fe618c5ed46221f15e12b9c8.woff | Bin 0 -> 46088 bytes .../fonts/4iCs6KVjbNBYlgo6ew.woff | Bin 0 -> 134032 bytes .../fonts/4iCs6KVjbNBYlgoKfw7w.woff | Bin 0 -> 39832 bytes .../fonts/4iCv6KVjbNBYlgoCxCvTtA.woff | Bin 0 -> 117140 bytes .../fonts/4iCv6KVjbNBYlgoCxCvjsGyL.woff | Bin 0 -> 34452 bytes .../6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7nsDQ.woff | Bin 0 -> 17760 bytes .../6xK1dSBYKcSV-LCoeQqfX1RYOo3qPa7j.woff | Bin 0 -> 49156 bytes .../fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3aPA.woff | Bin 0 -> 74684 bytes .../6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7j.woff | Bin 0 -> 18420 bytes .../6xKydSBYKcSV-LCoeQqfX1RYOo3i54rAkw.woff | Bin 0 -> 74348 bytes .../6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vAkw.woff | Bin 0 -> 74332 bytes .../6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdo.woff | Bin 0 -> 18388 bytes .../6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zAkw.woff | Bin 0 -> 74148 bytes .../6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdo.woff | Bin 0 -> 18340 bytes .../fonts/CSR54z1Qlv-GDxkbKVQ_dFsvWNRevw.woff | Bin 0 -> 16724 bytes .../fonts/CSR54z1Qlv-GDxkbKVQ_dFsvaNA.woff | Bin 0 -> 29672 bytes .../fonts/CSR64z1Qlv-GDxkbKVQ_TOQ.woff | Bin 0 -> 75128 bytes .../fonts/CSR64z1Qlv-GDxkbKVQ_fOAKSw.woff | Bin 0 -> 16516 bytes ...xRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DBKXhM0.woff | Bin 0 -> 55992 bytes ...xRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DMyQhM0.woff | Bin 0 -> 56004 bytes ...g3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTbI1rSg.woff | Bin 0 -> 47720 bytes ...g3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTsoprSg.woff | Bin 0 -> 47924 bytes ...HjIg1_i6t8kCHKm4532VJOt5-QNFgpCtZ6Ew9.woff | Bin 0 -> 50580 bytes ...HjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Ew9.woff | Bin 0 -> 50580 bytes ...HjIg1_i6t8kCHKm4532VJOt5-QNFgpCuM70w9.woff | Bin 0 -> 51108 bytes .../fonts/KFOlCnqEu92Fr1MmEU9fBBc-.woff | Bin 0 -> 20544 bytes .../fonts/KFOlCnqEu92Fr1MmEU9vAA.woff | Bin 0 -> 65756 bytes .../fonts/KFOlCnqEu92Fr1MmSU5fBBc-.woff | Bin 0 -> 20416 bytes .../fonts/KFOlCnqEu92Fr1MmSU5vAA.woff | Bin 0 -> 65164 bytes .../fonts/KFOlCnqEu92Fr1MmWUlfBBc-.woff | Bin 0 -> 20408 bytes .../fonts/KFOlCnqEu92Fr1MmWUlvAA.woff | Bin 0 -> 65556 bytes .../fonts/KFOmCnqEu92Fr1Me5g.woff | Bin 0 -> 65456 bytes .../fonts/KFOmCnqEu92Fr1Mu4mxM.woff | Bin 0 -> 20344 bytes .../fonts/QGYpz_kZZAGCONcK2A4bGOj8mNhL.woff | Bin 0 -> 89776 bytes .../fonts/S6u8w4BMUTPHjxsAXC-s.woff | Bin 0 -> 29864 bytes .../fonts/S6u8w4BMUTPHjxswWA.woff | Bin 0 -> 35436 bytes .../fonts/S6u9w4BMUTPHh6UVSwiPHw.woff | Bin 0 -> 28044 bytes .../fonts/S6u9w4BMUTPHh6UVeww.woff | Bin 0 -> 33296 bytes .../fonts/S6u9w4BMUTPHh7USSwiPHw.woff | Bin 0 -> 30016 bytes .../fonts/S6u9w4BMUTPHh7USeww.woff | Bin 0 -> 35168 bytes .../fonts/S6uyw4BMUTPHjx4wWA.woff | Bin 0 -> 28648 bytes .../fonts/S6uyw4BMUTPHvxo.woff | Bin 0 -> 34020 bytes ...HuS_fvQtMwCp50KnMw2boKoduKmMEVuFuYMZs.woff | Bin 0 -> 138900 bytes ...HuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fMZs.woff | Bin 0 -> 137508 bytes ...HuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZs.woff | Bin 0 -> 128192 bytes .../XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmRTA.woff | Bin 0 -> 53216 bytes .../XRXI3I6Li01BKofiOc5wtlZ2di8HDGUmRTA.woff | Bin 0 -> 54196 bytes .../XRXI3I6Li01BKofiOc5wtlZ2di8HDLshRTA.woff | Bin 0 -> 53856 bytes .../a98f7a7574819ba83bec6279a2cecd95.woff | Bin 0 -> 45884 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk0ZjaVQ.woff | Bin 0 -> 72136 bytes ...SCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWVAexg.woff | Bin 0 -> 23636 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkaVQ.woff | Bin 0 -> 74700 bytes ...SCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWVAexg.woff | Bin 0 -> 23576 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkaVQ.woff | Bin 0 -> 74564 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk_RkaVQ.woff | Bin 0 -> 74940 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjaVQ.woff | Bin 0 -> 74644 bytes ...SCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjWVAexg.woff | Bin 0 -> 22964 bytes ...cVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjaVQ.woff | Bin 0 -> 71660 bytes ...X2vVnXBbObj2OVZyOOSr4dVJWUgsg-1x4gaVQ.woff | Bin 0 -> 22332 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1y4k.woff | Bin 0 -> 68664 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1y4k.woff | Bin 0 -> 70652 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgshZ1y4k.woff | Bin 0 -> 69392 bytes ...X2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4gaVQ.woff | Bin 0 -> 22940 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0C4k.woff | Bin 0 -> 70524 bytes ...X2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVQ.woff | Bin 0 -> 22908 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0C4k.woff | Bin 0 -> 70792 bytes ...vWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjr0C4k.woff | Bin 0 -> 71144 bytes .../fonts/q5uGsou0JOdh94bfvQlr.woff | Bin 0 -> 31584 bytes .../docs/deps/data-deps.txt | 4 +- .../docs/favicon-16x16.png | Bin 1135 -> 1135 bytes .../docs/favicon-32x32.png | Bin 2097 -> 2097 bytes .../docs/index.html | 4 +- .../docs/pkgdown.yml | 4 +- .../docs/reference/index.html | 2 +- .../docs/reference/say_hello.html | 2 +- .../docs/reference/say_hi.html | 2 +- .../docs/search.json | 2 +- .../withpkgdown/DESCRIPTION | 2 +- .../withpkgdown/LICENSE | 2 +- .../withpkgdown/LICENSE.md | 2 +- .../favicon/apple-touch-icon-120x120.png | Bin 8745 -> 8745 bytes .../favicon/apple-touch-icon-152x152.png | Bin 10612 -> 10612 bytes .../favicon/apple-touch-icon-180x180.png | Bin 12774 -> 12774 bytes .../favicon/apple-touch-icon-60x60.png | Bin 4080 -> 4080 bytes .../favicon/apple-touch-icon-76x76.png | Bin 5179 -> 5179 bytes .../pkgdown/favicon/apple-touch-icon.png | Bin 12774 -> 12774 bytes .../pkgdown/favicon/favicon-16x16.png | Bin 1135 -> 1135 bytes .../pkgdown/favicon/favicon-32x32.png | Bin 2097 -> 2097 bytes .../create-withrcpp.html | 153 +-- .../withrcpp/DESCRIPTION | 2 +- .../withrcpp/LICENSE | 2 +- .../withrcpp/LICENSE.md | 2 +- examples/make-an-r-package/create-rhello.html | 134 +- examples/make-an-r-package/rhello/DESCRIPTION | 2 +- examples/make-an-r-package/rhello/LICENSE | 2 +- examples/make-an-r-package/rhello/LICENSE.md | 2 +- litr/DESCRIPTION | 3 +- litr/LICENSE | 2 +- litr/LICENSE.md | 2 +- litr/R/render.R | 55 +- litr/README.md | 8 +- .../favicon/apple-touch-icon-120x120.png | Bin 8745 -> 8745 bytes .../favicon/apple-touch-icon-152x152.png | Bin 10612 -> 10612 bytes .../favicon/apple-touch-icon-180x180.png | Bin 12774 -> 12774 bytes .../favicon/apple-touch-icon-60x60.png | Bin 4080 -> 4080 bytes .../favicon/apple-touch-icon-76x76.png | Bin 5179 -> 5179 bytes litr/pkgdown/favicon/apple-touch-icon.png | Bin 12774 -> 12774 bytes litr/pkgdown/favicon/favicon-16x16.png | Bin 1135 -> 1135 bytes litr/pkgdown/favicon/favicon-32x32.png | Bin 2097 -> 2097 bytes 319 files changed, 9597 insertions(+), 1094 deletions(-) create mode 100644 create-litr/_book/404.html create mode 100644 create-litr/_book/adding-extras-to-an-r-package.html create mode 100644 create-litr/_book/combining-.r-files.html create mode 100644 create-litr/_book/document.html create mode 100644 create-litr/_book/documenting-the-package-and-testing.html create mode 100644 create-litr/_book/functionality-to-facilitate-workflow.html create mode 100644 create-litr/_book/generating-package.html create mode 100644 create-litr/_book/hash.html create mode 100644 create-litr/_book/including-extras-for-litr.html create mode 100644 create-litr/_book/including-templates.html create mode 100644 create-litr/_book/index.html create mode 100644 create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections-hash.css create mode 100644 create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections.css create mode 100644 create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections.js create mode 100644 create-litr/_book/libs/gitbook-2.6.7/css/fontawesome/fontawesome-webfont.ttf create mode 100644 create-litr/_book/libs/gitbook-2.6.7/css/plugin-bookdown.css create mode 100644 create-litr/_book/libs/gitbook-2.6.7/css/plugin-clipboard.css create mode 100644 create-litr/_book/libs/gitbook-2.6.7/css/plugin-fontsettings.css create mode 100644 create-litr/_book/libs/gitbook-2.6.7/css/plugin-highlight.css create mode 100644 create-litr/_book/libs/gitbook-2.6.7/css/plugin-search.css create mode 100644 create-litr/_book/libs/gitbook-2.6.7/css/plugin-table.css create mode 100644 create-litr/_book/libs/gitbook-2.6.7/css/style.css create mode 100644 create-litr/_book/libs/gitbook-2.6.7/js/app.min.js create mode 100644 create-litr/_book/libs/gitbook-2.6.7/js/clipboard.min.js create mode 100644 create-litr/_book/libs/gitbook-2.6.7/js/jquery.highlight.js create mode 100644 create-litr/_book/libs/gitbook-2.6.7/js/plugin-bookdown.js create mode 100644 create-litr/_book/libs/gitbook-2.6.7/js/plugin-clipboard.js create mode 100644 create-litr/_book/libs/gitbook-2.6.7/js/plugin-fontsettings.js create mode 100644 create-litr/_book/libs/gitbook-2.6.7/js/plugin-search.js create mode 100644 create-litr/_book/libs/gitbook-2.6.7/js/plugin-sharing.js create mode 100644 create-litr/_book/libs/jquery-3.6.0/jquery-3.6.0.min.js create mode 100644 create-litr/_book/overview.html create mode 100644 create-litr/_book/package-setup.html create mode 100644 create-litr/_book/reference-keys.txt create mode 100644 create-litr/_book/rendering.html create mode 100644 create-litr/_book/search_index.json create mode 100644 create-litr/_book/tests.html create mode 100644 docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js create mode 100644 docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js.map create mode 100644 docs/deps/bootstrap-5.3.1/bootstrap.min.css create mode 100644 docs/deps/bootstrap-5.3.1/font.css create mode 100644 docs/deps/bootstrap-5.3.1/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVs9pbCIPrc.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrc.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/1adeadb2fe618c5ed46221f15e12b9c8.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCs6KVjbNBYlgo6ew.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCs6KVjbNBYlgoKfw7w.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCv6KVjbNBYlgoCxCvTtA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/4iCv6KVjbNBYlgoCxCvjsGyL.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7nsDQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPa7j.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3aPA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7j.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rAkw.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vAkw.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdo.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zAkw.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdo.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/CSR54z1Qlv-GDxkbKVQ_dFsvWNRevw.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/CSR54z1Qlv-GDxkbKVQ_dFsvaNA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/CSR64z1Qlv-GDxkbKVQ_TOQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/CSR64z1Qlv-GDxkbKVQ_fOAKSw.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DBKXhM0.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DMyQhM0.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTbI1rSg.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTsoprSg.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtZ6Ew9.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Ew9.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCuM70w9.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9fBBc-.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9vAA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5fBBc-.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5vAA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlfBBc-.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlvAA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Me5g.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Mu4mxM.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/QGYpz_kZZAGCONcK2A4bGOj8mNhL.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u8w4BMUTPHjxsAXC-s.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u8w4BMUTPHjxswWA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh6UVSwiPHw.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh6UVeww.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh7USSwiPHw.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh7USeww.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6uyw4BMUTPHjx4wWA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/S6uyw4BMUTPHvxo.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuFuYMZs.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fMZs.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZs.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmRTA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDGUmRTA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDLshRTA.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/a98f7a7574819ba83bec6279a2cecd95.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk0ZjaVQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWVAexg.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkaVQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWVAexg.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkaVQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk_RkaVQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjaVQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjWVAexg.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjaVQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1x4gaVQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1y4k.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1y4k.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgshZ1y4k.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4gaVQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0C4k.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVQ.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0C4k.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjr0C4k.woff create mode 100644 docs/deps/bootstrap-5.3.1/fonts/q5uGsou0JOdh94bfvQlr.woff delete mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js delete mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js.map delete mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.2.2/bootstrap.min.css create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js.map create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/bootstrap.min.css create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/font.css create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVs9pbCIPrc.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrc.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/1adeadb2fe618c5ed46221f15e12b9c8.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/4iCs6KVjbNBYlgo6ew.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/4iCs6KVjbNBYlgoKfw7w.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/4iCv6KVjbNBYlgoCxCvTtA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/4iCv6KVjbNBYlgoCxCvjsGyL.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7nsDQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPa7j.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3aPA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7j.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rAkw.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vAkw.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdo.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zAkw.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdo.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/CSR54z1Qlv-GDxkbKVQ_dFsvWNRevw.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/CSR54z1Qlv-GDxkbKVQ_dFsvaNA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/CSR64z1Qlv-GDxkbKVQ_TOQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/CSR64z1Qlv-GDxkbKVQ_fOAKSw.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DBKXhM0.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DMyQhM0.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTbI1rSg.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/HI_jiYsKILxRpg3hIP6sJ7fM7PqlOPHYvDP_W9O7GQTTsoprSg.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtZ6Ew9.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Ew9.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCuM70w9.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9fBBc-.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmEU9vAA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5fBBc-.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmSU5vAA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlfBBc-.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/KFOlCnqEu92Fr1MmWUlvAA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Me5g.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/KFOmCnqEu92Fr1Mu4mxM.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/QGYpz_kZZAGCONcK2A4bGOj8mNhL.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/S6u8w4BMUTPHjxsAXC-s.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/S6u8w4BMUTPHjxswWA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh6UVSwiPHw.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh6UVeww.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh7USSwiPHw.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/S6u9w4BMUTPHh7USeww.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/S6uyw4BMUTPHjx4wWA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/S6uyw4BMUTPHvxo.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuFuYMZs.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fMZs.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfMZs.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmRTA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDGUmRTA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/XRXI3I6Li01BKofiOc5wtlZ2di8HDLshRTA.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/a98f7a7574819ba83bec6279a2cecd95.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk0ZjaVQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWVAexg.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkaVQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWVAexg.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkaVQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk_RkaVQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjaVQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjWVAexg.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkyFjaVQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1x4gaVQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1y4k.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1y4k.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgshZ1y4k.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4gaVQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0C4k.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVQ.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0C4k.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjr0C4k.woff create mode 100644 examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.3.1/fonts/q5uGsou0JOdh94bfvQlr.woff diff --git a/create-litr/_book/404.html b/create-litr/_book/404.html new file mode 100644 index 0000000..9a158bf --- /dev/null +++ b/create-litr/_book/404.html @@ -0,0 +1,289 @@ + + + + + + + Page not found | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

Page not found

+

The page you requested cannot be found (perhaps it was moved or renamed).

+

You may want to try searching to find the page's new location, or use +the table of contents to find the page you are looking for.

+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/adding-extras-to-an-r-package.html b/create-litr/_book/adding-extras-to-an-r-package.html new file mode 100644 index 0000000..af346f5 --- /dev/null +++ b/create-litr/_book/adding-extras-to-an-r-package.html @@ -0,0 +1,398 @@ + + + + + + + 9 Adding extras to an R package | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

9 Adding extras to an R package

+

When writing an R package, there are some additional items one typically wants to include, such as a README file, one or more vignettes, and a pkgdown site. In this section, we define some helper functions that will be make it easy to add these “extras” to a package.

+

While a user could choose to generate all these items within the generating .Rmd file, for all but the simplest examples, it will probably be preferred to have these as separate source files. These source files can live in the same directory as the generating .Rmd file or in their own directory (e.g., source-files lives in the same directory as create-litr.Rmd, which is called litr-project).

+
+

9.1 Adding a README

+

We define a helper function that takes an externally defined README.Rmd and puts it into the package, creates a README.md, and makes sure that these will be added to the .Rbuildignore.

+
#' Add README to package
+#' 
+#' This function takes a README.Rmd file, copies it into the package, and then
+#' renders it to a README.md file.  It also adds these two files to the
+#' .Rbuildignore.
+#' 
+#' @param rmd_file The path to a .Rmd file.
+#' @export
+add_readme <- function(rmd_file) {
+  usethis::use_readme_rmd(open = FALSE)
+  fs::file_copy(rmd_file, new_path = "README.Rmd", overwrite = TRUE)
+  out <- xfun::Rscript_call(
+    rmarkdown::render,
+    args = list(
+      input = "README.Rmd",
+      output_options = list(html_preview = "false")
+    )
+  )
+}
+
+
+

9.2 Adding a hex sticker

+

We define a helper function that takes an externally defined hex sticker (.png file) and puts it into the package under man/figures. The suggestion for storing it in this directory came from here.

+
#' Add a hex sticker to package
+#' 
+#' In addition to calling this function, you should add to your README.Rmd something like this:
+#' 
+#' `# your-title <img src="man/figures/logo.png" align="right" height="139" />`
+#' 
+#' See [here](https://pkgdown.r-lib.org/reference/build_home.html#package-logo) 
+#' for more.
+#' 
+#' @param hex_png_file The .png file with your package's hex sticker
+#' @export
+add_hex_sticker <- function(hex_png_file) {
+  figures_dir <- file.path("man", "figures")
+  fs::dir_create(figures_dir)
+  fs::file_copy(path = hex_png_file, 
+                new_path = file.path(figures_dir, "logo.png"), 
+                overwrite = TRUE)
+}
+
+
+

9.3 Adding vignettes

+

We next define a helper function for adding vignettes to the package. This mimics usethis::use_vignette(). We couldn’t directly use that function because we want the project file to live outside of the package directory, which confuses usethis.

+
#' Add one or more vignettes to package
+#' 
+#' @param rmd_files A character vector of .Rmd files, each corresponding to 
+#' a vignette
+#' @param other_files A character vector of any other files needed in the 
+#' vignettes directory (.bib file, images, etc.)
+#' @export
+add_vignettes <- function(rmd_files, other_files = NULL) {
+  fs::dir_create("vignettes")
+  for (fn in c(rmd_files, other_files)) fs::file_copy(fn, "vignettes")
+  
+  # update DESCRIPTION file:
+  deps <- desc::desc_get_deps()$package
+  if (!("knitr" %in% deps))
+    desc::desc_set_dep("knitr", type = "Suggests")
+  if (!("rmarkdown" %in% deps))
+    desc::desc_set_dep("rmarkdown", type = "Suggests")
+  out <- desc::desc_set("VignetteBuilder", "knitr")
+}
+
+
+

9.4 Add a pkgdown site

+

We define a function based on usethis::use_pkgdown(), but with a few differences:

+
    +
  1. Avoid the parts to do with looking for projects
  2. +
  3. Allow one to use a custom _pkgdown.yml that is stored outside of package
  4. +
+
#' Add a pkgdown site
+#' 
+#' This function creates a website for your package.  You can see it locally by
+#' opening `docs/index.html` in your package.  To get it online you can copy the
+#' `docs` directory to your website's server.
+#' 
+#' Be sure that in the generating .Rmd file this is called *after*
+#' `litr::document()` has been called.  To customize the site, you may pass a
+#' customized `_pkgdown.yml` file as described in [this `pkgdown` vignette](https://pkgdown.r-lib.org/articles/customise.html).
+#' 
+#' @param config_path The _pkgdown.yml file that lives somewhere outside of your package.  If NULL, then a basic default will be used.
+#' @export
+add_pkgdown <- function(config_path = NULL) {
+  config_file <- "_pkgdown.yml"
+  destdir <- "docs"
+  usethis::use_build_ignore(c(config_file, destdir, "pkgdown"))
+  if (is.null(config_path)) {
+    # create a new config file (note it lives outside of package)
+    config <- usethis:::pkgdown_config(destdir)
+    usethis::write_over(config_file, yaml::as.yaml(config))
+  } else {
+    # copy the one that already exists:
+    fs::file_copy(config_path, config_file)  
+  }
+  pkgdown::build_site()
+}
+

After this step, you can locally see the site by opening docs/index.html in the browser. You can then copy the docs directory to your website’s server and you’re done.

+

Since the above function uses pkgdown and yaml, we include these in our package:

+
usethis::use_package("pkgdown")
+usethis::use_package("yaml")
+
## ✔ Adding 'pkgdown' to Imports field in DESCRIPTION
+## • Refer to functions with `pkgdown::fun()`
+
## ✔ Adding 'yaml' to Imports field in DESCRIPTION
+## • Refer to functions with `yaml::fun()`
+
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/combining-.r-files.html b/create-litr/_book/combining-.r-files.html new file mode 100644 index 0000000..338810d --- /dev/null +++ b/create-litr/_book/combining-.r-files.html @@ -0,0 +1,342 @@ + + + + + + + 10 Combining .R files | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

10 Combining .R files

+

This section should eventually be removed but for now I’m doing this to convince myself that the package generated by this .Rmd file really matches the initial version created without the package.

+
library(magrittr)
+library(purrr)
+rfiles <- fs::dir_ls("R")
+code <- rfiles %>% 
+  map(readLines) %>% 
+  set_names(
+    rfiles %>% stringr::str_remove("^.*/") %>% stringr::str_remove(".R$")
+    )
+hash_functions <- c("hash_package_directory",
+                    "description_litr_hash_field_name",
+                    "write_hash_to_description",
+                    "read_hash_from_description",
+                    "check_unedited")
+render_functions <- c("render",
+                      "with_cleanup",
+                      "litrify_output_format",
+                      "litr_pdf_document",
+                      "litr_html_document",
+                      "litr_gitbook",
+                      "replace_ansi_sequences",
+                      "add_function_hyperlinks",
+                      "insert_hrefs",
+                      "add_chunk_label_hyperlinks",
+                      "restore_knitr_objects",
+                      "remove_rstudio_extras",
+                      "get_params_used",
+                      "get_package_directory",
+                      "do_not_edit_message",
+                      "description_litr_version_field_name",
+                      "write_version_to_description",
+                      "document",
+                      "load_all")
+setup_functions <- c("setup",
+                     "make_noticeable",
+                     "send_to_package",
+                     "add_text_to_file",
+                     "find_labels")
+extras_functions <- c("add_readme",
+                      "add_hex_sticker",
+                      "add_vignettes",
+                      "add_pkgdown")
+remove_initial_lines <- function(code_list) {
+  # drop first line of each list element except for the first list element
+  c(code_list[1], map(code_list[-1], ~ .x[-1]))
+}
+fs::file_delete(setdiff(rfiles, "R/litr-package.R"))
+writeLines(unlist(remove_initial_lines(code[hash_functions])), "R/hash.R")
+writeLines(unlist(remove_initial_lines(code[render_functions])), "R/render.R")
+writeLines(unlist(remove_initial_lines(code[setup_functions])), "R/setup.R")
+writeLines(unlist(remove_initial_lines(code[extras_functions])), "R/extras.R")
+
## 
+## Attaching package: 'purrr'
+
## The following object is masked from 'package:magrittr':
+## 
+##     set_names
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/document.html b/create-litr/_book/document.html new file mode 100644 index 0000000..98e0804 --- /dev/null +++ b/create-litr/_book/document.html @@ -0,0 +1,327 @@ + + + + + + + 6 Wrapper to devtools::document() | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

6 Wrapper to devtools::document()

+

This function is nearly identical to devtools::document() except that it changes the roxygen2 message that says “Please edit documentation in R/[…].R” to instead mention the generating .Rmd file. When Rcpp is used, it also makes sure that #include <RcppArmadillo.h> comes before #include <Rcpp.h>.

+
#' Use roxygen to document a package from within a Rmd file
+#' 
+#' This is a wrapper for the `devtools::document()` function, which in turn is a
+#' wrapper for the `roxygen2::roxygenize()` function.  It is written assuming that
+#' it is being called from within a generating Rmd file.  The purpose for `litr` 
+#' having this wrapper is two-fold.  First, it ensures that the first line
+#' in the outputted `Rd` files should not say "Please edit documentation in 
+#' R/file.R" but instead should refer to the Rmd file that generates everything. 
+#' Second, in the case that Rcpp is being used, it makes some adjustments to ensure
+#' that the compiling of the C++ code should be successful.
+#' 
+#' @param ... Arguments to be passed to `devtools::document()`
+#' @export
+document <- function(...) {
+  # prepare Rcpp code for compiling
+  if (fs::file_exists("src/code.cpp")) {
+    # make sure that #include <RcppArmadillo.h> if it exists
+    # comes *before* (or instead of) <Rcpp.h>
+    txt <- readLines("src/code.cpp")
+    loc <- stringr::str_which(txt, r"(#include <RcppArmadillo.h>)")
+    if (length(loc) > 0) {
+      include_arma_line <- txt[loc[1]]
+      txt <- c(include_arma_line, txt[-loc])
+      writeLines(txt, "src/code.cpp")
+    }
+  }
+  
+  devtools::document(...)
+  # remove the line of the following form in each man/*.Rd file:
+  pattern <- "% Please edit documentation in .*$"
+  msg <- do_not_edit_message(knitr::current_input(), type = "man")
+  for (fname in fs::dir_ls("man")) {
+    txt <- stringr::str_replace(readLines(fname), pattern, msg)
+    cat(paste(txt, collapse = "\n"), file = fname)
+  }
+}
+

We used devtools, so let’s import it:

+
usethis::use_package("devtools")
+
## ✔ Adding 'devtools' to Imports field in DESCRIPTION
+## • Refer to functions with `devtools::fun()`
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/documenting-the-package-and-testing.html b/create-litr/_book/documenting-the-package-and-testing.html new file mode 100644 index 0000000..d65859a --- /dev/null +++ b/create-litr/_book/documenting-the-package-and-testing.html @@ -0,0 +1,425 @@ + + + + + + + 13 Documenting the package and testing | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

13 Documenting the package and testing

+

We finish by running commands that will document and test litr.

+

The formatting of the test output does not print out very neatly.

+

We download the latest release:

+
litr::document()
+install_old <- function() {
+  remotes::install_github("jacobbien/litr-project@*release", subdir = "litr")
+}
+xfun::Rscript_call(test_litr,
+                   list(install_old = install_old, location_of_new = "."))
+
## ℹ Updating litr documentation
+## ℹ Loading litr
+## Writing 'NAMESPACE'
+## Writing 'add_readme.Rd'
+## Writing 'add_hex_sticker.Rd'
+## Writing 'add_vignettes.Rd'
+## Writing 'add_pkgdown.Rd'
+## Writing 'hash_package_directory.Rd'
+## Writing 'description_litr_hash_field_name.Rd'
+## Writing 'write_hash_to_description.Rd'
+## Writing 'read_hash_from_description.Rd'
+## Writing 'check_unedited.Rd'
+## Writing 'litr-package.Rd'
+## Writing 'render.Rd'
+## Writing 'with_cleanup.Rd'
+## Writing 'litrify_output_format.Rd'
+## Writing 'litr_pdf_document.Rd'
+## Writing 'litr_html_document.Rd'
+## Writing 'litr_gitbook.Rd'
+## Writing 'replace_ansi_sequences.Rd'
+## Writing 'add_function_hyperlinks.Rd'
+## Writing 'insert_hrefs.Rd'
+## Writing 'add_chunk_label_hyperlinks.Rd'
+## Writing 'restore_knitr_objects.Rd'
+## Writing 'remove_rstudio_extras.Rd'
+## Writing 'get_params_used.Rd'
+## Writing 'get_package_directory.Rd'
+## Writing 'do_not_edit_message.Rd'
+## Writing 'description_litr_version_field_name.Rd'
+## Writing 'write_version_to_description.Rd'
+## Writing 'document.Rd'
+## Writing 'load_all.Rd'
+## Writing 'setup.Rd'
+## Writing 'make_noticeable.Rd'
+## Writing 'send_to_package.Rd'
+## Writing 'add_text_to_file.Rd'
+## Writing 'find_labels.Rd'
+## Writing 'test_litr.Rd'
+
##      file context                                   test nb failed skipped
+## 1 tests.R   tests               add_text_to_file() works 10      0   FALSE
+## 2 tests.R   tests          get_package_directory() works  2      0   FALSE
+## 3 tests.R   tests                       load_all() works  1      0   FALSE
+## 4 tests.R   tests                   check_unedited works 11      0   FALSE
+## 5 tests.R   tests                  get_params_used works  3      0   FALSE
+## 6 tests.R   tests            Knuth-style references work  1      0   FALSE
+## 7 tests.R   tests   Rendering in all possible ways works 12      0   FALSE
+## 8 tests.R   tests Rendering with minimal_eval=TRUE works  2      0   FALSE
+## 9 tests.R   tests                  templates can be knit 12      0   FALSE
+##   error warning  user system   real passed
+## 1 FALSE       0 0.049  0.004  0.053     10
+## 2 FALSE       0 0.003  0.000  0.003      2
+## 3 FALSE       0 0.262  0.019  2.354      1
+## 4 FALSE       0 0.097  0.039  1.880     11
+## 5 FALSE       0 0.007  0.001  0.008      3
+## 6 FALSE       0 0.020  0.003  1.884      1
+## 7 FALSE       0 0.443  0.043 12.580     12
+## 8 FALSE       0 0.314  0.034  5.271      2
+## 9 FALSE       0 0.128  0.077 58.672     12
+##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   result
+## 1 , 10, 3, 10, 77, 3, 77, 10, 10, testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE)), expect_condition_matching("error", {,     {,         object,     }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, ,     label = label), quasi_capture(enquo(object), label, capture_matching_condition, ,     matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), ,     ...), withCallingHandlers(expr, condition = function(cnd) {,     if (!is.null(matched) || !matches(cnd)) {,         return(),     },     if (can_entrace(cnd)) {,         cnd <- cnd_entrace(cnd),     },     matched <<- cnd,     if (inherits(cnd, "message") || inherits(cnd, "warning")) {,         cnd_muffle(cnd),     },     else if (inherits(cnd, "error") || inherits(cnd, "skip")) {,         return_from(tl, cnd),     }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, req_exist = TRUE), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, fs::file_exists(myfile) is not TRUE\n\n, 16, 3, 16, 48, 3, 48, 16, 16, 45, 47, add_text_to_file() works, `sometxt` (`actual`) not equal to readLines(myfile) (`expected`).\n\n, 17, 3, 17, 52, 3, 52, 17, 17, 45, 47, add_text_to_file() works, c(sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\n\n, 22, 3, 22, 64, 3, 64, 22, 22, 45, 47, add_text_to_file() works, , 25, 3, 25, 62, 3, 62, 25, 25, testthat::expect_error(add_text_to_file(sometxt, myfile, 0)), expect_condition_matching("error", {,     {,         object,     }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, ,     label = label), quasi_capture(enquo(object), label, capture_matching_condition, ,     matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), ,     ...), withCallingHandlers(expr, condition = function(cnd) {,     if (!is.null(matched) || !matches(cnd)) {,         return(),     },     if (can_entrace(cnd)) {,         cnd <- cnd_entrace(cnd),     },     matched <<- cnd,     if (inherits(cnd, "message") || inherits(cnd, "warning")) {,         cnd_muffle(cnd),     },     else if (inherits(cnd, "error") || inherits(cnd, "skip")) {,         return_from(tl, cnd),     }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, 0), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, , 26, 3, 26, 63, 3, 63, 26, 26, testthat::expect_error(add_text_to_file(sometxt, myfile, -1)), expect_condition_matching("error", {,     {,         object,     }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, ,     label = label), quasi_capture(enquo(object), label, capture_matching_condition, ,     matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), ,     ...), withCallingHandlers(expr, condition = function(cnd) {,     if (!is.null(matched) || !matches(cnd)) {,         return(),     },     if (can_entrace(cnd)) {,         cnd <- cnd_entrace(cnd),     },     matched <<- cnd,     if (inherits(cnd, "message") || inherits(cnd, "warning")) {,         cnd_muffle(cnd),     },     else if (inherits(cnd, "error") || inherits(cnd, "skip")) {,         return_from(tl, cnd),     }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, -1), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, , 27, 3, 27, 62, 3, 62, 27, 27, testthat::expect_error(add_text_to_file(sometxt, myfile, 5)), expect_condition_matching("error", {,     {,         object,     }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, ,     label = label), quasi_capture(enquo(object), label, capture_matching_condition, ,     matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), ,     ...), withCallingHandlers(expr, condition = function(cnd) {,     if (!is.null(matched) || !matches(cnd)) {,         return(),     },     if (can_entrace(cnd)) {,         cnd <- cnd_entrace(cnd),     },     matched <<- cnd,     if (inherits(cnd, "message") || inherits(cnd, "warning")) {,         cnd_muffle(cnd),     },     else if (inherits(cnd, "error") || inherits(cnd, "skip")) {,         return_from(tl, cnd),     }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, 5), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, c(moretxt2, sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\n\n, 32, 3, 32, 74, 3, 74, 32, 32, 45, 47, add_text_to_file() works, c(moretxt2, moretxt3, sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\n\n, 37, 3, 38, 43, 3, 43, 37, 38, 45, 47, add_text_to_file() works, c(moretxt2, moretxt3, sometxt, moretxt, moretxt4) (`actual`) not equal to readLines(myfile) (`expected`).\n\n, 43, 3, 44, 43, 3, 43, 43, 44, 45, 47, add_text_to_file() works
+## 2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  get_package_directory(".", "mypkg", input) (`actual`) not equal to file.path("inputdir", "mypkg") (`expected`).\n\n, 50, 3, 53, 3, 3, 3, 50, 53, 45, 47, get_package_directory() works, get_package_directory("..", "mypkg", input) (`actual`) not equal to file.path("inputdir", "..", "mypkg") (`expected`).\n\n, 54, 3, 57, 3, 3, 3, 54, 57, 45, 47, get_package_directory() works
+## 3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    say_hello("Jacob") (`actual`) not equal to "Hello Jacob!" (`expected`).\n\n, 70, 3, 70, 60, 3, 60, 70, 70, 45, 47, load_all() works
+## 4                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      check_unedited(package_path) is not TRUE\n\n, 88, 3, 88, 53, 3, 53, 88, 88, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\n\n, 93, 3, 93, 54, 3, 54, 93, 93, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\n\n, 97, 3, 97, 53, 3, 53, 97, 97, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\n\n, 102, 3, 102, 54, 3, 54, 102, 102, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\n\n, 105, 3, 105, 53, 3, 53, 105, 105, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\n\n, 112, 3, 112, 54, 3, 54, 112, 112, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\n\n, 115, 3, 115, 53, 3, 53, 115, 115, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\n\n, 123, 3, 123, 54, 3, 54, 123, 123, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\n\n, 126, 3, 126, 53, 3, 53, 126, 126, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\n\n, 134, 3, 134, 54, 3, 54, 134, 134, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\n\n, 137, 3, 137, 53, 3, 53, 137, 137, 45, 47, check_unedited works
+## 5                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       `default_params` (`actual`) not equal to rmarkdown::yaml_front_matter(rmd_file)$params (`expected`).\n\n, 150, 3, 153, 3, 3, 3, 150, 153, 45, 47, get_params_used works, get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir")) (`actual`) not equal to `params1` (`expected`).\n\n, 156, 3, 159, 3, 3, 3, 156, 159, 45, 47, get_params_used works, get_params_used(...) (`actual`) not equal to `params2` (`expected`).\n\n, 163, 3, 168, 3, 3, 3, 163, 168, 45, 47, get_params_used works
+## 6                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           fs::file_exists(file.path(dir, "create-rknuth.html")) is not TRUE\n\n, 179, 3, 179, 78, 3, 78, 179, 179, 45, 47, Knuth-style references work
+## 7                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\n\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\n\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\n\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\n\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\n\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\n\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\n\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\n\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\n\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\n\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\n\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\n\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works
+## 8                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\n\n, 291, 3, 292, 68, 3, 68, 291, 292, 45, 47, Rendering with minimal_eval=TRUE works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\n\n, 299, 3, 300, 68, 3, 68, 299, 300, 45, 47, Rendering with minimal_eval=TRUE works
+## 9                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          fs::file_exists(file.path(dir, "create-rhello.html")) is not TRUE\n\n, 316, 3, 316, 78, 3, 78, 316, 316, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "rhello")) is not TRUE\n\n, 317, 3, 317, 66, 3, 66, 317, 317, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-rhasdata.html")) is not TRUE\n\n, 325, 3, 325, 80, 3, 80, 325, 325, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "rhasdata")) is not TRUE\n\n, 326, 3, 326, 68, 3, 68, 326, 326, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-withrcpp.html")) is not TRUE\n\n, 334, 3, 334, 80, 3, 80, 334, 334, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "withrcpp")) is not TRUE\n\n, 335, 3, 335, 68, 3, 68, 335, 335, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-witharmadillo.Rmd")) is not TRUE\n\n, 343, 3, 343, 84, 3, 84, 343, 343, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "witharmadillo")) is not TRUE\n\n, 344, 3, 344, 73, 3, 73, 344, 344, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-withpkgdown.html")) is not TRUE\n\n, 352, 3, 352, 83, 3, 83, 352, 352, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "withpkgdown")) is not TRUE\n\n, 353, 3, 353, 71, 3, 71, 353, 353, 45, 47, templates can be knit, fs::file_exists(...) is not TRUE\n\n, 365, 3, 367, 5, 3, 5, 365, 367, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-frombookdown", "frombookdown")) is not TRUE\n\n, 368, 3, 370, 5, 3, 5, 368, 370, 45, 47, templates can be knit
+
+

13.1 Add examples folder with the output of knitting each example

+

In this section, we will litr-knit each template and put the outputs in an examples directory that lives outside of the litr R package. These examples are linked to in README.Rmd.

+
build_all_templates <- function(install_old, location_of_new) {
+  devtools::install(location_of_new)
+
+  example_dir <- file.path("..", "examples")
+  if (fs::dir_exists(example_dir)) fs::dir_delete(example_dir)
+  fs::dir_create(example_dir)
+  
+  templates <- fs::path_file(fs::dir_ls("inst/rmarkdown/templates"))
+  templates_bookdown <- stringr::str_subset(templates, "bookdown")
+  templates_nonbookdown <- setdiff(templates, templates_bookdown)
+  for (template in templates_nonbookdown) {
+    tmp_file <- file.path(example_dir, "temp.Rmd")
+    rmarkdown::draft(tmp_file, template, package = "litr", edit = FALSE)
+    pkg_name <- rmarkdown::yaml_front_matter(tmp_file)$params$package_name
+    rmd_file <- file.path(example_dir, paste0("create-", pkg_name, ".Rmd"))
+    fs::file_move(tmp_file, rmd_file)
+    render(rmd_file)
+    # move to a template-specific directory:
+    template_dir <- file.path(example_dir, template)
+    fs::dir_create(template_dir)
+    fs::file_move(fs::dir_ls(example_dir, regexp = pkg_name), template_dir)
+    # move the source-files directory
+    if (fs::dir_exists(file.path(example_dir, "source-files"))) {
+      fs::dir_create(file.path(example_dir, template, "source-files"))
+      fs::dir_copy(file.path(example_dir, "source-files"),
+                   file.path(example_dir, template))
+      fs::dir_delete(file.path(example_dir, "source-files"))
+    }
+    # move the docs directory (when pkgdown creates one)
+    if (fs::dir_exists(file.path(example_dir, "docs"))) {
+      fs::dir_create(file.path(example_dir, template, "docs"))
+      fs::dir_copy(file.path(example_dir, "docs"),
+                   file.path(example_dir, template))
+      fs::dir_delete(file.path(example_dir, "docs"))
+    }
+  }
+
+  for (template in templates_bookdown) {
+    tmp_dir <- file.path(example_dir, "temp")
+    rmarkdown::draft(file.path(example_dir, "temp.Rmd"),
+                     template,
+                     package = "litr",
+                     edit = FALSE)
+    prev_dir <- getwd()
+    setwd(tmp_dir)
+    fs::file_delete("temp.Rmd")
+    render("index.Rmd")
+    # move to a template-specific directory:
+    setwd(prev_dir)
+    fs::dir_copy(tmp_dir, file.path(example_dir, template))
+    fs::dir_delete(tmp_dir)
+  }
+
+  install_old()
+}
+
xfun::Rscript_call(build_all_templates,
+                   list(install_old = install_old, location_of_new = "."))
+
## [1] "litr"
+
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/functionality-to-facilitate-workflow.html b/create-litr/_book/functionality-to-facilitate-workflow.html new file mode 100644 index 0000000..d9e4056 --- /dev/null +++ b/create-litr/_book/functionality-to-facilitate-workflow.html @@ -0,0 +1,352 @@ + + + + + + + 8 Functionality to facilitate workflow | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

8 Functionality to facilitate workflow

+

When someone is writing an R package with devtools, it is common to use devtools::load_all() to quickly try out the functions of an R package in the console. We’d like to allow for a similar workflow using litr. We define a litr function called load_all(), which will do the following:

+
    +
  1. Litr-knit the .Rmd file with minimal_eval=TRUE to some temporary location (since the idea of load_all() is to leave the .html file alone) and then delete the .html file.

  2. +
  3. Run devtools::load_all() on the output.

  4. +
+
#' Load complete package
+#' 
+#' This is a litr wrapper to `devtools::load_all()`.  It first calls
+#' `litr::render()` with `minimal_eval=TRUE`, then it calls
+#' `devtools::load_all()` on the generated package.
+#' 
+#' @param input The input file to be rendered (see `rmarkdown::render`)
+#' @param output_dir By default (and in typical usage) this is NULL, meaning
+#' that no .html/bookdown/.pdf will result.  However, when a directory is given,
+#' the result of the litr-knitting will be saved to this location.
+#' @param ... Additional parameters to be passed to `devtools::load_all()`
+#' @export
+load_all <- function(input, output_dir = NULL, ...) {
+  no_output <- is.null(output_dir)
+  if (no_output) {
+    output_dir <- tempfile()
+    if (fs::file_exists(output_dir)) fs::file_delete(output_dir)
+    fs::dir_create(output_dir)
+  }
+  
+  # let's copy over everything from input directory to output directory
+  fs::dir_copy(fs::path_dir(input), output_dir, overwrite = TRUE)
+  input_path <- fs::path_split(input)[[1]]
+  moved_input <- file.path(output_dir, fs::path_file(input))
+  
+  # get package directory
+  params <- get_params_used(moved_input, list())
+  package_dir <- get_package_directory(
+    params$package_parent_dir,
+    params$package_name,
+    moved_input
+  )
+  
+  # but if a package directory was copied here, let's remove it before
+  # calling render to avoid a potential error
+  if (fs::dir_exists(package_dir)) fs::dir_delete(package_dir)
+  
+  litr::render(moved_input, minimal_eval = TRUE, output_dir = output_dir,
+               quiet = TRUE)
+  
+  new_package_dir <- file.path(fs::path_dir(input), params$package_name)
+  fs::dir_copy(package_dir, new_package_dir, overwrite = TRUE)
+  if (no_output) fs::dir_delete(output_dir)
+  
+  devtools::load_all(new_package_dir)
+}
+

Let’s test that this works. In particular, we’ll call load_all() and then try to use one of the functions from the package.

+
testthat::test_that('load_all() works', {
+  # setup files for tests:
+  dir <- tempfile()
+  if (fs::file_exists(dir)) fs::file_delete(dir)
+  fs::dir_create(dir)
+  rmd_file <- file.path(dir, 'create-pkg.Rmd')
+  fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file)
+  html_file <- file.path(dir, "create-pkg.html")
+
+  load_all(rmd_file)
+  testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!")
+  
+  fs::dir_delete(dir)
+})
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/generating-package.html b/create-litr/_book/generating-package.html new file mode 100644 index 0000000..2c7fd6a --- /dev/null +++ b/create-litr/_book/generating-package.html @@ -0,0 +1,734 @@ + + + + + + + 4 Generating the R package | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

4 Generating the R package

+
+

4.1 Sending code chunks to the package

+

We start by defining a chunk hook, which is a function that runs both before and after each code chunk is run in the knitting process. In this case, the function (called send_to_package) is responsible for determining whether the code chunk looks like something that should be exported to the R package. We don’t want all code sent off to our R package. For example, sometimes we’ll want to demonstrate in the Rmd file how a certain function we’ve just created is used by running it on an example or making a plot. That bit of example code would not be included in the package.

+

We start by making sure that code is only sent to the R package once (arbitrarily we have code outputted to the package before and not after the chunk is run).

+

The function then checks if this code chunk is code that should be put into the package. There are four specific cases it considers:

+
    +
  1. If the special option send_to is used in this code chunk, then things are very simple in that the user has explicitly told us where this code should be added. For example, if send_to="R/file.R", then the code in this chunk will be appended to R/file.R in the R package (and if that file doesn’t yet exist, it will be created).

  2. +
  3. Is it a piece of code to be sent to the R/ directory? In particular, it checks to see if the code chunk begins with the characteristic roxygen2 characters #'. If it does, then the name of the object being documented is identified (could be a function, a dataset, an S4 object, etc.) and then we write the code chunk to the file R/<objectname>.R.

  4. +
  5. If the code chunk does not start with #', then we check if it has any line starting with test_that( or testthat::test_that(. If so, then this whole code chunk is appended to tests/testthat/tests.R (and this file is created the first time a test chunk is sent to the package).

  6. +
  7. Next, it checks if the language engine is Rcpp. This occurs when the code chunk starts with {Rcpp, rather than the usual {r (or alternatively when the engine="Rcpp" option is used). We then set things up appropriately for the use of Rcpp within the package (by adapting some code from within the usethis::use_rcpp() function). Finally, we write the code chunk to src/code.cpp. There is a common header used,

  8. +
+
#include <Rcpp.h>
+using namespace Rcpp;
+

and we only want this to appear once in code.cpp, so we do a bit of work to remove that if it appears in the code chunk.

+
#' A knitr chunk hook for writing R code and tests
+#' 
+#' This chunk hook detects whether a chunk is defining a function or dataset
+#' to be included in the R package (looks for the `roxygen2` comment format `#' `).
+#' If so, then it is written to the `R/` directory.  It also looks for chunks 
+#' that have one or more lines that start with `test_that(` or 
+#' `testthat::test_that(` (potentially with some leading whitespace).  These 
+#' chunks are then written to the `tests` directory of the R package.
+#' 
+#' When the `send_to` option is used, this chunk hook instead simply writes the
+#' code chunk to the file specified.
+#' 
+#' @param before Indicates whether this is being called before or after the 
+#' chunk code is executed
+#' @param options Has information from the chunk
+#' @param envir Environment
+#' @keywords internal
+send_to_package <- function(before, options, envir) {
+  msg <- do_not_edit_message(knitr::current_input(), type = "R")
+  if (before == FALSE) {
+    # Don't do anything after the code chunk has been executed.
+    return()
+  }
+  package_dir <- knitr::opts_knit$get("root.dir")
+  package_name <- fs::path_file(package_dir)
+  if (!is.null(options$send_to)) {
+    # the user has defined an option that indicates where in the package this
+    # code should be written
+    file <- file.path(package_dir, options$send_to)
+    add_text_to_file(options$code, file, pad = TRUE, msg = msg)
+    return()
+  }
+  if (stringr::str_detect(options$code[1], "^#' ")) {
+    # starts with roxygen2, so let's assume this chunk is defining an R function
+    # or dataset that belongs in the package
+    non_comment <- stringr::str_subset(options$code, "^#", negate = TRUE)
+    if (length(non_comment) > 0) {
+      if (stringr::str_detect(non_comment[1], "<-")) {
+        # a function is being defined
+        objname <- stringr::str_match(non_comment[1], "^(.*)\\s*<-\\s*function")[, 2]
+        objname <- stringr::str_trim(objname)
+      } else if (stringr::str_detect(non_comment[1], '^".+"$')) {
+        # a dataset is being documented
+        objname <- stringr::str_sub(non_comment[1], start = 2, end = -2)
+      } else {
+        # Roxygen2 comment wasn't followed by anything recognized, so do not 
+        # send this to package
+        return()
+      }
+      file <- file.path(package_dir, "R", stringr::str_glue("{objname}.R"))
+      cat(paste(c(msg, "", options$code, ""), collapse = "\n"), file = file)
+    }
+  }
+  else if (any(stringr::str_detect(options$code,
+                                   "^\\s*(testthat::)?test_that\\("))) {
+    # This chunk is inferred to be a test
+    test_dir <- file.path(package_dir, "tests", "testthat")
+    test_file <- file.path(test_dir, "tests.R")
+    if (!file.exists(test_file)) {
+      # It's the first chunk with tests
+      if (!dir.exists(test_dir)) usethis::use_testthat()
+      cat(c(msg, ""), collapse = "\n", file = test_file)
+    }
+    cat(
+      paste(c(options$code, "", ""), collapse = "\n"),
+      file = test_file,
+      append = TRUE
+    )
+  } else if (options$engine == "Rcpp") {
+    # To add Rcpp code, we need the package documentation file to exist 
+    if (!file.exists(file.path(
+      package_dir,
+      "R",
+      paste0(package_name, "-package.R"))
+      )) {
+      usethis::use_package_doc(open = FALSE)
+    }
+    cpp_file <- file.path(package_dir, "src", "code.cpp")
+    if (!file.exists(cpp_file)) {
+      # set up package for Rcpp
+      # these next few lines are taken from usethis::use_rcpp()
+      # it approximates a call to usethis::use_rcpp(name = "code")
+      usethis:::use_dependency("Rcpp", "LinkingTo")
+      usethis:::use_dependency("Rcpp", "Imports")
+      usethis:::roxygen_ns_append("@importFrom Rcpp sourceCpp")
+      usethis:::use_src()
+      usethis::use_template("code.cpp", save_as = "src/code.cpp")
+
+      msg <- do_not_edit_message(knitr::current_input(), type = "c")
+      cat(msg, file = cpp_file, append = TRUE)
+    }
+    # append code to code.cpp, but remove lines that are `#include <Rcpp.h>`
+    # or `using namespace Rcpp;` since this already appears at top of file
+    cat(paste(c(
+      "",
+      stringr::str_subset(
+        options$code,
+        r"(^#include <Rcpp.h>$|^using namespace Rcpp;$)",
+        negate = TRUE),
+      ""), collapse = "\n"), 
+        file = cpp_file,
+        append = TRUE)
+  }
+  return()
+}
+

The above code makes use of a number of functions from the stringr and usethis packages, so we’ll need to add those packages to the Imports section of the DESCRIPTION file:

+
usethis::use_package("stringr")
+usethis::use_package("usethis")
+
## ✔ Adding 'stringr' to Imports field in DESCRIPTION
+## • Refer to functions with `stringr::fun()`
+
## ✔ Adding 'usethis' to Imports field in DESCRIPTION
+## • Refer to functions with `usethis::fun()`
+

The code also calls the function do_not_edit_message(), which adds a line at the top of the files sent to the R package reminding the user that these are not source files to be edited but rather output of the generating .Rmd file. There are two variations on this message.

+
#' Generate do-not-edit message to put at top of file
+#' 
+#' @param rmd_file Name of the Rmd file to mention
+#' @param type Whether this is a R/ file, man/ file, or a c file
+#' @keywords internal
+do_not_edit_message <- function(rmd_file, type = c("R", "man", "c")) {
+  if (type[1] == "R")
+    return(stringr::str_glue("# Generated from {rmd_file}: do not edit by hand"))
+  else if (type[1] == "man")
+    return(stringr::str_glue("% Please edit documentation in {rmd_file}."))
+  else if (type[1] == "c")
+    return(stringr::str_glue("// Generated from {rmd_file}: do not edit by hand"))
+  else
+    stop("type must be either 'R', 'man', or 'c'.")
+}
+

This function will also be used with type = "man" by litr::document().

+

The above also makes use of a simple helper function that inserts text into a specified location of a file (or creates that file if it doesn’t exist). Actually currently it doesn’t, but we can replace cat() in the above with calls to add_text_to_file().

+
#' Add Some Text to a File
+#' 
+#' The text will be added to the file at a particular line specified by
+#' `location`.  The first line of `txt` will be on line `location` of the
+#' modified file.  If `location` is NULL, then text is added to end of file.
+#' If file does not exist, it is created and `location` is ignored (unless 
+#' `req_exist` is `TRUE`, in which case an error is thrown).
+#' 
+#' @param txt Character vector to add to file
+#' @param filename Name of file
+#' @param location Specifies where text should be added. See description for more.
+#' @param req_exist If TRUE, then throws an error if file doesn't exist
+#' @param pad If TRUE, then when text is being added to a preexisting file, it adds a newline
+#' @param msg An optional message to put at top of file if this is a new file
+#' @keywords internal
+add_text_to_file <- function(txt, filename, location = NULL, req_exist = FALSE,
+                             pad = FALSE, msg = NULL) {
+  if (!file.exists(filename)) {
+    if (req_exist) stop(stringr::str_glue("Cannot find file {filename}."))
+    if (!is.null(msg)) txt <- c(msg, "", txt)
+    writeLines(txt, con = filename)
+    return()
+  }
+  if (pad) txt <- c("", txt)
+  filetxt <- readLines(filename)
+  if (is.null(location) || location == length(filetxt) + 1) {
+    filetxt <- c(filetxt, txt)
+  }
+  else if (location > length(filetxt) + 1 | location < 1) 
+    stop("Invalid location")
+  else if (location == 1) {
+    filetxt <- c(txt, filetxt)
+  } else {
+    # location is somewhere in middle
+    filetxt <- c(filetxt[1:(location - 1)],
+                 txt,
+                 filetxt[location:length(filetxt)])
+  }
+  writeLines(filetxt, con = filename)
+}
+
testthat::test_that("add_text_to_file() works", {
+  dir <- tempfile()
+  if (fs::file_exists(dir)) fs::file_delete(dir)
+  fs::dir_create(dir)
+  
+  # should throw error when file does not exist and req_exist is TRUE:
+  myfile <- file.path(dir, "file.txt")
+  sometxt <- c("hello", "there")
+  testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE))
+
+  # should create a new file where one does not exist:
+  myfile <- file.path(dir, "file.txt")
+  sometxt <- c("hello", "there")
+  add_text_to_file(sometxt, myfile)
+  testthat::expect_true(fs::file_exists(myfile))
+  testthat::expect_equal(sometxt, readLines(myfile))
+  
+  # should append to end of file by default
+  moretxt <- "world"
+  add_text_to_file(moretxt, myfile)
+  testthat::expect_equal(c(sometxt, moretxt), readLines(myfile))
+   
+  # should throw error for invalid locations:
+  testthat::expect_error(add_text_to_file(sometxt, myfile, 0))
+  testthat::expect_error(add_text_to_file(sometxt, myfile, -1))
+  testthat::expect_error(add_text_to_file(sometxt, myfile, 5))
+
+  # should add to specified line:
+  moretxt2 <- "hi"
+  add_text_to_file(moretxt2, myfile, 1)
+  testthat::expect_equal(c(moretxt2, sometxt, moretxt), readLines(myfile))
+
+  # should add to specified line:
+  moretxt3 <- "hi2"
+  add_text_to_file(moretxt3, myfile, 2)
+  testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt),
+                         readLines(myfile))
+
+  # should add to specified line:
+  moretxt4 <- "hi3"
+  add_text_to_file(moretxt4, myfile, 6)
+  testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt, moretxt4),
+                         readLines(myfile))
+  fs::dir_delete(dir)
+})
+
## Test passed
+
+
+

4.2 Setting up the R package creation

+

When the user calls litr::render() (either in the console or by pressing “Knit” in RStudio), one of the first things that function does is to call the function litr::setup(), which does several things:

+
    +
  1. Creates a new empty directory at the specified location while first making sure that it won’t overwrite something it shouldn’t. In particular, we guard against the case that the package was generated by litr::render() but then someone went in manually and made some changes. Even though users should never manually edit the package that was generated by litr::render(), we don’t want to have them inadvertently lose their work by doing so. Thus, we only overwrite an R package if we can tell that it is the unedited output of a call to litr::render(). The function check_unedited() is responsible for checking this, and is a pretty interesting function which we will describe in the next section. This part of the code also makes use of a function litr::make_noticeable(), which is simply a way of making error messages produced by litr more easy to see amid a lot of knitr output.

  2. +
  3. Adjusts the root directory from the generating .Rmd file’s location to the R package’s location. Note: This behavior might not actually be desirable now that additional files will be loaded in. It might be awkward for a user writing the generating .Rmd file to have to make everything relative to the package. It might be convenient to provide a litr::add_file(from, to) function, where from is the path relative to the .Rmd file and to is the path relative to the package’s location.

  4. +
  5. Makes it so that the send_to_package() chunk hook is active for each code chunk. This involves registering a new chunk hook using the function knitr::knit_hooks$set() and then setting an option with the same name to TRUE within each chunk.

  6. +
  7. Deactivates an internal function of the usethis package, usethis:::challenge_nested_project(). This was actually a difficult issue to address that involves the intersection of usethis, here, and our particular use case. The problem is that usethis was not designed for our setting in which an R package is being created programmatically. When using litr, the project directory will have the generating .Rmd file and when this is knit it will create an R package within this project. However, this leads usethis to prompt the user with a message of the form

  8. +
+
+

“New project ‘[…]’ is nested inside an existing project ‘[…]’. This is rarely a good idea. Do you wish to create anyway?”

+
+

But since this is encountered through knitting rather than interactively, this results in an error. This usethis issue describes this exact problem. The solution suggested there by jennybc involving testthat::with_mock() is along the lines of what we want; however, that would lead to some ugly looking code in the generating .Rmd file. The best solution I could find was to use utils::assignInNamespace() as described here. This function allows us to change the internal function usethis:::challenge_nested_project() so that it no longer prompts the user with concerns about nested projects.

+
    +
  1. Changes how chunk references are handled. In particular, consider the following code chunk:
  2. +
+
a <- 2
+<<my-chunk>>
+a
+

The way knitr handles this, the code chunk would no longer look like this but it would rather have replaced the <<my-chunk>> line by the code that appears in the code chunk labeled “my-chunk”. We instead would like the above code chunk to appear as written and then for the code chunk labeled “my-chunk” to have its label visible to the reader of the .html file. This gives the coder more control over when the reader learns about different parts of the code. It also more closely resembles Donald Knuth’s form of literate programming. For convenience, we’d like <<my-chunk>> to be a link that navigates to the code chunk labeled “my-chunk”. To accomplish this, we modify the document output hook in setup() (and then we also add a function called add_chunk_label_hyperlinks() within render()).

+
    +
  1. Define a package_doc engine which allows users to define package-level documentation.
  2. +
+
#' Code for setup chunk
+#' 
+#' * Creates directory where package will be. (Deletes what is currently there as 
+#' long as it appears to have been created by litr and does not have any 
+#' subsequent manual edits.)
+#' * Sets the root directory to this directory
+#' * Sets up the main chunk hook `litr::send_to_package()` that sends code to the 
+#' R package directory.
+#' * In the case that `minimal_eval=TRUE`, sets up an options hook for `eval` so
+#'   chunks are only evaluated if there is a `usethis` or `litr::document()`
+#'   command
+#' * Deactivates an internal function of the `usethis` package
+#' * Redefines the document output hook to handle chunk references differently  
+#' * Sets up a [custom language engine](https://bookdown.org/yihui/rmarkdown-cookbook/custom-engine.html) called
+#' `package_doc` that creates a package documentation file and then inserts
+#' whatever the user puts in the chunk.
+#' 
+#' Returns the original state of the knitr objects that have been modified in 
+#' setup.  This allows us to return things to the previous state after we are
+#' finished.  This is relevant in the case where litr-knitting occurs in the 
+#' current session and we don't want to leave things in a permanently modified
+#' state.
+#' 
+#' @param package_dir Directory where R package will be created
+<<param-minimal_eval>>
+#' @keywords internal
+setup <- function(package_dir, minimal_eval) {
+  if (file.exists(package_dir)) {
+    unedited <- tryCatch(check_unedited(package_dir),
+                         error = function(e) {
+                           # contents of package_dir does not resemble
+                           # a litr package
+                           return(FALSE)
+                         })
+    if (!unedited) {
+      stop(make_noticeable(paste(
+        stringr::str_glue("The directory {normalizePath(package_dir)}"),
+        "already exists and either was not created by litr or may have manual",
+        "edits. In either case, please rename that directory (or delete it)", 
+        "and then try again.", 
+        sep = "\n")))
+    }
+    unlink(package_dir, recursive = TRUE)
+  }
+  fs::dir_create(package_dir)
+  usethis:::proj_set_(usethis:::proj_path_prep(package_dir))
+
+  # let's keep a version of the knitr objects before modifying them:
+  original_knitr <- list(opts_knit = knitr::opts_knit$get(),
+                         knit_hooks = knitr::knit_hooks$get(),
+                         opts_chunk = knitr::opts_chunk$get(),
+                         opts_hooks = knitr::opts_hooks$get(),
+                         knit_engines = knitr::knit_engines$get()
+                         )
+  
+  knitr::opts_knit$set(root.dir = package_dir) # sets wd of future chunks
+  knitr::knit_hooks$set(send_to_package = send_to_package)
+  knitr::opts_chunk$set(send_to_package = TRUE)
+  if (minimal_eval) {
+    # only evaluate chunks that appear to include usethis commands or 
+    # a call to litr::document() but if someone has specifically set eval=FALSE
+    # in a particular chunk, do honor that
+    usethis_exports <- getNamespaceExports("usethis")
+    patterns <- paste(c("usethis::", usethis_exports, "litr::document\\("), collapse = "|")
+    knitr::opts_hooks$set(eval = function(options) {
+      if (options$eval)
+        options$eval <- any(stringr::str_detect(options$code, patterns))
+      return(options)
+    })
+  }
+  
+  
+  # change usethis:::challenge_nested_project so that it will not complain
+  # about creating a nested project (e.g. if this is called within a git 
+  # subdirectory)
+  utils::assignInNamespace("challenge_nested_project", function(...) NULL, ns = "usethis")
+
+  # define document hook to handle chunk references:
+  knitr::knit_hooks$set(document = function(x) {
+    # get the indices of x corresponding to code chunks
+    chunk_start <- "^(\n```+[a-zA-Z0-9_]+\n)"
+    idx_block <- stringr::str_which(x, chunk_start)
+    original_code <- knitr::knit_code$get()
+    # We first get indices of skipped chunks in original_code list
+    skipped_chunks <- which(sapply(original_code, function(x){
+      return(isFALSE(attr(x, "chunk_opts")$echo) || isFALSE(attr(x, "chunk_opts")$include))
+    }))
+
+    # Next we remove the indices of skipped chunks
+    original_code_idx_fixed <- setdiff(seq(length(original_code)), skipped_chunks)
+    
+    labels <- names(original_code)
+    # replace each x[i] that has code in it with the original code
+    for (i in seq_along(idx_block)) {
+      # break code into multiple lines:
+      chunk <- strsplit(x[idx_block[i]], "\n")[[1]]
+      # get the fence used (in case it's more than three ticks):
+      i_start <- stringr::str_which(chunk, "^```+[a-zA-Z0-9_]+")
+      fence <- stringr::str_replace(chunk[i_start[1]],
+                                    "^(```+)[a-zA-Z0-9_]+", "\\1")
+      i_fences <- stringr::str_which(chunk, paste0("^", fence))
+      # there can be multiple code and output chunks strung together 
+      # within a single x[i] if results are not held to end
+      i_all_code <- c()
+      for (j in seq_along(i_start)) {
+        # get the elements corresponding the j-th code chunk within chunk
+        i_code_end <- i_fences[which(i_fences == i_start[j]) + 1]
+        i_all_code <- c(i_all_code, i_start[j]:i_code_end)
+      }
+      i_all_code <- setdiff(i_all_code, i_start[1])
+      chunk_no_code <- chunk[-i_all_code]
+      chunk <- c(chunk_no_code[1:i_start[1]],
+                 original_code[original_code_idx_fixed[i]][[1]],
+                 # insert the original version, accounting for skipped chunks
+                 fence)
+      if (i_start[1] < length(chunk_no_code))
+        chunk <- c(chunk, chunk_no_code[(i_start[1] + 1):length(chunk_no_code)])
+        x[idx_block[i]] <- paste(chunk, collapse = "\n")
+    }
+    
+    # replace code chunks with the original code
+    # (so we'll still have <<label>> chunk references)
+    refs <- c() # labels that get referred to
+    for (label in labels) {
+      refs <- c(refs, find_labels(original_code[[label]])$chunk_ids)
+    }
+    refs <- unique(refs)
+    adj_labels <- labels[!labels %in% names(skipped_chunks)]
+    ref_id <- match(refs, adj_labels)
+    if (any(is.na(ref_id))) {
+      stop(make_noticeable(paste(
+        stringr::str_glue("The chunk reference <<{refs[is.na(ref_id)][1]}>> ",
+        "is used, but there is no chunk with that label.", 
+        sep = "\n"))))
+      }
+    to_insert <- paste0('###"', adj_labels[ref_id], '"###\n')
+    x[idx_block[ref_id]] <- stringr::str_replace(x[idx_block[ref_id]],
+                                                 chunk_start,
+                                                 paste0("\\1", to_insert))
+    x
+  })
+  
+  # setup package_doc engine
+  knitr::knit_engines$set(package_doc = function(options) {
+    # create package_doc
+    usethis::use_package_doc(open = FALSE)
+    
+    # insert the contents of the code chunk into the package_doc
+    pkgdoc <- file.path("R", paste0(fs::path_file(package_dir), "-package.R"))
+    add_text_to_file(options$code, filename = pkgdoc, location = 1)
+    
+    # now treat this as if it were standard R code with eval=FALSE
+    r_engine <- knitr::knit_engines$get("R")
+    options[["eval"]] <- FALSE
+    return(r_engine(options))
+  })
+  return(original_knitr)
+}
+

In our new document output hook defined above, we call a function find_labels(). It takes a block of code and returns both a logical vector of which lines contained chunk labels and another vector containing the labels of those referenced chunks. We define it here:

+
#' Find a .Rmd chunk label in a code chunk
+#' 
+#' @param chunk_code Character vector of code from a .Rmd code chunk. Each element is a line of the code chunk.
+#' @return List where chunk_idx is a logical vector for each line of the chunk corresponding to whether a chunk label of the form `<<label>>` was found and chunk_ids is a character vector of chunk label was found in that chunk.
+#' @keywords internal
+find_labels <- function(chunk_code) {
+  rc <- knitr::all_patterns$md$ref.chunk
+  chunk_idx <- any(idx = grepl(rc, chunk_code))
+  chunk_ids <- stringr::str_trim(sub(rc, "\\1", chunk_code[grepl(rc, chunk_code)]))
+  return(list(chunk_idx = chunk_idx, chunk_ids = chunk_ids))
+}
+

The setup() function also uses a small function, make_noticeable(), which we define here:

+
#' Make error messages noticeable
+#' 
+#' Since litr error messages are amid a lot of output from knitting, we'd like 
+#' the litr ones to be eye-catching.
+#' 
+#' @param msg Error message
+#' @keywords internal
+make_noticeable <- function(msg) {
+  paste("",
+        "======",
+        "Please read your friendly litr error message here:",
+        paste("> ", msg),
+        "======",
+        sep = "\n")
+}
+

The code in this section used the fs and knitr packages, so we import those:

+
usethis::use_package("fs")
+usethis::use_package("knitr")
+
## ✔ Adding 'fs' to Imports field in DESCRIPTION
+## • Refer to functions with `fs::fun()`
+
## ✔ Adding 'knitr' to Imports field in DESCRIPTION
+## • Refer to functions with `knitr::fun()`
+
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/hash.html b/create-litr/_book/hash.html new file mode 100644 index 0000000..266007a --- /dev/null +++ b/create-litr/_book/hash.html @@ -0,0 +1,364 @@ + + + + + + + 5 Not overwriting a manually edited R package | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

5 Not overwriting a manually edited R package

+

As described in the previous section, the function setup() will only overwrite a directory if it is the unedited output from using litr. The basic idea is that the function litr::render() when creating a new package finishes by adding a hash to the DESCRIPTION file. (And likewise when rmarkdown::render() is used with a litr output format, as described here.) This hash is a function of everything in the package, so if anything about the package changes (any file is modified, added, or removed) then the function check_unedited() will be able to detect that by recomputing the hash and seeing that it doesn’t match the hash in the DESCRIPTION file.

+

Let’s start by defining the function hash_package_directory() that does the hashing. The hash is a function of everything in the outputted package except for that special line in the DESCRIPTION file with the hash. We use tools::md5sum() and digest::digest() to do the hashing.

+
#' Hash package directory
+#' 
+#' Gets an identifier that can be used to uniquely (whp) identify the current 
+#' state of the package. This is formed by ignoring the `LitrId` field of the
+#' DESCRIPTION file, which is the location where the output of this function is 
+#' stored when `litr::render` generates the package.
+#' 
+#' @param package_dir Path to package
+#' @keywords internal
+hash_package_directory <- function(package_dir) {
+  pkg_files <- fs::dir_ls(package_dir, recurse = TRUE, all = TRUE, type = "file")
+  pkg_files <- stringr::str_subset(pkg_files, ".DS_Store$", negate = TRUE)
+  pkg_files <- normalizePath(pkg_files)
+  descr_file <- normalizePath(file.path(package_dir, "DESCRIPTION"))
+  i_descr <- which(pkg_files == descr_file)
+  if (length(i_descr) == 0) stop("Cannot find DESCRIPTION file.")
+  txt_descr <- readLines(pkg_files[i_descr])
+  txt_descr_mod <- stringr::str_subset(
+    txt_descr, 
+    stringr::str_glue("{description_litr_hash_field_name()}: .+$"),
+    negate = TRUE)
+  hashes <- as.character(tools::md5sum(pkg_files[-i_descr]))
+  digest::digest(c(hashes, list(txt_descr_mod)))
+}
+

We used digest, so let’s import it:

+
usethis::use_package("digest")
+
## ✔ Adding 'digest' to Imports field in DESCRIPTION
+## • Refer to functions with `digest::fun()`
+

We will store this hash in a special field within the DESCRIPTION file. Let’s call this field LitrId. However, in case we ever decide to change the name of this field, it’s better that we only define it in one place. So we do this with the following function:

+
#' Generate litr hash field name for DESCRIPTION file
+#' @keywords internal
+description_litr_hash_field_name <- function() return("LitrId")
+

Ok, now let’s write the function that litr::render() will call that will take the generated R package and add a line that puts the hash in the DESCRIPTION file under that special litr field:

+
#' Write the hash of the package to the DESCRIPTION file
+#' 
+#' @param package_dir Path to package
+#' @keywords internal
+write_hash_to_description <- function(package_dir) {
+  desc_file <- file.path(package_dir, "DESCRIPTION")
+  if (!file.exists(desc_file)) file.create(desc_file)
+  hash <- hash_package_directory(package_dir)
+  desc::desc_set(description_litr_hash_field_name(), hash, file = desc_file)
+}
+

Let’s include the desc package, which helps us manipulate DESCRIPTION files.

+
usethis::use_package("desc")
+
## ✔ Adding 'desc' to Imports field in DESCRIPTION
+## • Refer to functions with `desc::fun()`
+

And of course we’ll need a function that can read the value of that field as well:

+
#' Get the hash of the package from the DESCRIPTION file
+#' 
+#' @param package_dir Path to package
+#' @keywords internal
+read_hash_from_description <- function(package_dir) {
+  descr <- file.path(package_dir, "DESCRIPTION")
+  if (!file.exists(descr)) stop("Cannot find DESCRIPTION file.")
+  txt <- stringr::str_subset(
+    readLines(descr), 
+    stringr::str_glue("{description_litr_hash_field_name()}: .+$"))
+  if (length(txt) > 1) stop("More than one hash found in DESCRIPTION.")
+  if (length(txt) == 0) stop("No hash found in DESCRIPTION.")
+  stringr::str_extract(txt, "\\S+$")
+}
+

With all this hash functionality in place, the function check_unedited() is actually quite simple to define:

+
#' Check if package directory is the unedited output of litr::render()
+#' 
+#' Uses hash stored in a special `litr` field of DESCRIPTION file to check that 
+#' the current state of the R package directory is identical to its state at the
+#' time that it was created by `litr::render()`.
+#' 
+#' @param package_dir Path to package
+#' @keywords internal
+check_unedited <- function(package_dir) {
+  hash <- hash_package_directory(package_dir)
+  hash == read_hash_from_description(package_dir)
+}
+

It simply computes the hash of the current package and checks whether that hash is the same as what was originally written to the DESCRIPTION file by litr::render().

+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/including-extras-for-litr.html b/create-litr/_book/including-extras-for-litr.html new file mode 100644 index 0000000..110fc3e --- /dev/null +++ b/create-litr/_book/including-extras-for-litr.html @@ -0,0 +1,489 @@ + + + + + + + 14 Including extras for litr | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

14 Including extras for litr

+
+

14.1 README with hex sticker

+

We include a README.Rmd and then generate the README.md based on it:

+
add_readme(file.path("..", "source-files", "README.Rmd"))
+
## ✔ Writing 'README.Rmd'
+## ✔ Adding '^README\\.Rmd$' to '.Rbuildignore'
+## ✔ Creating '.git/hooks/'
+## ✔ Writing '.git/hooks/pre-commit'
+

Let’s add the litr hex sticker too (which is referred to in the README).

+
add_hex_sticker(file.path("..", "source-files", "litr-hex.png"))
+

Let’s also add a figure we include in the README.

+
fs::file_copy(file.path("..", "source-files", "diagram3.png"),
+              file.path("man", "figures"))
+
+
+

14.2 Vignettes

+
add_vignettes(c(file.path("..", "source-files", "package-templates.Rmd"),
+                file.path("..", "source-files", "packages-in-the-wild.Rmd"),
+                file.path("..", "source-files", "faqs.Rmd"),
+                file.path("..", "source-files", "basic-example.Rmd")))
+

The templates vignette uses dplyr and stringr, so we add them as “Suggests” in the DESCRIPTION file:

+
usethis::use_package("dplyr", type = "Suggests")
+usethis::use_package("stringr", type = "Suggests")
+
## ✔ Adding 'dplyr' to Suggests field in DESCRIPTION
+## • Use `requireNamespace("dplyr", quietly = TRUE)` to test if package is installed
+## • Then directly refer to functions with `dplyr::fun()`
+
## Warning: Package 'stringr' is already listed in 'Imports' in DESCRIPTION, no
+## change made.
+
+
+

14.3 A pkgdown site

+

We’ll first add the github url to the DESCRIPTION file.

+
desc::desc_set("URL", "https://github.com/jacobbien/litr-project/tree/main/litr")
+
## Package: litr
+## Title: Literate Programming for Writing R Packages
+## Version: 0.9.1
+## Authors@R (parsed):
+##     * Jacob Bien <jbien@usc.edu> [aut, cre]
+##     * Patrick Vossler [aut]
+## Description: Allows one to fully create an R package in a single .Rmd
+##     file.  Includes functionality and .Rmd templates for a literate
+##     programming approach to R package development.
+## License: MIT + file LICENSE
+## URL: https://github.com/jacobbien/litr-project/tree/main/litr
+## Imports:
+##     bookdown,
+##     desc,
+##     devtools,
+##     digest,
+##     fansi,
+##     fs,
+##     knitr,
+##     pkgdown,
+##     rmarkdown,
+##     stringr,
+##     usethis,
+##     xfun,
+##     xml2,
+##     yaml
+## Suggests:
+##     dplyr,
+##     Rcpp,
+##     testthat (>= 3.0.0)
+## VignetteBuilder:
+##     knitr
+## Config/testthat/edition: 3
+## Encoding: UTF-8
+## Roxygen: list(markdown = TRUE)
+## RoxygenNote: 7.2.3
+

Next, we create the pkgdown site. The customizations come from the source file source-files/_pkgdown.yml.

+
pkgdown_yml <- file.path("..", "source-files", "_pkgdown.yml")
+add_pkgdown(pkgdown_yml)
+
## ✔ Adding '^_pkgdown\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore'
+
## -- Installing package into temporary library -----------------------------------
+## == Building pkgdown site =======================================================
+## Reading from: '/Users/vossler/litr-project/litr'
+## Writing to:   '/Users/vossler/litr-project/docs'
+## -- Initialising site -----------------------------------------------------------
+## -- Building favicons -----------------------------------------------------------
+## Building favicons with realfavicongenerator.net...
+## Copying 'pkgdown/favicon/apple-touch-icon-120x120.png' to 'apple-touch-icon-120x120.png'
+## Copying 'pkgdown/favicon/apple-touch-icon-152x152.png' to 'apple-touch-icon-152x152.png'
+## Copying 'pkgdown/favicon/apple-touch-icon-180x180.png' to 'apple-touch-icon-180x180.png'
+## Copying 'pkgdown/favicon/apple-touch-icon-60x60.png' to 'apple-touch-icon-60x60.png'
+## Copying 'pkgdown/favicon/apple-touch-icon-76x76.png' to 'apple-touch-icon-76x76.png'
+## Copying 'pkgdown/favicon/apple-touch-icon.png' to 'apple-touch-icon.png'
+## Copying 'pkgdown/favicon/favicon-16x16.png' to 'favicon-16x16.png'
+## Copying 'pkgdown/favicon/favicon-32x32.png' to 'favicon-32x32.png'
+## -- Building home ---------------------------------------------------------------
+## Reading 'LICENSE.md'
+## Writing '404.html'
+## -- Building function reference -------------------------------------------------
+## Reading 'man/add_chunk_label_hyperlinks.Rd'
+## Reading 'man/add_function_hyperlinks.Rd'
+## Reading 'man/add_hex_sticker.Rd'
+## Reading 'man/add_pkgdown.Rd'
+## Reading 'man/add_readme.Rd'
+## Reading 'man/add_text_to_file.Rd'
+## Reading 'man/add_vignettes.Rd'
+## Reading 'man/check_unedited.Rd'
+## Reading 'man/description_litr_hash_field_name.Rd'
+## Reading 'man/description_litr_version_field_name.Rd'
+## Reading 'man/do_not_edit_message.Rd'
+## Reading 'man/document.Rd'
+## Reading 'man/find_labels.Rd'
+## Reading 'man/get_package_directory.Rd'
+## Reading 'man/get_params_used.Rd'
+## Reading 'man/hash_package_directory.Rd'
+## Reading 'man/insert_hrefs.Rd'
+## Reading 'man/litr-package.Rd'
+## Reading 'man/litr_gitbook.Rd'
+## Reading 'man/litr_html_document.Rd'
+## Reading 'man/litr_pdf_document.Rd'
+## Reading 'man/litrify_output_format.Rd'
+## Reading 'man/load_all.Rd'
+## Reading 'man/make_noticeable.Rd'
+## Reading 'man/read_hash_from_description.Rd'
+## Reading 'man/remove_rstudio_extras.Rd'
+## Reading 'man/render.Rd'
+## Reading 'man/replace_ansi_sequences.Rd'
+## Reading 'man/restore_knitr_objects.Rd'
+## Reading 'man/send_to_package.Rd'
+## Reading 'man/setup.Rd'
+## Reading 'man/test_litr.Rd'
+## Reading 'man/with_cleanup.Rd'
+## Reading 'man/write_hash_to_description.Rd'
+## Reading 'man/write_version_to_description.Rd'
+## -- Building articles -----------------------------------------------------------
+## Reading 'vignettes/basic-example.Rmd'
+## Reading 'vignettes/faqs.Rmd'
+## Reading 'vignettes/package-templates.Rmd'
+## Reading 'vignettes/packages-in-the-wild.Rmd'
+## Writing 'sitemap.xml'
+## -- Building search index -------------------------------------------------------
+## == DONE ========================================================================
+

We follow this pkgdown vignette in our customizations. Here is the contents of the _pkgdown.yml that was used:

+
cat(readLines("../source-files/_pkgdown.yml"), sep = '\n')
+
destination: ../docs/
+
+url: ~
+template:
+  bootstrap: 5
+  bootswatch: cosmo
+
+repo:
+  url:
+    home: https://github.com/jacobbien/litr-project/tree/main/litr/   
+    source: https://github.com/jacobbien/litr-project/tree/main/litr/
+    issue: https://github.com/jacobbien/litr-project/issues/
+    user: https://github.com/
+
+authors:
+  Jacob Bien:
+    href: http://faculty.marshall.usc.edu/jacob-bien/
+  Patrick Vossler:
+    href: https://www.patvoss.me/
+
+navbar:
+  structure:
+    left:  [reference, articles]
+    right: [github]
+  components:
+    github:
+      icon: fa-github
+      href: https://github.com/jacobbien/litr-project/tree/main/litr/
+
+reference:
+- title: Primary functions
+  desc: >
+    These are the functions you'll use the most.
+  contents:
+    - render
+    - document
+    - load_all
+
+- title: Functions for adding "extras" to your package
+  desc: >
+    These functions can help you add a README, vignettes, a pkgdown site, and a hex sticker to your package.
+  contents:
+    - add_readme
+    - add_vignettes
+    - add_pkgdown
+    - add_hex_sticker
+
+- title: Custom output formats
+  desc: >
+    These are the functions for producing different output formats.
+  contents:
+    - litr_html_document
+    - litr_pdf_document
+    - litr_gitbook
+    - litrify_output_format
+

After this step, you can locally see the site by opening docs/index.html in the browser. You can then copy the docs directory to your website’s server and you’re done.

+ +
+
+ + + + + + + + +
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/including-templates.html b/create-litr/_book/including-templates.html new file mode 100644 index 0000000..3c9410c --- /dev/null +++ b/create-litr/_book/including-templates.html @@ -0,0 +1,409 @@ + + + + + + + 11 Including templates | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

11 Including templates

+

We now add the .Rmd templates to the package. We have the skeleton.Rmd defined in source-files. Note that paths are relative to the outputted package’s location.

+

The first template is the simplest imaginable package with a single function:

+
usethis::use_rmarkdown_template(
+  template_name = "Template To Make an R Package",
+  template_dir = "make-an-r-package",
+  template_description = "Template for an Rmd file for writing an R package using literate programming.",
+  template_create_dir = FALSE
+)
+fs::file_copy(
+  path = file.path(
+    "..", "source-files", "make-an-r-package", "skeleton.Rmd"
+    ), 
+  new_path = file.path(
+    "inst", "rmarkdown", "templates", "make-an-r-package", "skeleton"
+    ), 
+  overwrite = TRUE
+)
+
## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package/skeleton/'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package/template.yaml'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package/skeleton/skeleton.Rmd'
+

The second template shows how to create a package with a dataset:

+
usethis::use_rmarkdown_template(
+  template_name = "Template To Make an R Package With a Dataset",
+  template_dir = "make-an-r-package-with-data",
+  template_description = "Template for an Rmd file for writing an R package with a dataset using literate programming.",
+  template_create_dir = FALSE
+)
+fs::file_copy(
+  path = file.path(
+    "..", "source-files", "make-an-r-package-with-data", "skeleton.Rmd"
+    ), 
+  new_path = file.path(
+    "inst", "rmarkdown", "templates", "make-an-r-package-with-data", "skeleton"
+    ), 
+  overwrite = TRUE
+)
+
## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-data/skeleton/'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-data/template.yaml'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-data/skeleton/skeleton.Rmd'
+

The third template shows how to create a package that uses Rcpp:

+
usethis::use_rmarkdown_template(
+  template_name = "Template To Make an R Package With Rcpp",
+  template_dir = "make-an-r-package-with-rcpp",
+  template_description = "Template for an Rmd file for writing an R package that makes use of Rcpp while using literate programming.",
+  template_create_dir = FALSE
+)
+fs::file_copy(
+  path = file.path(
+    "..", "source-files", "make-an-r-package-with-rcpp", "skeleton.Rmd"
+    ),
+  new_path = file.path(
+    "inst", "rmarkdown", "templates", "make-an-r-package-with-rcpp", "skeleton"
+    ),
+  overwrite = TRUE
+)
+
## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/skeleton/'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/template.yaml'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/skeleton/skeleton.Rmd'
+

The fourth template shows how to create a package with “extras” such as a README, a vignette, and a pkgdown site:

+
usethis::use_rmarkdown_template(
+  template_name = "Template To Make an R Package With a README, Vignette, and Pkgdown Site",
+  template_dir = "make-an-r-package-with-extras",
+  template_description = "Template for an Rmd file for writing an R package that has a README, vignette, and pkgdown site while using literate programming.",
+  template_create_dir = FALSE
+)
+fs::file_copy(
+  path = file.path(
+    "..", "source-files", "make-an-r-package-with-extras", "skeleton.Rmd"
+    ),
+  new_path = file.path(
+    "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton"
+    ),
+  overwrite = TRUE
+)
+fs::dir_copy(
+  path = file.path(
+    "..", "source-files", "make-an-r-package-with-extras", "source-files"
+    ),
+  new_path = file.path(
+    "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton",
+    "source-files"
+    ),
+  overwrite = TRUE
+)
+
## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-extras/skeleton/'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-extras/template.yaml'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-extras/skeleton/skeleton.Rmd'
+

The fifth template shows how to create a package from a bookdown site, i.e. instead of having just a single create-pkg.Rmd, we can have a series of .Rmd files that together create a bookdown:

+
usethis::use_rmarkdown_template(
+  template_name = "Template To Make an R Package From a Bookdown",
+  template_dir = "make-an-r-package-from-bookdown",
+  template_description = "Template for a bookdown that defines an R package using literate programming.",
+  template_create_dir = TRUE
+)
+fs::dir_copy(
+  path = file.path("..", "source-files", "make-an-r-package-from-bookdown"),
+  new_path = file.path(
+    "inst", "rmarkdown", "templates", "make-an-r-package-from-bookdown", "skeleton"
+    ),
+  overwrite = TRUE
+)
+
## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/skeleton/'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/template.yaml'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/skeleton/skeleton.Rmd'
+

The sixth template shows how to create a package that uses RcppArmadillo:

+
usethis::use_rmarkdown_template(
+  template_name = "Template To Make an R Package With RcppArmadillo",
+  template_dir = "make-an-r-package-with-armadillo",
+  template_description = "Template for an Rmd file for writing an R package that makes use of RcppArmadillo while using literate programming.",
+  template_create_dir = FALSE
+)
+fs::file_copy(
+  path = file.path(
+    "..", "source-files", "make-an-r-package-with-armadillo", "skeleton.Rmd"
+    ),
+  new_path = file.path(
+    "inst", "rmarkdown", "templates", "make-an-r-package-with-armadillo", "skeleton"
+    ),
+  overwrite = TRUE
+)
+
## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/skeleton/'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/template.yaml'
+## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/skeleton/skeleton.Rmd'
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/index.html b/create-litr/_book/index.html new file mode 100644 index 0000000..d1d45ae --- /dev/null +++ b/create-litr/_book/index.html @@ -0,0 +1,298 @@ + + + + + + + Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+ +
+

1 Preamble

+

This document uses literate programming to define the litr R package. This means that all source code for litr is contained and presented in this document alongside explanation. The litr R package gets created as an output when one runs a piece of code that takes this document as input. To modify the litr package, one should modify the code in this document (and the explanation, as needed) and then regenerate the package. But what piece of code do we use to create an R package from this document?

+

Well… it turns out that this is precisely what the litr package is for. But how can we use litr to create litr? We actually use version \(n-1\) of litr to create version \(n\). When an R markdown file is rendered with litr, an R package is created in addition to the usual html/pdf/etc document. To learn more about using litr, please visit the litr website. This document is not intended for people who wish to use litr. Rather, it is intended for people who want to know how it works and (possibly) modify it.

+

This document used litr version 0.9.0 to define the current version of litr. In particular, it was generated by running the following command in an R console from the working directory containing index.Rmd:

+
litr::render("index.Rmd")
+fs::dir_copy("_book", "../docs/create", overwrite = TRUE)
+fs::dir_delete("_book")
+

The second and third lines move the generated bookdown to the docs/ directory so that it will render nicely on github by visiting https://jacobbien.github.io/litr-project/create/.

+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections-hash.css b/create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections-hash.css new file mode 100644 index 0000000..b563ec9 --- /dev/null +++ b/create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections-hash.css @@ -0,0 +1,2 @@ +/* Styles for section anchors */ +a.anchor-section::before {content: '#';font-size: 80%;} diff --git a/create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections.css b/create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections.css new file mode 100644 index 0000000..041905f --- /dev/null +++ b/create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections.css @@ -0,0 +1,4 @@ +/* Styles for section anchors */ +a.anchor-section {margin-left: 10px; visibility: hidden; color: inherit;} +.hasAnchor:hover a.anchor-section {visibility: visible;} +ul > li > .anchor-section {display: none;} diff --git a/create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections.js b/create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections.js new file mode 100644 index 0000000..fee005d --- /dev/null +++ b/create-litr/_book/libs/anchor-sections-1.1.0/anchor-sections.js @@ -0,0 +1,11 @@ +document.addEventListener('DOMContentLoaded', function () { + // If section divs is used, we need to put the anchor in the child header + const headers = document.querySelectorAll("div.hasAnchor.section[class*='level'] > :first-child") + + headers.forEach(function (x) { + // Add to the header node + if (!x.classList.contains('hasAnchor')) x.classList.add('hasAnchor') + // Remove from the section or div created by Pandoc + x.parentElement.classList.remove('hasAnchor') + }) +}) diff --git a/create-litr/_book/libs/gitbook-2.6.7/css/fontawesome/fontawesome-webfont.ttf b/create-litr/_book/libs/gitbook-2.6.7/css/fontawesome/fontawesome-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..35acda2fa1196aad98c2adf4378a7611dd713aa3 GIT binary patch literal 165548 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 z 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 d0m tbody > tr > td { + vertical-align: top; +} +.book .book-body .page-wrapper .page-inner section.normal table tr.header { + border-top-width: 2px; +} +.book .book-body .page-wrapper .page-inner section.normal table tr:last-child td { + border-bottom-width: 2px; +} +.book .book-body .page-wrapper .page-inner section.normal table td, .book .book-body .page-wrapper .page-inner section.normal table th { + border-left: none; + border-right: none; +} +.book .book-body .page-wrapper .page-inner section.normal table.kable_wrapper > tbody > tr, .book .book-body .page-wrapper .page-inner section.normal table.kable_wrapper > tbody > tr > td { + border-top: none; +} +.book .book-body .page-wrapper .page-inner section.normal table.kable_wrapper > tbody > tr:last-child > td { + border-bottom: none; +} + +div.theorem, div.lemma, div.corollary, div.proposition, div.conjecture { + font-style: italic; +} +span.theorem, span.lemma, span.corollary, span.proposition, span.conjecture { + font-style: normal; +} +div.proof>*:last-child:after { + content: "\25a2"; + float: right; +} +.header-section-number { + padding-right: .5em; +} +#header .multi-author { + margin: 0.5em 0 -0.5em 0; +} +#header .date { + margin-top: 1.5em; +} diff --git a/create-litr/_book/libs/gitbook-2.6.7/css/plugin-clipboard.css b/create-litr/_book/libs/gitbook-2.6.7/css/plugin-clipboard.css new file mode 100644 index 0000000..6844a70 --- /dev/null +++ b/create-litr/_book/libs/gitbook-2.6.7/css/plugin-clipboard.css @@ -0,0 +1,18 @@ +div.sourceCode { + position: relative; +} + +.copy-to-clipboard-button { + position: absolute; + right: 0; + top: 0; + visibility: hidden; +} + +.copy-to-clipboard-button:focus { + outline: 0; +} + +div.sourceCode:hover > .copy-to-clipboard-button { + visibility: visible; +} diff --git a/create-litr/_book/libs/gitbook-2.6.7/css/plugin-fontsettings.css b/create-litr/_book/libs/gitbook-2.6.7/css/plugin-fontsettings.css new file mode 100644 index 0000000..3fa6f35 --- /dev/null +++ b/create-litr/_book/libs/gitbook-2.6.7/css/plugin-fontsettings.css @@ -0,0 +1,303 @@ +/* + * Theme 1 + */ +.color-theme-1 .dropdown-menu { + background-color: #111111; + border-color: #7e888b; +} +.color-theme-1 .dropdown-menu .dropdown-caret .caret-inner { + border-bottom: 9px solid #111111; +} +.color-theme-1 .dropdown-menu .buttons { + border-color: #7e888b; +} +.color-theme-1 .dropdown-menu .button { + color: #afa790; +} +.color-theme-1 .dropdown-menu .button:hover { + color: #73553c; +} +/* + * Theme 2 + */ +.color-theme-2 .dropdown-menu { + background-color: #2d3143; + border-color: #272a3a; +} +.color-theme-2 .dropdown-menu .dropdown-caret .caret-inner { + border-bottom: 9px solid #2d3143; +} +.color-theme-2 .dropdown-menu .buttons { + border-color: #272a3a; +} +.color-theme-2 .dropdown-menu .button { + color: #62677f; +} +.color-theme-2 .dropdown-menu .button:hover { + color: #f4f4f5; +} +.book .book-header .font-settings .font-enlarge { + line-height: 30px; + font-size: 1.4em; +} +.book .book-header .font-settings .font-reduce { + line-height: 30px; + font-size: 1em; +} + +/* sidebar transition background */ +div.book.color-theme-1 { + background: #f3eacb; +} +.book.color-theme-1 .book-body { + color: #704214; + background: #f3eacb; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section { + background: #f3eacb; +} + +/* sidebar transition background */ +div.book.color-theme-2 { + background: #1c1f2b; +} + +.book.color-theme-2 .book-body { + color: #bdcadb; + background: #1c1f2b; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section { + background: #1c1f2b; +} +.book.font-size-0 .book-body .page-inner section { + font-size: 1.2rem; +} +.book.font-size-1 .book-body .page-inner section { + font-size: 1.4rem; +} +.book.font-size-2 .book-body .page-inner section { + font-size: 1.6rem; +} +.book.font-size-3 .book-body .page-inner section { + font-size: 2.2rem; +} +.book.font-size-4 .book-body .page-inner section { + font-size: 4rem; +} +.book.font-family-0 { + font-family: Georgia, serif; +} +.book.font-family-1 { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal { + color: #704214; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal a { + color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h3, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h4, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h5, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 { + color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2 { + border-color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 { + color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal hr { + background-color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal blockquote { + border-color: #c4b29f; + opacity: 0.9; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code { + background: #fdf6e3; + color: #657b83; + border-color: #f8df9c; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal .highlight { + background-color: inherit; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table th, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table td { + border-color: #f5d06c; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr { + color: inherit; + background-color: #fdf6e3; + border-color: #444444; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) { + background-color: #fbeecb; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal { + color: #bdcadb; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal a { + color: #3eb1d0; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h3, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h4, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h5, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 { + color: #fffffa; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2 { + border-color: #373b4e; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 { + color: #373b4e; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal hr { + background-color: #373b4e; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal blockquote { + border-color: #373b4e; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code { + color: #9dbed8; + background: #2d3143; + border-color: #2d3143; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal .highlight { + background-color: #282a39; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table th, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table td { + border-color: #3b3f54; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr { + color: #b6c2d2; + background-color: #2d3143; + border-color: #3b3f54; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) { + background-color: #35394b; +} +.book.color-theme-1 .book-header { + color: #afa790; + background: transparent; +} +.book.color-theme-1 .book-header .btn { + color: #afa790; +} +.book.color-theme-1 .book-header .btn:hover { + color: #73553c; + background: none; +} +.book.color-theme-1 .book-header h1 { + color: #704214; +} +.book.color-theme-2 .book-header { + color: #7e888b; + background: transparent; +} +.book.color-theme-2 .book-header .btn { + color: #3b3f54; +} +.book.color-theme-2 .book-header .btn:hover { + color: #fffff5; + background: none; +} +.book.color-theme-2 .book-header h1 { + color: #bdcadb; +} +.book.color-theme-1 .book-body .navigation { + color: #afa790; +} +.book.color-theme-1 .book-body .navigation:hover { + color: #73553c; +} +.book.color-theme-2 .book-body .navigation { + color: #383f52; +} +.book.color-theme-2 .book-body .navigation:hover { + color: #fffff5; +} +/* + * Theme 1 + */ +.book.color-theme-1 .book-summary { + color: #afa790; + background: #111111; + border-right: 1px solid rgba(0, 0, 0, 0.07); +} +.book.color-theme-1 .book-summary .book-search { + background: transparent; +} +.book.color-theme-1 .book-summary .book-search input, +.book.color-theme-1 .book-summary .book-search input:focus { + border: 1px solid transparent; +} +.book.color-theme-1 .book-summary ul.summary li.divider { + background: #7e888b; + box-shadow: none; +} +.book.color-theme-1 .book-summary ul.summary li i.fa-check { + color: #33cc33; +} +.book.color-theme-1 .book-summary ul.summary li.done > a { + color: #877f6a; +} +.book.color-theme-1 .book-summary ul.summary li a, +.book.color-theme-1 .book-summary ul.summary li span { + color: #877f6a; + background: transparent; + font-weight: normal; +} +.book.color-theme-1 .book-summary ul.summary li.active > a, +.book.color-theme-1 .book-summary ul.summary li a:hover { + color: #704214; + background: transparent; + font-weight: normal; +} +/* + * Theme 2 + */ +.book.color-theme-2 .book-summary { + color: #bcc1d2; + background: #2d3143; + border-right: none; +} +.book.color-theme-2 .book-summary .book-search { + background: transparent; +} +.book.color-theme-2 .book-summary .book-search input, +.book.color-theme-2 .book-summary .book-search input:focus { + border: 1px solid transparent; +} +.book.color-theme-2 .book-summary ul.summary li.divider { + background: #272a3a; + box-shadow: none; +} +.book.color-theme-2 .book-summary ul.summary li i.fa-check { + color: #33cc33; +} +.book.color-theme-2 .book-summary ul.summary li.done > a { + color: #62687f; +} +.book.color-theme-2 .book-summary ul.summary li a, +.book.color-theme-2 .book-summary ul.summary li span { + color: #c1c6d7; + background: transparent; + font-weight: 600; +} +.book.color-theme-2 .book-summary ul.summary li.active > a, +.book.color-theme-2 .book-summary ul.summary li a:hover { + color: #f4f4f5; + background: #252737; + font-weight: 600; +} diff --git a/create-litr/_book/libs/gitbook-2.6.7/css/plugin-highlight.css b/create-litr/_book/libs/gitbook-2.6.7/css/plugin-highlight.css new file mode 100644 index 0000000..2aabd3d --- /dev/null +++ b/create-litr/_book/libs/gitbook-2.6.7/css/plugin-highlight.css @@ -0,0 +1,426 @@ +.book .book-body .page-wrapper .page-inner section.normal pre, +.book .book-body .page-wrapper .page-inner section.normal code { + /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + /* Tomorrow Comment */ + /* Tomorrow Red */ + /* Tomorrow Orange */ + /* Tomorrow Yellow */ + /* Tomorrow Green */ + /* Tomorrow Aqua */ + /* Tomorrow Blue */ + /* Tomorrow Purple */ +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-comment, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-comment, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-title { + color: #8e908c; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-variable, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-variable, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-attribute, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-attribute, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-tag, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-tag, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-regexp, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-regexp, +.book .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-constant, +.book .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-constant, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-tag .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .xml .hljs-tag .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-pi, +.book .book-body .page-wrapper .page-inner section.normal code .xml .hljs-pi, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-doctype, +.book .book-body .page-wrapper .page-inner section.normal code .xml .hljs-doctype, +.book .book-body .page-wrapper .page-inner section.normal pre .html .hljs-doctype, +.book .book-body .page-wrapper .page-inner section.normal code .html .hljs-doctype, +.book .book-body .page-wrapper .page-inner section.normal pre .css .hljs-id, +.book .book-body .page-wrapper .page-inner section.normal code .css .hljs-id, +.book .book-body .page-wrapper .page-inner section.normal pre .css .hljs-class, +.book .book-body .page-wrapper .page-inner section.normal code .css .hljs-class, +.book .book-body .page-wrapper .page-inner section.normal pre .css .hljs-pseudo, +.book .book-body .page-wrapper .page-inner section.normal code .css .hljs-pseudo { + color: #c82829; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-number, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-number, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-preprocessor, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-preprocessor, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-pragma, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-pragma, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-built_in, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-built_in, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-literal, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-literal, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-params, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-params, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-constant, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-constant { + color: #f5871f; +} +.book .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-class .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-class .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal pre .css .hljs-rules .hljs-attribute, +.book .book-body .page-wrapper .page-inner section.normal code .css .hljs-rules .hljs-attribute { + color: #eab700; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-string, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-string, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-value, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-value, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-inheritance, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-inheritance, +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-header, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-header, +.book .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-symbol, +.book .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-symbol, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-cdata, +.book .book-body .page-wrapper .page-inner section.normal code .xml .hljs-cdata { + color: #718c00; +} +.book .book-body .page-wrapper .page-inner section.normal pre .css .hljs-hexcolor, +.book .book-body .page-wrapper .page-inner section.normal code .css .hljs-hexcolor { + color: #3e999f; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-function, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-function, +.book .book-body .page-wrapper .page-inner section.normal pre .python .hljs-decorator, +.book .book-body .page-wrapper .page-inner section.normal code .python .hljs-decorator, +.book .book-body .page-wrapper .page-inner section.normal pre .python .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .python .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-function .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-function .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-title .hljs-keyword, +.book .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-title .hljs-keyword, +.book .book-body .page-wrapper .page-inner section.normal pre .perl .hljs-sub, +.book .book-body .page-wrapper .page-inner section.normal code .perl .hljs-sub, +.book .book-body .page-wrapper .page-inner section.normal pre .javascript .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .javascript .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal pre .coffeescript .hljs-title, +.book .book-body .page-wrapper .page-inner section.normal code .coffeescript .hljs-title { + color: #4271ae; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs-keyword, +.book .book-body .page-wrapper .page-inner section.normal code .hljs-keyword, +.book .book-body .page-wrapper .page-inner section.normal pre .javascript .hljs-function, +.book .book-body .page-wrapper .page-inner section.normal code .javascript .hljs-function { + color: #8959a8; +} +.book .book-body .page-wrapper .page-inner section.normal pre .hljs, +.book .book-body .page-wrapper .page-inner section.normal code .hljs { + display: block; + background: white; + color: #4d4d4c; + padding: 0.5em; +} +.book .book-body .page-wrapper .page-inner section.normal pre .coffeescript .javascript, +.book .book-body .page-wrapper .page-inner section.normal code .coffeescript .javascript, +.book .book-body .page-wrapper .page-inner section.normal pre .javascript .xml, +.book .book-body .page-wrapper .page-inner section.normal code .javascript .xml, +.book .book-body .page-wrapper .page-inner section.normal pre .tex .hljs-formula, +.book .book-body .page-wrapper .page-inner section.normal code .tex .hljs-formula, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .javascript, +.book .book-body .page-wrapper .page-inner section.normal code .xml .javascript, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .vbscript, +.book .book-body .page-wrapper .page-inner section.normal code .xml .vbscript, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .css, +.book .book-body .page-wrapper .page-inner section.normal code .xml .css, +.book .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-cdata, +.book .book-body .page-wrapper .page-inner section.normal code .xml .hljs-cdata { + opacity: 0.5; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code { + /* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull + +*/ + /* Solarized Green */ + /* Solarized Cyan */ + /* Solarized Blue */ + /* Solarized Yellow */ + /* Solarized Orange */ + /* Solarized Red */ + /* Solarized Violet */ +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs { + display: block; + padding: 0.5em; + background: #fdf6e3; + color: #657b83; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-comment, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-comment, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-template_comment, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-template_comment, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .diff .hljs-header, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .diff .hljs-header, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-doctype, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-doctype, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-pi, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-pi, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .lisp .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .lisp .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-javadoc, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-javadoc { + color: #93a1a1; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-keyword, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-keyword, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-winutils, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-winutils, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .method, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .method, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-addition, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-addition, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-tag, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .css .hljs-tag, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-request, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-request, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-status, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-status, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .nginx .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .nginx .hljs-title { + color: #859900; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-number, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-number, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-command, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-command, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-tag .hljs-value, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-tag .hljs-value, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-rules .hljs-value, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-rules .hljs-value, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-phpdoc, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-phpdoc, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .tex .hljs-formula, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .tex .hljs-formula, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-regexp, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-regexp, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-hexcolor, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-hexcolor, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-link_url, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-link_url { + color: #2aa198; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-localvars, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-localvars, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-chunk, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-chunk, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-decorator, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-decorator, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-built_in, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-built_in, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-identifier, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-identifier, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .vhdl .hljs-literal, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .vhdl .hljs-literal, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-id, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-id, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-function, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .css .hljs-function { + color: #268bd2; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-attribute, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-attribute, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-variable, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-variable, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .lisp .hljs-body, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .lisp .hljs-body, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .smalltalk .hljs-number, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .smalltalk .hljs-number, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-constant, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-constant, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-class .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-class .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-parent, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-parent, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .haskell .hljs-type, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .haskell .hljs-type, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-link_reference, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-link_reference { + color: #b58900; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-preprocessor, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-preprocessor, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-preprocessor .hljs-keyword, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-preprocessor .hljs-keyword, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-pragma, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-pragma, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-shebang, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-shebang, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-symbol, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-symbol, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-symbol .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-symbol .hljs-string, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .diff .hljs-change, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .diff .hljs-change, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-special, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-special, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-attr_selector, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-attr_selector, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-subst, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-subst, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-cdata, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-cdata, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .clojure .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .clojure .hljs-title, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-pseudo, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .css .hljs-pseudo, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-header, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-header { + color: #cb4b16; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-deletion, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-deletion, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-important, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-important { + color: #dc322f; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .hljs-link_label, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .hljs-link_label { + color: #6c71c4; +} +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre .tex .hljs-formula, +.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code .tex .hljs-formula { + background: #eee8d5; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code { + /* Tomorrow Night Bright Theme */ + /* Original theme - https://github.com/chriskempson/tomorrow-theme */ + /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + /* Tomorrow Comment */ + /* Tomorrow Red */ + /* Tomorrow Orange */ + /* Tomorrow Yellow */ + /* Tomorrow Green */ + /* Tomorrow Aqua */ + /* Tomorrow Blue */ + /* Tomorrow Purple */ +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-comment, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-comment, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-title { + color: #969896; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-variable, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-variable, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-attribute, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-attribute, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-tag, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-tag, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-regexp, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-regexp, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-constant, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-constant, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-tag .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .hljs-tag .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-pi, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .hljs-pi, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-doctype, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .hljs-doctype, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .html .hljs-doctype, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .html .hljs-doctype, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-id, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .css .hljs-id, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-class, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .css .hljs-class, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-pseudo, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .css .hljs-pseudo { + color: #d54e53; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-number, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-number, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-preprocessor, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-preprocessor, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-pragma, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-pragma, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-built_in, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-built_in, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-literal, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-literal, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-params, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-params, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-constant, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-constant { + color: #e78c45; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-class .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-class .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-rules .hljs-attribute, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .css .hljs-rules .hljs-attribute { + color: #e7c547; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-string, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-string, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-value, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-value, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-inheritance, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-inheritance, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-header, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-header, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-symbol, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-symbol, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-cdata, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .hljs-cdata { + color: #b9ca4a; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .css .hljs-hexcolor, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .css .hljs-hexcolor { + color: #70c0b1; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-function, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-function, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .python .hljs-decorator, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .python .hljs-decorator, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .python .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .python .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-function .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-function .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .ruby .hljs-title .hljs-keyword, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .ruby .hljs-title .hljs-keyword, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .perl .hljs-sub, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .perl .hljs-sub, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .javascript .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .javascript .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .coffeescript .hljs-title, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .coffeescript .hljs-title { + color: #7aa6da; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs-keyword, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs-keyword, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .javascript .hljs-function, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .javascript .hljs-function { + color: #c397d8; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .hljs, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .hljs { + display: block; + background: black; + color: #eaeaea; + padding: 0.5em; +} +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .coffeescript .javascript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .coffeescript .javascript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .javascript .xml, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .javascript .xml, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .tex .hljs-formula, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .tex .hljs-formula, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .javascript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .javascript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .vbscript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .vbscript, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .css, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .css, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre .xml .hljs-cdata, +.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code .xml .hljs-cdata { + opacity: 0.5; +} diff --git a/create-litr/_book/libs/gitbook-2.6.7/css/plugin-search.css b/create-litr/_book/libs/gitbook-2.6.7/css/plugin-search.css new file mode 100644 index 0000000..c85e557 --- /dev/null +++ b/create-litr/_book/libs/gitbook-2.6.7/css/plugin-search.css @@ -0,0 +1,31 @@ +.book .book-summary .book-search { + padding: 6px; + background: transparent; + position: absolute; + top: -50px; + left: 0px; + right: 0px; + transition: top 0.5s ease; +} +.book .book-summary .book-search input, +.book .book-summary .book-search input:focus, +.book .book-summary .book-search input:hover { + width: 100%; + background: transparent; + border: 1px solid #ccc; + box-shadow: none; + outline: none; + line-height: 22px; + padding: 7px 4px; + color: inherit; + box-sizing: border-box; +} +.book.with-search .book-summary .book-search { + top: 0px; +} +.book.with-search .book-summary ul.summary { + top: 50px; +} +.with-search .summary li[data-level] a[href*=".html#"] { + display: none; +} diff --git a/create-litr/_book/libs/gitbook-2.6.7/css/plugin-table.css b/create-litr/_book/libs/gitbook-2.6.7/css/plugin-table.css new file mode 100644 index 0000000..7fba1b9 --- /dev/null +++ b/create-litr/_book/libs/gitbook-2.6.7/css/plugin-table.css @@ -0,0 +1 @@ +.book .book-body .page-wrapper .page-inner section.normal table{display:table;width:100%;border-collapse:collapse;border-spacing:0;overflow:auto}.book .book-body .page-wrapper .page-inner section.normal table td,.book .book-body .page-wrapper .page-inner section.normal table th{padding:6px 13px;border:1px solid #ddd}.book .book-body .page-wrapper .page-inner section.normal table tr{background-color:#fff;border-top:1px solid #ccc}.book .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n){background-color:#f8f8f8}.book .book-body .page-wrapper .page-inner section.normal table th{font-weight:700} diff --git a/create-litr/_book/libs/gitbook-2.6.7/css/style.css b/create-litr/_book/libs/gitbook-2.6.7/css/style.css new file mode 100644 index 0000000..cba69b2 --- /dev/null +++ b/create-litr/_book/libs/gitbook-2.6.7/css/style.css @@ -0,0 +1,13 @@ +/*! normalize.css v2.1.0 | MIT License | git.io/normalize */img,legend{border:0}*{-webkit-font-smoothing:antialiased}sub,sup{position:relative}.book .book-body .page-wrapper .page-inner section.normal hr:after,.book-langs-index .inner .languages:after,.buttons:after,.dropdown-menu .buttons:after{clear:both}body,html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}.hidden,[hidden]{display:none}audio:not([controls]){display:none;height:0}html{font-family:sans-serif}body,figure{margin:0}a:focus{outline:dotted thin}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button{margin-right:10px;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}/*! + * Preboot v2 + * + * Open sourced under MIT license by @mdo. + * Some variables and mixins from Bootstrap (Apache 2 license). + */.link-inherit,.link-inherit:focus,.link-inherit:hover{color:inherit}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('./fontawesome/fontawesome-webfont.ttf?v=4.7.0') format('truetype');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} +.book .book-header,.book .book-summary{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.book-langs-index{width:100%;height:100%;padding:40px 0;margin:0;overflow:auto}@media (max-width:600px){.book-langs-index{padding:0}}.book-langs-index .inner{max-width:600px;width:100%;margin:0 auto;padding:30px;background:#fff;border-radius:3px}.book-langs-index .inner h3{margin:0}.book-langs-index .inner .languages{list-style:none;padding:20px 30px;margin-top:20px;border-top:1px solid #eee}.book-langs-index .inner .languages:after,.book-langs-index .inner .languages:before{content:" ";display:table;line-height:0}.book-langs-index .inner .languages li{width:50%;float:left;padding:10px 5px;font-size:16px}@media (max-width:600px){.book-langs-index .inner .languages li{width:100%;max-width:100%}}.book .book-header{overflow:visible;height:50px;padding:0 8px;z-index:2;font-size:.85em;color:#7e888b;background:0 0}.book .book-header .btn{display:block;height:50px;padding:0 15px;border-bottom:none;color:#ccc;text-transform:uppercase;line-height:50px;-webkit-box-shadow:none!important;box-shadow:none!important;position:relative;font-size:14px}.book .book-header .btn:hover{position:relative;text-decoration:none;color:#444;background:0 0}.book .book-header h1{margin:0;font-size:20px;font-weight:200;text-align:center;line-height:50px;opacity:0;padding-left:200px;padding-right:200px;-webkit-transition:opacity .2s ease;-moz-transition:opacity .2s ease;-o-transition:opacity .2s ease;transition:opacity .2s ease;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.book .book-header h1 a,.book .book-header h1 a:hover{color:inherit;text-decoration:none}@media (max-width:1000px){.book .book-header h1{display:none}}.book .book-header h1 i{display:none}.book .book-header:hover h1{opacity:1}.book.is-loading .book-header h1 i{display:inline-block}.book.is-loading .book-header h1 a{display:none}.dropdown{position:relative}.dropdown-menu{position:absolute;top:100%;left:0;z-index:100;display:none;float:left;min-width:160px;padding:0;margin:2px 0 0;list-style:none;font-size:14px;background-color:#fafafa;border:1px solid rgba(0,0,0,.07);border-radius:1px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.open{display:block}.dropdown-menu.dropdown-left{left:auto;right:4%}.dropdown-menu.dropdown-left .dropdown-caret{right:14px;left:auto}.dropdown-menu .dropdown-caret{position:absolute;top:-8px;left:14px;width:18px;height:10px;float:left;overflow:hidden}.dropdown-menu .dropdown-caret .caret-inner,.dropdown-menu .dropdown-caret .caret-outer{display:inline-block;top:0;border-left:9px solid transparent;border-right:9px solid transparent;position:absolute}.dropdown-menu .dropdown-caret .caret-outer{border-bottom:9px solid rgba(0,0,0,.1);height:auto;left:0;width:auto;margin-left:-1px}.dropdown-menu .dropdown-caret .caret-inner{margin-top:-1px;top:1px;border-bottom:9px solid #fafafa}.dropdown-menu .buttons{border-bottom:1px solid rgba(0,0,0,.07)}.dropdown-menu .buttons:after,.dropdown-menu .buttons:before{content:" ";display:table;line-height:0}.dropdown-menu .buttons:last-child{border-bottom:none}.dropdown-menu .buttons .button{border:0;background-color:transparent;color:#a6a6a6;width:100%;text-align:center;float:left;line-height:1.42857143;padding:8px 4px}.alert,.dropdown-menu .buttons .button:hover{color:#444}.dropdown-menu .buttons .button:focus,.dropdown-menu .buttons .button:hover{outline:0}.dropdown-menu .buttons .button.size-2{width:50%}.dropdown-menu .buttons .button.size-3{width:33%}.alert{padding:15px;margin-bottom:20px;background:#eee;border-bottom:5px solid #ddd}.alert-success{background:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-info{background:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-danger{background:#f2dede;border-color:#ebccd1;color:#a94442}.alert-warning{background:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.book .book-summary{position:absolute;top:0;left:-300px;bottom:0;z-index:1;width:300px;color:#364149;background:#fafafa;border-right:1px solid rgba(0,0,0,.07);-webkit-transition:left 250ms ease;-moz-transition:left 250ms ease;-o-transition:left 250ms ease;transition:left 250ms ease}.book .book-summary ul.summary{position:absolute;top:0;left:0;right:0;bottom:0;overflow-y:auto;list-style:none;margin:0;padding:0;-webkit-transition:top .5s ease;-moz-transition:top .5s ease;-o-transition:top .5s ease;transition:top .5s ease}.book .book-summary ul.summary li{list-style:none}.book .book-summary ul.summary li.divider{height:1px;margin:7px 0;overflow:hidden;background:rgba(0,0,0,.07)}.book .book-summary ul.summary li i.fa-check{display:none;position:absolute;right:9px;top:16px;font-size:9px;color:#3c3}.book .book-summary ul.summary li.done>a{color:#364149;font-weight:400}.book .book-summary ul.summary li.done>a i{display:inline}.book .book-summary ul.summary li a,.book .book-summary ul.summary li span{display:block;padding:10px 15px;border-bottom:none;color:#364149;background:0 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative}.book .book-summary ul.summary li span{cursor:not-allowed;opacity:.3;filter:alpha(opacity=30)}.book .book-summary ul.summary li a:hover,.book .book-summary ul.summary li.active>a{color:#008cff;background:0 0;text-decoration:none}.book .book-summary ul.summary li ul{padding-left:20px}@media (max-width:600px){.book .book-summary{width:calc(100% - 60px);bottom:0;left:-100%}}.book.with-summary .book-summary{left:0}.book.without-animation .book-summary{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;transition:none!important}.book{position:relative;width:100%;height:100%}.book .book-body,.book .book-body .body-inner{position:absolute;top:0;left:0;overflow-y:auto;bottom:0;right:0}.book .book-body{color:#000;background:#fff;-webkit-transition:left 250ms ease;-moz-transition:left 250ms ease;-o-transition:left 250ms ease;transition:left 250ms ease}.book .book-body .page-wrapper{position:relative;outline:0}.book .book-body .page-wrapper .page-inner{max-width:800px;margin:0 auto;padding:20px 0 40px}.book .book-body .page-wrapper .page-inner section{margin:0;padding:5px 15px;background:#fff;border-radius:2px;line-height:1.7;font-size:1.6rem}.book .book-body .page-wrapper .page-inner .btn-group .btn{border-radius:0;background:#eee;border:0}@media (max-width:1240px){.book .book-body{-webkit-transition:-webkit-transform 250ms ease;-moz-transition:-moz-transform 250ms ease;-o-transition:-o-transform 250ms ease;transition:transform 250ms ease;padding-bottom:20px}.book .book-body .body-inner{position:static;min-height:calc(100% - 50px)}}@media (min-width:600px){.book.with-summary .book-body{left:300px}}@media (max-width:600px){.book.with-summary{overflow:hidden}.book.with-summary .book-body{-webkit-transform:translate(calc(100% - 60px),0);-moz-transform:translate(calc(100% - 60px),0);-ms-transform:translate(calc(100% - 60px),0);-o-transform:translate(calc(100% - 60px),0);transform:translate(calc(100% - 60px),0)}}.book.without-animation .book-body{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;transition:none!important}.buttons:after,.buttons:before{content:" ";display:table;line-height:0}.button{border:0;background:#eee;color:#666;width:100%;text-align:center;float:left;line-height:1.42857143;padding:8px 4px}.button:hover{color:#444}.button:focus,.button:hover{outline:0}.button.size-2{width:50%}.button.size-3{width:33%}.book .book-body .page-wrapper .page-inner section{display:none}.book .book-body .page-wrapper .page-inner section.normal{display:block;word-wrap:break-word;overflow:hidden;color:#333;line-height:1.7;text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%}.book .book-body .page-wrapper .page-inner section.normal *{box-sizing:border-box;-webkit-box-sizing:border-box;}.book .book-body .page-wrapper .page-inner section.normal>:first-child{margin-top:0!important}.book .book-body .page-wrapper .page-inner section.normal>:last-child{margin-bottom:0!important}.book .book-body .page-wrapper .page-inner section.normal blockquote,.book .book-body .page-wrapper .page-inner section.normal code,.book .book-body .page-wrapper .page-inner section.normal figure,.book .book-body .page-wrapper .page-inner section.normal img,.book .book-body .page-wrapper .page-inner section.normal pre,.book .book-body .page-wrapper .page-inner section.normal table,.book .book-body .page-wrapper .page-inner section.normal tr{page-break-inside:avoid}.book .book-body .page-wrapper .page-inner section.normal h2,.book .book-body .page-wrapper .page-inner section.normal h3,.book .book-body .page-wrapper .page-inner section.normal h4,.book .book-body .page-wrapper .page-inner section.normal h5,.book .book-body .page-wrapper .page-inner section.normal p{orphans:3;widows:3}.book .book-body .page-wrapper .page-inner section.normal h1,.book .book-body .page-wrapper .page-inner section.normal h2,.book .book-body .page-wrapper .page-inner section.normal h3,.book .book-body .page-wrapper .page-inner section.normal h4,.book .book-body .page-wrapper .page-inner section.normal h5{page-break-after:avoid}.book .book-body .page-wrapper .page-inner section.normal b,.book .book-body .page-wrapper .page-inner section.normal strong{font-weight:700}.book .book-body .page-wrapper .page-inner section.normal em{font-style:italic}.book .book-body .page-wrapper .page-inner section.normal blockquote,.book .book-body .page-wrapper .page-inner section.normal dl,.book .book-body .page-wrapper .page-inner section.normal ol,.book .book-body .page-wrapper .page-inner section.normal p,.book .book-body .page-wrapper .page-inner section.normal table,.book .book-body .page-wrapper .page-inner section.normal ul{margin-top:0;margin-bottom:.85em}.book .book-body .page-wrapper .page-inner section.normal a{color:#4183c4;text-decoration:none;background:0 0}.book .book-body .page-wrapper .page-inner section.normal a:active,.book .book-body .page-wrapper .page-inner section.normal a:focus,.book .book-body .page-wrapper .page-inner section.normal a:hover{outline:0;text-decoration:underline}.book .book-body .page-wrapper .page-inner section.normal img{border:0;max-width:100%}.book .book-body .page-wrapper .page-inner section.normal hr{height:4px;padding:0;margin:1.7em 0;overflow:hidden;background-color:#e7e7e7;border:none}.book .book-body .page-wrapper .page-inner section.normal hr:after,.book .book-body .page-wrapper .page-inner section.normal hr:before{display:table;content:" "}.book .book-body .page-wrapper .page-inner section.normal h1,.book .book-body .page-wrapper .page-inner section.normal h2,.book .book-body .page-wrapper .page-inner section.normal h3,.book .book-body .page-wrapper .page-inner section.normal h4,.book .book-body .page-wrapper .page-inner section.normal h5,.book .book-body .page-wrapper .page-inner section.normal h6{margin-top:1.275em;margin-bottom:.85em;}.book .book-body .page-wrapper .page-inner section.normal h1{font-size:2em}.book .book-body .page-wrapper .page-inner section.normal h2{font-size:1.75em}.book .book-body .page-wrapper .page-inner section.normal h3{font-size:1.5em}.book .book-body .page-wrapper .page-inner section.normal h4{font-size:1.25em}.book .book-body .page-wrapper .page-inner section.normal h5{font-size:1em}.book .book-body .page-wrapper .page-inner section.normal h6{font-size:1em;color:#777}.book .book-body .page-wrapper .page-inner section.normal code,.book .book-body .page-wrapper .page-inner section.normal pre{font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;direction:ltr;border:none;color:inherit}.book .book-body .page-wrapper .page-inner section.normal pre{overflow:auto;word-wrap:normal;margin:0 0 1.275em;padding:.85em 1em;background:#f7f7f7}.book .book-body .page-wrapper .page-inner section.normal pre>code{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;font-size:.85em;white-space:pre;background:0 0}.book .book-body .page-wrapper .page-inner section.normal pre>code:after,.book .book-body .page-wrapper .page-inner section.normal pre>code:before{content:normal}.book .book-body .page-wrapper .page-inner section.normal code{padding:.2em;margin:0;font-size:.85em;background-color:#f7f7f7}.book .book-body .page-wrapper .page-inner section.normal code:after,.book .book-body .page-wrapper .page-inner section.normal code:before{letter-spacing:-.2em;content:"\00a0"}.book .book-body .page-wrapper .page-inner section.normal ol,.book .book-body .page-wrapper .page-inner section.normal ul{padding:0 0 0 2em;margin:0 0 .85em}.book .book-body .page-wrapper .page-inner section.normal ol ol,.book .book-body .page-wrapper .page-inner section.normal ol ul,.book .book-body .page-wrapper .page-inner section.normal ul ol,.book .book-body .page-wrapper .page-inner section.normal ul ul{margin-top:0;margin-bottom:0}.book .book-body .page-wrapper .page-inner section.normal ol ol{list-style-type:lower-roman}.book .book-body .page-wrapper .page-inner section.normal blockquote{margin:0 0 .85em;padding:0 15px;opacity:0.75;border-left:4px solid #dcdcdc}.book .book-body .page-wrapper .page-inner section.normal blockquote:first-child{margin-top:0}.book .book-body .page-wrapper .page-inner section.normal blockquote:last-child{margin-bottom:0}.book .book-body .page-wrapper .page-inner section.normal dl{padding:0}.book .book-body .page-wrapper .page-inner section.normal dl dt{padding:0;margin-top:.85em;font-style:italic;font-weight:700}.book .book-body .page-wrapper .page-inner section.normal dl dd{padding:0 .85em;margin-bottom:.85em}.book .book-body .page-wrapper .page-inner section.normal dd{margin-left:0}.book .book-body .page-wrapper .page-inner section.normal .glossary-term{cursor:help;text-decoration:underline}.book .book-body .navigation{position:absolute;top:50px;bottom:0;margin:0;max-width:150px;min-width:90px;display:flex;justify-content:center;align-content:center;flex-direction:column;font-size:40px;color:#ccc;text-align:center;-webkit-transition:all 350ms ease;-moz-transition:all 350ms ease;-o-transition:all 350ms ease;transition:all 350ms ease}.book .book-body .navigation:hover{text-decoration:none;color:#444}.book .book-body .navigation.navigation-next{right:0}.book .book-body .navigation.navigation-prev{left:0}@media (max-width:1240px){.book .book-body .navigation{position:static;top:auto;max-width:50%;width:50%;display:inline-block;float:left}.book .book-body .navigation.navigation-unique{max-width:100%;width:100%}}.book .book-body .page-wrapper .page-inner section.glossary{margin-bottom:40px}.book .book-body .page-wrapper .page-inner section.glossary h2 a,.book .book-body .page-wrapper .page-inner section.glossary h2 a:hover{color:inherit;text-decoration:none}.book .book-body .page-wrapper .page-inner section.glossary .glossary-index{list-style:none;margin:0;padding:0}.book .book-body .page-wrapper .page-inner section.glossary .glossary-index li{display:inline;margin:0 8px;white-space:nowrap}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-overflow-scrolling:auto;-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:none;-webkit-touch-callout:none}a{text-decoration:none}body,html{height:100%}html{font-size:62.5%}body{text-rendering:optimizeLegibility;font-smoothing:antialiased;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;letter-spacing:.2px;text-size-adjust:100%} +.book .book-summary ul.summary li a span {display:inline;padding:initial;overflow:visible;cursor:auto;opacity:1;} +/* show arrow before summary tag as in bootstrap */ +details > summary {display:list-item;cursor:pointer;} diff --git a/create-litr/_book/libs/gitbook-2.6.7/js/app.min.js b/create-litr/_book/libs/gitbook-2.6.7/js/app.min.js new file mode 100644 index 0000000..643f1f9 --- /dev/null +++ b/create-litr/_book/libs/gitbook-2.6.7/js/app.min.js @@ -0,0 +1 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o"'`]/g,reHasEscapedHtml=RegExp(reEscapedHtml.source),reHasUnescapedHtml=RegExp(reUnescapedHtml.source);var reEscape=/<%-([\s\S]+?)%>/g,reEvaluate=/<%([\s\S]+?)%>/g,reInterpolate=/<%=([\s\S]+?)%>/g;var reIsDeepProp=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,reIsPlainProp=/^\w*$/,rePropName=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g;var reRegExpChars=/^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,reHasRegExpChars=RegExp(reRegExpChars.source);var reComboMark=/[\u0300-\u036f\ufe20-\ufe23]/g;var reEscapeChar=/\\(\\)?/g;var reEsTemplate=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;var reFlags=/\w*$/;var reHasHexPrefix=/^0[xX]/;var reIsHostCtor=/^\[object .+?Constructor\]$/;var reIsUint=/^\d+$/;var reLatin1=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g;var reNoMatch=/($^)/;var reUnescapedString=/['\n\r\u2028\u2029\\]/g;var reWords=function(){var upper="[A-Z\\xc0-\\xd6\\xd8-\\xde]",lower="[a-z\\xdf-\\xf6\\xf8-\\xff]+";return RegExp(upper+"+(?="+upper+lower+")|"+upper+"?"+lower+"|"+upper+"+|[0-9]+","g")}();var contextProps=["Array","ArrayBuffer","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Math","Number","Object","RegExp","Set","String","_","clearTimeout","isFinite","parseFloat","parseInt","setTimeout","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap"];var templateCounter=-1;var typedArrayTags={};typedArrayTags[float32Tag]=typedArrayTags[float64Tag]=typedArrayTags[int8Tag]=typedArrayTags[int16Tag]=typedArrayTags[int32Tag]=typedArrayTags[uint8Tag]=typedArrayTags[uint8ClampedTag]=typedArrayTags[uint16Tag]=typedArrayTags[uint32Tag]=true;typedArrayTags[argsTag]=typedArrayTags[arrayTag]=typedArrayTags[arrayBufferTag]=typedArrayTags[boolTag]=typedArrayTags[dateTag]=typedArrayTags[errorTag]=typedArrayTags[funcTag]=typedArrayTags[mapTag]=typedArrayTags[numberTag]=typedArrayTags[objectTag]=typedArrayTags[regexpTag]=typedArrayTags[setTag]=typedArrayTags[stringTag]=typedArrayTags[weakMapTag]=false;var cloneableTags={};cloneableTags[argsTag]=cloneableTags[arrayTag]=cloneableTags[arrayBufferTag]=cloneableTags[boolTag]=cloneableTags[dateTag]=cloneableTags[float32Tag]=cloneableTags[float64Tag]=cloneableTags[int8Tag]=cloneableTags[int16Tag]=cloneableTags[int32Tag]=cloneableTags[numberTag]=cloneableTags[objectTag]=cloneableTags[regexpTag]=cloneableTags[stringTag]=cloneableTags[uint8Tag]=cloneableTags[uint8ClampedTag]=cloneableTags[uint16Tag]=cloneableTags[uint32Tag]=true;cloneableTags[errorTag]=cloneableTags[funcTag]=cloneableTags[mapTag]=cloneableTags[setTag]=cloneableTags[weakMapTag]=false;var deburredLetters={"À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","Ç":"C","ç":"c","Ð":"D","ð":"d","È":"E","É":"E","Ê":"E","Ë":"E","è":"e","é":"e","ê":"e","ë":"e","Ì":"I","Í":"I","Î":"I","Ï":"I","ì":"i","í":"i","î":"i","ï":"i","Ñ":"N","ñ":"n","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","Ù":"U","Ú":"U","Û":"U","Ü":"U","ù":"u","ú":"u","û":"u","ü":"u","Ý":"Y","ý":"y","ÿ":"y","Æ":"Ae","æ":"ae","Þ":"Th","þ":"th","ß":"ss"};var htmlEscapes={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"};var htmlUnescapes={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"};var objectTypes={function:true,object:true};var regexpEscapes={0:"x30",1:"x31",2:"x32",3:"x33",4:"x34",5:"x35",6:"x36",7:"x37",8:"x38",9:"x39",A:"x41",B:"x42",C:"x43",D:"x44",E:"x45",F:"x46",a:"x61",b:"x62",c:"x63",d:"x64",e:"x65",f:"x66",n:"x6e",r:"x72",t:"x74",u:"x75",v:"x76",x:"x78"};var stringEscapes={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"};var freeExports=objectTypes[typeof exports]&&exports&&!exports.nodeType&&exports;var freeModule=objectTypes[typeof module]&&module&&!module.nodeType&&module;var freeGlobal=freeExports&&freeModule&&typeof global=="object"&&global&&global.Object&&global;var freeSelf=objectTypes[typeof self]&&self&&self.Object&&self;var freeWindow=objectTypes[typeof window]&&window&&window.Object&&window;var moduleExports=freeModule&&freeModule.exports===freeExports&&freeExports;var root=freeGlobal||freeWindow!==(this&&this.window)&&freeWindow||freeSelf||this;function baseCompareAscending(value,other){if(value!==other){var valIsNull=value===null,valIsUndef=value===undefined,valIsReflexive=value===value;var othIsNull=other===null,othIsUndef=other===undefined,othIsReflexive=other===other;if(value>other&&!othIsNull||!valIsReflexive||valIsNull&&!othIsUndef&&othIsReflexive||valIsUndef&&othIsReflexive){return 1}if(value-1){}return index}function charsRightIndex(string,chars){var index=string.length;while(index--&&chars.indexOf(string.charAt(index))>-1){}return index}function compareAscending(object,other){return baseCompareAscending(object.criteria,other.criteria)||object.index-other.index}function compareMultiple(object,other,orders){var index=-1,objCriteria=object.criteria,othCriteria=other.criteria,length=objCriteria.length,ordersLength=orders.length;while(++index=ordersLength){return result}var order=orders[index];return result*(order==="asc"||order===true?1:-1)}}return object.index-other.index}function deburrLetter(letter){return deburredLetters[letter]}function escapeHtmlChar(chr){return htmlEscapes[chr]}function escapeRegExpChar(chr,leadingChar,whitespaceChar){if(leadingChar){chr=regexpEscapes[chr]}else if(whitespaceChar){chr=stringEscapes[chr]}return"\\"+chr}function escapeStringChar(chr){return"\\"+stringEscapes[chr]}function indexOfNaN(array,fromIndex,fromRight){var length=array.length,index=fromIndex+(fromRight?0:-1);while(fromRight?index--:++index=9&&charCode<=13)||charCode==32||charCode==160||charCode==5760||charCode==6158||charCode>=8192&&(charCode<=8202||charCode==8232||charCode==8233||charCode==8239||charCode==8287||charCode==12288||charCode==65279)}function replaceHolders(array,placeholder){var index=-1,length=array.length,resIndex=-1,result=[];while(++index>>1;var MAX_SAFE_INTEGER=9007199254740991;var metaMap=WeakMap&&new WeakMap;var realNames={};function lodash(value){if(isObjectLike(value)&&!isArray(value)&&!(value instanceof LazyWrapper)){if(value instanceof LodashWrapper){return value}if(hasOwnProperty.call(value,"__chain__")&&hasOwnProperty.call(value,"__wrapped__")){return wrapperClone(value)}}return new LodashWrapper(value)}function baseLodash(){}function LodashWrapper(value,chainAll,actions){this.__wrapped__=value;this.__actions__=actions||[];this.__chain__=!!chainAll}var support=lodash.support={};lodash.templateSettings={escape:reEscape,evaluate:reEvaluate,interpolate:reInterpolate,variable:"",imports:{_:lodash}};function LazyWrapper(value){this.__wrapped__=value;this.__actions__=[];this.__dir__=1;this.__filtered__=false;this.__iteratees__=[];this.__takeCount__=POSITIVE_INFINITY;this.__views__=[]}function lazyClone(){var result=new LazyWrapper(this.__wrapped__);result.__actions__=arrayCopy(this.__actions__);result.__dir__=this.__dir__;result.__filtered__=this.__filtered__;result.__iteratees__=arrayCopy(this.__iteratees__);result.__takeCount__=this.__takeCount__;result.__views__=arrayCopy(this.__views__);return result}function lazyReverse(){if(this.__filtered__){var result=new LazyWrapper(this);result.__dir__=-1;result.__filtered__=true}else{result=this.clone();result.__dir__*=-1}return result}function lazyValue(){var array=this.__wrapped__.value(),dir=this.__dir__,isArr=isArray(array),isRight=dir<0,arrLength=isArr?array.length:0,view=getView(0,arrLength,this.__views__),start=view.start,end=view.end,length=end-start,index=isRight?end:start-1,iteratees=this.__iteratees__,iterLength=iteratees.length,resIndex=0,takeCount=nativeMin(length,this.__takeCount__);if(!isArr||arrLength=LARGE_ARRAY_SIZE?createCache(values):null,valuesLength=values.length;if(cache){indexOf=cacheIndexOf;isCommon=false;values=cache}outer:while(++indexlength?0:length+start}end=end===undefined||end>length?length:+end||0;if(end<0){end+=length}length=start>end?0:end>>>0;start>>>=0;while(startlength?0:length+start}end=end===undefined||end>length?length:+end||0;if(end<0){end+=length}length=start>end?0:end-start>>>0;start>>>=0;var result=Array(length);while(++index=LARGE_ARRAY_SIZE,seen=isLarge?createCache():null,result=[];if(seen){indexOf=cacheIndexOf;isCommon=false}else{isLarge=false;seen=iteratee?[]:result}outer:while(++index>>1,computed=array[mid];if((retHighest?computed<=value:computed2?sources[length-2]:undefined,guard=length>2?sources[2]:undefined,thisArg=length>1?sources[length-1]:undefined;if(typeof customizer=="function"){customizer=bindCallback(customizer,thisArg,5);length-=2}else{customizer=typeof thisArg=="function"?thisArg:undefined;length-=customizer?1:0}if(guard&&isIterateeCall(sources[0],sources[1],guard)){customizer=length<3?undefined:customizer;length=1}while(++index-1?collection[index]:undefined}return baseFind(collection,predicate,eachFunc)}}function createFindIndex(fromRight){return function(array,predicate,thisArg){if(!(array&&array.length)){return-1}predicate=getCallback(predicate,thisArg,3);return baseFindIndex(array,predicate,fromRight)}}function createFindKey(objectFunc){return function(object,predicate,thisArg){predicate=getCallback(predicate,thisArg,3);return baseFind(object,predicate,objectFunc,true)}}function createFlow(fromRight){return function(){var wrapper,length=arguments.length,index=fromRight?length:-1,leftIndex=0,funcs=Array(length);while(fromRight?index--:++index=LARGE_ARRAY_SIZE){return wrapper.plant(value).value()}var index=0,result=length?funcs[index].apply(this,args):value;while(++index=length||!nativeIsFinite(length)){return""}var padLength=length-strLength;chars=chars==null?" ":chars+"";return repeat(chars,nativeCeil(padLength/chars.length)).slice(0,padLength)}function createPartialWrapper(func,bitmask,thisArg,partials){var isBind=bitmask&BIND_FLAG,Ctor=createCtorWrapper(func);function wrapper(){var argsIndex=-1,argsLength=arguments.length,leftIndex=-1,leftLength=partials.length,args=Array(leftLength+argsLength);while(++leftIndexarrLength)){return false}while(++index-1&&value%1==0&&value-1&&value%1==0&&value<=MAX_SAFE_INTEGER}function isStrictComparable(value){return value===value&&!isObject(value)}function mergeData(data,source){var bitmask=data[1],srcBitmask=source[1],newBitmask=bitmask|srcBitmask,isCommon=newBitmask0){if(++count>=HOT_COUNT){return key}}else{count=0}return baseSetData(key,value)}}();function shimKeys(object){var props=keysIn(object),propsLength=props.length,length=propsLength&&object.length;var allowIndexes=!!length&&isLength(length)&&(isArray(object)||isArguments(object));var index=-1,result=[];while(++index=120?createCache(othIndex&&value):null}var array=arrays[0],index=-1,length=array?array.length:0,seen=caches[0];outer:while(++index-1){splice.call(array,fromIndex,1)}}return array}var pullAt=restParam(function(array,indexes){indexes=baseFlatten(indexes);var result=baseAt(array,indexes);basePullAt(array,indexes.sort(baseCompareAscending));return result});function remove(array,predicate,thisArg){var result=[];if(!(array&&array.length)){return result}var index=-1,indexes=[],length=array.length;predicate=getCallback(predicate,thisArg,3);while(++index2?arrays[length-2]:undefined,thisArg=length>1?arrays[length-1]:undefined;if(length>2&&typeof iteratee=="function"){length-=2}else{iteratee=length>1&&typeof thisArg=="function"?(--length,thisArg):undefined;thisArg=undefined}arrays.length=length;return unzipWith(arrays,iteratee,thisArg)});function chain(value){var result=lodash(value);result.__chain__=true;return result}function tap(value,interceptor,thisArg){interceptor.call(thisArg,value);return value}function thru(value,interceptor,thisArg){return interceptor.call(thisArg,value)}function wrapperChain(){return chain(this)}function wrapperCommit(){return new LodashWrapper(this.value(),this.__chain__)}var wrapperConcat=restParam(function(values){values=baseFlatten(values);return this.thru(function(array){return arrayConcat(isArray(array)?array:[toObject(array)],values)})});function wrapperPlant(value){var result,parent=this;while(parent instanceof baseLodash){var clone=wrapperClone(parent);if(result){previous.__wrapped__=clone}else{result=clone}var previous=clone;parent=parent.__wrapped__}previous.__wrapped__=value;return result}function wrapperReverse(){var value=this.__wrapped__;var interceptor=function(value){return wrapped&&wrapped.__dir__<0?value:value.reverse()};if(value instanceof LazyWrapper){var wrapped=value;if(this.__actions__.length){wrapped=new LazyWrapper(this)}wrapped=wrapped.reverse();wrapped.__actions__.push({func:thru,args:[interceptor],thisArg:undefined});return new LodashWrapper(wrapped,this.__chain__)}return this.thru(interceptor)}function wrapperToString(){return this.value()+""}function wrapperValue(){return baseWrapperValue(this.__wrapped__,this.__actions__)}var at=restParam(function(collection,props){return baseAt(collection,baseFlatten(props))});var countBy=createAggregator(function(result,value,key){hasOwnProperty.call(result,key)?++result[key]:result[key]=1});function every(collection,predicate,thisArg){var func=isArray(collection)?arrayEvery:baseEvery;if(thisArg&&isIterateeCall(collection,predicate,thisArg)){predicate=undefined}if(typeof predicate!="function"||thisArg!==undefined){predicate=getCallback(predicate,thisArg,3)}return func(collection,predicate)}function filter(collection,predicate,thisArg){var func=isArray(collection)?arrayFilter:baseFilter;predicate=getCallback(predicate,thisArg,3);return func(collection,predicate)}var find=createFind(baseEach);var findLast=createFind(baseEachRight,true);function findWhere(collection,source){return find(collection,baseMatches(source))}var forEach=createForEach(arrayEach,baseEach);var forEachRight=createForEach(arrayEachRight,baseEachRight);var groupBy=createAggregator(function(result,value,key){if(hasOwnProperty.call(result,key)){result[key].push(value)}else{result[key]=[value]}});function includes(collection,target,fromIndex,guard){var length=collection?getLength(collection):0;if(!isLength(length)){collection=values(collection);length=collection.length}if(typeof fromIndex!="number"||guard&&isIterateeCall(target,fromIndex,guard)){fromIndex=0}else{fromIndex=fromIndex<0?nativeMax(length+fromIndex,0):fromIndex||0}return typeof collection=="string"||!isArray(collection)&&isString(collection)?fromIndex<=length&&collection.indexOf(target,fromIndex)>-1:!!length&&getIndexOf(collection,target,fromIndex)>-1}var indexBy=createAggregator(function(result,value,key){result[key]=value});var invoke=restParam(function(collection,path,args){var index=-1,isFunc=typeof path=="function",isProp=isKey(path),result=isArrayLike(collection)?Array(collection.length):[];baseEach(collection,function(value){var func=isFunc?path:isProp&&value!=null?value[path]:undefined;result[++index]=func?func.apply(value,args):invokePath(value,path,args)});return result});function map(collection,iteratee,thisArg){var func=isArray(collection)?arrayMap:baseMap;iteratee=getCallback(iteratee,thisArg,3);return func(collection,iteratee)}var partition=createAggregator(function(result,value,key){result[key?0:1].push(value)},function(){return[[],[]]});function pluck(collection,path){return map(collection,property(path))}var reduce=createReduce(arrayReduce,baseEach);var reduceRight=createReduce(arrayReduceRight,baseEachRight);function reject(collection,predicate,thisArg){var func=isArray(collection)?arrayFilter:baseFilter;predicate=getCallback(predicate,thisArg,3);return func(collection,function(value,index,collection){return!predicate(value,index,collection)})}function sample(collection,n,guard){if(guard?isIterateeCall(collection,n,guard):n==null){collection=toIterable(collection);var length=collection.length;return length>0?collection[baseRandom(0,length-1)]:undefined}var index=-1,result=toArray(collection),length=result.length,lastIndex=length-1;n=nativeMin(n<0?0:+n||0,length);while(++index0){result=func.apply(this,arguments)}if(n<=1){func=undefined}return result}}var bind=restParam(function(func,thisArg,partials){var bitmask=BIND_FLAG;if(partials.length){var holders=replaceHolders(partials,bind.placeholder);bitmask|=PARTIAL_FLAG}return createWrapper(func,bitmask,thisArg,partials,holders)});var bindAll=restParam(function(object,methodNames){methodNames=methodNames.length?baseFlatten(methodNames):functions(object);var index=-1,length=methodNames.length;while(++indexwait){complete(trailingCall,maxTimeoutId)}else{timeoutId=setTimeout(delayed,remaining)}}function maxDelayed(){complete(trailing,timeoutId)}function debounced(){args=arguments;stamp=now();thisArg=this;trailingCall=trailing&&(timeoutId||!leading);if(maxWait===false){var leadingCall=leading&&!timeoutId}else{if(!maxTimeoutId&&!leading){lastCalled=stamp}var remaining=maxWait-(stamp-lastCalled),isCalled=remaining<=0||remaining>maxWait;if(isCalled){if(maxTimeoutId){maxTimeoutId=clearTimeout(maxTimeoutId)}lastCalled=stamp;result=func.apply(thisArg,args)}else if(!maxTimeoutId){maxTimeoutId=setTimeout(maxDelayed,remaining)}}if(isCalled&&timeoutId){timeoutId=clearTimeout(timeoutId)}else if(!timeoutId&&wait!==maxWait){timeoutId=setTimeout(delayed,wait)}if(leadingCall){isCalled=true;result=func.apply(thisArg,args)}if(isCalled&&!timeoutId&&!maxTimeoutId){args=thisArg=undefined}return result}debounced.cancel=cancel;return debounced}var defer=restParam(function(func,args){return baseDelay(func,1,args)});var delay=restParam(function(func,wait,args){return baseDelay(func,wait,args)});var flow=createFlow();var flowRight=createFlow(true);function memoize(func,resolver){if(typeof func!="function"||resolver&&typeof resolver!="function"){throw new TypeError(FUNC_ERROR_TEXT)}var memoized=function(){var args=arguments,key=resolver?resolver.apply(this,args):args[0],cache=memoized.cache;if(cache.has(key)){return cache.get(key)}var result=func.apply(this,args);memoized.cache=cache.set(key,result);return result};memoized.cache=new memoize.Cache;return memoized}var modArgs=restParam(function(func,transforms){transforms=baseFlatten(transforms);if(typeof func!="function"||!arrayEvery(transforms,baseIsFunction)){throw new TypeError(FUNC_ERROR_TEXT)}var length=transforms.length;return restParam(function(args){var index=nativeMin(args.length,length);while(index--){args[index]=transforms[index](args[index])}return func.apply(this,args)})});function negate(predicate){if(typeof predicate!="function"){throw new TypeError(FUNC_ERROR_TEXT)}return function(){return!predicate.apply(this,arguments)}}function once(func){return before(2,func)}var partial=createPartial(PARTIAL_FLAG);var partialRight=createPartial(PARTIAL_RIGHT_FLAG);var rearg=restParam(function(func,indexes){return createWrapper(func,REARG_FLAG,undefined,undefined,undefined,baseFlatten(indexes))});function restParam(func,start){if(typeof func!="function"){throw new TypeError(FUNC_ERROR_TEXT)}start=nativeMax(start===undefined?func.length-1:+start||0,0);return function(){var args=arguments,index=-1,length=nativeMax(args.length-start,0),rest=Array(length);while(++indexother}function gte(value,other){return value>=other}function isArguments(value){return isObjectLike(value)&&isArrayLike(value)&&hasOwnProperty.call(value,"callee")&&!propertyIsEnumerable.call(value,"callee")}var isArray=nativeIsArray||function(value){return isObjectLike(value)&&isLength(value.length)&&objToString.call(value)==arrayTag};function isBoolean(value){return value===true||value===false||isObjectLike(value)&&objToString.call(value)==boolTag}function isDate(value){return isObjectLike(value)&&objToString.call(value)==dateTag}function isElement(value){return!!value&&value.nodeType===1&&isObjectLike(value)&&!isPlainObject(value)}function isEmpty(value){if(value==null){return true}if(isArrayLike(value)&&(isArray(value)||isString(value)||isArguments(value)||isObjectLike(value)&&isFunction(value.splice))){return!value.length}return!keys(value).length}function isEqual(value,other,customizer,thisArg){customizer=typeof customizer=="function"?bindCallback(customizer,thisArg,3):undefined;var result=customizer?customizer(value,other):undefined;return result===undefined?baseIsEqual(value,other,customizer):!!result}function isError(value){return isObjectLike(value)&&typeof value.message=="string"&&objToString.call(value)==errorTag}function isFinite(value){return typeof value=="number"&&nativeIsFinite(value)}function isFunction(value){return isObject(value)&&objToString.call(value)==funcTag}function isObject(value){var type=typeof value;return!!value&&(type=="object"||type=="function")}function isMatch(object,source,customizer,thisArg){customizer=typeof customizer=="function"?bindCallback(customizer,thisArg,3):undefined;return baseIsMatch(object,getMatchData(source),customizer)}function isNaN(value){return isNumber(value)&&value!=+value}function isNative(value){if(value==null){return false}if(isFunction(value)){return reIsNative.test(fnToString.call(value))}return isObjectLike(value)&&reIsHostCtor.test(value)}function isNull(value){return value===null}function isNumber(value){return typeof value=="number"||isObjectLike(value)&&objToString.call(value)==numberTag}function isPlainObject(value){var Ctor;if(!(isObjectLike(value)&&objToString.call(value)==objectTag&&!isArguments(value))||!hasOwnProperty.call(value,"constructor")&&(Ctor=value.constructor,typeof Ctor=="function"&&!(Ctor instanceof Ctor))){return false}var result;baseForIn(value,function(subValue,key){result=key});return result===undefined||hasOwnProperty.call(value,result)}function isRegExp(value){return isObject(value)&&objToString.call(value)==regexpTag}function isString(value){return typeof value=="string"||isObjectLike(value)&&objToString.call(value)==stringTag}function isTypedArray(value){return isObjectLike(value)&&isLength(value.length)&&!!typedArrayTags[objToString.call(value)]}function isUndefined(value){return value===undefined}function lt(value,other){return value0;while(++index=nativeMin(start,end)&&value=0&&string.indexOf(target,position)==position}function escape(string){string=baseToString(string);return string&&reHasUnescapedHtml.test(string)?string.replace(reUnescapedHtml,escapeHtmlChar):string}function escapeRegExp(string){string=baseToString(string);return string&&reHasRegExpChars.test(string)?string.replace(reRegExpChars,escapeRegExpChar):string||"(?:)"}var kebabCase=createCompounder(function(result,word,index){return result+(index?"-":"")+word.toLowerCase()});function pad(string,length,chars){string=baseToString(string);length=+length;var strLength=string.length;if(strLength>=length||!nativeIsFinite(length)){return string}var mid=(length-strLength)/2,leftLength=nativeFloor(mid),rightLength=nativeCeil(mid);chars=createPadding("",rightLength,chars);return chars.slice(0,leftLength)+string+chars}var padLeft=createPadDir();var padRight=createPadDir(true);function parseInt(string,radix,guard){if(guard?isIterateeCall(string,radix,guard):radix==null){radix=0}else if(radix){radix=+radix}string=trim(string);return nativeParseInt(string,radix||(reHasHexPrefix.test(string)?16:10))}function repeat(string,n){var result="";string=baseToString(string);n=+n;if(n<1||!string||!nativeIsFinite(n)){return result}do{if(n%2){result+=string}n=nativeFloor(n/2);string+=string}while(n);return result}var snakeCase=createCompounder(function(result,word,index){return result+(index?"_":"")+word.toLowerCase()});var startCase=createCompounder(function(result,word,index){return result+(index?" ":"")+(word.charAt(0).toUpperCase()+word.slice(1))});function startsWith(string,target,position){string=baseToString(string);position=position==null?0:nativeMin(position<0?0:+position||0,string.length);return string.lastIndexOf(target,position)==position}function template(string,options,otherOptions){var settings=lodash.templateSettings;if(otherOptions&&isIterateeCall(string,options,otherOptions)){options=otherOptions=undefined}string=baseToString(string);options=assignWith(baseAssign({},otherOptions||options),settings,assignOwnDefaults);var imports=assignWith(baseAssign({},options.imports),settings.imports,assignOwnDefaults),importsKeys=keys(imports),importsValues=baseValues(imports,importsKeys);var isEscaping,isEvaluating,index=0,interpolate=options.interpolate||reNoMatch,source="__p += '";var reDelimiters=RegExp((options.escape||reNoMatch).source+"|"+interpolate.source+"|"+(interpolate===reInterpolate?reEsTemplate:reNoMatch).source+"|"+(options.evaluate||reNoMatch).source+"|$","g");var sourceURL="//# sourceURL="+("sourceURL"in options?options.sourceURL:"lodash.templateSources["+ ++templateCounter+"]")+"\n";string.replace(reDelimiters,function(match,escapeValue,interpolateValue,esTemplateValue,evaluateValue,offset){interpolateValue||(interpolateValue=esTemplateValue);source+=string.slice(index,offset).replace(reUnescapedString,escapeStringChar);if(escapeValue){isEscaping=true;source+="' +\n__e("+escapeValue+") +\n'"}if(evaluateValue){isEvaluating=true;source+="';\n"+evaluateValue+";\n__p += '"}if(interpolateValue){source+="' +\n((__t = ("+interpolateValue+")) == null ? '' : __t) +\n'"}index=offset+match.length;return match});source+="';\n";var variable=options.variable;if(!variable){source="with (obj) {\n"+source+"\n}\n"}source=(isEvaluating?source.replace(reEmptyStringLeading,""):source).replace(reEmptyStringMiddle,"$1").replace(reEmptyStringTrailing,"$1;");source="function("+(variable||"obj")+") {\n"+(variable?"":"obj || (obj = {});\n")+"var __t, __p = ''"+(isEscaping?", __e = _.escape":"")+(isEvaluating?", __j = Array.prototype.join;\n"+"function print() { __p += __j.call(arguments, '') }\n":";\n")+source+"return __p\n}";var result=attempt(function(){return Function(importsKeys,sourceURL+"return "+source).apply(undefined,importsValues)});result.source=source;if(isError(result)){throw result}return result}function trim(string,chars,guard){var value=string;string=baseToString(string);if(!string){return string}if(guard?isIterateeCall(value,chars,guard):chars==null){return string.slice(trimmedLeftIndex(string),trimmedRightIndex(string)+1)}chars=chars+"";return string.slice(charsLeftIndex(string,chars),charsRightIndex(string,chars)+1)}function trimLeft(string,chars,guard){var value=string;string=baseToString(string);if(!string){return string}if(guard?isIterateeCall(value,chars,guard):chars==null){return string.slice(trimmedLeftIndex(string))}return string.slice(charsLeftIndex(string,chars+""))}function trimRight(string,chars,guard){var value=string;string=baseToString(string);if(!string){return string}if(guard?isIterateeCall(value,chars,guard):chars==null){return string.slice(0,trimmedRightIndex(string)+1)}return string.slice(0,charsRightIndex(string,chars+"")+1)}function trunc(string,options,guard){if(guard&&isIterateeCall(string,options,guard)){options=undefined}var length=DEFAULT_TRUNC_LENGTH,omission=DEFAULT_TRUNC_OMISSION;if(options!=null){if(isObject(options)){var separator="separator"in options?options.separator:separator;length="length"in options?+options.length||0:length;omission="omission"in options?baseToString(options.omission):omission}else{length=+options||0}}string=baseToString(string);if(length>=string.length){return string}var end=length-omission.length;if(end<1){return omission}var result=string.slice(0,end);if(separator==null){return result+omission}if(isRegExp(separator)){if(string.slice(end).search(separator)){var match,newEnd,substring=string.slice(0,end);if(!separator.global){separator=RegExp(separator.source,(reFlags.exec(separator)||"")+"g")}separator.lastIndex=0;while(match=separator.exec(substring)){newEnd=match.index}result=result.slice(0,newEnd==null?end:newEnd)}}else if(string.indexOf(separator,end)!=end){var index=result.lastIndexOf(separator);if(index>-1){result=result.slice(0,index)}}return result+omission}function unescape(string){string=baseToString(string);return string&&reHasEscapedHtml.test(string)?string.replace(reEscapedHtml,unescapeHtmlChar):string}function words(string,pattern,guard){if(guard&&isIterateeCall(string,pattern,guard)){pattern=undefined}string=baseToString(string);return string.match(pattern||reWords)||[]}var attempt=restParam(function(func,args){try{return func.apply(undefined,args)}catch(e){return isError(e)?e:new Error(e)}});function callback(func,thisArg,guard){if(guard&&isIterateeCall(func,thisArg,guard)){thisArg=undefined}return isObjectLike(func)?matches(func):baseCallback(func,thisArg)}function constant(value){return function(){return value}}function identity(value){return value}function matches(source){return baseMatches(baseClone(source,true))}function matchesProperty(path,srcValue){return baseMatchesProperty(path,baseClone(srcValue,true))}var method=restParam(function(path,args){return function(object){return invokePath(object,path,args)}});var methodOf=restParam(function(object,args){return function(path){return invokePath(object,path,args)}});function mixin(object,source,options){if(options==null){var isObj=isObject(source),props=isObj?keys(source):undefined,methodNames=props&&props.length?baseFunctions(source,props):undefined;if(!(methodNames?methodNames.length:isObj)){methodNames=false;options=source;source=object;object=this}}if(!methodNames){methodNames=baseFunctions(source,keys(source))}var chain=true,index=-1,isFunc=isFunction(object),length=methodNames.length;if(options===false){chain=false}else if(isObject(options)&&"chain"in options){chain=options.chain}while(++index0||end<0)){return new LazyWrapper(result)}if(start<0){result=result.takeRight(-start)}else if(start){result=result.drop(start)}if(end!==undefined){end=+end||0;result=end<0?result.dropRight(-end):result.take(end-start)}return result};LazyWrapper.prototype.takeRightWhile=function(predicate,thisArg){return this.reverse().takeWhile(predicate,thisArg).reverse()};LazyWrapper.prototype.toArray=function(){return this.take(POSITIVE_INFINITY)};baseForOwn(LazyWrapper.prototype,function(func,methodName){var checkIteratee=/^(?:filter|map|reject)|While$/.test(methodName),retUnwrapped=/^(?:first|last)$/.test(methodName),lodashFunc=lodash[retUnwrapped?"take"+(methodName=="last"?"Right":""):methodName];if(!lodashFunc){return}lodash.prototype[methodName]=function(){var args=retUnwrapped?[1]:arguments,chainAll=this.__chain__,value=this.__wrapped__,isHybrid=!!this.__actions__.length,isLazy=value instanceof LazyWrapper,iteratee=args[0],useLazy=isLazy||isArray(value);if(useLazy&&checkIteratee&&typeof iteratee=="function"&&iteratee.length!=1){isLazy=useLazy=false}var interceptor=function(value){return retUnwrapped&&chainAll?lodashFunc(value,1)[0]:lodashFunc.apply(undefined,arrayPush([value],args))};var action={func:thru,args:[interceptor],thisArg:undefined},onlyLazy=isLazy&&!isHybrid;if(retUnwrapped&&!chainAll){if(onlyLazy){value=value.clone();value.__actions__.push(action);return func.call(value)}return lodashFunc.call(undefined,this.value())[0]}if(!retUnwrapped&&useLazy){value=onlyLazy?value:new LazyWrapper(this);var result=func.apply(value,args);result.__actions__.push(action);return new LodashWrapper(result,chainAll)}return this.thru(interceptor)}});arrayEach(["join","pop","push","replace","shift","sort","splice","split","unshift"],function(methodName){var func=(/^(?:replace|split)$/.test(methodName)?stringProto:arrayProto)[methodName],chainName=/^(?:push|sort|unshift)$/.test(methodName)?"tap":"thru",retUnwrapped=/^(?:join|pop|replace|shift)$/.test(methodName);lodash.prototype[methodName]=function(){var args=arguments;if(retUnwrapped&&!this.__chain__){return func.apply(this.value(),args)}return this[chainName](function(value){return func.apply(value,args)})}});baseForOwn(LazyWrapper.prototype,function(func,methodName){var lodashFunc=lodash[methodName];if(lodashFunc){var key=lodashFunc.name,names=realNames[key]||(realNames[key]=[]);names.push({name:methodName,func:lodashFunc})}});realNames[createHybridWrapper(undefined,BIND_KEY_FLAG).name]=[{name:"wrapper",func:undefined}];LazyWrapper.prototype.clone=lazyClone;LazyWrapper.prototype.reverse=lazyReverse;LazyWrapper.prototype.value=lazyValue;lodash.prototype.chain=wrapperChain;lodash.prototype.commit=wrapperCommit;lodash.prototype.concat=wrapperConcat;lodash.prototype.plant=wrapperPlant;lodash.prototype.reverse=wrapperReverse;lodash.prototype.toString=wrapperToString;lodash.prototype.run=lodash.prototype.toJSON=lodash.prototype.valueOf=lodash.prototype.value=wrapperValue;lodash.prototype.collect=lodash.prototype.map;lodash.prototype.head=lodash.prototype.first;lodash.prototype.select=lodash.prototype.filter;lodash.prototype.tail=lodash.prototype.rest;return lodash}var _=runInContext();if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){root._=_;define(function(){return _})}else if(freeExports&&freeModule){if(moduleExports){(freeModule.exports=_)._=_}else{freeExports._=_}}else{root._=_}}).call(this)}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],3:[function(require,module,exports){(function(window,document,undefined){var _MAP={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"};var _KEYCODE_MAP={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"};var _SHIFT_MAP={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"};var _SPECIAL_ALIASES={option:"alt",command:"meta",return:"enter",escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"};var _REVERSE_MAP;for(var i=1;i<20;++i){_MAP[111+i]="f"+i}for(i=0;i<=9;++i){_MAP[i+96]=i}function _addEvent(object,type,callback){if(object.addEventListener){object.addEventListener(type,callback,false);return}object.attachEvent("on"+type,callback)}function _characterFromEvent(e){if(e.type=="keypress"){var character=String.fromCharCode(e.which);if(!e.shiftKey){character=character.toLowerCase()}return character}if(_MAP[e.which]){return _MAP[e.which]}if(_KEYCODE_MAP[e.which]){return _KEYCODE_MAP[e.which]}return String.fromCharCode(e.which).toLowerCase()}function _modifiersMatch(modifiers1,modifiers2){return modifiers1.sort().join(",")===modifiers2.sort().join(",")}function _eventModifiers(e){var modifiers=[];if(e.shiftKey){modifiers.push("shift")}if(e.altKey){modifiers.push("alt")}if(e.ctrlKey){modifiers.push("ctrl")}if(e.metaKey){modifiers.push("meta")}return modifiers}function _preventDefault(e){if(e.preventDefault){e.preventDefault();return}e.returnValue=false}function _stopPropagation(e){if(e.stopPropagation){e.stopPropagation();return}e.cancelBubble=true}function _isModifier(key){return key=="shift"||key=="ctrl"||key=="alt"||key=="meta"}function _getReverseMap(){if(!_REVERSE_MAP){_REVERSE_MAP={};for(var key in _MAP){if(key>95&&key<112){continue}if(_MAP.hasOwnProperty(key)){_REVERSE_MAP[_MAP[key]]=key}}}return _REVERSE_MAP}function _pickBestAction(key,modifiers,action){if(!action){action=_getReverseMap()[key]?"keydown":"keypress"}if(action=="keypress"&&modifiers.length){action="keydown"}return action}function _keysFromString(combination){if(combination==="+"){return["+"]}combination=combination.replace(/\+{2}/g,"+plus");return combination.split("+")}function _getKeyInfo(combination,action){var keys;var key;var i;var modifiers=[];keys=_keysFromString(combination);for(i=0;i1){_bindSequence(combination,sequence,callback,action);return}info=_getKeyInfo(combination,action);self._callbacks[info.key]=self._callbacks[info.key]||[];_getMatches(info.key,info.modifiers,{type:info.action},sequenceName,combination,level);self._callbacks[info.key][sequenceName?"unshift":"push"]({callback:callback,modifiers:info.modifiers,action:info.action,seq:sequenceName,level:level,combo:combination})}self._bindMultiple=function(combinations,callback,action){for(var i=0;i-1){return false}if(_belongsTo(element,self.target)){return false}return element.tagName=="INPUT"||element.tagName=="SELECT"||element.tagName=="TEXTAREA"||element.isContentEditable};Mousetrap.prototype.handleKey=function(){var self=this;return self._handleKey.apply(self,arguments)};Mousetrap.init=function(){var documentMousetrap=Mousetrap(document);for(var method in documentMousetrap){if(method.charAt(0)!=="_"){Mousetrap[method]=function(method){return function(){return documentMousetrap[method].apply(documentMousetrap,arguments)}}(method)}}};Mousetrap.init();window.Mousetrap=Mousetrap;if(typeof module!=="undefined"&&module.exports){module.exports=Mousetrap}if(typeof define==="function"&&define.amd){define(function(){return Mousetrap})}})(window,document)},{}],4:[function(require,module,exports){(function(process){function normalizeArray(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift("..")}}return parts}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;var splitPath=function(filename){return splitPathRe.exec(filename).slice(1)};exports.resolve=function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:process.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){continue}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=normalizeArray(filter(resolvedPath.split("/"),function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==="/";path=normalizeArray(filter(path.split("/"),function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path};exports.isAbsolute=function(path){return path.charAt(0)==="/"};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=="string"){throw new TypeError("Arguments to path.join must be strings")}return p}).join("/"))};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i1){for(var i=1;i= 0x80 (not a basic code point)","invalid-input":"Invalid input"},baseMinusTMin=base-tMin,floor=Math.floor,stringFromCharCode=String.fromCharCode,key;function error(type){throw RangeError(errors[type])}function map(array,fn){var length=array.length;var result=[];while(length--){result[length]=fn(array[length])}return result}function mapDomain(string,fn){var parts=string.split("@");var result="";if(parts.length>1){result=parts[0]+"@";string=parts[1]}string=string.replace(regexSeparators,".");var labels=string.split(".");var encoded=map(labels,fn).join(".");return result+encoded}function ucs2decode(string){var output=[],counter=0,length=string.length,value,extra;while(counter=55296&&value<=56319&&counter65535){value-=65536;output+=stringFromCharCode(value>>>10&1023|55296);value=56320|value&1023}output+=stringFromCharCode(value);return output}).join("")}function basicToDigit(codePoint){if(codePoint-48<10){return codePoint-22}if(codePoint-65<26){return codePoint-65}if(codePoint-97<26){return codePoint-97}return base}function digitToBasic(digit,flag){return digit+22+75*(digit<26)-((flag!=0)<<5)}function adapt(delta,numPoints,firstTime){var k=0;delta=firstTime?floor(delta/damp):delta>>1;delta+=floor(delta/numPoints);for(;delta>baseMinusTMin*tMax>>1;k+=base){delta=floor(delta/baseMinusTMin)}return floor(k+(baseMinusTMin+1)*delta/(delta+skew))}function decode(input){var output=[],inputLength=input.length,out,i=0,n=initialN,bias=initialBias,basic,j,index,oldi,w,k,digit,t,baseMinusT;basic=input.lastIndexOf(delimiter);if(basic<0){basic=0}for(j=0;j=128){error("not-basic")}output.push(input.charCodeAt(j))}for(index=basic>0?basic+1:0;index=inputLength){error("invalid-input")}digit=basicToDigit(input.charCodeAt(index++));if(digit>=base||digit>floor((maxInt-i)/w)){error("overflow")}i+=digit*w;t=k<=bias?tMin:k>=bias+tMax?tMax:k-bias;if(digitfloor(maxInt/baseMinusT)){error("overflow")}w*=baseMinusT}out=output.length+1;bias=adapt(i-oldi,out,oldi==0);if(floor(i/out)>maxInt-n){error("overflow")}n+=floor(i/out);i%=out;output.splice(i++,0,n)}return ucs2encode(output)}function encode(input){var n,delta,handledCPCount,basicLength,bias,j,m,q,k,t,currentValue,output=[],inputLength,handledCPCountPlusOne,baseMinusT,qMinusT;input=ucs2decode(input);inputLength=input.length;n=initialN;delta=0;bias=initialBias;for(j=0;j=n&¤tValuefloor((maxInt-delta)/handledCPCountPlusOne)){error("overflow")}delta+=(m-n)*handledCPCountPlusOne;n=m;for(j=0;jmaxInt){error("overflow")}if(currentValue==n){for(q=delta,k=base;;k+=base){t=k<=bias?tMin:k>=bias+tMax?tMax:k-bias;if(q0&&len>maxKeys){len=maxKeys}for(var i=0;i=0){kstr=x.substr(0,idx);vstr=x.substr(idx+1)}else{kstr=x;vstr=""}k=decodeURIComponent(kstr);v=decodeURIComponent(vstr);if(!hasOwnProperty(obj,k)){obj[k]=v}else if(isArray(obj[k])){obj[k].push(v)}else{obj[k]=[obj[k],v]}}return obj};var isArray=Array.isArray||function(xs){return Object.prototype.toString.call(xs)==="[object Array]"}},{}],8:[function(require,module,exports){"use strict";var stringifyPrimitive=function(v){switch(typeof v){case"string":return v;case"boolean":return v?"true":"false";case"number":return isFinite(v)?v:"";default:return""}};module.exports=function(obj,sep,eq,name){sep=sep||"&";eq=eq||"=";if(obj===null){obj=undefined}if(typeof obj==="object"){return map(objectKeys(obj),function(k){var ks=encodeURIComponent(stringifyPrimitive(k))+eq;if(isArray(obj[k])){return map(obj[k],function(v){return ks+encodeURIComponent(stringifyPrimitive(v))}).join(sep)}else{return ks+encodeURIComponent(stringifyPrimitive(obj[k]))}}).join(sep)}if(!name)return"";return encodeURIComponent(stringifyPrimitive(name))+eq+encodeURIComponent(stringifyPrimitive(obj))};var isArray=Array.isArray||function(xs){return Object.prototype.toString.call(xs)==="[object Array]"};function map(xs,f){if(xs.map)return xs.map(f);var res=[];for(var i=0;i",'"',"`"," ","\r","\n","\t"],unwise=["{","}","|","\\","^","`"].concat(delims),autoEscape=["'"].concat(unwise),nonHostChars=["%","/","?",";","#"].concat(autoEscape),hostEndingChars=["/","?","#"],hostnameMaxLen=255,hostnamePartPattern=/^[a-z0-9A-Z_-]{0,63}$/,hostnamePartStart=/^([a-z0-9A-Z_-]{0,63})(.*)$/,unsafeProtocol={javascript:true,"javascript:":true},hostlessProtocol={javascript:true,"javascript:":true},slashedProtocol={http:true,https:true,ftp:true,gopher:true,file:true,"http:":true,"https:":true,"ftp:":true,"gopher:":true,"file:":true},querystring=require("querystring");function urlParse(url,parseQueryString,slashesDenoteHost){if(url&&isObject(url)&&url instanceof Url)return url;var u=new Url;u.parse(url,parseQueryString,slashesDenoteHost);return u}Url.prototype.parse=function(url,parseQueryString,slashesDenoteHost){if(!isString(url)){throw new TypeError("Parameter 'url' must be a string, not "+typeof url)}var rest=url;rest=rest.trim();var proto=protocolPattern.exec(rest);if(proto){proto=proto[0];var lowerProto=proto.toLowerCase();this.protocol=lowerProto;rest=rest.substr(proto.length)}if(slashesDenoteHost||proto||rest.match(/^\/\/[^@\/]+@[^@\/]+/)){var slashes=rest.substr(0,2)==="//";if(slashes&&!(proto&&hostlessProtocol[proto])){rest=rest.substr(2);this.slashes=true}}if(!hostlessProtocol[proto]&&(slashes||proto&&!slashedProtocol[proto])){var hostEnd=-1;for(var i=0;i127){newpart+="x"}else{newpart+=part[j]}}if(!newpart.match(hostnamePartPattern)){var validParts=hostparts.slice(0,i);var notHost=hostparts.slice(i+1);var bit=part.match(hostnamePartStart);if(bit){validParts.push(bit[1]);notHost.unshift(bit[2])}if(notHost.length){rest="/"+notHost.join(".")+rest}this.hostname=validParts.join(".");break}}}}if(this.hostname.length>hostnameMaxLen){this.hostname=""}else{this.hostname=this.hostname.toLowerCase()}if(!ipv6Hostname){var domainArray=this.hostname.split(".");var newOut=[];for(var i=0;i0?result.host.split("@"):false;if(authInHost){result.auth=authInHost.shift();result.host=result.hostname=authInHost.shift()}}result.search=relative.search;result.query=relative.query;if(!isNull(result.pathname)||!isNull(result.search)){result.path=(result.pathname?result.pathname:"")+(result.search?result.search:"")}result.href=result.format();return result}if(!srcPath.length){result.pathname=null;if(result.search){result.path="/"+result.search}else{result.path=null}result.href=result.format();return result}var last=srcPath.slice(-1)[0];var hasTrailingSlash=(result.host||relative.host)&&(last==="."||last==="..")||last==="";var up=0;for(var i=srcPath.length;i>=0;i--){last=srcPath[i];if(last=="."){srcPath.splice(i,1)}else if(last===".."){srcPath.splice(i,1);up++}else if(up){srcPath.splice(i,1);up--}}if(!mustEndAbs&&!removeAllDots){for(;up--;up){srcPath.unshift("..")}}if(mustEndAbs&&srcPath[0]!==""&&(!srcPath[0]||srcPath[0].charAt(0)!=="/")){srcPath.unshift("")}if(hasTrailingSlash&&srcPath.join("/").substr(-1)!=="/"){srcPath.push("")}var isAbsolute=srcPath[0]===""||srcPath[0]&&srcPath[0].charAt(0)==="/";if(psychotic){result.hostname=result.host=isAbsolute?"":srcPath.length?srcPath.shift():"";var authInHost=result.host&&result.host.indexOf("@")>0?result.host.split("@"):false;if(authInHost){result.auth=authInHost.shift();result.host=result.hostname=authInHost.shift()}}mustEndAbs=mustEndAbs||result.host&&srcPath.length;if(mustEndAbs&&!isAbsolute){srcPath.unshift("")}if(!srcPath.length){result.pathname=null;result.path=null}else{result.pathname=srcPath.join("/")}if(!isNull(result.pathname)||!isNull(result.search)){result.path=(result.pathname?result.pathname:"")+(result.search?result.search:"")}result.auth=relative.auth||result.auth;result.slashes=result.slashes||relative.slashes;result.href=result.format();return result};Url.prototype.parseHost=function(){var host=this.host;var port=portPattern.exec(host);if(port){port=port[0];if(port!==":"){this.port=port.substr(1)}host=host.substr(0,host.length-port.length)}if(host)this.hostname=host};function isString(arg){return typeof arg==="string"}function isObject(arg){return typeof arg==="object"&&arg!==null}function isNull(arg){return arg===null}function isNullOrUndefined(arg){return arg==null}},{punycode:6,querystring:9}],11:[function(require,module,exports){var $=require("jquery");function toggleDropdown(e){var $dropdown=$(e.currentTarget).parent().find(".dropdown-menu");$dropdown.toggleClass("open");e.stopPropagation();e.preventDefault()}function closeDropdown(e){$(".dropdown-menu").removeClass("open")}function init(){$(document).on("click",".toggle-dropdown",toggleDropdown);$(document).on("click",".dropdown-menu",function(e){e.stopPropagation()});$(document).on("click",closeDropdown)}module.exports={init:init}},{jquery:1}],12:[function(require,module,exports){var $=require("jquery");module.exports=$({})},{jquery:1}],13:[function(require,module,exports){var $=require("jquery");var _=require("lodash");var storage=require("./storage");var dropdown=require("./dropdown");var events=require("./events");var state=require("./state");var keyboard=require("./keyboard");var navigation=require("./navigation");var sidebar=require("./sidebar");var toolbar=require("./toolbar");function start(config){sidebar.init();keyboard.init();dropdown.init();navigation.init();toolbar.createButton({index:0,icon:"fa fa-align-justify",label:"Toggle Sidebar",onClick:function(e){e.preventDefault();sidebar.toggle()}});events.trigger("start",config);navigation.notify()}var gitbook={start:start,events:events,state:state,toolbar:toolbar,sidebar:sidebar,storage:storage,keyboard:keyboard};var MODULES={gitbook:gitbook,jquery:$,lodash:_};window.gitbook=gitbook;window.$=$;window.jQuery=$;gitbook.require=function(mods,fn){mods=_.map(mods,function(mod){mod=mod.toLowerCase();if(!MODULES[mod]){throw new Error("GitBook module "+mod+" doesn't exist")}return MODULES[mod]});fn.apply(null,mods)};module.exports={}},{"./dropdown":11,"./events":12,"./keyboard":14,"./navigation":16,"./sidebar":18,"./state":19,"./storage":20,"./toolbar":21,jquery:1,lodash:2}],14:[function(require,module,exports){var Mousetrap=require("mousetrap");var navigation=require("./navigation");var sidebar=require("./sidebar");function bindShortcut(keys,fn){Mousetrap.bind(keys,function(e){fn();return false})}function init(){bindShortcut(["right"],function(e){navigation.goNext()});bindShortcut(["left"],function(e){navigation.goPrev()});bindShortcut(["s"],function(e){sidebar.toggle()})}module.exports={init:init,bind:bindShortcut}},{"./navigation":16,"./sidebar":18,mousetrap:3}],15:[function(require,module,exports){var state=require("./state");function showLoading(p){state.$book.addClass("is-loading");p.always(function(){state.$book.removeClass("is-loading")});return p}module.exports={show:showLoading}},{"./state":19}],16:[function(require,module,exports){var $=require("jquery");var url=require("url");var events=require("./events");var state=require("./state");var loading=require("./loading");var usePushState=typeof history.pushState!=="undefined";function handleNavigation(relativeUrl,push){var uri=url.resolve(window.location.pathname,relativeUrl);notifyPageChange();location.href=relativeUrl;return}function updateNavigationPosition(){var bodyInnerWidth,pageWrapperWidth;bodyInnerWidth=parseInt($(".body-inner").css("width"),10);pageWrapperWidth=parseInt($(".page-wrapper").css("width"),10);$(".navigation-next").css("margin-right",bodyInnerWidth-pageWrapperWidth+"px")}function notifyPageChange(){events.trigger("page.change")}function preparePage(notify){var $bookBody=$(".book-body");var $bookInner=$bookBody.find(".body-inner");var $pageWrapper=$bookInner.find(".page-wrapper");updateNavigationPosition();$bookInner.scrollTop(0);$bookBody.scrollTop(0);if(notify!==false)notifyPageChange()}function isLeftClickEvent(e){return e.button===0}function isModifiedEvent(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function handlePagination(e){if(isModifiedEvent(e)||!isLeftClickEvent(e)){return}e.stopPropagation();e.preventDefault();var url=$(this).attr("href");if(url)handleNavigation(url,true)}function goNext(){var url=$(".navigation-next").attr("href");if(url)handleNavigation(url,true)}function goPrev(){var url=$(".navigation-prev").attr("href");if(url)handleNavigation(url,true)}function init(){$.ajaxSetup({});if(location.protocol!=="file:"){history.replaceState({path:window.location.href},"")}window.onpopstate=function(event){if(event.state===null){return}return handleNavigation(event.state.path,false)};$(document).on("click",".navigation-prev",handlePagination);$(document).on("click",".navigation-next",handlePagination);$(document).on("click",".summary [data-path] a",handlePagination);$(window).resize(updateNavigationPosition);preparePage(false)}module.exports={init:init,goNext:goNext,goPrev:goPrev,notify:notifyPageChange}},{"./events":12,"./loading":15,"./state":19,jquery:1,url:10}],17:[function(require,module,exports){module.exports={isMobile:function(){return document.body.clientWidth<=600}}},{}],18:[function(require,module,exports){var $=require("jquery");var _=require("lodash");var storage=require("./storage");var platform=require("./platform");var state=require("./state");function toggleSidebar(_state,animation){if(state!=null&&isOpen()==_state)return;if(animation==null)animation=true;state.$book.toggleClass("without-animation",!animation);state.$book.toggleClass("with-summary",_state);storage.set("sidebar",isOpen())}function isOpen(){return state.$book.hasClass("with-summary")}function init(){if(platform.isMobile()){toggleSidebar(false,false)}else{toggleSidebar(storage.get("sidebar",true),false)}$(document).on("click",".book-summary li.chapter a",function(e){if(platform.isMobile())toggleSidebar(false,false)})}function filterSummary(paths){var $summary=$(".book-summary");$summary.find("li").each(function(){var path=$(this).data("path");var st=paths==null||_.contains(paths,path);$(this).toggle(st);if(st)$(this).parents("li").show()})}module.exports={init:init,isOpen:isOpen,toggle:toggleSidebar,filter:filterSummary}},{"./platform":17,"./state":19,"./storage":20,jquery:1,lodash:2}],19:[function(require,module,exports){var $=require("jquery");var url=require("url");var path=require("path");var state={};state.update=function(dom){var $book=$(dom.find(".book"));state.$book=$book;state.level=$book.data("level");state.basePath=$book.data("basepath");state.innerLanguage=$book.data("innerlanguage");state.revision=$book.data("revision");state.filepath=$book.data("filepath");state.chapterTitle=$book.data("chapter-title");state.root=url.resolve(location.protocol+"//"+location.host,path.dirname(path.resolve(location.pathname.replace(/\/$/,"/index.html"),state.basePath))).replace(/\/?$/,"/");state.bookRoot=state.innerLanguage?url.resolve(state.root,".."):state.root};state.update($);module.exports=state},{jquery:1,path:4,url:10}],20:[function(require,module,exports){var baseKey="";module.exports={setBaseKey:function(key){baseKey=key},set:function(key,value){key=baseKey+":"+key;try{sessionStorage[key]=JSON.stringify(value)}catch(e){}},get:function(key,def){key=baseKey+":"+key;if(sessionStorage[key]===undefined)return def;try{var v=JSON.parse(sessionStorage[key]);return v==null?def:v}catch(err){return sessionStorage[key]||def}},remove:function(key){key=baseKey+":"+key;sessionStorage.removeItem(key)}}},{}],21:[function(require,module,exports){var $=require("jquery");var _=require("lodash");var events=require("./events");var buttons=[];function insertAt(parent,selector,index,element){var lastIndex=parent.children(selector).length;if(index<0){index=Math.max(0,lastIndex+1+index)}parent.append(element);if(index",{class:"dropdown-menu",html:'

'});if(_.isString(dropdown)){$menu.append(dropdown)}else{var groups=_.map(dropdown,function(group){if(_.isArray(group))return group;else return[group]});_.each(groups,function(group){var $group=$("
",{class:"buttons"});var sizeClass="size-"+group.length;_.each(group,function(btn){btn=_.defaults(btn||{},{text:"",className:"",onClick:defaultOnClick});var $btn=$("'; + var clipboard; + + gitbook.events.bind("page.change", function() { + + if (!ClipboardJS.isSupported()) return; + + // the page.change event is thrown twice: before and after the page changes + if (clipboard) { + // clipboard is already defined but we are on the same page + if (clipboard._prevPage === window.location.pathname) return; + // clipboard is already defined and url path change + // we can deduct that we are before page changes + clipboard.destroy(); // destroy the previous events listeners + clipboard = undefined; // reset the clipboard object + return; + } + + $(copyButton).prependTo("div.sourceCode"); + + clipboard = new ClipboardJS(".copy-to-clipboard-button", { + text: function(trigger) { + return trigger.parentNode.textContent; + } + }); + + clipboard._prevPage = window.location.pathname + + }); + +}); diff --git a/create-litr/_book/libs/gitbook-2.6.7/js/plugin-fontsettings.js b/create-litr/_book/libs/gitbook-2.6.7/js/plugin-fontsettings.js new file mode 100644 index 0000000..a70f0fb --- /dev/null +++ b/create-litr/_book/libs/gitbook-2.6.7/js/plugin-fontsettings.js @@ -0,0 +1,152 @@ +gitbook.require(["gitbook", "lodash", "jQuery"], function(gitbook, _, $) { + var fontState; + + var THEMES = { + "white": 0, + "sepia": 1, + "night": 2 + }; + + var FAMILY = { + "serif": 0, + "sans": 1 + }; + + // Save current font settings + function saveFontSettings() { + gitbook.storage.set("fontState", fontState); + update(); + } + + // Increase font size + function enlargeFontSize(e) { + e.preventDefault(); + if (fontState.size >= 4) return; + + fontState.size++; + saveFontSettings(); + }; + + // Decrease font size + function reduceFontSize(e) { + e.preventDefault(); + if (fontState.size <= 0) return; + + fontState.size--; + saveFontSettings(); + }; + + // Change font family + function changeFontFamily(index, e) { + e.preventDefault(); + + fontState.family = index; + saveFontSettings(); + }; + + // Change type of color + function changeColorTheme(index, e) { + e.preventDefault(); + + var $book = $(".book"); + + if (fontState.theme !== 0) + $book.removeClass("color-theme-"+fontState.theme); + + fontState.theme = index; + if (fontState.theme !== 0) + $book.addClass("color-theme-"+fontState.theme); + + saveFontSettings(); + }; + + function update() { + var $book = gitbook.state.$book; + + $(".font-settings .font-family-list li").removeClass("active"); + $(".font-settings .font-family-list li:nth-child("+(fontState.family+1)+")").addClass("active"); + + $book[0].className = $book[0].className.replace(/\bfont-\S+/g, ''); + $book.addClass("font-size-"+fontState.size); + $book.addClass("font-family-"+fontState.family); + + if(fontState.theme !== 0) { + $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, ''); + $book.addClass("color-theme-"+fontState.theme); + } + }; + + function init(config) { + var $bookBody, $book; + + //Find DOM elements. + $book = gitbook.state.$book; + $bookBody = $book.find(".book-body"); + + // Instantiate font state object + fontState = gitbook.storage.get("fontState", { + size: config.size || 2, + family: FAMILY[config.family || "sans"], + theme: THEMES[config.theme || "white"] + }); + + update(); + }; + + + gitbook.events.bind("start", function(e, config) { + var opts = config.fontsettings; + if (!opts) return; + + // Create buttons in toolbar + gitbook.toolbar.createButton({ + icon: 'fa fa-font', + label: 'Font Settings', + className: 'font-settings', + dropdown: [ + [ + { + text: 'A', + className: 'font-reduce', + onClick: reduceFontSize + }, + { + text: 'A', + className: 'font-enlarge', + onClick: enlargeFontSize + } + ], + [ + { + text: 'Serif', + onClick: _.partial(changeFontFamily, 0) + }, + { + text: 'Sans', + onClick: _.partial(changeFontFamily, 1) + } + ], + [ + { + text: 'White', + onClick: _.partial(changeColorTheme, 0) + }, + { + text: 'Sepia', + onClick: _.partial(changeColorTheme, 1) + }, + { + text: 'Night', + onClick: _.partial(changeColorTheme, 2) + } + ] + ] + }); + + + // Init current settings + init(opts); + }); +}); + + diff --git a/create-litr/_book/libs/gitbook-2.6.7/js/plugin-search.js b/create-litr/_book/libs/gitbook-2.6.7/js/plugin-search.js new file mode 100644 index 0000000..747fcce --- /dev/null +++ b/create-litr/_book/libs/gitbook-2.6.7/js/plugin-search.js @@ -0,0 +1,270 @@ +gitbook.require(["gitbook", "lodash", "jQuery"], function(gitbook, _, $) { + var index = null; + var fuse = null; + var _search = {engine: 'lunr', opts: {}}; + var $searchInput, $searchLabel, $searchForm; + var $highlighted = [], hi, hiOpts = { className: 'search-highlight' }; + var collapse = false, toc_visible = []; + + function init(config) { + // Instantiate search settings + _search = gitbook.storage.get("search", { + engine: config.search.engine || 'lunr', + opts: config.search.options || {}, + }); + }; + + // Save current search settings + function saveSearchSettings() { + gitbook.storage.set("search", _search); + } + + // Use a specific index + function loadIndex(data) { + // [Yihui] In bookdown, I use a character matrix to store the chapter + // content, and the index is dynamically built on the client side. + // Gitbook prebuilds the index data instead: https://github.com/GitbookIO/plugin-search + // We can certainly do that via R packages V8 and jsonlite, but let's + // see how slow it really is before improving it. On the other hand, + // lunr cannot handle non-English text very well, e.g. the default + // tokenizer cannot deal with Chinese text, so we may want to replace + // lunr with a dumb simple text matching approach. + if (_search.engine === 'lunr') { + index = lunr(function () { + this.ref('url'); + this.field('title', { boost: 10 }); + this.field('body'); + }); + data.map(function(item) { + index.add({ + url: item[0], + title: item[1], + body: item[2] + }); + }); + return; + } + fuse = new Fuse(data.map((_data => { + return { + url: _data[0], + title: _data[1], + body: _data[2] + }; + })), Object.assign( + { + includeScore: true, + threshold: 0.1, + ignoreLocation: true, + keys: ["title", "body"] + }, + _search.opts + )); + } + + // Fetch the search index + function fetchIndex() { + return $.getJSON(gitbook.state.basePath+"/search_index.json") + .then(loadIndex); // [Yihui] we need to use this object later + } + + // Search for a term and return results + function search(q) { + let results = []; + switch (_search.engine) { + case 'fuse': + if (!fuse) return; + results = fuse.search(q).map(function(result) { + var parts = result.item.url.split('#'); + return { + path: parts[0], + hash: parts[1] + }; + }); + break; + case 'lunr': + default: + if (!index) return; + results = _.chain(index.search(q)).map(function(result) { + var parts = result.ref.split("#"); + return { + path: parts[0], + hash: parts[1] + }; + }) + .value(); + } + + // [Yihui] Highlight the search keyword on current page + $highlighted = $('.page-inner') + .unhighlight(hiOpts).highlight(q, hiOpts).find('span.search-highlight'); + scrollToHighlighted(0); + + return results; + } + + // [Yihui] Scroll the chapter body to the i-th highlighted string + function scrollToHighlighted(d) { + var n = $highlighted.length; + hi = hi === undefined ? 0 : hi + d; + // navignate to the previous/next page in the search results if reached the top/bottom + var b = hi < 0; + if (d !== 0 && (b || hi >= n)) { + var path = currentPath(), n2 = toc_visible.length; + if (n2 === 0) return; + for (var i = b ? 0 : n2; (b && i < n2) || (!b && i >= 0); i += b ? 1 : -1) { + if (toc_visible.eq(i).data('path') === path) break; + } + i += b ? -1 : 1; + if (i < 0) i = n2 - 1; + if (i >= n2) i = 0; + var lnk = toc_visible.eq(i).find('a[href$=".html"]'); + if (lnk.length) lnk[0].click(); + return; + } + if (n === 0) return; + var $p = $highlighted.eq(hi); + $p[0].scrollIntoView(); + $highlighted.css('background-color', ''); + // an orange background color on the current item and removed later + $p.css('background-color', 'orange'); + setTimeout(function() { + $p.css('background-color', ''); + }, 2000); + } + + function currentPath() { + var href = window.location.pathname; + href = href.substr(href.lastIndexOf('/') + 1); + return href === '' ? 'index.html' : href; + } + + // Create search form + function createForm(value) { + if ($searchForm) $searchForm.remove(); + if ($searchLabel) $searchLabel.remove(); + if ($searchInput) $searchInput.remove(); + + $searchForm = $('
', { + 'class': 'book-search', + 'role': 'search' + }); + + $searchLabel = $('",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 + + + + + + 2 Overview | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

2 Overview

+

The litr package consists of (a) document templates that users can use as the basis for creating an R package, (b) code needed so that when such a R markdown file is knitted, it will generate not just an .html (or some other output format) but also an R package. The code has the following components:

+
    +
  1. Functionality for generating the R package when the .Rmd file is knitted. In particular, we define a knitr chunk hook, which we call send_to_package() that identifies code chunks in the .Rmd file that should be included in the package. To make it so that this chunk hook will be active, we also have a function called setup() that is called at the start of litr::render() right before rmarkdown::render() is called. The setup() function makes it so that when the .Rmd file is knitted, the chunk hook send_to_package() will be invoked on each code chunk. It is also responsible for other preliminaries, such as defining a new knitr language engine that can interpret package-level documentation.

  2. +
  3. Functionality for making sure the R package outputted will not overwrite a manually edited R package. Our approach here is to use a hash that will make it clear whether something has been modified.

  4. +
  5. Wrapper to devtools::document() The reason we write a wrapper for devtools::document() is because we want it to behave slightly differently. In particular, devtools::document() reminds the reader to edit the roxygen in the R/ files, whereas in our case, we want to make sure they edit the original .Rmd file, not the R/ files.

  6. +
  7. Functionality to alter the rendering process The focus here is to make it so that all the special litr functionality will be active when the .Rmd is rendered. We implement two ways this can happen. First, we have several custom output formats, such as litr_html_document(). When rmarkdown::render() is called with one of these litr output formats, setup() gets called before knitting occurs and some other litr-specific post-processing occurs as well. The second way is through the function litr::render(). If a user calls litr::render() with a non-litr output format, e.g. rmarkdown::html_document(), then setup() gets called before rmarkdown::render() and also post-processing can again happen. Another thing of note about litr::render() is that it renders the document in a fresh environment, which ensures identical behavior to when a user presses “Knit” in RStudio.

  8. +
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/package-setup.html b/create-litr/_book/package-setup.html new file mode 100644 index 0000000..e408fdc --- /dev/null +++ b/create-litr/_book/package-setup.html @@ -0,0 +1,345 @@ + + + + + + + 3 Package setup | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

3 Package setup

+

We start by specifying the information needed in the DESCRIPTION file of the R package. We mostly follow the R Packages book’s description of version numbering. Releases 0.0.1, 0.0.2, and 0.0.3 should really have been 0.1.0, 0.2.0, and 0.3.0, because these were not just patches. Rather, each added quite substantial new functionality. For this reason, we have gone straight from 0.0.3 to 0.4.0. We will keep the leftmost number (“major release”) at 0 until we feel like the package is complete with all the intended features.

+
usethis::create_package(
+  path = ".",
+  fields = list(
+    Package = params$package_name,
+    Version = "0.9.1",
+    Title = "Literate Programming for Writing R Packages",
+    Description = "Allows one to fully create an R package in a single .Rmd 
+    file.  Includes functionality and .Rmd templates for a literate programming
+    approach to R package development.",
+    `Authors@R` = c(
+      person("Jacob", "Bien", email = "jbien@usc.edu", role = c("aut", "cre")),
+      person("Patrick Vossler", role = "aut")
+      )
+   )
+ )
+
+usethis::use_mit_license(copyright_holder = "J. Bien")
+

Let’s add some package-level documentation. This is what will show up when someone types package?litr in the console.

+
#' Literate Programming for Writing R Packages
+#'
+#' Allows one to fully create an R package in a single .Rmd file.  Includes
+#' functionality and .Rmd templates for a literate programming approach to R
+#' package development.
+#' 
+#' @examples
+#' # Make a file create-rhello.Rmd based on a template
+#' \dontrun{
+#' rmarkdown::draft("create-rhello.Rmd", 
+#'                  template = "make-an-r-package",
+#'                  package = "litr",
+#'                  edit = FALSE)
+#' # Now call litr::render (or press Knit if in RStudio) to generate not just
+#' # create-rhello.html, but also an R package called `rhello`.
+#' litr::render("create-rhello.Rmd")
+#' }
+#' @docType package
+#' @seealso \code{\link{render}}
+
+

3.1 A note on circularity

+

Keeping track of the version of litr used is particularly important when using litr to develop litr. There is a tendency to want to use the new functionality that we are creating in create-litr.Rmd itself as soon as we have defined it. However, this is circular and thus must be avoided. To see why we need to be careful, let’s consider an actual example that arose when working on litr version 0.0.3. After release v0.0.2, one of the new features we added is a new language engine called package_doc that allows us to have a special kind of code block defining the package documentation. In trying out this feature and making sure it works on skeleton.Rmd, we would most likely install version 0.0.3. Now that version 0.0.3 is installed, there will be a tendency to want to add a package_doc block to create-litr.Rmd, and it will appear to work. However, this is circular, because we have used version 0.0.3 to create version 0.0.3! In particular, if we remove litr and re-install it from github, we will get version 0.0.2 so that when we attempt to create the package using litr::render("create-litr.Rmd"), we will get an error telling us that it doesn’t have a language engine named package_doc.

+

The code chunk in this section is for preventing this from happening.

+

For more on circularity, see the section on testing litr.

+
install_version_of_litr <- utils::packageVersion("litr")
+remote <- remotes::github_remote(
+  repo = "jacobbien/litr-project@*release",
+  subdir = "litr"
+  )
+version_of_latest_release <- stringr::str_remove(remote$ref, "v")
+if (install_version_of_litr != version_of_latest_release)
+  stop(stringr::str_glue(
+    "You should be using the version of litr from the latest release (version",
+    " {version_of_latest_release}),\n but you are using version",
+    " {install_version_of_litr}.\n",
+    "You can install the release version of litr from GitHub by running the",
+    " the following command:\n",
+    "remotes::install_github(repo='jacobbien/litr-project@*release', subdir = 'litr')"
+  ))
+
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/reference-keys.txt b/create-litr/_book/reference-keys.txt new file mode 100644 index 0000000..15744a2 --- /dev/null +++ b/create-litr/_book/reference-keys.txt @@ -0,0 +1,35 @@ +preamble +overview +package-setup +a-note-on-circularity +generating-package +sending-code-chunks-to-the-package +setting-up-the-r-package-creation +hash +document +rendering +output-format +pdf-output-format +html-output-format +bookdown-output-format +render +functionality-to-facilitate-workflow +adding-extras-to-an-r-package +adding-a-readme +adding-a-hex-sticker +adding-vignettes +add-a-pkgdown-site +combining-.r-files +including-templates +tests +test-check-unedited +testing-get_params_used +testing-chunk-referencing +testing-different-ways-of-rendering +testing-other-templates +documenting-the-package-and-testing +add-examples-folder-with-the-output-of-knitting-each-example +including-extras-for-litr +readme-with-hex-sticker +vignettes +a-pkgdown-site diff --git a/create-litr/_book/rendering.html b/create-litr/_book/rendering.html new file mode 100644 index 0000000..f1c4a4b --- /dev/null +++ b/create-litr/_book/rendering.html @@ -0,0 +1,1090 @@ + + + + + + + 7 Altering the rendering process | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

7 Altering the rendering process

+

The focus here is to make it so that all the special litr functionality will be active when the .Rmd is rendered. In particular, we need to make sure that setup() has been called before knitting occurs (so that, for example, send_to_package() will be active). We also need to make sure that after knitting certain things occur, such as the litr-hash being written to the package and, in some cases, hyperlinks are added for easier navigation.

+

We implement two ways for the above to occur, which we describe in the next two subsections:

+
    +
  1. The first approach is by defining custom litr output formats. When rmarkdown::render() (or bookdown::render_book()) is called with one of these litr output formats, the litr-specific operations occur before and after knitting.

  2. +
  3. The second way is through the function litr::render(). If a user calls litr::render() with a non-litr output format, e.g. rmarkdown::html_document(), then it adds the necessary litr-specific operations before/after rmarkdown::render() is called. Another thing of note about litr::render() is that it renders the document in a fresh environment, which ensures identical behavior to when a user presses “Knit” in RStudio.

  4. +
+

Although these are presented as two separate approaches, we have written litr::render() so that if a user passes one of the litr output formats to litr::render(), it will still work.

+

We encourage users to use litr::render()1 rather than rmarkdown::render() since in litr::render() we’re able to wrap the call to rmarkdown::render() in the function with_cleanup(). This ensures that, if an error occurs during the knitting process, the special litr hash will still be created. This is desirable since it means that the next time we try to litr-knit, we will not get an error about overwriting a manually edited package directory.

+

When coding an R package with litr, sometimes there are code chunks that can take a while to evaluate (e.g., tests), which slows down the coding process. We therefore provide an argument (to both litr::render() and the various litr output formats) that allows for “minimal eval” to occur. The goal is to allow the R package to be updated completely but without any of the code chunks being evaluated, except those whose involving usethis or a call to litr::document(), since these commands lead to changes in the R package itself. Here is the documentation associated with the minimal_eval parameter, which is an argument to a number of functions in this section:

+
###"param-minimal_eval"###
+#' @param minimal_eval If `TRUE`, then only chunks with `litr::document()` or 
+#' `usethis` commands will be evaluated.  This can be convenient in coding when 
+#' you just want to quickly update the R package without having to wait for long
+#' evaluations to occur.
+
+

7.1 Defining litr output formats

+

The function rmarkdown::render() allows for customizable behavior through the use of custom output formats.

+

Given a preexisting output format (e.g. rmarkdown::html_document), we would like to modify it to have litr-behavior – i.e., to create a package as it is being rendered. This next function takes a preexisting output format and “litr-ifies” it by making three changes:

+
    +
  1. It modifies the pre_knit() function.

  2. +
  3. It modifies the post_processor() function.

  4. +
  5. It adds a marker (litr_format <- TRUE) that will help litr::render() know when a litr output format is being passed to it.

  6. +
+

We present the function and then describe the details of the new pre_knit() and post_processor() functions below.

+
#' Modify an existing output format to have `litr` behavior
+#' 
+#' This function modifies the `pre_knit()` and `post_processor()` functions of a
+#' preexisting output format so that it will have the `litr` behavior (meaning that an R package will be created when `rmarkdown::render()` is called).
+#' 
+#' @param base_format a preexisting, non-litr output format such as `rmarkdown::html_document`
+<<param-minimal_eval>>
+#' @export
+litrify_output_format <- function(base_format = rmarkdown::html_document,
+                                  minimal_eval = FALSE) {
+  force(base_format) # I think using force here is advisable?
+  force(minimal_eval) # https://adv-r.hadley.nz/function-factories.html
+  function(...) {
+    old <- base_format(...)
+    new <- old
+    new$original_knitr_objects <- list()
+    new$pre_knit <- function(...) {
+      args <- list(...)
+      input <- args$input
+      params <- knitr::knit_params(readLines(input))
+      package_dir <- get_package_directory(
+        params$package_parent_dir$value,
+        params$package_name$value,
+        input)
+      new$original_knitr_objects <<- litr:::setup(package_dir, minimal_eval)
+      if (!is.null(old$pre_knit)) old$pre_knit(...)
+    }
+
+    new$post_processor <- function(metadata, input_file, output_file, ...) {
+      # typically the post_processor function returns the output file path
+      # if old$post_processor is NULL, as in the case of pdf_document,
+      # then R will throw an error when trying to call old$post_processor
+      # if we only add a check for non null old$post_processor and otherwise
+      # set out <- NULL then R will throw an error later in rmarkdown::render
+      # since output_file is set to the output of the post_processor if 
+      # output_format$post_processor is not null (See line 478 in rmarkdown::render)
+      # Therefore, our solution is to set out to the output_file path if old$post_process is null.
+      if (!is.null(old$post_processor)){
+        out <- old$post_processor(metadata, input_file, output_file, ...)  
+      } else {
+        out <- output_file 
+      }
+      package_dir <- get_package_directory(
+        metadata$params$package_parent_dir,
+        metadata$params$package_name,
+        input_file
+      )
+      # remove .Rproj and .gitignore if usethis::create_package() added these
+      remove_rstudio_extras(package_dir)
+
+      # add to DESCRIPTION file the version of litr used to create package:
+      write_version_to_description(package_dir)
+
+      # add litr hash so we can tell later if package files were manually edited:
+      write_hash_to_description(package_dir)
+      
+      out
+    }
+    
+    new$on_exit <- function() {
+      old$on_exit()
+      
+      # restore knitr to its original state
+      restore_knitr_objects(new$original_knitr_objects)
+    }
+    
+    # mark this as a litr_format
+    new$litr_format <- TRUE
+    
+    # litr formats have minimal_eval as an option
+    new$minimal_eval <- minimal_eval
+
+    new
+  }
+}
+
    +
  1. The pre_knit() function is modified so that setup() is called before the preexisting output format’s pre_knit() function is called. As the name suggests, this is a function that gets called before knitting. The purpose of the call to setup() is to create the R package directory and make it so that when we knit the file using rmarkdown::render(), a lot of special things will happen, such as code being sent to the R package directory. The function setup() returns the state of the knitr settings before any changes were made. This previous state of the knitr settings will be restored at the end of the rendering process in on_exit().

  2. +
  3. The post_processor() function is modified so that the DESCRIPTION file gets marked with the version of litr used and with the litr hash (as already described here). Some special care is taken for the case that the original output format doesn’t have a post processor (e.g., this is the case for the pdf_document output format). The particulars of this are given in a comment in the code chunk above.

  4. +
  5. The on_exit() function is modified so that it restores the state of all the knitr settings to how it was when render was first called.

  6. +
+

We use the above function to create some litr versions of common output formats, as seen in the next few subsections.

+

Before proceeding, we define the function write_version_to_description() that is called above.

+
#' Generate litr version field name for DESCRIPTION file
+#' @keywords internal
+description_litr_version_field_name <- function() return("LitrVersionUsed")
+
#' Write the version of litr used to the DESCRIPTION file
+#' 
+#' @param package_dir Path to package
+#' @keywords internal
+write_version_to_description <- function(package_dir) {
+  ver <- as.character(utils::packageVersion("litr"))
+  add_text_to_file(
+    txt = stringr::str_glue("{description_litr_version_field_name()}: {ver}"),
+    filename = file.path(package_dir, "DESCRIPTION"),
+    req_exist = TRUE
+    )
+}
+

Also, we made use of a small function for getting the package directory based on the input file’s location and the parameters that are being used in the rendering process. We define it here:

+
#' Get package directory
+#' 
+#' @param package_parent_dir The directory of where the package should go (relative to the input directory)
+#' @param package_name The name of the package
+#' @param input The file name of the input
+#' @keywords internal
+get_package_directory <- function(package_parent_dir, package_name, input) {
+  if (package_parent_dir == ".")
+    return(file.path(dirname(input), package_name))
+  file.path(dirname(input), package_parent_dir, package_name)
+}
+

Let’s write some tests to make sure it’s behaving as expected:

+
testthat::test_that("get_package_directory() works", {
+  input <- file.path("inputdir", "input.Rmd")
+  testthat::expect_equal(
+    get_package_directory(".", "mypkg", input),
+    file.path("inputdir", "mypkg") # inputdir/mypkg
+  )
+  testthat::expect_equal(
+    get_package_directory("..", "mypkg", input),
+    file.path("inputdir", "..", "mypkg") # inputdir/../mypkg
+  )
+})
+
## Test passed
+
+

7.1.1 .pdf output format

+

We want our .pdf documents to accurately display the “logging” output from functions in packages such as devtools that use special ANSI escape codes for displaying information in the terminal. Unfortunately, these codes use escape characters which cause problems when creating .pdf versions of our documents. While it is relatively straightforward to map ANSI escape codes to HTML tags, as we will see in the .html output format section, converting these escape codes to Latex commands is more complicated. As a result, we define a post_knit function in our litr_pdf_document format to avoid this issue by stripping out all escape codes in the file before it is converted into a .tex file and then compiled into a .pdf document.

+

Specifically, the post_knit function modifies the intermediate .knit.md file, which contains both the .Rmd file, as well as the output of each code chunk. Thus, we can inspect the output text of each code chunk and remove any ANSI escape codes before the .knit.md is converted to a .tex file and then a .pdf document. We rely upon two internal functions from the fansi R package to remove all possible escape codes and return a clean character vector.

+
#' litr version of `rmarkdown::pdf_document()`
+#' 
+#' This behaves exactly like `rmarkdown::pdf_document()` except it creates an 
+#' R package.
+#' 
+<<param-minimal_eval>>
+#' @param ... Parameters to be passed to `rmarkdown::pdf_document()` 
+#' @export
+litr_pdf_document <- function(minimal_eval = FALSE, ...) {
+  litr_pdf_document_ <- litrify_output_format(rmarkdown::pdf_document,
+                                              minimal_eval = minimal_eval)
+  old <- litr_pdf_document_(...)
+  new <- old
+
+  # post_knit
+  new$post_knit = function(...){
+    args = list(...)
+    input_filename <- args[[2]]
+    knitted_filename <- fs::path_ext_set(input_filename, ".knit.md")
+    knitted_output <- readLines(knitted_filename)
+    cleaned_output <- sapply(1:length(knitted_output), function(i){
+      test_str <- knitted_output[i]
+      fansi:::VAL_IN_ENV(x=test_str, ctl="all", warn=TRUE, warn.mask=fansi:::get_warn_mangled())
+      .Call(fansi:::FANSI_strip_csi, test_str, CTL.INT, WARN.INT)
+    })
+    writeLines(cleaned_output, knitted_filename)
+  }
+  new
+}
+

Since the above section uses the fansi package for handling ANSI escape sequences, we include it in our package:

+
usethis::use_package("fansi")
+
## ✔ Adding 'fansi' to Imports field in DESCRIPTION
+## • Refer to functions with `fansi::fun()`
+
+
+

7.1.2 .html output format

+

For .html documents, we’d like to add a bit more in the postprocessing step. In particular, we include some special function and chunk hyperlinking behavior described below. The function add_function_hyperlinks() processes the outputted .html file(s), making it so that one can easily navigate to function definitions. (This function is described lower in this section.) We likewise call a function add_chunk_label_hyperlinks(), which makes chunk references into clickable links. In particular, the chunk reference <<my-chunk>> within a code chunk would link to a chunk named “my-chunk” that begins with ###"my-chunk"###. The ###"my-chunk"### line is added by a document hook defined in setup(). Finally, we replace ANSI sequences with HTML tag equivalents (the need for this is explained in the section on the .pdf output format).

+
#' litr version of `rmarkdown::html_document()`
+#' 
+#' This behaves like `rmarkdown::html_document()` with a few differences:
+#' - It creates an R package.
+#' - It adds hyperlinks to function definitions whenever a function is used
+#' elsewhere in the document.
+#' - It does "Knuth-style" chunk referencing with hyperlinks.
+#' 
+<<param-minimal_eval>>
+#' @param ... Parameters to be passed to `rmarkdown::pdf_document()` 
+#' @export
+litr_html_document <- function(minimal_eval = FALSE, ...) {
+  litr_html_document_ <- litrify_output_format(rmarkdown::html_document,
+                                               minimal_eval = minimal_eval)
+  old <- litr_html_document_(...)
+  new <- old
+  # modify post_processor
+  new$post_processor = function(metadata, input_file, output_file, ...) {
+    out <- old$post_processor(metadata, input_file, output_file, ...)
+    # html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$")
+    # add hyperlinks within html output to make it easier to navigate:
+    add_function_hyperlinks(output_file, metadata$params$package_name)
+    add_chunk_label_hyperlinks(output_file)
+    # replace ANSI sequences with HTML tag equivalents
+    replace_ansi_sequences(output_file)
+    out
+  }
+  new
+}
+

We describe these two add_*_hyperlinks() functions next.

+

The function add_function_hyperlinks() looks for foo followed by <- function( and then wraps foo in a span tag with id="foo"; whenever foo is found elsewhere in the document, it calls the insert_hrefs() function to wrap a a href="file.html#foo" tag (where file.html is the file where foo is defined), so that it will be a hyperlink to foo’s definition.

+
#' Add hyperlinks to function definitions
+#' 
+#' Finds functions that are defined in the html file(s) by looking for text of the 
+#' form `foo <- function(` and then wraps `foo` in a `span` tag with `id="foo"` 
+#' and then whenever `foo` is found it wraps a `a href="file.html#foo"` tag so 
+#' that it will be a hyperlink to `foo`'s definition.
+#' 
+#' @param html_files Character vector of file names of html files that were created
+#' from Rmd files
+#' @param pkg_name Name of the package created by litr. Taken from YAML front matter
+#' @keywords internal
+add_function_hyperlinks <- function(html_files, pkg_name) {
+  find_function_defs <- function(html_file) {
+    txt <- readLines(html_file)
+    start_line <- which(txt == "<body>")
+    pattern1 <- '([a-zA-Z0-9_.]+)(\\s*&lt;-\\s*function)'
+    pattern2 <- stringr::str_replace(pattern1,
+                                     '&lt;-',
+                                     '<span class="ot">&lt;-</span>')
+    pattern2 <- stringr::str_replace(pattern2,
+                                     'function',
+                                     '<span class="cf">function</span>')
+    # find functions that are defined in this file:
+    function_names <- character(0)
+    for (pattern in c(pattern1, pattern2)) {
+      for (i in seq(start_line + 1, length(txt))) {
+        fn_name <- stringr::str_match(txt[i], pattern)[, 2]
+        if(is.na(fn_name)) next
+        # a function was defined in this line, so put a span around it
+        txt[i] <- stringr::str_replace(
+          txt[i],
+          pattern,
+          stringr::str_glue("<span id='{fn_name}'>\\1</span>\\2")
+        )
+        # and keep track of it for later:
+        function_names <- c(function_names, fn_name)
+      }
+    }
+    list(function_names = function_names, txt = txt)
+  }
+  fdefs <- lapply(html_files, find_function_defs)
+  all_function_names <- unlist(lapply(fdefs, function(lst) lst$function_names))
+  # if a function is defined multiple times, then it's ambiguous where to link to
+  # so let's not try linking to it (this can occur when a function is defined 
+  # within a function, such as `new$post_processor()`)
+  repeated <- names(which(table(all_function_names) > 1))
+  all_function_names <- setdiff(all_function_names, repeated)
+  if (length(all_function_names) == 0) {
+    # no functions defined in package, so nothing more to be done here
+    return()
+  }
+  num_per_file <- unlist(lapply(fdefs, 
+                                function(lst) {
+                                  length(setdiff(lst$function_names, repeated))
+                                }))
+  where_defined <- rep(fs::path_file(html_files), times = num_per_file)
+  defined_functions_pattern <- paste0("(::)?",all_function_names, "\\(", collapse = "|")
+  # There's also this case: <span class="fu">myfunction</span>
+  defined_functions_pattern2 <- paste0(
+    '<span class="fu">', all_function_names, '</span>\\(',
+    collapse = "|")
+  
+  for (i in seq_along(html_files)) {
+    # whenever one of the defined functions is named, link to its definition
+    # using the format `file_where_foo_is_defined.html#foo`
+    modified_txt <- insert_hrefs(fdefs[[i]]$txt, defined_functions_pattern,
+                                 where_defined, all_function_names, pkg_name)
+    modified_txt <- insert_hrefs(modified_txt, defined_functions_pattern2,
+                                 where_defined, all_function_names, pkg_name, remove_span=TRUE)
+    writeLines(modified_txt, con = html_files[i])
+  }
+}
+

We define next the helper function insert_hrefs(), which was called in the previous function. If the function foo() is defined in the .Rmd file that defines a package named pkg, then whenever foo() or pkg::foo() appears in the .Rmd, a link will be added; however, if other_pkg::foo() appears, then no link will be added.

+
#' Replace a function's name with a link to its definition
+#' 
+#' A helper function for `add_function_hyperlinks` that wraps references to a 
+#' function in an anchor tag with a link to the function's definition.
+#' 
+#' @param txt Character vector where each element is a row of the knitted HTML file.
+#' @param function_pattern Regular Expression passed from `add_function_hyperlinks` that contains all referenced functions in the document.
+#' @param where_defined Character vector that contains the name of the file in which a function was defined.
+#' @param all_function_names Character vector of all referenced functions in the document.
+#' @param pkg_name Name of the package created by litr. Taken from YAML front matter.
+#' @param remove_span Boolean argument for removing span tags. Used for minimizing code duplication.
+#' @keywords internal
+insert_hrefs <- function(txt, function_pattern, where_defined,
+                         all_function_names, pkg_name, remove_span=FALSE){
+  # filter down matches of defined_functions_pattern
+  has_fn_name <- which(stringr::str_detect(txt, function_pattern))
+  has_colon_prefix <- which(stringr::str_detect(txt, paste0("::", all_function_names, "\\(", collapse = "|")))
+  has_only_fn_name <- setdiff(has_fn_name, has_colon_prefix)
+  has_pkg_colon_prefix <- which(stringr::str_detect(txt, paste0(stringr::str_glue("{pkg_name}::"))))
+  
+  # define different replacement functions for colon prefix cases and regular cases
+  colon_pref_replace_fn <- function(x){
+    if(remove_span){
+      fn_name <- stringr::str_remove(x, "</span>\\(")
+      fn_name <- stringr::str_remove(fn_name, '<span class="fu">')
+    } else{
+      fn_name <- stringr::str_remove(x, "\\(")
+    }
+    fn_name <- stringr::str_remove(fn_name, stringr::str_glue('{pkg_name}::'))
+    # implicitly assuming that a function is not redefined in another file
+    def_file <- where_defined[all_function_names == fn_name]
+    return(stringr::str_glue("{pkg_name}::<a href='{def_file}#{fn_name}'>{fn_name}</a>("))
+    
+  }
+  regular_replace_fn <- function(x){
+    if(remove_span){
+      fn_name <- stringr::str_remove(x, '</span>\\(')
+      fn_name <- stringr::str_remove(fn_name, '<span class="fu">')  
+    } else {
+      fn_name <- stringr::str_remove(x, "\\(")
+    }
+    # implicitly assuming that a function is not redefined in another file
+    def_file <- where_defined[all_function_names == fn_name]
+    stringr::str_glue("<a href='{def_file}#{fn_name}'>{fn_name}</a>(")
+  }  
+  
+  colon_prefix_function_pattern <- paste0(stringr::str_glue("{pkg_name}::"),all_function_names, "\\(", collapse = "|")
+  colon_prefix_refs <- stringr::str_replace_all(
+    txt[has_pkg_colon_prefix],
+    colon_prefix_function_pattern,
+    colon_pref_replace_fn
+  )
+  
+  regular_refs <- stringr::str_replace_all(
+    txt[has_only_fn_name],
+    function_pattern,
+    regular_replace_fn
+  )
+  # now put back in the changed lines
+  txt[has_pkg_colon_prefix] <- colon_prefix_refs
+  txt[has_only_fn_name] <- regular_refs
+  txt
+}
+

In addition to adding hyperlinks to function definitions, we also want to add hyperlinks for chunk references of the form <<chunk-name>> that link to user-defined chunk names that take the form ###chunk-name###. We want these user-defined chunk names to stand out to readers and in their original form as code comments they might be harder to notice. To solve this issue, we modify the formatting of these chunks to add a border with the name of the chunk set into the border, mimicking the look of a <fieldset> tag. The additional formatting requires inserting additional HTML tags as well as css into the knitted HTML file so we use the xml2 package to parse and manipulate the knitted HTML file.

+
#' Add hyperlinks to embedded chunks
+#' 
+#' Finds chunks that are referenced in the html file(s) by looking for comments
+#' of the form `###"foo"###` and then wraps `foo` in a `span` tag with `id="foo"` 
+#' and then whenever the chunk label `<<foo>>` is found it wraps it in a 
+#' `a href="file.html#foo"` tag so that it will be a hyperlink to `foo`'s 
+#' definition.
+#' 
+#' @param html_files Character vector of file names of html files that were created
+#' from Rmd files
+#' @param reference_start The delimiter used to indicate the start of a chunk label 
+#' @param reference_end The delimiter used to indicate the end of a chunk label 
+#' @keywords internal
+add_chunk_label_hyperlinks <- function(html_files,
+                                       reference_start = "&lt;&lt;",
+                                       reference_end = "&gt;&gt;"){
+  find_chunk_defs <- function(html_file) {
+    txt <- readLines(html_file)
+    start_line <- which(txt == "<body>")
+    pattern <- '###&quot;([a-zA-Z0-9-_.]+)&quot;###'
+    # find chunks that are defined in this file:
+    chunk_names <- character(0)
+    for (i in seq(start_line + 1, length(txt))) {
+      chunk_name <- stringr::str_match(txt[i], pattern)[, 2]
+      if(is.na(chunk_name)) next
+      # a chunk was defined in this line, so put a span around it
+      txt[i] <- stringr::str_replace(
+        txt[i],
+        pattern,
+        stringr::str_glue("<span id='{chunk_name}'>###&quot;\\1&quot;###</span>")
+      )
+      # and keep track of it for later.
+      # we're using setNames here to make sure that we keep the name of file
+      # where the chunk name is defined 
+      chunk_names <- setNames(c(chunk_names, chunk_name), c(names(chunk_names), html_file))
+    }
+    list(chunk_names = chunk_names, txt = txt)
+  }
+  
+  cdefs <- lapply(html_files, find_chunk_defs)
+  all_chunk_names <- unlist(lapply(cdefs, function(lst) lst$chunk_names))
+  num_per_file <- unlist(lapply(cdefs, function(lst) length(lst$chunk_names)))
+  where_defined <- rep(fs::path_file(html_files), times = num_per_file)
+  
+  defined_chunks_pattern <- paste0(reference_start, all_chunk_names, reference_end, 
+                                   collapse = "|")
+  ref_start <- '<span class="sc">&lt;</span><span class="er">&lt;</span>'
+  ref_start_alt <- '<span class=\"er\">&lt;&lt;</span>'
+  ref_end <- '<span class="sc">&gt;</span><span class="er">&gt;</span>'
+  hyphen_with_extras <- '<span class="sc">-</span>'
+  all_chunk_names2 <- stringr::str_replace_all(all_chunk_names, "-", hyphen_with_extras)
+  defined_chunks_pattern2 <- paste0(
+    ref_start, all_chunk_names2, ref_end, collapse = "|"
+    )
+  defined_chunks_pattern2_alt <- paste0(
+    ref_start_alt, all_chunk_names2, ref_end, collapse = "|"
+    )
+  defined_chunks_pattern2 <- paste(
+    defined_chunks_pattern2, defined_chunks_pattern2_alt, sep = "|"
+    )
+
+  for (i in seq_along(html_files)) {
+    # whenever one of these named chunks is referenced, link to its definition
+    # using the format `file_where_chunk_is_defined.html#chunkname`
+    txt <- stringr::str_replace_all(
+      cdefs[[i]]$txt,
+      defined_chunks_pattern,
+      function(x) {
+        cname <- stringr::str_remove_all(
+          x,
+          paste(reference_start, reference_end, sep = "|")
+        )
+        def_file <- where_defined[all_chunk_names == cname]
+        stringr::str_glue(
+          "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>"
+          )
+      }
+    )
+    txt <- stringr::str_replace_all(
+      txt,
+      defined_chunks_pattern2,
+      function(x) {
+        cname <- stringr::str_remove_all(
+          x,
+          paste(ref_start, ref_start_alt, ref_end, sep = "|")
+        )
+        def_file <- where_defined[all_chunk_names2 == cname]
+        cname <- stringr::str_replace_all(cname, hyphen_with_extras, "-")
+        stringr::str_glue(
+          "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>"
+          )
+      }
+    )
+
+    parsed_html <- xml2::read_html(paste(txt,collapse="\n"))
+    # get all possible chunk names in this file.
+    chunk_names <- all_chunk_names[which(names(all_chunk_names) == html_files[i])]
+    
+    if(length(chunk_names) > 0){
+      for(j in seq_along(chunk_names)){
+        span_node <- xml2::xml_find_first(parsed_html, stringr::str_glue('(.//span[@id="{chunk_names[j]}"])'))
+        span_node_path <- stringr::str_split(xml2::xml_path(span_node),"/")
+        
+        pre_path <- paste(span_node_path[[1]][1:(max(which(stringr::str_detect(span_node_path[[1]], "pre"))))],collapse="/")
+        if(nchar(pre_path) == 0){
+          next()
+        }
+        pre_parent <- xml2::xml_find_first(parsed_html, pre_path)
+        if(is.na(pre_parent)){
+          next()
+        }
+        xml2::xml_add_parent(pre_parent
+                             , xml2::read_xml(stringr::str_glue('<fieldset id="{chunk_names[j]}" class="chunkfield"> </fieldset>')))
+        xml2::xml_add_sibling(pre_parent, xml2::read_xml(stringr::str_glue('<legend class="chunklegend">{chunk_names[j]}</legend>')), where="before")
+        # xml2::xml_add_parent(pre_parent, xml2::read_xml(stringr::str_glue('<fieldset id="{chunk_names[j]}" style="border:4px solid black"> <legend>{chunk_names[j]}</legend> </div>')))
+        xml2::xml_remove(span_node)
+        # remove the extra line break that is left over from removing the span
+        code_node <- xml2::xml_child(pre_parent)
+        changed_txt <- stringr::str_remove(paste(as.character(xml2::xml_contents(code_node)),collapse=""), '\n')
+        xml2::xml_replace(code_node, xml2::read_xml(stringr::str_glue('<code>{changed_txt}</code>')))
+      }
+    }
+    # last thing is to insert an additional style node in the head with our CSS so we have a standard style whether we are using bookdown or Rmarkdown
+    css_string <- "fieldset.chunkfield {border:4px solid black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em}
+    legend.chunklegend {padding:0;font-size:21px;width:auto;border:0; border-bottom: none; margin-bottom:0}
+    "
+    head_node <- xml2::xml_find_first(parsed_html, ".//head")
+    xml2::xml_add_child(head_node, xml2::read_xml(stringr::str_glue("<style type='text/css'>{css_string}</style>")))
+    txt <- xml2::write_html(parsed_html, html_files[i])
+  }
+}
+

Since we rely on the xml2 package to add in the extra label formatting, let’s import it:

+
usethis::use_package("xml2")
+
## ✔ Adding 'xml2' to Imports field in DESCRIPTION
+## • Refer to functions with `xml2::fun()`
+

Finally, we want to replace the ANSI escape sequences used by packages such as testthat and devtools with their HTML equivalents so the output matches what we see in the terminal.

+
#' Replace ANSI escape sequences with their HTML equivalents
+#' 
+#' Finds ANSI escape sequences and replaces them with HTML tags using the `fansi` package
+#' 
+#' @param html_files Character vector of file names of html files that were created
+#' from Rmd files
+#' @keywords internal
+replace_ansi_sequences <- function(html_files) {
+  for (i in seq_along(html_files)) {
+    file_lines <- readLines(html_files[i])
+    # look for lines with escape sequences for URLs and remove the URL
+    # escape sequences before we convert to HTML
+    url_code_regex <- "\\033]8;;.*\\a(.*?)\\033]8;;\\a"
+    url_seq_idx <- which(stringr::str_detect(file_lines, url_code_regex))
+    file_lines[url_seq_idx] <- sapply(url_seq_idx, function(idx){
+      line <- file_lines[idx]
+      stringr::str_replace(line, url_code_regex, stringr::str_glue("\\1"))  
+    })
+    
+    txt <-
+      fansi::sgr_to_html(x = file_lines,
+                         warn = FALSE,
+                         term.cap = "256")
+    writeLines(txt, con = html_files[i])
+  }
+}
+
+
+

7.1.3 bookdown output format

+

It turns out that our modification to the bookdown::gitbook() format’s postprocessor is identical to the above. This suggests that we should probably reuse code more effectively. But for now I will leave it how it is:

+
#' litr version of `bookdown::gitbook()`
+#' 
+#' This behaves like `bookdown::gitbook()` with a few differences:
+#' - It creates an R package.
+#' - It adds hyperlinks to function definitions whenever a function is used
+#' elsewhere in the document.
+#' - It does "Knuth-style" chunk referencing with hyperlinks.
+#' 
+<<param-minimal_eval>>
+#' @param ... Parameters to be passed to `bookdown::gitbook()` 
+#' @export
+litr_gitbook <- function(minimal_eval = FALSE, ...) {
+  litr_gitbook_ <- litrify_output_format(bookdown::gitbook,
+                                         minimal_eval = minimal_eval)
+  old <- litr_gitbook_(...)
+  new <- old
+  # modify post_processor
+  new$post_processor = function(metadata, input_file, output_file, ...) {
+    out <- old$post_processor(metadata, input_file, output_file, ...)
+    out_dir <- fs::path_dir(out)
+    file_stems <- readLines(file.path(out_dir, "reference-keys.txt"))
+    html_files <- file.path(out_dir, paste0(file_stems, ".html"))
+    html_files <- unique(intersect(c(out, html_files), fs::dir_ls(out_dir)))
+    # add hyperlinks within html output to make it easier to navigate:
+    add_function_hyperlinks(html_files, metadata$params$package_name)
+    add_chunk_label_hyperlinks(html_files)
+    # replace ANSI sequences with HTML tag equivalents
+    replace_ansi_sequences(html_files)
+    out
+  }
+  new
+}
+

To use this output format, one would use bookdown::render_book() instead of rmarkdown::render(). In particular:

+
bookdown::render_book(output_format = litr::litr_gitbook())
+

The preamble in index.Rmd would look something like this:

+
---
+title: "A `litr` Book"
+author: "Your Name"
+site: bookdown::bookdown_site
+params:
+  package_name: "frombookdown" # <-- change this to your package name
+  package_parent_dir: "." # <-- relative to this file's location
+documentclass: book
+---
+

Or one can add to the preamble the lines

+
knit: litr::render
+output: litr::litr_gitbook
+

This first line makes it so that in RStudio when you press “Knit”, it calls litr::render(), and the second line makes it so that it will use the special litr bookdown output format.

+

Since the above function uses bookdown, we include it in our package:

+
usethis::use_package("bookdown")
+
## ✔ Adding 'bookdown' to Imports field in DESCRIPTION
+## • Refer to functions with `bookdown::fun()`
+
+
+
+

7.2 Defining litr::render()

+

There are two primary use cases for this function:

+
    +
  1. To render a .Rmd with a non-litr output format (e.g., rmarkdown::html_document) in such a way that it will generate an R package (and include the special litr-hyperlinking if .html files were created).

  2. +
  3. To render a .Rmd with a litr output format (including the litr_gitbook() format).

  4. +
+

The second use case might seem unnecessary in that

+
rmarkdown::render("create-pkg.Rmd", output_format = litr::litr_html_document())
+

or

+
bookdown::render_book("index.Rmd", output_format = litr::litr_gitbook())
+

would do what we want. However, a reason to still prefer litr::render() is that this function ensures the identical behavior to when one clicks the “Knit” in RStudio. It does this by opening a fresh R session (when fresh_session=TRUE, which is the default) in which rmarkdown::render() (or bookdown::render_book()) is called. This is based on the description in the Rmarkdown Cookbook. Another reason to prefer litr::render() is that if there is an error in the rendering process, the special litr hash will still be written to the DESCRIPTION file. This means that after fixing that error when one calls litr::render(), one will not get the error telling the user to delete the partially generated package directory. We accomplish this with the function with_cleanup() defined below.

+

In the first use case, litr::render() is responsible for ensuring all the special litr things happen (like setup() being called before knitting, the litr-hash being written afterwards, and hyperlinking occurs). The details of what it does is very similar to what is described in the output formats section, especially the one on the html output format.

+

One thing that is different is that we need a function get_params_used(), defined at the end of this section, that gets the actual parameters that are used so that the location of the outputted package can be found.

+
#' Render R markdown file
+#' 
+#' Wrapper to `rmarkdown::render()` that produces an R package as output in 
+#' addition to the standard output document.  It does some post-processing on the 
+#' .html file when that is the output.  In particular, when an .html file is among
+#' the outputs, it adds hyperlinks to functions defined within the file to make 
+#' it easier for someone reading the code to see where different functions are
+#' defined.
+#' 
+#' @param input The input file to be rendered (see `rmarkdown::render`)
+<<param-minimal_eval>>
+#' @param fresh_session Whether to call `rmarkdown::render` from a fresh R 
+#' session. By default TRUE, so that it matches the behavior of pressing "Knitr"
+#' in RStudio.  However, for debugging it can be useful to set this to FALSE so 
+#' that functions like `debug()` and `browser()` will work.
+#' @param ... Additional parameters to pass to `rmarkdown::render`
+#' @export
+render <- function(input, minimal_eval, fresh_session = TRUE, ...) {
+  # call rmarkdown::render in a new environment so it behaves the same as 
+  # pressing the knit button in RStudio:
+  # https://bookdown.org/yihui/rmarkdown-cookbook/rmarkdown-render.html
+  args <- list(...)
+
+  # let's determine if the output format being used is a litr format.
+  # If it is, then we'll simply want to call rmarkdown::render() since the 
+  # special litr behavior will be attained through the output format.
+  litr_format <- FALSE
+  bookdown_format <- FALSE
+  output_format_arg <- FALSE
+  if ("output_format" %in% names(args)) {
+    output_format_arg <- TRUE
+    if ("litr_format" %in% names(args$output_format)) {
+      litr_format <- TRUE
+    }
+    if ("bookdown_output_format" %in% names(args$output_format)) {
+      bookdown_format <- TRUE
+    }
+  } else {
+    frontmatter <- rmarkdown::yaml_front_matter(input)
+    if ("output" %in% names(frontmatter)) {
+      formats <- ifelse(is.list(frontmatter$output),
+                        names(frontmatter$output),
+                        frontmatter$output)
+      if (any(stringr::str_detect(formats, "litr::"))) {
+        litr_format <- TRUE
+      }
+      if (any(stringr::str_detect(formats, "litr::litr_gitbook"))) {
+        bookdown_format <- TRUE
+      }
+    }
+  }
+
+  # get package_directory
+  params <- get_params_used(input, args$params)
+  package_dir <- get_package_directory(
+    params$package_parent_dir,
+    params$package_name,
+    input
+    )
+  
+  # if minimal_eval was passed to render, add this to the output_options
+  # argument that will be passed to rmarkdown::render
+  if (is.null(args$output_options)) args$output_options <- list()
+  if (!missing(minimal_eval)) args$output_options$minimal_eval <- minimal_eval
+  
+  # determine whether a new R session will be created when we run the rendering 
+  # function of rmarkdown/bookdown
+  if (fresh_session)
+    run_function <- xfun::Rscript_call
+  else
+    run_function <- do.call
+  
+  if (litr_format) {
+    # this uses a litr output format, so we don't need to do anything litr-specific
+    # here because it will happen through the output format
+    
+    if (output_format_arg & !missing(minimal_eval)) {
+      # the output format was passed through the output_format argument rather 
+      # than through the metadata
+      if (minimal_eval) {
+        stop(make_noticeable(paste(
+          "When passing a litr output format using the output_format argument,",
+          "you should not pass minimal_eval = TRUE directly to render.",
+          "Instead, pass it to the litr output format function.  For example,",
+          "litr::litr_html_document(minimal_eval = TRUE).",
+          collapse = " "
+          )))
+      }
+    }
+    
+    if (bookdown_format) {
+      if (fs::is_file(input)) input <- fs::path_dir(input)
+      return(invisible(run_function(with_cleanup(bookdown::render_book,
+                                                 package_dir),
+                                    c(input = input, args))))
+    }
+    else
+      return(invisible(run_function(with_cleanup(rmarkdown::render,
+                                                 package_dir),
+                                    c(input = input, args))))
+  }
+  
+  # the output format being used is not a litr-specific one, so we need to make
+  # sure that all the special litr things happen
+  args$package_dir <- package_dir
+
+  render_ <- function(input, package_dir, minimal_eval, ...) {
+    knitr_objects <- litr:::setup(package_dir, minimal_eval)
+    out <- rmarkdown::render(input, ...)
+    restore_knitr_objects(knitr_objects)
+    # remove .Rproj and .gitignore if usethis::create_package() added these
+    remove_rstudio_extras(package_dir)
+    return(out)
+  }
+
+  if (missing(minimal_eval)) minimal_eval <- FALSE
+  out <- run_function(with_cleanup(render_, package_dir),
+                      c(input = input, minimal_eval = minimal_eval, args))
+
+
+  # add hyperlinks within html output to make it easier to navigate:
+  if (any(stringr::str_detect(out, "html$"))) {
+    html_file <- stringr::str_subset(out, "html$")
+    add_function_hyperlinks(html_file, params$package_name)
+    add_chunk_label_hyperlinks(html_file)
+  }
+  
+  # add to DESCRIPTION file the version of litr used to create package:
+  write_version_to_description(package_dir)
+  
+  # add litr hash so we can tell later if package files were manually edited:
+  write_hash_to_description(package_dir)
+}
+

We used the package xfun, so let’s import it:

+
usethis::use_package("xfun")
+
## ✔ Adding 'xfun' to Imports field in DESCRIPTION
+## • Refer to functions with `xfun::fun()`
+

When litr::render() encounters an error, it can leave the output directory partially modified. We want to make sure the litr hash still gets written to the DESCRIPTION file. Otherwise, the next time one calls litr::render() it does not allow this directory to be overwritten. We do this by using withCallingHandlers(). This function, explained here, is similar to tryCatch(), but with the advantage that it lets the function to continue to run normally, meaning that we will still get the error message as it would appear if we hadn’t done the condition handling.

+
#' Add litr hash to DESCRIPTION file if error encountered
+#' 
+#' This creates a function that calls the passed function within the context of
+#' a try-catch.  If an error is encountered, the litr hash is still added to
+#' the DESCRIPTION file so that future calls to `litr::render()` will recognize
+#' that it can safely overwrite the package directory (i.e., no manual editing
+#' occurred).
+#' 
+#' @param fun function being called
+#' @param package_dir directory where package is being written to
+#' @param ... arguments to be passed to `fun`
+#' @keywords internal
+with_cleanup <- function(fun, package_dir) {
+  return(function(...) {
+    withCallingHandlers(
+      fun(...),
+      error = function(e) {
+        # add litr hash so we can tell later if package files were manually edited:
+        write_hash_to_description(package_dir)
+      })
+  })
+}
+

In setup(), we modified the knitr objects (e.g., adding hooks, engines, etc.). We call the function restore_knitr_objects() after we’re done, to put things back how they were:

+
#' Return the knitr objects to their original state
+#' 
+#' @param original_knitr_objects As returned by `setup()`
+#' @keywords internal
+restore_knitr_objects <- function(original_knitr_objects) {
+  knitr::opts_knit$restore(original_knitr_objects$opts_knit)
+  knitr::knit_hooks$restore(original_knitr_objects$knit_hooks)
+  knitr::opts_chunk$restore(original_knitr_objects$opts_chunk)
+  knitr::opts_hooks$restore(original_knitr_objects$opts_hooks)
+  knitr::knit_engines$restore(original_knitr_objects$knit_engines)
+}
+

Another thing we want to do at the end of the rendering process is to remove two files that might have been created by usethis: .Rproj and .gitignore. These are created by usethis::create_package() when rstudio = TRUE. We don’t want these files created since this would suggest to a user that the R package should be worked on from within it rather than from the generating .Rmd file.

+
#' Remove extra files added by usethis
+#' 
+#' Remove .Rproj and .gitignore files if they are in the package directory.
+#' 
+#' @param package_dir Path to package
+#' @keywords internal
+remove_rstudio_extras <- function(package_dir) {
+  extras <- fs::dir_ls(package_dir,
+                       all = TRUE,
+                       regexp = "[.]Rproj$|[.]gitignore$")
+  rbuildignore <- file.path(package_dir, ".Rbuildignore")
+  txt <- readLines(rbuildignore)
+  txt <- stringr::str_subset(txt, "^.*Rproj.*$", negate = TRUE)
+  writeLines(txt, con = rbuildignore)
+  for (extra in extras) fs::file_delete(extra)
+}
+

As described earlier, the function get_params_used() combines the parameters from the YAML but allows for those values to be overridden through arguments passed to render().

+
#' Get parameter values used in rendering
+#' 
+#' When the `params` argument of `rmarkdown::render()` is explicitly used, this
+#' overrides the default that appears in `input`.
+#' @param input The input file to be rendered (see `rmarkdown::render`)
+#' @param passed_params The list of parameters that were passed to `render`.
+#' @keywords internal
+get_params_used <- function(input, passed_params) {
+  params <- rmarkdown::yaml_front_matter(input)$params
+  for (param in names(passed_params)) {
+    params[[param]] <- passed_params[[param]]
+  }
+  params
+}
+

We used the package rmarkdown, so let’s import it:

+
usethis::use_package("rmarkdown")
+
## ✔ Adding 'rmarkdown' to Imports field in DESCRIPTION
+## • Refer to functions with `rmarkdown::fun()`
+
+
+
+
+
    +
  1. For example, we include the line knit: litr::render in the yaml of the templates for this reason.↩︎

  2. +
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/_book/search_index.json b/create-litr/_book/search_index.json new file mode 100644 index 0000000..c2a993b --- /dev/null +++ b/create-litr/_book/search_index.json @@ -0,0 +1 @@ +[["index.html", "Creating the litr R package 1 Preamble", " Creating the litr R package Jacob Bien May 27, 2022 1 Preamble This document uses literate programming to define the litr R package. This means that all source code for litr is contained and presented in this document alongside explanation. The litr R package gets created as an output when one runs a piece of code that takes this document as input. To modify the litr package, one should modify the code in this document (and the explanation, as needed) and then regenerate the package. But what piece of code do we use to create an R package from this document? Well… it turns out that this is precisely what the litr package is for. But how can we use litr to create litr? We actually use version \\(n-1\\) of litr to create version \\(n\\). When an R markdown file is rendered with litr, an R package is created in addition to the usual html/pdf/etc document. To learn more about using litr, please visit the litr website. This document is not intended for people who wish to use litr. Rather, it is intended for people who want to know how it works and (possibly) modify it. This document used litr version 0.9.0 to define the current version of litr. In particular, it was generated by running the following command in an R console from the working directory containing index.Rmd: litr::render("index.Rmd") fs::dir_copy("_book", "../docs/create", overwrite = TRUE) fs::dir_delete("_book") The second and third lines move the generated bookdown to the docs/ directory so that it will render nicely on github by visiting https://jacobbien.github.io/litr-project/create/. "],["overview.html", "2 Overview", " 2 Overview The litr package consists of (a) document templates that users can use as the basis for creating an R package, (b) code needed so that when such a R markdown file is knitted, it will generate not just an .html (or some other output format) but also an R package. The code has the following components: Functionality for generating the R package when the .Rmd file is knitted. In particular, we define a knitr chunk hook, which we call send_to_package() that identifies code chunks in the .Rmd file that should be included in the package. To make it so that this chunk hook will be active, we also have a function called setup() that is called at the start of litr::render() right before rmarkdown::render() is called. The setup() function makes it so that when the .Rmd file is knitted, the chunk hook send_to_package() will be invoked on each code chunk. It is also responsible for other preliminaries, such as defining a new knitr language engine that can interpret package-level documentation. Functionality for making sure the R package outputted will not overwrite a manually edited R package. Our approach here is to use a hash that will make it clear whether something has been modified. Wrapper to devtools::document() The reason we write a wrapper for devtools::document() is because we want it to behave slightly differently. In particular, devtools::document() reminds the reader to edit the roxygen in the R/ files, whereas in our case, we want to make sure they edit the original .Rmd file, not the R/ files. Functionality to alter the rendering process The focus here is to make it so that all the special litr functionality will be active when the .Rmd is rendered. We implement two ways this can happen. First, we have several custom output formats, such as litr_html_document(). When rmarkdown::render() is called with one of these litr output formats, setup() gets called before knitting occurs and some other litr-specific post-processing occurs as well. The second way is through the function litr::render(). If a user calls litr::render() with a non-litr output format, e.g. rmarkdown::html_document(), then setup() gets called before rmarkdown::render() and also post-processing can again happen. Another thing of note about litr::render() is that it renders the document in a fresh environment, which ensures identical behavior to when a user presses “Knit” in RStudio. "],["package-setup.html", "3 Package setup 3.1 A note on circularity", " 3 Package setup We start by specifying the information needed in the DESCRIPTION file of the R package. We mostly follow the R Packages book’s description of version numbering. Releases 0.0.1, 0.0.2, and 0.0.3 should really have been 0.1.0, 0.2.0, and 0.3.0, because these were not just patches. Rather, each added quite substantial new functionality. For this reason, we have gone straight from 0.0.3 to 0.4.0. We will keep the leftmost number (“major release”) at 0 until we feel like the package is complete with all the intended features. usethis::create_package( path = ".", fields = list( Package = params$package_name, Version = "0.9.1", Title = "Literate Programming for Writing R Packages", Description = "Allows one to fully create an R package in a single .Rmd file. Includes functionality and .Rmd templates for a literate programming approach to R package development.", `Authors@R` = c( person("Jacob", "Bien", email = "jbien@usc.edu", role = c("aut", "cre")), person("Patrick Vossler", role = "aut") ) ) ) usethis::use_mit_license(copyright_holder = "J. Bien") Let’s add some package-level documentation. This is what will show up when someone types package?litr in the console. #' Literate Programming for Writing R Packages #' #' Allows one to fully create an R package in a single .Rmd file. Includes #' functionality and .Rmd templates for a literate programming approach to R #' package development. #' #' @examples #' # Make a file create-rhello.Rmd based on a template #' \\dontrun{ #' rmarkdown::draft("create-rhello.Rmd", #' template = "make-an-r-package", #' package = "litr", #' edit = FALSE) #' # Now call litr::render (or press Knit if in RStudio) to generate not just #' # create-rhello.html, but also an R package called `rhello`. #' litr::render("create-rhello.Rmd") #' } #' @docType package #' @seealso \\code{\\link{render}} 3.1 A note on circularity Keeping track of the version of litr used is particularly important when using litr to develop litr. There is a tendency to want to use the new functionality that we are creating in create-litr.Rmd itself as soon as we have defined it. However, this is circular and thus must be avoided. To see why we need to be careful, let’s consider an actual example that arose when working on litr version 0.0.3. After release v0.0.2, one of the new features we added is a new language engine called package_doc that allows us to have a special kind of code block defining the package documentation. In trying out this feature and making sure it works on skeleton.Rmd, we would most likely install version 0.0.3. Now that version 0.0.3 is installed, there will be a tendency to want to add a package_doc block to create-litr.Rmd, and it will appear to work. However, this is circular, because we have used version 0.0.3 to create version 0.0.3! In particular, if we remove litr and re-install it from github, we will get version 0.0.2 so that when we attempt to create the package using litr::render(\"create-litr.Rmd\"), we will get an error telling us that it doesn’t have a language engine named package_doc. The code chunk in this section is for preventing this from happening. For more on circularity, see the section on testing litr. install_version_of_litr <- utils::packageVersion("litr") remote <- remotes::github_remote( repo = "jacobbien/litr-project@*release", subdir = "litr" ) version_of_latest_release <- stringr::str_remove(remote$ref, "v") if (install_version_of_litr != version_of_latest_release) stop(stringr::str_glue( "You should be using the version of litr from the latest release (version", " {version_of_latest_release}),\\n but you are using version", " {install_version_of_litr}.\\n", "You can install the release version of litr from GitHub by running the", " the following command:\\n", "remotes::install_github(repo='jacobbien/litr-project@*release', subdir = 'litr')" )) "],["generating-package.html", "4 Generating the R package 4.1 Sending code chunks to the package 4.2 Setting up the R package creation", " 4 Generating the R package 4.1 Sending code chunks to the package We start by defining a chunk hook, which is a function that runs both before and after each code chunk is run in the knitting process. In this case, the function (called send_to_package) is responsible for determining whether the code chunk looks like something that should be exported to the R package. We don’t want all code sent off to our R package. For example, sometimes we’ll want to demonstrate in the Rmd file how a certain function we’ve just created is used by running it on an example or making a plot. That bit of example code would not be included in the package. We start by making sure that code is only sent to the R package once (arbitrarily we have code outputted to the package before and not after the chunk is run). The function then checks if this code chunk is code that should be put into the package. There are four specific cases it considers: If the special option send_to is used in this code chunk, then things are very simple in that the user has explicitly told us where this code should be added. For example, if send_to=\"R/file.R\", then the code in this chunk will be appended to R/file.R in the R package (and if that file doesn’t yet exist, it will be created). Is it a piece of code to be sent to the R/ directory? In particular, it checks to see if the code chunk begins with the characteristic roxygen2 characters #'. If it does, then the name of the object being documented is identified (could be a function, a dataset, an S4 object, etc.) and then we write the code chunk to the file R/<objectname>.R. If the code chunk does not start with #', then we check if it has any line starting with test_that( or testthat::test_that(. If so, then this whole code chunk is appended to tests/testthat/tests.R (and this file is created the first time a test chunk is sent to the package). Next, it checks if the language engine is Rcpp. This occurs when the code chunk starts with {Rcpp, rather than the usual {r (or alternatively when the engine=\"Rcpp\" option is used). We then set things up appropriately for the use of Rcpp within the package (by adapting some code from within the usethis::use_rcpp() function). Finally, we write the code chunk to src/code.cpp. There is a common header used, #include <Rcpp.h> using namespace Rcpp; and we only want this to appear once in code.cpp, so we do a bit of work to remove that if it appears in the code chunk. #' A knitr chunk hook for writing R code and tests #' #' This chunk hook detects whether a chunk is defining a function or dataset #' to be included in the R package (looks for the `roxygen2` comment format `#' `). #' If so, then it is written to the `R/` directory. It also looks for chunks #' that have one or more lines that start with `test_that(` or #' `testthat::test_that(` (potentially with some leading whitespace). These #' chunks are then written to the `tests` directory of the R package. #' #' When the `send_to` option is used, this chunk hook instead simply writes the #' code chunk to the file specified. #' #' @param before Indicates whether this is being called before or after the #' chunk code is executed #' @param options Has information from the chunk #' @param envir Environment #' @keywords internal send_to_package <- function(before, options, envir) { msg <- do_not_edit_message(knitr::current_input(), type = "R") if (before == FALSE) { # Don't do anything after the code chunk has been executed. return() } package_dir <- knitr::opts_knit$get("root.dir") package_name <- fs::path_file(package_dir) if (!is.null(options$send_to)) { # the user has defined an option that indicates where in the package this # code should be written file <- file.path(package_dir, options$send_to) add_text_to_file(options$code, file, pad = TRUE, msg = msg) return() } if (stringr::str_detect(options$code[1], "^#' ")) { # starts with roxygen2, so let's assume this chunk is defining an R function # or dataset that belongs in the package non_comment <- stringr::str_subset(options$code, "^#", negate = TRUE) if (length(non_comment) > 0) { if (stringr::str_detect(non_comment[1], "<-")) { # a function is being defined objname <- stringr::str_match(non_comment[1], "^(.*)\\\\s*<-\\\\s*function")[, 2] objname <- stringr::str_trim(objname) } else if (stringr::str_detect(non_comment[1], '^".+"$')) { # a dataset is being documented objname <- stringr::str_sub(non_comment[1], start = 2, end = -2) } else { # Roxygen2 comment wasn't followed by anything recognized, so do not # send this to package return() } file <- file.path(package_dir, "R", stringr::str_glue("{objname}.R")) cat(paste(c(msg, "", options$code, ""), collapse = "\\n"), file = file) } } else if (any(stringr::str_detect(options$code, "^\\\\s*(testthat::)?test_that\\\\("))) { # This chunk is inferred to be a test test_dir <- file.path(package_dir, "tests", "testthat") test_file <- file.path(test_dir, "tests.R") if (!file.exists(test_file)) { # It's the first chunk with tests if (!dir.exists(test_dir)) usethis::use_testthat() cat(c(msg, ""), collapse = "\\n", file = test_file) } cat( paste(c(options$code, "", ""), collapse = "\\n"), file = test_file, append = TRUE ) } else if (options$engine == "Rcpp") { # To add Rcpp code, we need the package documentation file to exist if (!file.exists(file.path( package_dir, "R", paste0(package_name, "-package.R")) )) { usethis::use_package_doc(open = FALSE) } cpp_file <- file.path(package_dir, "src", "code.cpp") if (!file.exists(cpp_file)) { # set up package for Rcpp # these next few lines are taken from usethis::use_rcpp() # it approximates a call to usethis::use_rcpp(name = "code") usethis:::use_dependency("Rcpp", "LinkingTo") usethis:::use_dependency("Rcpp", "Imports") usethis:::roxygen_ns_append("@importFrom Rcpp sourceCpp") usethis:::use_src() usethis::use_template("code.cpp", save_as = "src/code.cpp") msg <- do_not_edit_message(knitr::current_input(), type = "c") cat(msg, file = cpp_file, append = TRUE) } # append code to code.cpp, but remove lines that are `#include <Rcpp.h>` # or `using namespace Rcpp;` since this already appears at top of file cat(paste(c( "", stringr::str_subset( options$code, r"(^#include <Rcpp.h>$|^using namespace Rcpp;$)", negate = TRUE), ""), collapse = "\\n"), file = cpp_file, append = TRUE) } return() } The above code makes use of a number of functions from the stringr and usethis packages, so we’ll need to add those packages to the Imports section of the DESCRIPTION file: usethis::use_package("stringr") usethis::use_package("usethis") ## ✔ Adding 'stringr' to Imports field in DESCRIPTION ## • Refer to functions with `stringr::fun()` ## ✔ Adding 'usethis' to Imports field in DESCRIPTION ## • Refer to functions with `usethis::fun()` The code also calls the function do_not_edit_message(), which adds a line at the top of the files sent to the R package reminding the user that these are not source files to be edited but rather output of the generating .Rmd file. There are two variations on this message. #' Generate do-not-edit message to put at top of file #' #' @param rmd_file Name of the Rmd file to mention #' @param type Whether this is a R/ file, man/ file, or a c file #' @keywords internal do_not_edit_message <- function(rmd_file, type = c("R", "man", "c")) { if (type[1] == "R") return(stringr::str_glue("# Generated from {rmd_file}: do not edit by hand")) else if (type[1] == "man") return(stringr::str_glue("% Please edit documentation in {rmd_file}.")) else if (type[1] == "c") return(stringr::str_glue("// Generated from {rmd_file}: do not edit by hand")) else stop("type must be either 'R', 'man', or 'c'.") } This function will also be used with type = \"man\" by litr::document(). The above also makes use of a simple helper function that inserts text into a specified location of a file (or creates that file if it doesn’t exist). Actually currently it doesn’t, but we can replace cat() in the above with calls to add_text_to_file(). #' Add Some Text to a File #' #' The text will be added to the file at a particular line specified by #' `location`. The first line of `txt` will be on line `location` of the #' modified file. If `location` is NULL, then text is added to end of file. #' If file does not exist, it is created and `location` is ignored (unless #' `req_exist` is `TRUE`, in which case an error is thrown). #' #' @param txt Character vector to add to file #' @param filename Name of file #' @param location Specifies where text should be added. See description for more. #' @param req_exist If TRUE, then throws an error if file doesn't exist #' @param pad If TRUE, then when text is being added to a preexisting file, it adds a newline #' @param msg An optional message to put at top of file if this is a new file #' @keywords internal add_text_to_file <- function(txt, filename, location = NULL, req_exist = FALSE, pad = FALSE, msg = NULL) { if (!file.exists(filename)) { if (req_exist) stop(stringr::str_glue("Cannot find file {filename}.")) if (!is.null(msg)) txt <- c(msg, "", txt) writeLines(txt, con = filename) return() } if (pad) txt <- c("", txt) filetxt <- readLines(filename) if (is.null(location) || location == length(filetxt) + 1) { filetxt <- c(filetxt, txt) } else if (location > length(filetxt) + 1 | location < 1) stop("Invalid location") else if (location == 1) { filetxt <- c(txt, filetxt) } else { # location is somewhere in middle filetxt <- c(filetxt[1:(location - 1)], txt, filetxt[location:length(filetxt)]) } writeLines(filetxt, con = filename) } testthat::test_that("add_text_to_file() works", { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) # should throw error when file does not exist and req_exist is TRUE: myfile <- file.path(dir, "file.txt") sometxt <- c("hello", "there") testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE)) # should create a new file where one does not exist: myfile <- file.path(dir, "file.txt") sometxt <- c("hello", "there") add_text_to_file(sometxt, myfile) testthat::expect_true(fs::file_exists(myfile)) testthat::expect_equal(sometxt, readLines(myfile)) # should append to end of file by default moretxt <- "world" add_text_to_file(moretxt, myfile) testthat::expect_equal(c(sometxt, moretxt), readLines(myfile)) # should throw error for invalid locations: testthat::expect_error(add_text_to_file(sometxt, myfile, 0)) testthat::expect_error(add_text_to_file(sometxt, myfile, -1)) testthat::expect_error(add_text_to_file(sometxt, myfile, 5)) # should add to specified line: moretxt2 <- "hi" add_text_to_file(moretxt2, myfile, 1) testthat::expect_equal(c(moretxt2, sometxt, moretxt), readLines(myfile)) # should add to specified line: moretxt3 <- "hi2" add_text_to_file(moretxt3, myfile, 2) testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt), readLines(myfile)) # should add to specified line: moretxt4 <- "hi3" add_text_to_file(moretxt4, myfile, 6) testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt, moretxt4), readLines(myfile)) fs::dir_delete(dir) }) ## Test passed 4.2 Setting up the R package creation When the user calls litr::render() (either in the console or by pressing “Knit” in RStudio), one of the first things that function does is to call the function litr::setup(), which does several things: Creates a new empty directory at the specified location while first making sure that it won’t overwrite something it shouldn’t. In particular, we guard against the case that the package was generated by litr::render() but then someone went in manually and made some changes. Even though users should never manually edit the package that was generated by litr::render(), we don’t want to have them inadvertently lose their work by doing so. Thus, we only overwrite an R package if we can tell that it is the unedited output of a call to litr::render(). The function check_unedited() is responsible for checking this, and is a pretty interesting function which we will describe in the next section. This part of the code also makes use of a function litr::make_noticeable(), which is simply a way of making error messages produced by litr more easy to see amid a lot of knitr output. Adjusts the root directory from the generating .Rmd file’s location to the R package’s location. Note: This behavior might not actually be desirable now that additional files will be loaded in. It might be awkward for a user writing the generating .Rmd file to have to make everything relative to the package. It might be convenient to provide a litr::add_file(from, to) function, where from is the path relative to the .Rmd file and to is the path relative to the package’s location. Makes it so that the send_to_package() chunk hook is active for each code chunk. This involves registering a new chunk hook using the function knitr::knit_hooks$set() and then setting an option with the same name to TRUE within each chunk. Deactivates an internal function of the usethis package, usethis:::challenge_nested_project(). This was actually a difficult issue to address that involves the intersection of usethis, here, and our particular use case. The problem is that usethis was not designed for our setting in which an R package is being created programmatically. When using litr, the project directory will have the generating .Rmd file and when this is knit it will create an R package within this project. However, this leads usethis to prompt the user with a message of the form “New project ‘[…]’ is nested inside an existing project ‘[…]’. This is rarely a good idea. Do you wish to create anyway?” But since this is encountered through knitting rather than interactively, this results in an error. This usethis issue describes this exact problem. The solution suggested there by jennybc involving testthat::with_mock() is along the lines of what we want; however, that would lead to some ugly looking code in the generating .Rmd file. The best solution I could find was to use utils::assignInNamespace() as described here. This function allows us to change the internal function usethis:::challenge_nested_project() so that it no longer prompts the user with concerns about nested projects. Changes how chunk references are handled. In particular, consider the following code chunk: a <- 2 <<my-chunk>> a The way knitr handles this, the code chunk would no longer look like this but it would rather have replaced the <<my-chunk>> line by the code that appears in the code chunk labeled “my-chunk”. We instead would like the above code chunk to appear as written and then for the code chunk labeled “my-chunk” to have its label visible to the reader of the .html file. This gives the coder more control over when the reader learns about different parts of the code. It also more closely resembles Donald Knuth’s form of literate programming. For convenience, we’d like <<my-chunk>> to be a link that navigates to the code chunk labeled “my-chunk”. To accomplish this, we modify the document output hook in setup() (and then we also add a function called add_chunk_label_hyperlinks() within render()). Define a package_doc engine which allows users to define package-level documentation. #' Code for setup chunk #' #' * Creates directory where package will be. (Deletes what is currently there as #' long as it appears to have been created by litr and does not have any #' subsequent manual edits.) #' * Sets the root directory to this directory #' * Sets up the main chunk hook `litr::send_to_package()` that sends code to the #' R package directory. #' * In the case that `minimal_eval=TRUE`, sets up an options hook for `eval` so #' chunks are only evaluated if there is a `usethis` or `litr::document()` #' command #' * Deactivates an internal function of the `usethis` package #' * Redefines the document output hook to handle chunk references differently #' * Sets up a [custom language engine](https://bookdown.org/yihui/rmarkdown-cookbook/custom-engine.html) called #' `package_doc` that creates a package documentation file and then inserts #' whatever the user puts in the chunk. #' #' Returns the original state of the knitr objects that have been modified in #' setup. This allows us to return things to the previous state after we are #' finished. This is relevant in the case where litr-knitting occurs in the #' current session and we don't want to leave things in a permanently modified #' state. #' #' @param package_dir Directory where R package will be created <<param-minimal_eval>> #' @keywords internal setup <- function(package_dir, minimal_eval) { if (file.exists(package_dir)) { unedited <- tryCatch(check_unedited(package_dir), error = function(e) { # contents of package_dir does not resemble # a litr package return(FALSE) }) if (!unedited) { stop(make_noticeable(paste( stringr::str_glue("The directory {normalizePath(package_dir)}"), "already exists and either was not created by litr or may have manual", "edits. In either case, please rename that directory (or delete it)", "and then try again.", sep = "\\n"))) } unlink(package_dir, recursive = TRUE) } fs::dir_create(package_dir) usethis:::proj_set_(usethis:::proj_path_prep(package_dir)) # let's keep a version of the knitr objects before modifying them: original_knitr <- list(opts_knit = knitr::opts_knit$get(), knit_hooks = knitr::knit_hooks$get(), opts_chunk = knitr::opts_chunk$get(), opts_hooks = knitr::opts_hooks$get(), knit_engines = knitr::knit_engines$get() ) knitr::opts_knit$set(root.dir = package_dir) # sets wd of future chunks knitr::knit_hooks$set(send_to_package = send_to_package) knitr::opts_chunk$set(send_to_package = TRUE) if (minimal_eval) { # only evaluate chunks that appear to include usethis commands or # a call to litr::document() but if someone has specifically set eval=FALSE # in a particular chunk, do honor that usethis_exports <- getNamespaceExports("usethis") patterns <- paste(c("usethis::", usethis_exports, "litr::document\\\\("), collapse = "|") knitr::opts_hooks$set(eval = function(options) { if (options$eval) options$eval <- any(stringr::str_detect(options$code, patterns)) return(options) }) } # change usethis:::challenge_nested_project so that it will not complain # about creating a nested project (e.g. if this is called within a git # subdirectory) utils::assignInNamespace("challenge_nested_project", function(...) NULL, ns = "usethis") # define document hook to handle chunk references: knitr::knit_hooks$set(document = function(x) { # get the indices of x corresponding to code chunks chunk_start <- "^(\\n```+[a-zA-Z0-9_]+\\n)" idx_block <- stringr::str_which(x, chunk_start) original_code <- knitr::knit_code$get() # We first get indices of skipped chunks in original_code list skipped_chunks <- which(sapply(original_code, function(x){ return(isFALSE(attr(x, "chunk_opts")$echo) || isFALSE(attr(x, "chunk_opts")$include)) })) # Next we remove the indices of skipped chunks original_code_idx_fixed <- setdiff(seq(length(original_code)), skipped_chunks) labels <- names(original_code) # replace each x[i] that has code in it with the original code for (i in seq_along(idx_block)) { # break code into multiple lines: chunk <- strsplit(x[idx_block[i]], "\\n")[[1]] # get the fence used (in case it's more than three ticks): i_start <- stringr::str_which(chunk, "^```+[a-zA-Z0-9_]+") fence <- stringr::str_replace(chunk[i_start[1]], "^(```+)[a-zA-Z0-9_]+", "\\\\1") i_fences <- stringr::str_which(chunk, paste0("^", fence)) # there can be multiple code and output chunks strung together # within a single x[i] if results are not held to end i_all_code <- c() for (j in seq_along(i_start)) { # get the elements corresponding the j-th code chunk within chunk i_code_end <- i_fences[which(i_fences == i_start[j]) + 1] i_all_code <- c(i_all_code, i_start[j]:i_code_end) } i_all_code <- setdiff(i_all_code, i_start[1]) chunk_no_code <- chunk[-i_all_code] chunk <- c(chunk_no_code[1:i_start[1]], original_code[original_code_idx_fixed[i]][[1]], # insert the original version, accounting for skipped chunks fence) if (i_start[1] < length(chunk_no_code)) chunk <- c(chunk, chunk_no_code[(i_start[1] + 1):length(chunk_no_code)]) x[idx_block[i]] <- paste(chunk, collapse = "\\n") } # replace code chunks with the original code # (so we'll still have <<label>> chunk references) refs <- c() # labels that get referred to for (label in labels) { refs <- c(refs, find_labels(original_code[[label]])$chunk_ids) } refs <- unique(refs) adj_labels <- labels[!labels %in% names(skipped_chunks)] ref_id <- match(refs, adj_labels) if (any(is.na(ref_id))) { stop(make_noticeable(paste( stringr::str_glue("The chunk reference <<{refs[is.na(ref_id)][1]}>> ", "is used, but there is no chunk with that label.", sep = "\\n")))) } to_insert <- paste0('###"', adj_labels[ref_id], '"###\\n') x[idx_block[ref_id]] <- stringr::str_replace(x[idx_block[ref_id]], chunk_start, paste0("\\\\1", to_insert)) x }) # setup package_doc engine knitr::knit_engines$set(package_doc = function(options) { # create package_doc usethis::use_package_doc(open = FALSE) # insert the contents of the code chunk into the package_doc pkgdoc <- file.path("R", paste0(fs::path_file(package_dir), "-package.R")) add_text_to_file(options$code, filename = pkgdoc, location = 1) # now treat this as if it were standard R code with eval=FALSE r_engine <- knitr::knit_engines$get("R") options[["eval"]] <- FALSE return(r_engine(options)) }) return(original_knitr) } In our new document output hook defined above, we call a function find_labels(). It takes a block of code and returns both a logical vector of which lines contained chunk labels and another vector containing the labels of those referenced chunks. We define it here: #' Find a .Rmd chunk label in a code chunk #' #' @param chunk_code Character vector of code from a .Rmd code chunk. Each element is a line of the code chunk. #' @return List where chunk_idx is a logical vector for each line of the chunk corresponding to whether a chunk label of the form `<<label>>` was found and chunk_ids is a character vector of chunk label was found in that chunk. #' @keywords internal find_labels <- function(chunk_code) { rc <- knitr::all_patterns$md$ref.chunk chunk_idx <- any(idx = grepl(rc, chunk_code)) chunk_ids <- stringr::str_trim(sub(rc, "\\\\1", chunk_code[grepl(rc, chunk_code)])) return(list(chunk_idx = chunk_idx, chunk_ids = chunk_ids)) } The setup() function also uses a small function, make_noticeable(), which we define here: #' Make error messages noticeable #' #' Since litr error messages are amid a lot of output from knitting, we'd like #' the litr ones to be eye-catching. #' #' @param msg Error message #' @keywords internal make_noticeable <- function(msg) { paste("", "======", "Please read your friendly litr error message here:", paste("> ", msg), "======", sep = "\\n") } The code in this section used the fs and knitr packages, so we import those: usethis::use_package("fs") usethis::use_package("knitr") ## ✔ Adding 'fs' to Imports field in DESCRIPTION ## • Refer to functions with `fs::fun()` ## ✔ Adding 'knitr' to Imports field in DESCRIPTION ## • Refer to functions with `knitr::fun()` "],["hash.html", "5 Not overwriting a manually edited R package", " 5 Not overwriting a manually edited R package As described in the previous section, the function setup() will only overwrite a directory if it is the unedited output from using litr. The basic idea is that the function litr::render() when creating a new package finishes by adding a hash to the DESCRIPTION file. (And likewise when rmarkdown::render() is used with a litr output format, as described here.) This hash is a function of everything in the package, so if anything about the package changes (any file is modified, added, or removed) then the function check_unedited() will be able to detect that by recomputing the hash and seeing that it doesn’t match the hash in the DESCRIPTION file. Let’s start by defining the function hash_package_directory() that does the hashing. The hash is a function of everything in the outputted package except for that special line in the DESCRIPTION file with the hash. We use tools::md5sum() and digest::digest() to do the hashing. #' Hash package directory #' #' Gets an identifier that can be used to uniquely (whp) identify the current #' state of the package. This is formed by ignoring the `LitrId` field of the #' DESCRIPTION file, which is the location where the output of this function is #' stored when `litr::render` generates the package. #' #' @param package_dir Path to package #' @keywords internal hash_package_directory <- function(package_dir) { pkg_files <- fs::dir_ls(package_dir, recurse = TRUE, all = TRUE, type = "file") pkg_files <- stringr::str_subset(pkg_files, ".DS_Store$", negate = TRUE) pkg_files <- normalizePath(pkg_files) descr_file <- normalizePath(file.path(package_dir, "DESCRIPTION")) i_descr <- which(pkg_files == descr_file) if (length(i_descr) == 0) stop("Cannot find DESCRIPTION file.") txt_descr <- readLines(pkg_files[i_descr]) txt_descr_mod <- stringr::str_subset( txt_descr, stringr::str_glue("{description_litr_hash_field_name()}: .+$"), negate = TRUE) hashes <- as.character(tools::md5sum(pkg_files[-i_descr])) digest::digest(c(hashes, list(txt_descr_mod))) } We used digest, so let’s import it: usethis::use_package("digest") ## ✔ Adding 'digest' to Imports field in DESCRIPTION ## • Refer to functions with `digest::fun()` We will store this hash in a special field within the DESCRIPTION file. Let’s call this field LitrId. However, in case we ever decide to change the name of this field, it’s better that we only define it in one place. So we do this with the following function: #' Generate litr hash field name for DESCRIPTION file #' @keywords internal description_litr_hash_field_name <- function() return("LitrId") Ok, now let’s write the function that litr::render() will call that will take the generated R package and add a line that puts the hash in the DESCRIPTION file under that special litr field: #' Write the hash of the package to the DESCRIPTION file #' #' @param package_dir Path to package #' @keywords internal write_hash_to_description <- function(package_dir) { desc_file <- file.path(package_dir, "DESCRIPTION") if (!file.exists(desc_file)) file.create(desc_file) hash <- hash_package_directory(package_dir) desc::desc_set(description_litr_hash_field_name(), hash, file = desc_file) } Let’s include the desc package, which helps us manipulate DESCRIPTION files. usethis::use_package("desc") ## ✔ Adding 'desc' to Imports field in DESCRIPTION ## • Refer to functions with `desc::fun()` And of course we’ll need a function that can read the value of that field as well: #' Get the hash of the package from the DESCRIPTION file #' #' @param package_dir Path to package #' @keywords internal read_hash_from_description <- function(package_dir) { descr <- file.path(package_dir, "DESCRIPTION") if (!file.exists(descr)) stop("Cannot find DESCRIPTION file.") txt <- stringr::str_subset( readLines(descr), stringr::str_glue("{description_litr_hash_field_name()}: .+$")) if (length(txt) > 1) stop("More than one hash found in DESCRIPTION.") if (length(txt) == 0) stop("No hash found in DESCRIPTION.") stringr::str_extract(txt, "\\\\S+$") } With all this hash functionality in place, the function check_unedited() is actually quite simple to define: #' Check if package directory is the unedited output of litr::render() #' #' Uses hash stored in a special `litr` field of DESCRIPTION file to check that #' the current state of the R package directory is identical to its state at the #' time that it was created by `litr::render()`. #' #' @param package_dir Path to package #' @keywords internal check_unedited <- function(package_dir) { hash <- hash_package_directory(package_dir) hash == read_hash_from_description(package_dir) } It simply computes the hash of the current package and checks whether that hash is the same as what was originally written to the DESCRIPTION file by litr::render(). "],["document.html", "6 Wrapper to devtools::document()", " 6 Wrapper to devtools::document() This function is nearly identical to devtools::document() except that it changes the roxygen2 message that says “Please edit documentation in R/[…].R” to instead mention the generating .Rmd file. When Rcpp is used, it also makes sure that #include <RcppArmadillo.h> comes before #include <Rcpp.h>. #' Use roxygen to document a package from within a Rmd file #' #' This is a wrapper for the `devtools::document()` function, which in turn is a #' wrapper for the `roxygen2::roxygenize()` function. It is written assuming that #' it is being called from within a generating Rmd file. The purpose for `litr` #' having this wrapper is two-fold. First, it ensures that the first line #' in the outputted `Rd` files should not say "Please edit documentation in #' R/file.R" but instead should refer to the Rmd file that generates everything. #' Second, in the case that Rcpp is being used, it makes some adjustments to ensure #' that the compiling of the C++ code should be successful. #' #' @param ... Arguments to be passed to `devtools::document()` #' @export document <- function(...) { # prepare Rcpp code for compiling if (fs::file_exists("src/code.cpp")) { # make sure that #include <RcppArmadillo.h> if it exists # comes *before* (or instead of) <Rcpp.h> txt <- readLines("src/code.cpp") loc <- stringr::str_which(txt, r"(#include <RcppArmadillo.h>)") if (length(loc) > 0) { include_arma_line <- txt[loc[1]] txt <- c(include_arma_line, txt[-loc]) writeLines(txt, "src/code.cpp") } } devtools::document(...) # remove the line of the following form in each man/*.Rd file: pattern <- "% Please edit documentation in .*$" msg <- do_not_edit_message(knitr::current_input(), type = "man") for (fname in fs::dir_ls("man")) { txt <- stringr::str_replace(readLines(fname), pattern, msg) cat(paste(txt, collapse = "\\n"), file = fname) } } We used devtools, so let’s import it: usethis::use_package("devtools") ## ✔ Adding 'devtools' to Imports field in DESCRIPTION ## • Refer to functions with `devtools::fun()` "],["rendering.html", "7 Altering the rendering process 7.1 Defining litr output formats 7.2 Defining litr::render()", " 7 Altering the rendering process The focus here is to make it so that all the special litr functionality will be active when the .Rmd is rendered. In particular, we need to make sure that setup() has been called before knitting occurs (so that, for example, send_to_package() will be active). We also need to make sure that after knitting certain things occur, such as the litr-hash being written to the package and, in some cases, hyperlinks are added for easier navigation. We implement two ways for the above to occur, which we describe in the next two subsections: The first approach is by defining custom litr output formats. When rmarkdown::render() (or bookdown::render_book()) is called with one of these litr output formats, the litr-specific operations occur before and after knitting. The second way is through the function litr::render(). If a user calls litr::render() with a non-litr output format, e.g. rmarkdown::html_document(), then it adds the necessary litr-specific operations before/after rmarkdown::render() is called. Another thing of note about litr::render() is that it renders the document in a fresh environment, which ensures identical behavior to when a user presses “Knit” in RStudio. Although these are presented as two separate approaches, we have written litr::render() so that if a user passes one of the litr output formats to litr::render(), it will still work. We encourage users to use litr::render()1 rather than rmarkdown::render() since in litr::render() we’re able to wrap the call to rmarkdown::render() in the function with_cleanup(). This ensures that, if an error occurs during the knitting process, the special litr hash will still be created. This is desirable since it means that the next time we try to litr-knit, we will not get an error about overwriting a manually edited package directory. When coding an R package with litr, sometimes there are code chunks that can take a while to evaluate (e.g., tests), which slows down the coding process. We therefore provide an argument (to both litr::render() and the various litr output formats) that allows for “minimal eval” to occur. The goal is to allow the R package to be updated completely but without any of the code chunks being evaluated, except those whose involving usethis or a call to litr::document(), since these commands lead to changes in the R package itself. Here is the documentation associated with the minimal_eval parameter, which is an argument to a number of functions in this section: ###"param-minimal_eval"### #' @param minimal_eval If `TRUE`, then only chunks with `litr::document()` or #' `usethis` commands will be evaluated. This can be convenient in coding when #' you just want to quickly update the R package without having to wait for long #' evaluations to occur. 7.1 Defining litr output formats The function rmarkdown::render() allows for customizable behavior through the use of custom output formats. Given a preexisting output format (e.g. rmarkdown::html_document), we would like to modify it to have litr-behavior – i.e., to create a package as it is being rendered. This next function takes a preexisting output format and “litr-ifies” it by making three changes: It modifies the pre_knit() function. It modifies the post_processor() function. It adds a marker (litr_format <- TRUE) that will help litr::render() know when a litr output format is being passed to it. We present the function and then describe the details of the new pre_knit() and post_processor() functions below. #' Modify an existing output format to have `litr` behavior #' #' This function modifies the `pre_knit()` and `post_processor()` functions of a #' preexisting output format so that it will have the `litr` behavior (meaning that an R package will be created when `rmarkdown::render()` is called). #' #' @param base_format a preexisting, non-litr output format such as `rmarkdown::html_document` <<param-minimal_eval>> #' @export litrify_output_format <- function(base_format = rmarkdown::html_document, minimal_eval = FALSE) { force(base_format) # I think using force here is advisable? force(minimal_eval) # https://adv-r.hadley.nz/function-factories.html function(...) { old <- base_format(...) new <- old new$original_knitr_objects <- list() new$pre_knit <- function(...) { args <- list(...) input <- args$input params <- knitr::knit_params(readLines(input)) package_dir <- get_package_directory( params$package_parent_dir$value, params$package_name$value, input) new$original_knitr_objects <<- litr:::setup(package_dir, minimal_eval) if (!is.null(old$pre_knit)) old$pre_knit(...) } new$post_processor <- function(metadata, input_file, output_file, ...) { # typically the post_processor function returns the output file path # if old$post_processor is NULL, as in the case of pdf_document, # then R will throw an error when trying to call old$post_processor # if we only add a check for non null old$post_processor and otherwise # set out <- NULL then R will throw an error later in rmarkdown::render # since output_file is set to the output of the post_processor if # output_format$post_processor is not null (See line 478 in rmarkdown::render) # Therefore, our solution is to set out to the output_file path if old$post_process is null. if (!is.null(old$post_processor)){ out <- old$post_processor(metadata, input_file, output_file, ...) } else { out <- output_file } package_dir <- get_package_directory( metadata$params$package_parent_dir, metadata$params$package_name, input_file ) # remove .Rproj and .gitignore if usethis::create_package() added these remove_rstudio_extras(package_dir) # add to DESCRIPTION file the version of litr used to create package: write_version_to_description(package_dir) # add litr hash so we can tell later if package files were manually edited: write_hash_to_description(package_dir) out } new$on_exit <- function() { old$on_exit() # restore knitr to its original state restore_knitr_objects(new$original_knitr_objects) } # mark this as a litr_format new$litr_format <- TRUE # litr formats have minimal_eval as an option new$minimal_eval <- minimal_eval new } } The pre_knit() function is modified so that setup() is called before the preexisting output format’s pre_knit() function is called. As the name suggests, this is a function that gets called before knitting. The purpose of the call to setup() is to create the R package directory and make it so that when we knit the file using rmarkdown::render(), a lot of special things will happen, such as code being sent to the R package directory. The function setup() returns the state of the knitr settings before any changes were made. This previous state of the knitr settings will be restored at the end of the rendering process in on_exit(). The post_processor() function is modified so that the DESCRIPTION file gets marked with the version of litr used and with the litr hash (as already described here). Some special care is taken for the case that the original output format doesn’t have a post processor (e.g., this is the case for the pdf_document output format). The particulars of this are given in a comment in the code chunk above. The on_exit() function is modified so that it restores the state of all the knitr settings to how it was when render was first called. We use the above function to create some litr versions of common output formats, as seen in the next few subsections. Before proceeding, we define the function write_version_to_description() that is called above. #' Generate litr version field name for DESCRIPTION file #' @keywords internal description_litr_version_field_name <- function() return("LitrVersionUsed") #' Write the version of litr used to the DESCRIPTION file #' #' @param package_dir Path to package #' @keywords internal write_version_to_description <- function(package_dir) { ver <- as.character(utils::packageVersion("litr")) add_text_to_file( txt = stringr::str_glue("{description_litr_version_field_name()}: {ver}"), filename = file.path(package_dir, "DESCRIPTION"), req_exist = TRUE ) } Also, we made use of a small function for getting the package directory based on the input file’s location and the parameters that are being used in the rendering process. We define it here: #' Get package directory #' #' @param package_parent_dir The directory of where the package should go (relative to the input directory) #' @param package_name The name of the package #' @param input The file name of the input #' @keywords internal get_package_directory <- function(package_parent_dir, package_name, input) { if (package_parent_dir == ".") return(file.path(dirname(input), package_name)) file.path(dirname(input), package_parent_dir, package_name) } Let’s write some tests to make sure it’s behaving as expected: testthat::test_that("get_package_directory() works", { input <- file.path("inputdir", "input.Rmd") testthat::expect_equal( get_package_directory(".", "mypkg", input), file.path("inputdir", "mypkg") # inputdir/mypkg ) testthat::expect_equal( get_package_directory("..", "mypkg", input), file.path("inputdir", "..", "mypkg") # inputdir/../mypkg ) }) ## Test passed 7.1.1 .pdf output format We want our .pdf documents to accurately display the “logging” output from functions in packages such as devtools that use special ANSI escape codes for displaying information in the terminal. Unfortunately, these codes use escape characters which cause problems when creating .pdf versions of our documents. While it is relatively straightforward to map ANSI escape codes to HTML tags, as we will see in the .html output format section, converting these escape codes to Latex commands is more complicated. As a result, we define a post_knit function in our litr_pdf_document format to avoid this issue by stripping out all escape codes in the file before it is converted into a .tex file and then compiled into a .pdf document. Specifically, the post_knit function modifies the intermediate .knit.md file, which contains both the .Rmd file, as well as the output of each code chunk. Thus, we can inspect the output text of each code chunk and remove any ANSI escape codes before the .knit.md is converted to a .tex file and then a .pdf document. We rely upon two internal functions from the fansi R package to remove all possible escape codes and return a clean character vector. #' litr version of `rmarkdown::pdf_document()` #' #' This behaves exactly like `rmarkdown::pdf_document()` except it creates an #' R package. #' <<param-minimal_eval>> #' @param ... Parameters to be passed to `rmarkdown::pdf_document()` #' @export litr_pdf_document <- function(minimal_eval = FALSE, ...) { litr_pdf_document_ <- litrify_output_format(rmarkdown::pdf_document, minimal_eval = minimal_eval) old <- litr_pdf_document_(...) new <- old # post_knit new$post_knit = function(...){ args = list(...) input_filename <- args[[2]] knitted_filename <- fs::path_ext_set(input_filename, ".knit.md") knitted_output <- readLines(knitted_filename) cleaned_output <- sapply(1:length(knitted_output), function(i){ test_str <- knitted_output[i] fansi:::VAL_IN_ENV(x=test_str, ctl="all", warn=TRUE, warn.mask=fansi:::get_warn_mangled()) .Call(fansi:::FANSI_strip_csi, test_str, CTL.INT, WARN.INT) }) writeLines(cleaned_output, knitted_filename) } new } Since the above section uses the fansi package for handling ANSI escape sequences, we include it in our package: usethis::use_package("fansi") ## ✔ Adding 'fansi' to Imports field in DESCRIPTION ## • Refer to functions with `fansi::fun()` 7.1.2 .html output format For .html documents, we’d like to add a bit more in the postprocessing step. In particular, we include some special function and chunk hyperlinking behavior described below. The function add_function_hyperlinks() processes the outputted .html file(s), making it so that one can easily navigate to function definitions. (This function is described lower in this section.) We likewise call a function add_chunk_label_hyperlinks(), which makes chunk references into clickable links. In particular, the chunk reference <<my-chunk>> within a code chunk would link to a chunk named “my-chunk” that begins with ###\"my-chunk\"###. The ###\"my-chunk\"### line is added by a document hook defined in setup(). Finally, we replace ANSI sequences with HTML tag equivalents (the need for this is explained in the section on the .pdf output format). #' litr version of `rmarkdown::html_document()` #' #' This behaves like `rmarkdown::html_document()` with a few differences: #' - It creates an R package. #' - It adds hyperlinks to function definitions whenever a function is used #' elsewhere in the document. #' - It does "Knuth-style" chunk referencing with hyperlinks. #' <<param-minimal_eval>> #' @param ... Parameters to be passed to `rmarkdown::pdf_document()` #' @export litr_html_document <- function(minimal_eval = FALSE, ...) { litr_html_document_ <- litrify_output_format(rmarkdown::html_document, minimal_eval = minimal_eval) old <- litr_html_document_(...) new <- old # modify post_processor new$post_processor = function(metadata, input_file, output_file, ...) { out <- old$post_processor(metadata, input_file, output_file, ...) # html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$") # add hyperlinks within html output to make it easier to navigate: add_function_hyperlinks(output_file, metadata$params$package_name) add_chunk_label_hyperlinks(output_file) # replace ANSI sequences with HTML tag equivalents replace_ansi_sequences(output_file) out } new } We describe these two add_*_hyperlinks() functions next. The function add_function_hyperlinks() looks for foo followed by <- function( and then wraps foo in a span tag with id=\"foo\"; whenever foo is found elsewhere in the document, it calls the insert_hrefs() function to wrap a a href=\"file.html#foo\" tag (where file.html is the file where foo is defined), so that it will be a hyperlink to foo’s definition. #' Add hyperlinks to function definitions #' #' Finds functions that are defined in the html file(s) by looking for text of the #' form `foo <- function(` and then wraps `foo` in a `span` tag with `id="foo"` #' and then whenever `foo` is found it wraps a `a href="file.html#foo"` tag so #' that it will be a hyperlink to `foo`'s definition. #' #' @param html_files Character vector of file names of html files that were created #' from Rmd files #' @param pkg_name Name of the package created by litr. Taken from YAML front matter #' @keywords internal add_function_hyperlinks <- function(html_files, pkg_name) { find_function_defs <- function(html_file) { txt <- readLines(html_file) start_line <- which(txt == "<body>") pattern1 <- '([a-zA-Z0-9_.]+)(\\\\s*&lt;-\\\\s*function)' pattern2 <- stringr::str_replace(pattern1, '&lt;-', '<span class="ot">&lt;-</span>') pattern2 <- stringr::str_replace(pattern2, 'function', '<span class="cf">function</span>') # find functions that are defined in this file: function_names <- character(0) for (pattern in c(pattern1, pattern2)) { for (i in seq(start_line + 1, length(txt))) { fn_name <- stringr::str_match(txt[i], pattern)[, 2] if(is.na(fn_name)) next # a function was defined in this line, so put a span around it txt[i] <- stringr::str_replace( txt[i], pattern, stringr::str_glue("<span id='{fn_name}'>\\\\1</span>\\\\2") ) # and keep track of it for later: function_names <- c(function_names, fn_name) } } list(function_names = function_names, txt = txt) } fdefs <- lapply(html_files, find_function_defs) all_function_names <- unlist(lapply(fdefs, function(lst) lst$function_names)) # if a function is defined multiple times, then it's ambiguous where to link to # so let's not try linking to it (this can occur when a function is defined # within a function, such as `new$post_processor()`) repeated <- names(which(table(all_function_names) > 1)) all_function_names <- setdiff(all_function_names, repeated) if (length(all_function_names) == 0) { # no functions defined in package, so nothing more to be done here return() } num_per_file <- unlist(lapply(fdefs, function(lst) { length(setdiff(lst$function_names, repeated)) })) where_defined <- rep(fs::path_file(html_files), times = num_per_file) defined_functions_pattern <- paste0("(::)?",all_function_names, "\\\\(", collapse = "|") # There's also this case: <span class="fu">myfunction</span> defined_functions_pattern2 <- paste0( '<span class="fu">', all_function_names, '</span>\\\\(', collapse = "|") for (i in seq_along(html_files)) { # whenever one of the defined functions is named, link to its definition # using the format `file_where_foo_is_defined.html#foo` modified_txt <- insert_hrefs(fdefs[[i]]$txt, defined_functions_pattern, where_defined, all_function_names, pkg_name) modified_txt <- insert_hrefs(modified_txt, defined_functions_pattern2, where_defined, all_function_names, pkg_name, remove_span=TRUE) writeLines(modified_txt, con = html_files[i]) } } We define next the helper function insert_hrefs(), which was called in the previous function. If the function foo() is defined in the .Rmd file that defines a package named pkg, then whenever foo() or pkg::foo() appears in the .Rmd, a link will be added; however, if other_pkg::foo() appears, then no link will be added. #' Replace a function's name with a link to its definition #' #' A helper function for `add_function_hyperlinks` that wraps references to a #' function in an anchor tag with a link to the function's definition. #' #' @param txt Character vector where each element is a row of the knitted HTML file. #' @param function_pattern Regular Expression passed from `add_function_hyperlinks` that contains all referenced functions in the document. #' @param where_defined Character vector that contains the name of the file in which a function was defined. #' @param all_function_names Character vector of all referenced functions in the document. #' @param pkg_name Name of the package created by litr. Taken from YAML front matter. #' @param remove_span Boolean argument for removing span tags. Used for minimizing code duplication. #' @keywords internal insert_hrefs <- function(txt, function_pattern, where_defined, all_function_names, pkg_name, remove_span=FALSE){ # filter down matches of defined_functions_pattern has_fn_name <- which(stringr::str_detect(txt, function_pattern)) has_colon_prefix <- which(stringr::str_detect(txt, paste0("::", all_function_names, "\\\\(", collapse = "|"))) has_only_fn_name <- setdiff(has_fn_name, has_colon_prefix) has_pkg_colon_prefix <- which(stringr::str_detect(txt, paste0(stringr::str_glue("{pkg_name}::")))) # define different replacement functions for colon prefix cases and regular cases colon_pref_replace_fn <- function(x){ if(remove_span){ fn_name <- stringr::str_remove(x, "</span>\\\\(") fn_name <- stringr::str_remove(fn_name, '<span class="fu">') } else{ fn_name <- stringr::str_remove(x, "\\\\(") } fn_name <- stringr::str_remove(fn_name, stringr::str_glue('{pkg_name}::')) # implicitly assuming that a function is not redefined in another file def_file <- where_defined[all_function_names == fn_name] return(stringr::str_glue("{pkg_name}::<a href='{def_file}#{fn_name}'>{fn_name}</a>(")) } regular_replace_fn <- function(x){ if(remove_span){ fn_name <- stringr::str_remove(x, '</span>\\\\(') fn_name <- stringr::str_remove(fn_name, '<span class="fu">') } else { fn_name <- stringr::str_remove(x, "\\\\(") } # implicitly assuming that a function is not redefined in another file def_file <- where_defined[all_function_names == fn_name] stringr::str_glue("<a href='{def_file}#{fn_name}'>{fn_name}</a>(") } colon_prefix_function_pattern <- paste0(stringr::str_glue("{pkg_name}::"),all_function_names, "\\\\(", collapse = "|") colon_prefix_refs <- stringr::str_replace_all( txt[has_pkg_colon_prefix], colon_prefix_function_pattern, colon_pref_replace_fn ) regular_refs <- stringr::str_replace_all( txt[has_only_fn_name], function_pattern, regular_replace_fn ) # now put back in the changed lines txt[has_pkg_colon_prefix] <- colon_prefix_refs txt[has_only_fn_name] <- regular_refs txt } In addition to adding hyperlinks to function definitions, we also want to add hyperlinks for chunk references of the form <<chunk-name>> that link to user-defined chunk names that take the form ###chunk-name###. We want these user-defined chunk names to stand out to readers and in their original form as code comments they might be harder to notice. To solve this issue, we modify the formatting of these chunks to add a border with the name of the chunk set into the border, mimicking the look of a <fieldset> tag. The additional formatting requires inserting additional HTML tags as well as css into the knitted HTML file so we use the xml2 package to parse and manipulate the knitted HTML file. #' Add hyperlinks to embedded chunks #' #' Finds chunks that are referenced in the html file(s) by looking for comments #' of the form `###"foo"###` and then wraps `foo` in a `span` tag with `id="foo"` #' and then whenever the chunk label `<<foo>>` is found it wraps it in a #' `a href="file.html#foo"` tag so that it will be a hyperlink to `foo`'s #' definition. #' #' @param html_files Character vector of file names of html files that were created #' from Rmd files #' @param reference_start The delimiter used to indicate the start of a chunk label #' @param reference_end The delimiter used to indicate the end of a chunk label #' @keywords internal add_chunk_label_hyperlinks <- function(html_files, reference_start = "&lt;&lt;", reference_end = "&gt;&gt;"){ find_chunk_defs <- function(html_file) { txt <- readLines(html_file) start_line <- which(txt == "<body>") pattern <- '###&quot;([a-zA-Z0-9-_.]+)&quot;###' # find chunks that are defined in this file: chunk_names <- character(0) for (i in seq(start_line + 1, length(txt))) { chunk_name <- stringr::str_match(txt[i], pattern)[, 2] if(is.na(chunk_name)) next # a chunk was defined in this line, so put a span around it txt[i] <- stringr::str_replace( txt[i], pattern, stringr::str_glue("<span id='{chunk_name}'>###&quot;\\\\1&quot;###</span>") ) # and keep track of it for later. # we're using setNames here to make sure that we keep the name of file # where the chunk name is defined chunk_names <- setNames(c(chunk_names, chunk_name), c(names(chunk_names), html_file)) } list(chunk_names = chunk_names, txt = txt) } cdefs <- lapply(html_files, find_chunk_defs) all_chunk_names <- unlist(lapply(cdefs, function(lst) lst$chunk_names)) num_per_file <- unlist(lapply(cdefs, function(lst) length(lst$chunk_names))) where_defined <- rep(fs::path_file(html_files), times = num_per_file) defined_chunks_pattern <- paste0(reference_start, all_chunk_names, reference_end, collapse = "|") ref_start <- '<span class="sc">&lt;</span><span class="er">&lt;</span>' ref_start_alt <- '<span class=\\"er\\">&lt;&lt;</span>' ref_end <- '<span class="sc">&gt;</span><span class="er">&gt;</span>' hyphen_with_extras <- '<span class="sc">-</span>' all_chunk_names2 <- stringr::str_replace_all(all_chunk_names, "-", hyphen_with_extras) defined_chunks_pattern2 <- paste0( ref_start, all_chunk_names2, ref_end, collapse = "|" ) defined_chunks_pattern2_alt <- paste0( ref_start_alt, all_chunk_names2, ref_end, collapse = "|" ) defined_chunks_pattern2 <- paste( defined_chunks_pattern2, defined_chunks_pattern2_alt, sep = "|" ) for (i in seq_along(html_files)) { # whenever one of these named chunks is referenced, link to its definition # using the format `file_where_chunk_is_defined.html#chunkname` txt <- stringr::str_replace_all( cdefs[[i]]$txt, defined_chunks_pattern, function(x) { cname <- stringr::str_remove_all( x, paste(reference_start, reference_end, sep = "|") ) def_file <- where_defined[all_chunk_names == cname] stringr::str_glue( "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>" ) } ) txt <- stringr::str_replace_all( txt, defined_chunks_pattern2, function(x) { cname <- stringr::str_remove_all( x, paste(ref_start, ref_start_alt, ref_end, sep = "|") ) def_file <- where_defined[all_chunk_names2 == cname] cname <- stringr::str_replace_all(cname, hyphen_with_extras, "-") stringr::str_glue( "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>" ) } ) parsed_html <- xml2::read_html(paste(txt,collapse="\\n")) # get all possible chunk names in this file. chunk_names <- all_chunk_names[which(names(all_chunk_names) == html_files[i])] if(length(chunk_names) > 0){ for(j in seq_along(chunk_names)){ span_node <- xml2::xml_find_first(parsed_html, stringr::str_glue('(.//span[@id="{chunk_names[j]}"])')) span_node_path <- stringr::str_split(xml2::xml_path(span_node),"/") pre_path <- paste(span_node_path[[1]][1:(max(which(stringr::str_detect(span_node_path[[1]], "pre"))))],collapse="/") if(nchar(pre_path) == 0){ next() } pre_parent <- xml2::xml_find_first(parsed_html, pre_path) if(is.na(pre_parent)){ next() } xml2::xml_add_parent(pre_parent , xml2::read_xml(stringr::str_glue('<fieldset id="{chunk_names[j]}" class="chunkfield"> </fieldset>'))) xml2::xml_add_sibling(pre_parent, xml2::read_xml(stringr::str_glue('<legend class="chunklegend">{chunk_names[j]}</legend>')), where="before") # xml2::xml_add_parent(pre_parent, xml2::read_xml(stringr::str_glue('<fieldset id="{chunk_names[j]}" style="border:4px solid black"> <legend>{chunk_names[j]}</legend> </div>'))) xml2::xml_remove(span_node) # remove the extra line break that is left over from removing the span code_node <- xml2::xml_child(pre_parent) changed_txt <- stringr::str_remove(paste(as.character(xml2::xml_contents(code_node)),collapse=""), '\\n') xml2::xml_replace(code_node, xml2::read_xml(stringr::str_glue('<code>{changed_txt}</code>'))) } } # last thing is to insert an additional style node in the head with our CSS so we have a standard style whether we are using bookdown or Rmarkdown css_string <- "fieldset.chunkfield {border:4px solid black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em} legend.chunklegend {padding:0;font-size:21px;width:auto;border:0; border-bottom: none; margin-bottom:0} " head_node <- xml2::xml_find_first(parsed_html, ".//head") xml2::xml_add_child(head_node, xml2::read_xml(stringr::str_glue("<style type='text/css'>{css_string}</style>"))) txt <- xml2::write_html(parsed_html, html_files[i]) } } Since we rely on the xml2 package to add in the extra label formatting, let’s import it: usethis::use_package("xml2") ## ✔ Adding 'xml2' to Imports field in DESCRIPTION ## • Refer to functions with `xml2::fun()` Finally, we want to replace the ANSI escape sequences used by packages such as testthat and devtools with their HTML equivalents so the output matches what we see in the terminal. #' Replace ANSI escape sequences with their HTML equivalents #' #' Finds ANSI escape sequences and replaces them with HTML tags using the `fansi` package #' #' @param html_files Character vector of file names of html files that were created #' from Rmd files #' @keywords internal replace_ansi_sequences <- function(html_files) { for (i in seq_along(html_files)) { file_lines <- readLines(html_files[i]) # look for lines with escape sequences for URLs and remove the URL # escape sequences before we convert to HTML url_code_regex <- "\\\\033]8;;.*\\\\a(.*?)\\\\033]8;;\\\\a" url_seq_idx <- which(stringr::str_detect(file_lines, url_code_regex)) file_lines[url_seq_idx] <- sapply(url_seq_idx, function(idx){ line <- file_lines[idx] stringr::str_replace(line, url_code_regex, stringr::str_glue("\\\\1")) }) txt <- fansi::sgr_to_html(x = file_lines, warn = FALSE, term.cap = "256") writeLines(txt, con = html_files[i]) } } 7.1.3 bookdown output format It turns out that our modification to the bookdown::gitbook() format’s postprocessor is identical to the above. This suggests that we should probably reuse code more effectively. But for now I will leave it how it is: #' litr version of `bookdown::gitbook()` #' #' This behaves like `bookdown::gitbook()` with a few differences: #' - It creates an R package. #' - It adds hyperlinks to function definitions whenever a function is used #' elsewhere in the document. #' - It does "Knuth-style" chunk referencing with hyperlinks. #' <<param-minimal_eval>> #' @param ... Parameters to be passed to `bookdown::gitbook()` #' @export litr_gitbook <- function(minimal_eval = FALSE, ...) { litr_gitbook_ <- litrify_output_format(bookdown::gitbook, minimal_eval = minimal_eval) old <- litr_gitbook_(...) new <- old # modify post_processor new$post_processor = function(metadata, input_file, output_file, ...) { out <- old$post_processor(metadata, input_file, output_file, ...) out_dir <- fs::path_dir(out) file_stems <- readLines(file.path(out_dir, "reference-keys.txt")) html_files <- file.path(out_dir, paste0(file_stems, ".html")) html_files <- unique(intersect(c(out, html_files), fs::dir_ls(out_dir))) # add hyperlinks within html output to make it easier to navigate: add_function_hyperlinks(html_files, metadata$params$package_name) add_chunk_label_hyperlinks(html_files) # replace ANSI sequences with HTML tag equivalents replace_ansi_sequences(html_files) out } new } To use this output format, one would use bookdown::render_book() instead of rmarkdown::render(). In particular: bookdown::render_book(output_format = litr::litr_gitbook()) The preamble in index.Rmd would look something like this: --- title: "A `litr` Book" author: "Your Name" site: bookdown::bookdown_site params: package_name: "frombookdown" # <-- change this to your package name package_parent_dir: "." # <-- relative to this file's location documentclass: book --- Or one can add to the preamble the lines knit: litr::render output: litr::litr_gitbook This first line makes it so that in RStudio when you press “Knit”, it calls litr::render(), and the second line makes it so that it will use the special litr bookdown output format. Since the above function uses bookdown, we include it in our package: usethis::use_package("bookdown") ## ✔ Adding 'bookdown' to Imports field in DESCRIPTION ## • Refer to functions with `bookdown::fun()` 7.2 Defining litr::render() There are two primary use cases for this function: To render a .Rmd with a non-litr output format (e.g., rmarkdown::html_document) in such a way that it will generate an R package (and include the special litr-hyperlinking if .html files were created). To render a .Rmd with a litr output format (including the litr_gitbook() format). The second use case might seem unnecessary in that rmarkdown::render("create-pkg.Rmd", output_format = litr::litr_html_document()) or bookdown::render_book("index.Rmd", output_format = litr::litr_gitbook()) would do what we want. However, a reason to still prefer litr::render() is that this function ensures the identical behavior to when one clicks the “Knit” in RStudio. It does this by opening a fresh R session (when fresh_session=TRUE, which is the default) in which rmarkdown::render() (or bookdown::render_book()) is called. This is based on the description in the Rmarkdown Cookbook. Another reason to prefer litr::render() is that if there is an error in the rendering process, the special litr hash will still be written to the DESCRIPTION file. This means that after fixing that error when one calls litr::render(), one will not get the error telling the user to delete the partially generated package directory. We accomplish this with the function with_cleanup() defined below. In the first use case, litr::render() is responsible for ensuring all the special litr things happen (like setup() being called before knitting, the litr-hash being written afterwards, and hyperlinking occurs). The details of what it does is very similar to what is described in the output formats section, especially the one on the html output format. One thing that is different is that we need a function get_params_used(), defined at the end of this section, that gets the actual parameters that are used so that the location of the outputted package can be found. #' Render R markdown file #' #' Wrapper to `rmarkdown::render()` that produces an R package as output in #' addition to the standard output document. It does some post-processing on the #' .html file when that is the output. In particular, when an .html file is among #' the outputs, it adds hyperlinks to functions defined within the file to make #' it easier for someone reading the code to see where different functions are #' defined. #' #' @param input The input file to be rendered (see `rmarkdown::render`) <<param-minimal_eval>> #' @param fresh_session Whether to call `rmarkdown::render` from a fresh R #' session. By default TRUE, so that it matches the behavior of pressing "Knitr" #' in RStudio. However, for debugging it can be useful to set this to FALSE so #' that functions like `debug()` and `browser()` will work. #' @param ... Additional parameters to pass to `rmarkdown::render` #' @export render <- function(input, minimal_eval, fresh_session = TRUE, ...) { # call rmarkdown::render in a new environment so it behaves the same as # pressing the knit button in RStudio: # https://bookdown.org/yihui/rmarkdown-cookbook/rmarkdown-render.html args <- list(...) # let's determine if the output format being used is a litr format. # If it is, then we'll simply want to call rmarkdown::render() since the # special litr behavior will be attained through the output format. litr_format <- FALSE bookdown_format <- FALSE output_format_arg <- FALSE if ("output_format" %in% names(args)) { output_format_arg <- TRUE if ("litr_format" %in% names(args$output_format)) { litr_format <- TRUE } if ("bookdown_output_format" %in% names(args$output_format)) { bookdown_format <- TRUE } } else { frontmatter <- rmarkdown::yaml_front_matter(input) if ("output" %in% names(frontmatter)) { formats <- ifelse(is.list(frontmatter$output), names(frontmatter$output), frontmatter$output) if (any(stringr::str_detect(formats, "litr::"))) { litr_format <- TRUE } if (any(stringr::str_detect(formats, "litr::litr_gitbook"))) { bookdown_format <- TRUE } } } # get package_directory params <- get_params_used(input, args$params) package_dir <- get_package_directory( params$package_parent_dir, params$package_name, input ) # if minimal_eval was passed to render, add this to the output_options # argument that will be passed to rmarkdown::render if (is.null(args$output_options)) args$output_options <- list() if (!missing(minimal_eval)) args$output_options$minimal_eval <- minimal_eval # determine whether a new R session will be created when we run the rendering # function of rmarkdown/bookdown if (fresh_session) run_function <- xfun::Rscript_call else run_function <- do.call if (litr_format) { # this uses a litr output format, so we don't need to do anything litr-specific # here because it will happen through the output format if (output_format_arg & !missing(minimal_eval)) { # the output format was passed through the output_format argument rather # than through the metadata if (minimal_eval) { stop(make_noticeable(paste( "When passing a litr output format using the output_format argument,", "you should not pass minimal_eval = TRUE directly to render.", "Instead, pass it to the litr output format function. For example,", "litr::litr_html_document(minimal_eval = TRUE).", collapse = " " ))) } } if (bookdown_format) { if (fs::is_file(input)) input <- fs::path_dir(input) return(invisible(run_function(with_cleanup(bookdown::render_book, package_dir), c(input = input, args)))) } else return(invisible(run_function(with_cleanup(rmarkdown::render, package_dir), c(input = input, args)))) } # the output format being used is not a litr-specific one, so we need to make # sure that all the special litr things happen args$package_dir <- package_dir render_ <- function(input, package_dir, minimal_eval, ...) { knitr_objects <- litr:::setup(package_dir, minimal_eval) out <- rmarkdown::render(input, ...) restore_knitr_objects(knitr_objects) # remove .Rproj and .gitignore if usethis::create_package() added these remove_rstudio_extras(package_dir) return(out) } if (missing(minimal_eval)) minimal_eval <- FALSE out <- run_function(with_cleanup(render_, package_dir), c(input = input, minimal_eval = minimal_eval, args)) # add hyperlinks within html output to make it easier to navigate: if (any(stringr::str_detect(out, "html$"))) { html_file <- stringr::str_subset(out, "html$") add_function_hyperlinks(html_file, params$package_name) add_chunk_label_hyperlinks(html_file) } # add to DESCRIPTION file the version of litr used to create package: write_version_to_description(package_dir) # add litr hash so we can tell later if package files were manually edited: write_hash_to_description(package_dir) } We used the package xfun, so let’s import it: usethis::use_package("xfun") ## ✔ Adding 'xfun' to Imports field in DESCRIPTION ## • Refer to functions with `xfun::fun()` When litr::render() encounters an error, it can leave the output directory partially modified. We want to make sure the litr hash still gets written to the DESCRIPTION file. Otherwise, the next time one calls litr::render() it does not allow this directory to be overwritten. We do this by using withCallingHandlers(). This function, explained here, is similar to tryCatch(), but with the advantage that it lets the function to continue to run normally, meaning that we will still get the error message as it would appear if we hadn’t done the condition handling. #' Add litr hash to DESCRIPTION file if error encountered #' #' This creates a function that calls the passed function within the context of #' a try-catch. If an error is encountered, the litr hash is still added to #' the DESCRIPTION file so that future calls to `litr::render()` will recognize #' that it can safely overwrite the package directory (i.e., no manual editing #' occurred). #' #' @param fun function being called #' @param package_dir directory where package is being written to #' @param ... arguments to be passed to `fun` #' @keywords internal with_cleanup <- function(fun, package_dir) { return(function(...) { withCallingHandlers( fun(...), error = function(e) { # add litr hash so we can tell later if package files were manually edited: write_hash_to_description(package_dir) }) }) } In setup(), we modified the knitr objects (e.g., adding hooks, engines, etc.). We call the function restore_knitr_objects() after we’re done, to put things back how they were: #' Return the knitr objects to their original state #' #' @param original_knitr_objects As returned by `setup()` #' @keywords internal restore_knitr_objects <- function(original_knitr_objects) { knitr::opts_knit$restore(original_knitr_objects$opts_knit) knitr::knit_hooks$restore(original_knitr_objects$knit_hooks) knitr::opts_chunk$restore(original_knitr_objects$opts_chunk) knitr::opts_hooks$restore(original_knitr_objects$opts_hooks) knitr::knit_engines$restore(original_knitr_objects$knit_engines) } Another thing we want to do at the end of the rendering process is to remove two files that might have been created by usethis: .Rproj and .gitignore. These are created by usethis::create_package() when rstudio = TRUE. We don’t want these files created since this would suggest to a user that the R package should be worked on from within it rather than from the generating .Rmd file. #' Remove extra files added by usethis #' #' Remove .Rproj and .gitignore files if they are in the package directory. #' #' @param package_dir Path to package #' @keywords internal remove_rstudio_extras <- function(package_dir) { extras <- fs::dir_ls(package_dir, all = TRUE, regexp = "[.]Rproj$|[.]gitignore$") rbuildignore <- file.path(package_dir, ".Rbuildignore") txt <- readLines(rbuildignore) txt <- stringr::str_subset(txt, "^.*Rproj.*$", negate = TRUE) writeLines(txt, con = rbuildignore) for (extra in extras) fs::file_delete(extra) } As described earlier, the function get_params_used() combines the parameters from the YAML but allows for those values to be overridden through arguments passed to render(). #' Get parameter values used in rendering #' #' When the `params` argument of `rmarkdown::render()` is explicitly used, this #' overrides the default that appears in `input`. #' @param input The input file to be rendered (see `rmarkdown::render`) #' @param passed_params The list of parameters that were passed to `render`. #' @keywords internal get_params_used <- function(input, passed_params) { params <- rmarkdown::yaml_front_matter(input)$params for (param in names(passed_params)) { params[[param]] <- passed_params[[param]] } params } We used the package rmarkdown, so let’s import it: usethis::use_package("rmarkdown") ## ✔ Adding 'rmarkdown' to Imports field in DESCRIPTION ## • Refer to functions with `rmarkdown::fun()` For example, we include the line knit: litr::render in the yaml of the templates for this reason.↩︎ "],["functionality-to-facilitate-workflow.html", "8 Functionality to facilitate workflow", " 8 Functionality to facilitate workflow When someone is writing an R package with devtools, it is common to use devtools::load_all() to quickly try out the functions of an R package in the console. We’d like to allow for a similar workflow using litr. We define a litr function called load_all(), which will do the following: Litr-knit the .Rmd file with minimal_eval=TRUE to some temporary location (since the idea of load_all() is to leave the .html file alone) and then delete the .html file. Run devtools::load_all() on the output. #' Load complete package #' #' This is a litr wrapper to `devtools::load_all()`. It first calls #' `litr::render()` with `minimal_eval=TRUE`, then it calls #' `devtools::load_all()` on the generated package. #' #' @param input The input file to be rendered (see `rmarkdown::render`) #' @param output_dir By default (and in typical usage) this is NULL, meaning #' that no .html/bookdown/.pdf will result. However, when a directory is given, #' the result of the litr-knitting will be saved to this location. #' @param ... Additional parameters to be passed to `devtools::load_all()` #' @export load_all <- function(input, output_dir = NULL, ...) { no_output <- is.null(output_dir) if (no_output) { output_dir <- tempfile() if (fs::file_exists(output_dir)) fs::file_delete(output_dir) fs::dir_create(output_dir) } # let's copy over everything from input directory to output directory fs::dir_copy(fs::path_dir(input), output_dir, overwrite = TRUE) input_path <- fs::path_split(input)[[1]] moved_input <- file.path(output_dir, fs::path_file(input)) # get package directory params <- get_params_used(moved_input, list()) package_dir <- get_package_directory( params$package_parent_dir, params$package_name, moved_input ) # but if a package directory was copied here, let's remove it before # calling render to avoid a potential error if (fs::dir_exists(package_dir)) fs::dir_delete(package_dir) litr::render(moved_input, minimal_eval = TRUE, output_dir = output_dir, quiet = TRUE) new_package_dir <- file.path(fs::path_dir(input), params$package_name) fs::dir_copy(package_dir, new_package_dir, overwrite = TRUE) if (no_output) fs::dir_delete(output_dir) devtools::load_all(new_package_dir) } Let’s test that this works. In particular, we’ll call load_all() and then try to use one of the functions from the package. testthat::test_that('load_all() works', { # setup files for tests: dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, 'create-pkg.Rmd') fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file) html_file <- file.path(dir, "create-pkg.html") load_all(rmd_file) testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!") fs::dir_delete(dir) }) "],["adding-extras-to-an-r-package.html", "9 Adding extras to an R package 9.1 Adding a README 9.2 Adding a hex sticker 9.3 Adding vignettes 9.4 Add a pkgdown site", " 9 Adding extras to an R package When writing an R package, there are some additional items one typically wants to include, such as a README file, one or more vignettes, and a pkgdown site. In this section, we define some helper functions that will be make it easy to add these “extras” to a package. While a user could choose to generate all these items within the generating .Rmd file, for all but the simplest examples, it will probably be preferred to have these as separate source files. These source files can live in the same directory as the generating .Rmd file or in their own directory (e.g., source-files lives in the same directory as create-litr.Rmd, which is called litr-project). 9.1 Adding a README We define a helper function that takes an externally defined README.Rmd and puts it into the package, creates a README.md, and makes sure that these will be added to the .Rbuildignore. #' Add README to package #' #' This function takes a README.Rmd file, copies it into the package, and then #' renders it to a README.md file. It also adds these two files to the #' .Rbuildignore. #' #' @param rmd_file The path to a .Rmd file. #' @export add_readme <- function(rmd_file) { usethis::use_readme_rmd(open = FALSE) fs::file_copy(rmd_file, new_path = "README.Rmd", overwrite = TRUE) out <- xfun::Rscript_call( rmarkdown::render, args = list( input = "README.Rmd", output_options = list(html_preview = "false") ) ) } 9.2 Adding a hex sticker We define a helper function that takes an externally defined hex sticker (.png file) and puts it into the package under man/figures. The suggestion for storing it in this directory came from here. #' Add a hex sticker to package #' #' In addition to calling this function, you should add to your README.Rmd something like this: #' #' `# your-title <img src="man/figures/logo.png" align="right" height="139" />` #' #' See [here](https://pkgdown.r-lib.org/reference/build_home.html#package-logo) #' for more. #' #' @param hex_png_file The .png file with your package's hex sticker #' @export add_hex_sticker <- function(hex_png_file) { figures_dir <- file.path("man", "figures") fs::dir_create(figures_dir) fs::file_copy(path = hex_png_file, new_path = file.path(figures_dir, "logo.png"), overwrite = TRUE) } 9.3 Adding vignettes We next define a helper function for adding vignettes to the package. This mimics usethis::use_vignette(). We couldn’t directly use that function because we want the project file to live outside of the package directory, which confuses usethis. #' Add one or more vignettes to package #' #' @param rmd_files A character vector of .Rmd files, each corresponding to #' a vignette #' @param other_files A character vector of any other files needed in the #' vignettes directory (.bib file, images, etc.) #' @export add_vignettes <- function(rmd_files, other_files = NULL) { fs::dir_create("vignettes") for (fn in c(rmd_files, other_files)) fs::file_copy(fn, "vignettes") # update DESCRIPTION file: deps <- desc::desc_get_deps()$package if (!("knitr" %in% deps)) desc::desc_set_dep("knitr", type = "Suggests") if (!("rmarkdown" %in% deps)) desc::desc_set_dep("rmarkdown", type = "Suggests") out <- desc::desc_set("VignetteBuilder", "knitr") } 9.4 Add a pkgdown site We define a function based on usethis::use_pkgdown(), but with a few differences: Avoid the parts to do with looking for projects Allow one to use a custom _pkgdown.yml that is stored outside of package #' Add a pkgdown site #' #' This function creates a website for your package. You can see it locally by #' opening `docs/index.html` in your package. To get it online you can copy the #' `docs` directory to your website's server. #' #' Be sure that in the generating .Rmd file this is called *after* #' `litr::document()` has been called. To customize the site, you may pass a #' customized `_pkgdown.yml` file as described in [this `pkgdown` vignette](https://pkgdown.r-lib.org/articles/customise.html). #' #' @param config_path The _pkgdown.yml file that lives somewhere outside of your package. If NULL, then a basic default will be used. #' @export add_pkgdown <- function(config_path = NULL) { config_file <- "_pkgdown.yml" destdir <- "docs" usethis::use_build_ignore(c(config_file, destdir, "pkgdown")) if (is.null(config_path)) { # create a new config file (note it lives outside of package) config <- usethis:::pkgdown_config(destdir) usethis::write_over(config_file, yaml::as.yaml(config)) } else { # copy the one that already exists: fs::file_copy(config_path, config_file) } pkgdown::build_site() } After this step, you can locally see the site by opening docs/index.html in the browser. You can then copy the docs directory to your website’s server and you’re done. Since the above function uses pkgdown and yaml, we include these in our package: usethis::use_package("pkgdown") usethis::use_package("yaml") ## ✔ Adding 'pkgdown' to Imports field in DESCRIPTION ## • Refer to functions with `pkgdown::fun()` ## ✔ Adding 'yaml' to Imports field in DESCRIPTION ## • Refer to functions with `yaml::fun()` "],["combining-.r-files.html", "10 Combining .R files", " 10 Combining .R files This section should eventually be removed but for now I’m doing this to convince myself that the package generated by this .Rmd file really matches the initial version created without the package. library(magrittr) library(purrr) rfiles <- fs::dir_ls("R") code <- rfiles %>% map(readLines) %>% set_names( rfiles %>% stringr::str_remove("^.*/") %>% stringr::str_remove(".R$") ) hash_functions <- c("hash_package_directory", "description_litr_hash_field_name", "write_hash_to_description", "read_hash_from_description", "check_unedited") render_functions <- c("render", "with_cleanup", "litrify_output_format", "litr_pdf_document", "litr_html_document", "litr_gitbook", "replace_ansi_sequences", "add_function_hyperlinks", "insert_hrefs", "add_chunk_label_hyperlinks", "restore_knitr_objects", "remove_rstudio_extras", "get_params_used", "get_package_directory", "do_not_edit_message", "description_litr_version_field_name", "write_version_to_description", "document", "load_all") setup_functions <- c("setup", "make_noticeable", "send_to_package", "add_text_to_file", "find_labels") extras_functions <- c("add_readme", "add_hex_sticker", "add_vignettes", "add_pkgdown") remove_initial_lines <- function(code_list) { # drop first line of each list element except for the first list element c(code_list[1], map(code_list[-1], ~ .x[-1])) } fs::file_delete(setdiff(rfiles, "R/litr-package.R")) writeLines(unlist(remove_initial_lines(code[hash_functions])), "R/hash.R") writeLines(unlist(remove_initial_lines(code[render_functions])), "R/render.R") writeLines(unlist(remove_initial_lines(code[setup_functions])), "R/setup.R") writeLines(unlist(remove_initial_lines(code[extras_functions])), "R/extras.R") ## ## Attaching package: 'purrr' ## The following object is masked from 'package:magrittr': ## ## set_names "],["including-templates.html", "11 Including templates", " 11 Including templates We now add the .Rmd templates to the package. We have the skeleton.Rmd defined in source-files. Note that paths are relative to the outputted package’s location. The first template is the simplest imaginable package with a single function: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package", template_dir = "make-an-r-package", template_description = "Template for an Rmd file for writing an R package using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package/skeleton/skeleton.Rmd' The second template shows how to create a package with a dataset: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With a Dataset", template_dir = "make-an-r-package-with-data", template_description = "Template for an Rmd file for writing an R package with a dataset using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-data", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-data", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-data/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-data/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-data/skeleton/skeleton.Rmd' The third template shows how to create a package that uses Rcpp: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With Rcpp", template_dir = "make-an-r-package-with-rcpp", template_description = "Template for an Rmd file for writing an R package that makes use of Rcpp while using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-rcpp", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-rcpp", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/skeleton/skeleton.Rmd' The fourth template shows how to create a package with “extras” such as a README, a vignette, and a pkgdown site: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With a README, Vignette, and Pkgdown Site", template_dir = "make-an-r-package-with-extras", template_description = "Template for an Rmd file for writing an R package that has a README, vignette, and pkgdown site while using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-extras", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton" ), overwrite = TRUE ) fs::dir_copy( path = file.path( "..", "source-files", "make-an-r-package-with-extras", "source-files" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton", "source-files" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-extras/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-extras/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-extras/skeleton/skeleton.Rmd' The fifth template shows how to create a package from a bookdown site, i.e. instead of having just a single create-pkg.Rmd, we can have a series of .Rmd files that together create a bookdown: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package From a Bookdown", template_dir = "make-an-r-package-from-bookdown", template_description = "Template for a bookdown that defines an R package using literate programming.", template_create_dir = TRUE ) fs::dir_copy( path = file.path("..", "source-files", "make-an-r-package-from-bookdown"), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-from-bookdown", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/skeleton/skeleton.Rmd' The sixth template shows how to create a package that uses RcppArmadillo: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With RcppArmadillo", template_dir = "make-an-r-package-with-armadillo", template_description = "Template for an Rmd file for writing an R package that makes use of RcppArmadillo while using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-armadillo", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-armadillo", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/skeleton/skeleton.Rmd' "],["tests.html", "12 Defining some tests 12.1 Testing check_unedited() 12.2 Testing get_params_used() 12.3 Testing chunk referencing 12.4 Testing different ways of rendering 12.5 Testing other templates", " 12 Defining some tests When using litr to create packages that are not litr, one should be able to run tests along the way as we did above in testing the function add_text_to_file(). However, creating litr is a special case so we need to do something different for the tests that involve creating a .Rmd from template and then calling litr::render() on them (such as the tests in this section). In particular, we use eval=FALSE for these code blocks and then at the end of this document we will install the newly created version of litr and then call devtools::test(). Doing it this way is important for ensuring that the version of litr we are testing is the newest version, i.e. the version defined in this document. To understand the reason we are doing it this way, imagine what would happen if instead we left eval=TRUE in the test in the next section. When we use rmarkdown::draft() to create a .Rmd file from template, the file it will give us will be an old version (namely the installed version of litr’s template) rather than the latest version.2 Furthermore, consider what happens when we call render() in the test below. This will start the knitting process on my-package.Rmd. However, inside my-package.Rmd, we have litr::setup() and litr::document(). When these are called in the knitting process, it will be the versions of the functions from the currently installed litr rather than the versions defined in this document. Once we are done testing the new version of the package, we’d like to restore the state of litr to what it was previously. If we don’t do this, then this can lead to inadvertent circularity in which the next time we call litr::render(\"create-litr.Rmd\"), we are using the version currently under development, which is bad because ultimately we need this version to be rendered by the previous version of litr. The following function implements this approach to testing litr: #' Run tests for `litr` itself #' #' Special function for testing `litr`. The trick is to temporarily install #' the new version of `litr`, run the test, and then put things back how it was #' before. #' #' Typical values for `install_old` could be #' - `function() devtools::install("[location of old version]")` #' - `function() remotes::install_github("jacobbien/litr-project@*release", subdir = "litr")`. #' #' @param install_old A function that when run will install the old version #' @param location_of_new Path to the new package directory #' @keywords internal test_litr <- function(install_old, location_of_new) { devtools::unload(params$package_name) devtools::install(location_of_new) out <- devtools::test(location_of_new) install_old() return(out) } Note: The call to devtools::unload() is to address an issue discussed here. 12.1 Testing check_unedited() For our tests, we create a temporary directory (which we delete at the end). In this directory, we create a generating .Rmd file from one of the templates. We make repeated modifications to the package and each time verify that check_unedited() is FALSE with the modification and returns to TRUE when we put things back how they were. The modifications we try are the following: Adding a file Removing a file Making a change to a file (in particular, adding a comment to an R file) Changing something in the DESCRIPTION file (but not on the special litr line) Changing the litr hash line itself testthat::test_that("check_unedited works", { # Including this next line seems to be necessary for R CMD check on the cmd line: #Sys.setenv(RSTUDIO_PANDOC = "/Applications/RStudio.app/Contents/MacOS/pandoc") dir <- tempfile() fs::dir_create(dir) rmd_file <- file.path(dir, "my-package.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr", edit = FALSE) # create R package (named "rhello") from the Rmd template: render(rmd_file) package_path <- file.path(dir, "rhello") testthat::expect_true(check_unedited(package_path)) # what if a file has been added? added_file <- file.path(package_path, "R", "say_hello2.R") writeLines("# Added something here.", added_file) testthat::expect_false(check_unedited(package_path)) # what if we now remove it? fs::file_delete(added_file) testthat::expect_true(check_unedited(package_path)) # what if a file is removed from package? rfile <- file.path(package_path, "R", "say_hello.R") fs::file_move(rfile, dir) testthat::expect_false(check_unedited(package_path)) # now put it back fs::file_move(file.path(dir, "say_hello.R"), file.path(package_path, "R")) testthat::expect_true(check_unedited(package_path)) # what if something is changed in a file? txt <- readLines(rfile) txt_mod <- txt txt_mod[3] <- paste0(txt[3], " # added a comment!!") writeLines(txt_mod, rfile) testthat::expect_false(check_unedited(package_path)) # now put it back writeLines(txt, rfile) testthat::expect_true(check_unedited(package_path)) # what if something is changed in the DESCRIPTION file? descfile <- file.path(package_path, "DESCRIPTION") txt <- readLines(descfile) txt_mod <- txt txt_mod[1] <- "Package: newname" writeLines(txt_mod, descfile) testthat::expect_false(check_unedited(package_path)) # now put it back writeLines(txt, descfile) testthat::expect_true(check_unedited(package_path)) # what if the special litr hash field is changed in the DESCRIPTION file? txt <- readLines(descfile) i_litr <- stringr::str_which(txt, description_litr_hash_field_name()) txt_mod <- txt txt_mod[i_litr] <- paste0(txt_mod[i_litr], "a") writeLines(txt_mod, descfile) testthat::expect_false(check_unedited(package_path)) # now put it back writeLines(txt, descfile) testthat::expect_true(check_unedited(package_path)) fs::dir_delete(dir) }) 12.2 Testing get_params_used() Let’s now test the get_params_used() function, making sure it behaves how we expect it to: testthat::test_that("get_params_used works", { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, "my-package.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr", edit = FALSE) default_params <- get_params_used(rmd_file, passed_params = list()) testthat::expect_equal( default_params, rmarkdown::yaml_front_matter(rmd_file)$params ) params1 <- default_params params1$package_parent_dir <- "dir" testthat::expect_equal( get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir")), params1 ) params2 <- default_params params2$package_name <- "pkg" params2$package_parent_dir <- "dir" testthat::expect_equal( get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir", package_name = "pkg")), params2 ) fs::dir_delete(dir) }) 12.3 Testing chunk referencing Here we test the handling of chunk references (as implemented in the document output hook set within setup()). In particular, we use a .Rmd that uses chunk references in several different ways. Within the .Rmd itself, we have tests that ensure that the code can still be run as expected. fs::file_copy( path = file.path( "..", "source-files", "test-example-files", "create-rknuth.Rmd" ), new_path = file.path("tests", "testthat"), overwrite = TRUE ) testthat::test_that('Knuth-style references work', { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, 'create-rknuth.Rmd') fs::file_copy(path = testthat::test_path("create-rknuth.Rmd"), new_path = rmd_file) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, 'create-rknuth.html'))) fs::dir_delete(dir) }) 12.4 Testing different ways of rendering The mechanism by which rendering occurs depends on several factors: Whether litr::render() or rmarkdown::render() is being called. Whether there is a litr output format specified in the preamble of the .Rmd. Whether there is a litr output format being passed an argument to the render function. In this section, we will test that one gets the same result regardless of how rendering was invoked.3 We will use variations on a base .Rmd file whose preamble is simply the following: --- title: 'A Test' params: package_name: 'pkg' # <-- change this to your package name package_parent_dir: '.' # <-- relative to this file location --- fs::file_copy( path = file.path( "..", "source-files", "test-example-files", "create-pkg.Rmd" ), new_path = file.path("tests", "testthat"), overwrite = TRUE ) There are 7 cases to consider (\\(2^3-1\\), since we exclude the case where rmarkdown::render() is called and no argument or preamble would indicate that this should be a litr-knit). testthat::test_that('Rendering in all possible ways works', { # setup files for tests: dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) # .Rmd without output format in preamble rmd_file1 <- file.path(dir, 'create-pkg1.Rmd') fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file1) # .Rmd without output format in preamble rmd_file2 <- file.path(dir, 'create-pkg2.Rmd') fs::file_copy(rmd_file1, rmd_file2) litr:::add_text_to_file("output: litr::litr_html_document", rmd_file2, 3) # files names rmd_file <- file.path(dir, "create-pkg.Rmd") html_file <- file.path(dir, "create-pkg.html") html_file_a <- file.path(dir, "a","create-pkg.html") pkg <- file.path(dir, "pkg") pkg_a <- file.path(dir, "a", "pkg") check_outputs_are_same <- function() { # html files should be the same: testthat::expect_equal(readLines(html_file_a), readLines(html_file)) # packages should be the same (relying here on litr-hash in DESCRIPTION): testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")), readLines(file.path(pkg_a, "DESCRIPTION"))) } ## Now test that all the cases give the same outputs: # Case 1: no preamble + litr::render() fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE) render(rmd_file, output_file = html_file) if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a")) fs::dir_create(file.path(dir, "a")) fs::dir_copy(pkg, pkg_a) fs::dir_delete(pkg) fs::file_move(html_file, html_file_a) # Case 2: with preamble + litr::render() fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) render(rmd_file, output_file = html_file) check_outputs_are_same() # Case 3: no preamble + litr::render() with output format argument fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE) render(rmd_file, output_format = litr::litr_html_document(), output_file = html_file) check_outputs_are_same() # Case 4: with preamble + litr::render() with output format argument fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) render(rmd_file, output_format = litr::litr_html_document(), output_file = html_file) check_outputs_are_same() # Case 5: with preamble + rmarkdown::render() fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) xfun::Rscript_call(rmarkdown::render, list(input = rmd_file, output_file = html_file) ) check_outputs_are_same() # Case 6: no preamble + rmarkdown::render() with output format argument fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE) xfun::Rscript_call(rmarkdown::render, list(input = rmd_file, output_format = litr::litr_html_document(), output_file = html_file) ) check_outputs_are_same() # Case 7: with preamble + rmarkdown::render() with output format argument fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) xfun::Rscript_call(rmarkdown::render, list(input = rmd_file, output_format = litr::litr_html_document(), output_file = html_file) ) check_outputs_are_same() fs::dir_delete(dir) }) Let’s also make sure that we get the same R package output when using minimal_eval=TRUE as minimal_eval=TRUE. testthat::test_that('Rendering with minimal_eval=TRUE works', { # setup files for tests: dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, 'create-pkg.Rmd') fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file) # .Rmd without output format in preamble html_file <- file.path(dir, "create-pkg.html") html_file_a <- file.path(dir, "a","create-pkg.html") pkg <- file.path(dir, "pkg") pkg_a <- file.path(dir, "a", "pkg") ## Now test that all the cases give the same outputs: # Case 1: minimal_eval = FALSE render(rmd_file, output_file = html_file, minimal_eval = FALSE) if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a")) fs::dir_create(file.path(dir, "a")) fs::dir_copy(pkg, pkg_a) fs::dir_delete(pkg) # Case 2: minimal_eval = TRUE passed to render render(rmd_file, output_file = html_file, minimal_eval = TRUE) testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")), readLines(file.path(pkg_a, "DESCRIPTION"))) # Case 3: minimal_eval = TRUE passed to output format render(rmd_file, output_file = html_file, output_format = litr::litr_html_document(minimal_eval = TRUE) ) testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")), readLines(file.path(pkg_a, "DESCRIPTION"))) fs::dir_delete(dir) }) 12.5 Testing other templates Let’s now make sure that each template can be knit without error. testthat::test_that("templates can be knit", { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, "create-rhello.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-rhello.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "rhello"))) rmd_file <- file.path(dir, "create-rhasdata.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-data", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-rhasdata.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "rhasdata"))) rmd_file <- file.path(dir, "create-withrcpp.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-rcpp", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-withrcpp.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "withrcpp"))) rmd_file <- file.path(dir, "create-witharmadillo.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-armadillo", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-witharmadillo.Rmd"))) testthat::expect_true(fs::file_exists(file.path(dir, "witharmadillo"))) rmd_file <- file.path(dir, "create-withpkgdown.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-extras", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-withpkgdown.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "withpkgdown"))) rmd_file <- file.path(dir, "create-frombookdown.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-from-bookdown", package = "litr", edit = FALSE) prev_dir <- getwd() setwd(file.path(dir, "create-frombookdown")) fs::file_delete("create-frombookdown.Rmd") render("index.Rmd") setwd(prev_dir) testthat::expect_true( fs::file_exists(file.path(dir, "create-frombookdown", "_book", "index.html")) ) testthat::expect_true( fs::file_exists(file.path(dir, "create-frombookdown", "frombookdown")) ) fs::dir_delete(dir) }) Even though litr doesn’t directly use Rcpp, we’ll add it as a “Suggests” package since it would be required for running the above test. usethis::use_package("Rcpp", type = "Suggests") ## ✔ Adding 'Rcpp' to Suggests field in DESCRIPTION ## • Use `requireNamespace("Rcpp", quietly = TRUE)` to test if package is installed ## • Then directly refer to functions with `Rcpp::fun()` If this were the only problem, we could get around this by using pkgload::package_file() to get the proper file; however, the next problem discussed was something that seemed quite hard to resolve.↩︎ Note: When we call rmarkdown::render(), we call it in a fresh, non-interactive R session.↩︎ "],["documenting-the-package-and-testing.html", "13 Documenting the package and testing 13.1 Add examples folder with the output of knitting each example", " 13 Documenting the package and testing We finish by running commands that will document and test litr. The formatting of the test output does not print out very neatly. We download the latest release: litr::document() install_old <- function() { remotes::install_github("jacobbien/litr-project@*release", subdir = "litr") } xfun::Rscript_call(test_litr, list(install_old = install_old, location_of_new = ".")) ## ℹ Updating litr documentation ## ℹ Loading litr ## Writing 'NAMESPACE' ## Writing 'add_readme.Rd' ## Writing 'add_hex_sticker.Rd' ## Writing 'add_vignettes.Rd' ## Writing 'add_pkgdown.Rd' ## Writing 'hash_package_directory.Rd' ## Writing 'description_litr_hash_field_name.Rd' ## Writing 'write_hash_to_description.Rd' ## Writing 'read_hash_from_description.Rd' ## Writing 'check_unedited.Rd' ## Writing 'litr-package.Rd' ## Writing 'render.Rd' ## Writing 'with_cleanup.Rd' ## Writing 'litrify_output_format.Rd' ## Writing 'litr_pdf_document.Rd' ## Writing 'litr_html_document.Rd' ## Writing 'litr_gitbook.Rd' ## Writing 'replace_ansi_sequences.Rd' ## Writing 'add_function_hyperlinks.Rd' ## Writing 'insert_hrefs.Rd' ## Writing 'add_chunk_label_hyperlinks.Rd' ## Writing 'restore_knitr_objects.Rd' ## Writing 'remove_rstudio_extras.Rd' ## Writing 'get_params_used.Rd' ## Writing 'get_package_directory.Rd' ## Writing 'do_not_edit_message.Rd' ## Writing 'description_litr_version_field_name.Rd' ## Writing 'write_version_to_description.Rd' ## Writing 'document.Rd' ## Writing 'load_all.Rd' ## Writing 'setup.Rd' ## Writing 'make_noticeable.Rd' ## Writing 'send_to_package.Rd' ## Writing 'add_text_to_file.Rd' ## Writing 'find_labels.Rd' ## Writing 'test_litr.Rd' ## file context test nb failed skipped ## 1 tests.R tests add_text_to_file() works 10 0 FALSE ## 2 tests.R tests get_package_directory() works 2 0 FALSE ## 3 tests.R tests load_all() works 1 0 FALSE ## 4 tests.R tests check_unedited works 11 0 FALSE ## 5 tests.R tests get_params_used works 3 0 FALSE ## 6 tests.R tests Knuth-style references work 1 0 FALSE ## 7 tests.R tests Rendering in all possible ways works 12 0 FALSE ## 8 tests.R tests Rendering with minimal_eval=TRUE works 2 0 FALSE ## 9 tests.R tests templates can be knit 12 0 FALSE ## error warning user system real passed ## 1 FALSE 0 0.049 0.004 0.053 10 ## 2 FALSE 0 0.003 0.000 0.003 2 ## 3 FALSE 0 0.262 0.019 2.354 1 ## 4 FALSE 0 0.097 0.039 1.880 11 ## 5 FALSE 0 0.007 0.001 0.008 3 ## 6 FALSE 0 0.020 0.003 1.884 1 ## 7 FALSE 0 0.443 0.043 12.580 12 ## 8 FALSE 0 0.314 0.034 5.271 2 ## 9 FALSE 0 0.128 0.077 58.672 12 ## result ## 1 , 10, 3, 10, 77, 3, 77, 10, 10, testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, req_exist = TRUE), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, fs::file_exists(myfile) is not TRUE\\n\\n, 16, 3, 16, 48, 3, 48, 16, 16, 45, 47, add_text_to_file() works, `sometxt` (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 17, 3, 17, 52, 3, 52, 17, 17, 45, 47, add_text_to_file() works, c(sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 22, 3, 22, 64, 3, 64, 22, 22, 45, 47, add_text_to_file() works, , 25, 3, 25, 62, 3, 62, 25, 25, testthat::expect_error(add_text_to_file(sometxt, myfile, 0)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, 0), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, , 26, 3, 26, 63, 3, 63, 26, 26, testthat::expect_error(add_text_to_file(sometxt, myfile, -1)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, -1), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, , 27, 3, 27, 62, 3, 62, 27, 27, testthat::expect_error(add_text_to_file(sometxt, myfile, 5)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, 5), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, c(moretxt2, sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 32, 3, 32, 74, 3, 74, 32, 32, 45, 47, add_text_to_file() works, c(moretxt2, moretxt3, sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 37, 3, 38, 43, 3, 43, 37, 38, 45, 47, add_text_to_file() works, c(moretxt2, moretxt3, sometxt, moretxt, moretxt4) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 43, 3, 44, 43, 3, 43, 43, 44, 45, 47, add_text_to_file() works ## 2 get_package_directory(".", "mypkg", input) (`actual`) not equal to file.path("inputdir", "mypkg") (`expected`).\\n\\n, 50, 3, 53, 3, 3, 3, 50, 53, 45, 47, get_package_directory() works, get_package_directory("..", "mypkg", input) (`actual`) not equal to file.path("inputdir", "..", "mypkg") (`expected`).\\n\\n, 54, 3, 57, 3, 3, 3, 54, 57, 45, 47, get_package_directory() works ## 3 say_hello("Jacob") (`actual`) not equal to "Hello Jacob!" (`expected`).\\n\\n, 70, 3, 70, 60, 3, 60, 70, 70, 45, 47, load_all() works ## 4 check_unedited(package_path) is not TRUE\\n\\n, 88, 3, 88, 53, 3, 53, 88, 88, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 93, 3, 93, 54, 3, 54, 93, 93, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 97, 3, 97, 53, 3, 53, 97, 97, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 102, 3, 102, 54, 3, 54, 102, 102, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 105, 3, 105, 53, 3, 53, 105, 105, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 112, 3, 112, 54, 3, 54, 112, 112, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 115, 3, 115, 53, 3, 53, 115, 115, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 123, 3, 123, 54, 3, 54, 123, 123, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 126, 3, 126, 53, 3, 53, 126, 126, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 134, 3, 134, 54, 3, 54, 134, 134, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 137, 3, 137, 53, 3, 53, 137, 137, 45, 47, check_unedited works ## 5 `default_params` (`actual`) not equal to rmarkdown::yaml_front_matter(rmd_file)$params (`expected`).\\n\\n, 150, 3, 153, 3, 3, 3, 150, 153, 45, 47, get_params_used works, get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir")) (`actual`) not equal to `params1` (`expected`).\\n\\n, 156, 3, 159, 3, 3, 3, 156, 159, 45, 47, get_params_used works, get_params_used(...) (`actual`) not equal to `params2` (`expected`).\\n\\n, 163, 3, 168, 3, 3, 3, 163, 168, 45, 47, get_params_used works ## 6 fs::file_exists(file.path(dir, "create-rknuth.html")) is not TRUE\\n\\n, 179, 3, 179, 78, 3, 78, 179, 179, 45, 47, Knuth-style references work ## 7 readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works ## 8 readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 291, 3, 292, 68, 3, 68, 291, 292, 45, 47, Rendering with minimal_eval=TRUE works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 299, 3, 300, 68, 3, 68, 299, 300, 45, 47, Rendering with minimal_eval=TRUE works ## 9 fs::file_exists(file.path(dir, "create-rhello.html")) is not TRUE\\n\\n, 316, 3, 316, 78, 3, 78, 316, 316, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "rhello")) is not TRUE\\n\\n, 317, 3, 317, 66, 3, 66, 317, 317, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-rhasdata.html")) is not TRUE\\n\\n, 325, 3, 325, 80, 3, 80, 325, 325, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "rhasdata")) is not TRUE\\n\\n, 326, 3, 326, 68, 3, 68, 326, 326, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-withrcpp.html")) is not TRUE\\n\\n, 334, 3, 334, 80, 3, 80, 334, 334, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "withrcpp")) is not TRUE\\n\\n, 335, 3, 335, 68, 3, 68, 335, 335, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-witharmadillo.Rmd")) is not TRUE\\n\\n, 343, 3, 343, 84, 3, 84, 343, 343, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "witharmadillo")) is not TRUE\\n\\n, 344, 3, 344, 73, 3, 73, 344, 344, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-withpkgdown.html")) is not TRUE\\n\\n, 352, 3, 352, 83, 3, 83, 352, 352, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "withpkgdown")) is not TRUE\\n\\n, 353, 3, 353, 71, 3, 71, 353, 353, 45, 47, templates can be knit, fs::file_exists(...) is not TRUE\\n\\n, 365, 3, 367, 5, 3, 5, 365, 367, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-frombookdown", "frombookdown")) is not TRUE\\n\\n, 368, 3, 370, 5, 3, 5, 368, 370, 45, 47, templates can be knit 13.1 Add examples folder with the output of knitting each example In this section, we will litr-knit each template and put the outputs in an examples directory that lives outside of the litr R package. These examples are linked to in README.Rmd. build_all_templates <- function(install_old, location_of_new) { devtools::install(location_of_new) example_dir <- file.path("..", "examples") if (fs::dir_exists(example_dir)) fs::dir_delete(example_dir) fs::dir_create(example_dir) templates <- fs::path_file(fs::dir_ls("inst/rmarkdown/templates")) templates_bookdown <- stringr::str_subset(templates, "bookdown") templates_nonbookdown <- setdiff(templates, templates_bookdown) for (template in templates_nonbookdown) { tmp_file <- file.path(example_dir, "temp.Rmd") rmarkdown::draft(tmp_file, template, package = "litr", edit = FALSE) pkg_name <- rmarkdown::yaml_front_matter(tmp_file)$params$package_name rmd_file <- file.path(example_dir, paste0("create-", pkg_name, ".Rmd")) fs::file_move(tmp_file, rmd_file) render(rmd_file) # move to a template-specific directory: template_dir <- file.path(example_dir, template) fs::dir_create(template_dir) fs::file_move(fs::dir_ls(example_dir, regexp = pkg_name), template_dir) # move the source-files directory if (fs::dir_exists(file.path(example_dir, "source-files"))) { fs::dir_create(file.path(example_dir, template, "source-files")) fs::dir_copy(file.path(example_dir, "source-files"), file.path(example_dir, template)) fs::dir_delete(file.path(example_dir, "source-files")) } # move the docs directory (when pkgdown creates one) if (fs::dir_exists(file.path(example_dir, "docs"))) { fs::dir_create(file.path(example_dir, template, "docs")) fs::dir_copy(file.path(example_dir, "docs"), file.path(example_dir, template)) fs::dir_delete(file.path(example_dir, "docs")) } } for (template in templates_bookdown) { tmp_dir <- file.path(example_dir, "temp") rmarkdown::draft(file.path(example_dir, "temp.Rmd"), template, package = "litr", edit = FALSE) prev_dir <- getwd() setwd(tmp_dir) fs::file_delete("temp.Rmd") render("index.Rmd") # move to a template-specific directory: setwd(prev_dir) fs::dir_copy(tmp_dir, file.path(example_dir, template)) fs::dir_delete(tmp_dir) } install_old() } xfun::Rscript_call(build_all_templates, list(install_old = install_old, location_of_new = ".")) ## [1] "litr" "],["including-extras-for-litr.html", "14 Including extras for litr 14.1 README with hex sticker 14.2 Vignettes 14.3 A pkgdown site", " 14 Including extras for litr 14.1 README with hex sticker We include a README.Rmd and then generate the README.md based on it: add_readme(file.path("..", "source-files", "README.Rmd")) ## ✔ Writing 'README.Rmd' ## ✔ Adding '^README\\\\.Rmd$' to '.Rbuildignore' ## ✔ Creating '.git/hooks/' ## ✔ Writing '.git/hooks/pre-commit' Let’s add the litr hex sticker too (which is referred to in the README). add_hex_sticker(file.path("..", "source-files", "litr-hex.png")) Let’s also add a figure we include in the README. fs::file_copy(file.path("..", "source-files", "diagram3.png"), file.path("man", "figures")) 14.2 Vignettes add_vignettes(c(file.path("..", "source-files", "package-templates.Rmd"), file.path("..", "source-files", "packages-in-the-wild.Rmd"), file.path("..", "source-files", "faqs.Rmd"), file.path("..", "source-files", "basic-example.Rmd"))) The templates vignette uses dplyr and stringr, so we add them as “Suggests” in the DESCRIPTION file: usethis::use_package("dplyr", type = "Suggests") usethis::use_package("stringr", type = "Suggests") ## ✔ Adding 'dplyr' to Suggests field in DESCRIPTION ## • Use `requireNamespace("dplyr", quietly = TRUE)` to test if package is installed ## • Then directly refer to functions with `dplyr::fun()` ## Warning: Package 'stringr' is already listed in 'Imports' in DESCRIPTION, no ## change made. 14.3 A pkgdown site We’ll first add the github url to the DESCRIPTION file. desc::desc_set("URL", "https://github.com/jacobbien/litr-project/tree/main/litr") ## Package: litr ## Title: Literate Programming for Writing R Packages ## Version: 0.9.1 ## Authors@R (parsed): ## * Jacob Bien <jbien@usc.edu> [aut, cre] ## * Patrick Vossler [aut] ## Description: Allows one to fully create an R package in a single .Rmd ## file. Includes functionality and .Rmd templates for a literate ## programming approach to R package development. ## License: MIT + file LICENSE ## URL: https://github.com/jacobbien/litr-project/tree/main/litr ## Imports: ## bookdown, ## desc, ## devtools, ## digest, ## fansi, ## fs, ## knitr, ## pkgdown, ## rmarkdown, ## stringr, ## usethis, ## xfun, ## xml2, ## yaml ## Suggests: ## dplyr, ## Rcpp, ## testthat (>= 3.0.0) ## VignetteBuilder: ## knitr ## Config/testthat/edition: 3 ## Encoding: UTF-8 ## Roxygen: list(markdown = TRUE) ## RoxygenNote: 7.2.3 Next, we create the pkgdown site. The customizations come from the source file source-files/_pkgdown.yml. pkgdown_yml <- file.path("..", "source-files", "_pkgdown.yml") add_pkgdown(pkgdown_yml) ## ✔ Adding '^_pkgdown\\\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore' ## -- Installing package into temporary library ----------------------------------- ## == Building pkgdown site ======================================================= ## Reading from: '/Users/vossler/litr-project/litr' ## Writing to: '/Users/vossler/litr-project/docs' ## -- Initialising site ----------------------------------------------------------- ## -- Building favicons ----------------------------------------------------------- ## Building favicons with realfavicongenerator.net... ## Copying 'pkgdown/favicon/apple-touch-icon-120x120.png' to 'apple-touch-icon-120x120.png' ## Copying 'pkgdown/favicon/apple-touch-icon-152x152.png' to 'apple-touch-icon-152x152.png' ## Copying 'pkgdown/favicon/apple-touch-icon-180x180.png' to 'apple-touch-icon-180x180.png' ## Copying 'pkgdown/favicon/apple-touch-icon-60x60.png' to 'apple-touch-icon-60x60.png' ## Copying 'pkgdown/favicon/apple-touch-icon-76x76.png' to 'apple-touch-icon-76x76.png' ## Copying 'pkgdown/favicon/apple-touch-icon.png' to 'apple-touch-icon.png' ## Copying 'pkgdown/favicon/favicon-16x16.png' to 'favicon-16x16.png' ## Copying 'pkgdown/favicon/favicon-32x32.png' to 'favicon-32x32.png' ## -- Building home --------------------------------------------------------------- ## Reading 'LICENSE.md' ## Writing '404.html' ## -- Building function reference ------------------------------------------------- ## Reading 'man/add_chunk_label_hyperlinks.Rd' ## Reading 'man/add_function_hyperlinks.Rd' ## Reading 'man/add_hex_sticker.Rd' ## Reading 'man/add_pkgdown.Rd' ## Reading 'man/add_readme.Rd' ## Reading 'man/add_text_to_file.Rd' ## Reading 'man/add_vignettes.Rd' ## Reading 'man/check_unedited.Rd' ## Reading 'man/description_litr_hash_field_name.Rd' ## Reading 'man/description_litr_version_field_name.Rd' ## Reading 'man/do_not_edit_message.Rd' ## Reading 'man/document.Rd' ## Reading 'man/find_labels.Rd' ## Reading 'man/get_package_directory.Rd' ## Reading 'man/get_params_used.Rd' ## Reading 'man/hash_package_directory.Rd' ## Reading 'man/insert_hrefs.Rd' ## Reading 'man/litr-package.Rd' ## Reading 'man/litr_gitbook.Rd' ## Reading 'man/litr_html_document.Rd' ## Reading 'man/litr_pdf_document.Rd' ## Reading 'man/litrify_output_format.Rd' ## Reading 'man/load_all.Rd' ## Reading 'man/make_noticeable.Rd' ## Reading 'man/read_hash_from_description.Rd' ## Reading 'man/remove_rstudio_extras.Rd' ## Reading 'man/render.Rd' ## Reading 'man/replace_ansi_sequences.Rd' ## Reading 'man/restore_knitr_objects.Rd' ## Reading 'man/send_to_package.Rd' ## Reading 'man/setup.Rd' ## Reading 'man/test_litr.Rd' ## Reading 'man/with_cleanup.Rd' ## Reading 'man/write_hash_to_description.Rd' ## Reading 'man/write_version_to_description.Rd' ## -- Building articles ----------------------------------------------------------- ## Reading 'vignettes/basic-example.Rmd' ## Reading 'vignettes/faqs.Rmd' ## Reading 'vignettes/package-templates.Rmd' ## Reading 'vignettes/packages-in-the-wild.Rmd' ## Writing 'sitemap.xml' ## -- Building search index ------------------------------------------------------- ## == DONE ======================================================================== We follow this pkgdown vignette in our customizations. Here is the contents of the _pkgdown.yml that was used: cat(readLines("../source-files/_pkgdown.yml"), sep = '\\n') destination: ../docs/ url: ~ template: bootstrap: 5 bootswatch: cosmo repo: url: home: https://github.com/jacobbien/litr-project/tree/main/litr/ source: https://github.com/jacobbien/litr-project/tree/main/litr/ issue: https://github.com/jacobbien/litr-project/issues/ user: https://github.com/ authors: Jacob Bien: href: http://faculty.marshall.usc.edu/jacob-bien/ Patrick Vossler: href: https://www.patvoss.me/ navbar: structure: left: [reference, articles] right: [github] components: github: icon: fa-github href: https://github.com/jacobbien/litr-project/tree/main/litr/ reference: - title: Primary functions desc: > These are the functions you'll use the most. contents: - render - document - load_all - title: Functions for adding "extras" to your package desc: > These functions can help you add a README, vignettes, a pkgdown site, and a hex sticker to your package. contents: - add_readme - add_vignettes - add_pkgdown - add_hex_sticker - title: Custom output formats desc: > These are the functions for producing different output formats. contents: - litr_html_document - litr_pdf_document - litr_gitbook - litrify_output_format After this step, you can locally see the site by opening docs/index.html in the browser. You can then copy the docs directory to your website’s server and you’re done. "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]] diff --git a/create-litr/_book/tests.html b/create-litr/_book/tests.html new file mode 100644 index 0000000..fe3b894 --- /dev/null +++ b/create-litr/_book/tests.html @@ -0,0 +1,671 @@ + + + + + + + 12 Defining some tests | Creating the litr R package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+ +
+
+

12 Defining some tests

+

When using litr to create packages that are not litr, one should be able to run tests along the way as we did above in testing the function add_text_to_file(). However, creating litr is a special case so we need to do something different for the tests that involve creating a .Rmd from template and then calling litr::render() on them (such as the tests in this section). In particular, we use eval=FALSE for these code blocks and then at the end of this document we will install the newly created version of litr and then call devtools::test(). Doing it this way is important for ensuring that the version of litr we are testing is the newest version, i.e. the version defined in this document.

+

To understand the reason we are doing it this way, imagine what would happen if instead we left eval=TRUE in the test in the next section. When we use rmarkdown::draft() to create a .Rmd file from template, the file it will give us will be an old version (namely the installed version of litr’s template) rather than the latest version.2 Furthermore, consider what happens when we call render() in the test below. This will start the knitting process on my-package.Rmd. However, inside my-package.Rmd, we have litr::setup() and litr::document(). When these are called in the knitting process, it will be the versions of the functions from the currently installed litr rather than the versions defined in this document.

+

Once we are done testing the new version of the package, we’d like to restore the state of litr to what it was previously. If we don’t do this, then this can lead to inadvertent circularity in which the next time we call litr::render("create-litr.Rmd"), we are using the version currently under development, which is bad because ultimately we need this version to be rendered by the previous version of litr. The following function implements this approach to testing litr:

+
#' Run tests for `litr` itself
+#' 
+#' Special function for testing `litr`.  The trick is to temporarily install
+#' the new version of `litr`, run the test, and then put things back how it was
+#' before.
+#' 
+#' Typical values for `install_old` could be
+#' - `function() devtools::install("[location of old version]")`
+#' - `function() remotes::install_github("jacobbien/litr-project@*release", subdir = "litr")`.
+#' 
+#' @param install_old A function that when run will install the old version
+#' @param location_of_new Path to the new package directory
+#' @keywords internal
+test_litr <- function(install_old, location_of_new) {
+  devtools::unload(params$package_name)
+  devtools::install(location_of_new)
+  out <- devtools::test(location_of_new)
+  install_old()
+  return(out)
+}
+

Note: The call to devtools::unload() is to address an issue discussed here.

+
+

12.1 Testing check_unedited()

+

For our tests, we create a temporary directory (which we delete at the end). In this directory, we create a generating .Rmd file from one of the templates. We make repeated modifications to the package and each time verify that check_unedited() is FALSE with the modification and returns to TRUE when we put things back how they were. The modifications we try are the following:

+
    +
  • Adding a file

  • +
  • Removing a file

  • +
  • Making a change to a file (in particular, adding a comment to an R file)

  • +
  • Changing something in the DESCRIPTION file (but not on the special litr line)

  • +
  • Changing the litr hash line itself

  • +
+
testthat::test_that("check_unedited works", {
+  # Including this next line seems to be necessary for R CMD check on the cmd line:
+  #Sys.setenv(RSTUDIO_PANDOC = "/Applications/RStudio.app/Contents/MacOS/pandoc")
+  dir <- tempfile()
+  fs::dir_create(dir)
+  rmd_file <- file.path(dir, "my-package.Rmd")
+  rmarkdown::draft(rmd_file,
+                   template = "make-an-r-package",
+                   package = "litr",
+                   edit = FALSE)
+  # create R package (named "rhello") from the Rmd template:
+  render(rmd_file)
+  package_path <- file.path(dir, "rhello")
+  testthat::expect_true(check_unedited(package_path))
+
+  # what if a file has been added?
+  added_file <- file.path(package_path, "R", "say_hello2.R")
+  writeLines("# Added something here.", added_file)
+  testthat::expect_false(check_unedited(package_path))
+
+  # what if we now remove it?
+  fs::file_delete(added_file)
+  testthat::expect_true(check_unedited(package_path))
+
+  # what if a file is removed from package?
+  rfile <- file.path(package_path, "R", "say_hello.R")
+  fs::file_move(rfile, dir)
+  testthat::expect_false(check_unedited(package_path))
+  # now put it back
+  fs::file_move(file.path(dir, "say_hello.R"), file.path(package_path, "R"))
+  testthat::expect_true(check_unedited(package_path))
+
+  # what if something is changed in a file?
+  txt <- readLines(rfile)
+  txt_mod <- txt
+  txt_mod[3] <- paste0(txt[3], " # added a comment!!")
+  writeLines(txt_mod, rfile)
+  testthat::expect_false(check_unedited(package_path))
+  # now put it back
+  writeLines(txt, rfile)
+  testthat::expect_true(check_unedited(package_path))
+
+  # what if something is changed in the DESCRIPTION file?
+  descfile <- file.path(package_path, "DESCRIPTION")
+  txt <- readLines(descfile)
+  txt_mod <- txt
+  txt_mod[1] <- "Package: newname"
+  writeLines(txt_mod, descfile)
+  testthat::expect_false(check_unedited(package_path))
+  # now put it back
+  writeLines(txt, descfile)
+  testthat::expect_true(check_unedited(package_path))
+
+  # what if the special litr hash field is changed in the DESCRIPTION file?
+  txt <- readLines(descfile)
+  i_litr <- stringr::str_which(txt, description_litr_hash_field_name())
+  txt_mod <- txt
+  txt_mod[i_litr] <- paste0(txt_mod[i_litr], "a")
+  writeLines(txt_mod, descfile)
+  testthat::expect_false(check_unedited(package_path))
+  # now put it back
+  writeLines(txt, descfile)
+  testthat::expect_true(check_unedited(package_path))
+
+  fs::dir_delete(dir)
+})
+
+
+

12.2 Testing get_params_used()

+

Let’s now test the get_params_used() function, making sure it behaves how we expect it to:

+
testthat::test_that("get_params_used works", {
+  dir <- tempfile()
+  if (fs::file_exists(dir)) fs::file_delete(dir)
+  fs::dir_create(dir)
+  rmd_file <- file.path(dir, "my-package.Rmd")
+  rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr",
+                   edit = FALSE)
+  default_params <- get_params_used(rmd_file, passed_params = list())
+  testthat::expect_equal(
+    default_params,
+    rmarkdown::yaml_front_matter(rmd_file)$params
+  )
+  params1 <- default_params
+  params1$package_parent_dir <- "dir"
+  testthat::expect_equal(
+    get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir")),
+    params1
+  )
+  params2 <- default_params
+  params2$package_name <- "pkg"
+  params2$package_parent_dir <- "dir"
+  testthat::expect_equal(
+    get_params_used(rmd_file,
+                    passed_params = list(package_parent_dir = "dir",
+                                         package_name = "pkg")),
+    params2
+  )
+  fs::dir_delete(dir)
+})
+
+
+

12.3 Testing chunk referencing

+

Here we test the handling of chunk references (as implemented in the document output hook set within setup()). In particular, we use a .Rmd that uses chunk references in several different ways. Within the .Rmd itself, we have tests that ensure that the code can still be run as expected.

+
fs::file_copy(
+  path = file.path(
+    "..", "source-files", "test-example-files", "create-rknuth.Rmd"
+    ), 
+  new_path = file.path("tests", "testthat"), 
+  overwrite = TRUE
+)
+
testthat::test_that('Knuth-style references work', {
+  dir <- tempfile()
+  if (fs::file_exists(dir)) fs::file_delete(dir)
+  fs::dir_create(dir)
+  rmd_file <- file.path(dir, 'create-rknuth.Rmd')
+  fs::file_copy(path = testthat::test_path("create-rknuth.Rmd"), new_path = rmd_file)
+  render(rmd_file)
+  testthat::expect_true(fs::file_exists(file.path(dir, 'create-rknuth.html')))
+  fs::dir_delete(dir)
+})
+
+
+

12.4 Testing different ways of rendering

+

The mechanism by which rendering occurs depends on several factors:

+
    +
  1. Whether litr::render() or rmarkdown::render() is being called.

  2. +
  3. Whether there is a litr output format specified in the preamble of the .Rmd.

  4. +
  5. Whether there is a litr output format being passed an argument to the render function.

  6. +
+

In this section, we will test that one gets the same result regardless of how rendering was invoked.3 We will use variations on a base .Rmd file whose preamble is simply the following:

+
---
+title: 'A Test'
+params:
+  package_name: 'pkg' # <-- change this to your package name
+  package_parent_dir: '.' # <-- relative to this file location
+---
+
fs::file_copy(
+  path = file.path(
+    "..", "source-files", "test-example-files", "create-pkg.Rmd"
+    ), 
+  new_path = file.path("tests", "testthat"), 
+  overwrite = TRUE
+)
+

There are 7 cases to consider (\(2^3-1\), since we exclude the case where rmarkdown::render() is called and no argument or preamble would indicate that this should be a litr-knit).

+
testthat::test_that('Rendering in all possible ways works', {
+  
+  # setup files for tests:
+  dir <- tempfile()
+  if (fs::file_exists(dir)) fs::file_delete(dir)
+  fs::dir_create(dir)
+  # .Rmd without output format in preamble
+  rmd_file1 <- file.path(dir, 'create-pkg1.Rmd')
+  fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file1)
+  # .Rmd without output format in preamble
+  rmd_file2 <- file.path(dir, 'create-pkg2.Rmd')
+  fs::file_copy(rmd_file1, rmd_file2)
+  litr:::add_text_to_file("output: litr::litr_html_document", rmd_file2, 3)
+  # files names
+  rmd_file <- file.path(dir, "create-pkg.Rmd")  
+  html_file <- file.path(dir, "create-pkg.html")
+  html_file_a <- file.path(dir, "a","create-pkg.html")
+  pkg <- file.path(dir, "pkg")
+  pkg_a <- file.path(dir, "a", "pkg")
+  check_outputs_are_same <- function() {
+    # html files should be the same:
+    testthat::expect_equal(readLines(html_file_a), readLines(html_file))
+    # packages should be the same (relying here on litr-hash in DESCRIPTION):
+    testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")),
+                           readLines(file.path(pkg_a, "DESCRIPTION")))
+  }
+
+  ## Now test that all the cases give the same outputs:
+  
+  # Case 1: no preamble + litr::render()
+  fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE)
+  render(rmd_file, output_file = html_file)
+  if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a"))
+  fs::dir_create(file.path(dir, "a"))
+  fs::dir_copy(pkg, pkg_a)
+  fs::dir_delete(pkg)
+  fs::file_move(html_file, html_file_a)
+
+  # Case 2: with preamble + litr::render()
+  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
+  render(rmd_file, output_file = html_file)
+  check_outputs_are_same()
+  
+  # Case 3: no preamble + litr::render() with output format argument
+  fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE)
+  render(rmd_file, output_format = litr::litr_html_document(),
+         output_file = html_file)
+  check_outputs_are_same()
+  
+  # Case 4: with preamble + litr::render() with output format argument
+  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
+  render(rmd_file, output_format = litr::litr_html_document(),
+         output_file = html_file)
+  check_outputs_are_same()
+
+  # Case 5: with preamble + rmarkdown::render()
+  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
+  xfun::Rscript_call(rmarkdown::render,
+                     list(input = rmd_file, output_file = html_file)
+                     )
+  check_outputs_are_same()
+
+  # Case 6: no preamble + rmarkdown::render() with output format argument
+  fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE)
+  xfun::Rscript_call(rmarkdown::render,
+                     list(input = rmd_file,
+                          output_format = litr::litr_html_document(),
+                          output_file = html_file)
+                     )
+  check_outputs_are_same()
+
+  # Case 7: with preamble + rmarkdown::render() with output format argument
+  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
+  xfun::Rscript_call(rmarkdown::render,
+                     list(input = rmd_file,
+                          output_format = litr::litr_html_document(),
+                          output_file = html_file)
+                     )
+  check_outputs_are_same()
+  
+  fs::dir_delete(dir)
+})
+

Let’s also make sure that we get the same R package output when using minimal_eval=TRUE as minimal_eval=TRUE.

+
testthat::test_that('Rendering with minimal_eval=TRUE works', {
+  
+  # setup files for tests:
+  dir <- tempfile()
+  if (fs::file_exists(dir)) fs::file_delete(dir)
+  fs::dir_create(dir)
+  rmd_file <- file.path(dir, 'create-pkg.Rmd')
+  fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file)
+  # .Rmd without output format in preamble
+  html_file <- file.path(dir, "create-pkg.html")
+  html_file_a <- file.path(dir, "a","create-pkg.html")
+  pkg <- file.path(dir, "pkg")
+  pkg_a <- file.path(dir, "a", "pkg")
+
+  ## Now test that all the cases give the same outputs:
+  
+  # Case 1: minimal_eval = FALSE
+  render(rmd_file, output_file = html_file, minimal_eval = FALSE)
+  if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a"))
+  fs::dir_create(file.path(dir, "a"))
+  fs::dir_copy(pkg, pkg_a)
+  fs::dir_delete(pkg)
+
+  # Case 2: minimal_eval = TRUE passed to render
+  render(rmd_file, output_file = html_file, minimal_eval = TRUE)
+  testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")),
+                         readLines(file.path(pkg_a, "DESCRIPTION")))
+
+  # Case 3: minimal_eval = TRUE passed to output format
+  render(rmd_file,
+         output_file = html_file,
+         output_format = litr::litr_html_document(minimal_eval = TRUE)
+         )
+  testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")),
+                         readLines(file.path(pkg_a, "DESCRIPTION")))
+
+  fs::dir_delete(dir)
+})
+
+
+

12.5 Testing other templates

+

Let’s now make sure that each template can be knit without error.

+
testthat::test_that("templates can be knit", {
+  dir <- tempfile()
+  if (fs::file_exists(dir)) fs::file_delete(dir)
+  fs::dir_create(dir)
+  
+  rmd_file <- file.path(dir, "create-rhello.Rmd")
+  rmarkdown::draft(rmd_file,
+                   template = "make-an-r-package",
+                   package = "litr",
+                   edit = FALSE)
+  render(rmd_file)
+  testthat::expect_true(fs::file_exists(file.path(dir, "create-rhello.html")))
+  testthat::expect_true(fs::file_exists(file.path(dir, "rhello")))
+
+  rmd_file <- file.path(dir, "create-rhasdata.Rmd")
+  rmarkdown::draft(rmd_file,
+                   template = "make-an-r-package-with-data",
+                   package = "litr",
+                   edit = FALSE)
+  render(rmd_file)
+  testthat::expect_true(fs::file_exists(file.path(dir, "create-rhasdata.html")))
+  testthat::expect_true(fs::file_exists(file.path(dir, "rhasdata")))
+
+  rmd_file <- file.path(dir, "create-withrcpp.Rmd")
+  rmarkdown::draft(rmd_file,
+                   template = "make-an-r-package-with-rcpp",
+                   package = "litr",
+                   edit = FALSE)
+  render(rmd_file)
+  testthat::expect_true(fs::file_exists(file.path(dir, "create-withrcpp.html")))
+  testthat::expect_true(fs::file_exists(file.path(dir, "withrcpp")))
+
+  rmd_file <- file.path(dir, "create-witharmadillo.Rmd")
+  rmarkdown::draft(rmd_file,
+                   template = "make-an-r-package-with-armadillo",
+                   package = "litr",
+                   edit = FALSE)
+  render(rmd_file)
+  testthat::expect_true(fs::file_exists(file.path(dir, "create-witharmadillo.Rmd")))
+  testthat::expect_true(fs::file_exists(file.path(dir, "witharmadillo")))
+    
+  rmd_file <- file.path(dir, "create-withpkgdown.Rmd")
+  rmarkdown::draft(rmd_file,
+                   template = "make-an-r-package-with-extras",
+                   package = "litr",
+                   edit = FALSE)
+  render(rmd_file)
+  testthat::expect_true(fs::file_exists(file.path(dir, "create-withpkgdown.html")))
+  testthat::expect_true(fs::file_exists(file.path(dir, "withpkgdown")))
+
+  rmd_file <- file.path(dir, "create-frombookdown.Rmd")
+  rmarkdown::draft(rmd_file,
+                   template = "make-an-r-package-from-bookdown",
+                   package = "litr",
+                   edit = FALSE)
+  prev_dir <- getwd()
+  setwd(file.path(dir, "create-frombookdown"))
+  fs::file_delete("create-frombookdown.Rmd")
+  render("index.Rmd")
+  setwd(prev_dir)
+  testthat::expect_true(
+    fs::file_exists(file.path(dir, "create-frombookdown", "_book", "index.html"))
+    )
+  testthat::expect_true(
+    fs::file_exists(file.path(dir, "create-frombookdown", "frombookdown"))
+    )
+
+  fs::dir_delete(dir)
+ })
+

Even though litr doesn’t directly use Rcpp, we’ll add it as a “Suggests” package since it would be required for running the above test.

+
usethis::use_package("Rcpp", type = "Suggests")
+
## ✔ Adding 'Rcpp' to Suggests field in DESCRIPTION
+## • Use `requireNamespace("Rcpp", quietly = TRUE)` to test if package is installed
+## • Then directly refer to functions with `Rcpp::fun()`
+
+
+
+
+
    +
  1. If this were the only problem, we could get around this by using pkgload::package_file() to get the proper file; however, the next problem discussed was something that seemed quite hard to resolve.↩︎

  2. +
  3. Note: When we call rmarkdown::render(), we call it in a fresh, non-interactive R session.↩︎

  4. +
+
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + + diff --git a/create-litr/index.Rmd b/create-litr/index.Rmd index 8299d80..bdbc025 100644 --- a/create-litr/index.Rmd +++ b/create-litr/index.Rmd @@ -108,7 +108,10 @@ if (install_version_of_litr != version_of_latest_release) stop(stringr::str_glue( "You should be using the version of litr from the latest release (version", " {version_of_latest_release}),\n but you are using version", - " {install_version_of_litr}." + " {install_version_of_litr}.\n", + "You can install the release version of litr from GitHub by running the", + " the following command:\n", + "remotes::install_github(repo='jacobbien/litr-project@*release', subdir = 'litr')" )) ``` @@ -1010,12 +1013,12 @@ litr_html_document <- function(minimal_eval = FALSE, ...) { # modify post_processor new$post_processor = function(metadata, input_file, output_file, ...) { out <- old$post_processor(metadata, input_file, output_file, ...) - html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$") + # html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$") # add hyperlinks within html output to make it easier to navigate: - add_function_hyperlinks(html_files, metadata$params$package_name) - add_chunk_label_hyperlinks(html_files) + add_function_hyperlinks(output_file, metadata$params$package_name) + add_chunk_label_hyperlinks(output_file) # replace ANSI sequences with HTML tag equivalents - replace_ansi_sequences(html_files) + replace_ansi_sequences(output_file) out } new @@ -1170,7 +1173,7 @@ insert_hrefs <- function(txt, function_pattern, where_defined, ``` -In addition to adding hyperlinks to function definitions, we also want to add hyperlinks for chunk references of the form `<>` by wrapping comments of the form `###"chunk-name"###` in a `span` tag with `id="chunk-name"`. +In addition to adding hyperlinks to function definitions, we also want to add hyperlinks for chunk references of the form `<>` that link to user-defined chunk names that take the form `###chunk-name###`. We want these user-defined chunk names to stand out to readers and in their original form as code comments they might be harder to notice. To solve this issue, we modify the formatting of these chunks to add a border with the name of the chunk set into the border, mimicking the look of a `
` tag. The additional formatting requires inserting additional HTML tags as well as css into the knitted HTML file so we use the `xml2` package to parse and manipulate the knitted HTML file. ```{r} #' Add hyperlinks to embedded chunks @@ -1204,8 +1207,10 @@ add_chunk_label_hyperlinks <- function(html_files, pattern, stringr::str_glue("###"\\1"###") ) - # and keep track of it for later: - chunk_names <- c(chunk_names, chunk_name) + # and keep track of it for later. + # we're using setNames here to make sure that we keep the name of file + # where the chunk name is defined + chunk_names <- setNames(c(chunk_names, chunk_name), c(names(chunk_names), html_file)) } list(chunk_names = chunk_names, txt = txt) } @@ -1265,10 +1270,49 @@ add_chunk_label_hyperlinks <- function(html_files, } ) - writeLines(txt, con = html_files[i]) + parsed_html <- xml2::read_html(paste(txt,collapse="\n")) + # get all possible chunk names in this file. + chunk_names <- all_chunk_names[which(names(all_chunk_names) == html_files[i])] + + if(length(chunk_names) > 0){ + for(j in seq_along(chunk_names)){ + span_node <- xml2::xml_find_first(parsed_html, stringr::str_glue('(.//span[@id="{chunk_names[j]}"])')) + span_node_path <- stringr::str_split(xml2::xml_path(span_node),"/") + + pre_path <- paste(span_node_path[[1]][1:(max(which(stringr::str_detect(span_node_path[[1]], "pre"))))],collapse="/") + if(nchar(pre_path) == 0){ + next() + } + pre_parent <- xml2::xml_find_first(parsed_html, pre_path) + if(is.na(pre_parent)){ + next() + } + xml2::xml_add_parent(pre_parent + , xml2::read_xml(stringr::str_glue('
'))) + xml2::xml_add_sibling(pre_parent, xml2::read_xml(stringr::str_glue('{chunk_names[j]}')), where="before") + # xml2::xml_add_parent(pre_parent, xml2::read_xml(stringr::str_glue('
{chunk_names[j]}
'))) + xml2::xml_remove(span_node) + # remove the extra line break that is left over from removing the span + code_node <- xml2::xml_child(pre_parent) + changed_txt <- stringr::str_remove(paste(as.character(xml2::xml_contents(code_node)),collapse=""), '\n') + xml2::xml_replace(code_node, xml2::read_xml(stringr::str_glue('{changed_txt}'))) + } + } + # last thing is to insert an additional style node in the head with our CSS so we have a standard style whether we are using bookdown or Rmarkdown + css_string <- "fieldset.chunkfield {border:4px solid black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em} + legend.chunklegend {padding:0;font-size:21px;width:auto;border:0; border-bottom: none; margin-bottom:0} + " + head_node <- xml2::xml_find_first(parsed_html, ".//head") + xml2::xml_add_child(head_node, xml2::read_xml(stringr::str_glue(""))) + txt <- xml2::write_html(parsed_html, html_files[i]) } } ``` +Since we rely on the `xml2` package to add in the extra label formatting, let's import it: +```{r} +usethis::use_package("xml2") +``` + Finally, we want to replace the ANSI escape sequences used by packages such as `testthat` and `devtools` with their HTML equivalents so the output matches what we see in the terminal. @@ -1326,7 +1370,10 @@ litr_gitbook <- function(minimal_eval = FALSE, ...) { # modify post_processor new$post_processor = function(metadata, input_file, output_file, ...) { out <- old$post_processor(metadata, input_file, output_file, ...) - html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$") + out_dir <- fs::path_dir(out) + file_stems <- readLines(file.path(out_dir, "reference-keys.txt")) + html_files <- file.path(out_dir, paste0(file_stems, ".html")) + html_files <- unique(intersect(c(out, html_files), fs::dir_ls(out_dir))) # add hyperlinks within html output to make it easier to navigate: add_function_hyperlinks(html_files, metadata$params$package_name) add_chunk_label_hyperlinks(html_files) diff --git a/docs/404.html b/docs/404.html index a579720..d7b19eb 100644 --- a/docs/404.html +++ b/docs/404.html @@ -13,8 +13,8 @@ - - + + diff --git a/docs/LICENSE-text.html b/docs/LICENSE-text.html index 3772ecf..bc00c22 100644 --- a/docs/LICENSE-text.html +++ b/docs/LICENSE-text.html @@ -1,5 +1,5 @@ -License • litrLicense • litr @@ -50,7 +50,7 @@
-
YEAR: 2023
+
YEAR: 2024
 COPYRIGHT HOLDER: J. Bien
 
diff --git a/docs/LICENSE.html b/docs/LICENSE.html index 7d45a78..ef8bf3e 100644 --- a/docs/LICENSE.html +++ b/docs/LICENSE.html @@ -1,5 +1,5 @@ -MIT License • litrMIT License • litr @@ -52,7 +52,7 @@
-

Copyright (c) 2023 J. Bien

+

Copyright (c) 2024 J. Bien

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

diff --git a/docs/apple-touch-icon-120x120.png b/docs/apple-touch-icon-120x120.png index b8f3cff3fd486c34b324c811f5207fa8270a223a..4689f32ac061c5db08094b55ccaee9f7683759f8 100644 GIT binary patch delta 101 zcmZ4KveIQj1=kBk77?-Kc18=KxLcpK{)8t57sh8S8{nHX4^nrj;vSQ!{xaBaBA lz`&qd;u=wsl30>zm7AZEnO4bQWMG6{!$Jv*+{wEX^8q)M9mW6v diff --git a/docs/apple-touch-icon-152x152.png b/docs/apple-touch-icon-152x152.png index a322fe00c9162bb64d246683862d9546222972d8..36cc6569277c54ff40069368cdc03f38d5889c6f 100644 GIT binary patch delta 101 zcmewo^d)FQ1=kBk77?-Kc18=EXNyp44Y4RnnRLkumfObo0{&9w~-tPBh;xHep5 lU|>)!ag8WRNi0dV%FR#7OsixtGBCofVWEUY?qpWYd;mj#9UcGx diff --git a/docs/apple-touch-icon-180x180.png b/docs/apple-touch-icon-180x180.png index 415515a0bbd55fe0b33c80f83f5229f3fe440cda..55799f6b5f257d343ba401801a9d1328b4777b5d 100644 GIT binary patch delta 101 zcmaEs{49Aw1=kBk77g)9FqV5 delta 101 zcmaEs{49Aw1=n*fQ6UYhJVCn&8=F=ccpK{)8t57sh8S8{nHX4^T4);>SQ!|o@qVyi lU|>)!ag8WRNi0dV%FR#7OsixtGBCofAw0mNWpcS;J^&PX9LfLy diff --git a/docs/apple-touch-icon-60x60.png b/docs/apple-touch-icon-60x60.png index 5b53766abd2b8704dcafa46104119025f00ebda2..d524f5bf3e3325da3a1592a53bd21b90345feecc 100644 GIT binary patch delta 83 zcmew$|3Q921=kBk77mFw`|L2{AITGB&p|GSfCNure@s;k{`; m0|SFRc?MtW?ChKk%19*4Ku~Km?xhR&IbTqTOGgv delta 101 zcmdn3v0Gz81=n*fQ6UYB2Ko3s8=L+Mc^m5*8t57sh8S8{nHX4^nrRytSQ!|U8Fi;J lFfgc=xJHzuB$lLF<>sekrd2W+85m*Lz@Be>bMhJCd;kbZ9UlMy diff --git a/docs/apple-touch-icon.png b/docs/apple-touch-icon.png index 055f0fb09917f030642298d12e8dc7ed9a8a1204..95b6b56efc0801b4a95f12ae9a5b79daddf56a72 100644 GIT binary patch delta 101 zcmaEs{49Aw1=kBk77zm7AZEnO4bQWMG6{!@|#ceUr-#^8rDb9%BFi delta 101 zcmaEs{49Aw1=n*fQ6UX$NijaQjZLczyp44Y4RnnRLkumfObo0{Ewv2{tPBhqs($w{ lFfgc=xJHzuB$lLF<>sekrd2W+85m*L@KN2_U~;)(J^=lJ9RUCU diff --git a/docs/articles/basic-example.html b/docs/articles/basic-example.html index 7a1bc0c..9991b22 100644 --- a/docs/articles/basic-example.html +++ b/docs/articles/basic-example.html @@ -14,8 +14,8 @@ - - + + diff --git a/docs/articles/faqs.html b/docs/articles/faqs.html index c89d072..496e202 100644 --- a/docs/articles/faqs.html +++ b/docs/articles/faqs.html @@ -14,8 +14,8 @@ - - + + diff --git a/docs/articles/index.html b/docs/articles/index.html index b3534ae..f4df960 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -1,5 +1,5 @@ -Articles • litrArticles • litr diff --git a/docs/articles/package-templates.html b/docs/articles/package-templates.html index 62046d5..669a091 100644 --- a/docs/articles/package-templates.html +++ b/docs/articles/package-templates.html @@ -14,8 +14,8 @@ - - + + diff --git a/docs/articles/packages-in-the-wild.html b/docs/articles/packages-in-the-wild.html index 1fbb366..3ffb91f 100644 --- a/docs/articles/packages-in-the-wild.html +++ b/docs/articles/packages-in-the-wild.html @@ -14,8 +14,8 @@ - - + + diff --git a/docs/authors.html b/docs/authors.html index 07bc1fb..05f744e 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -1,5 +1,5 @@ -Authors and Citation • litrAuthors and Citation • litr @@ -66,14 +66,14 @@

Authors

Citation

Source: DESCRIPTION

-

Bien J, Patrick Vossler (2023). +

Bien J, Patrick Vossler (2024). litr: Literate Programming for Writing R Packages. R package version 0.9.1, https://github.com/jacobbien/litr-project/tree/main/litr.

@Manual{,
   title = {litr: Literate Programming for Writing R Packages},
   author = {Jacob Bien and {Patrick Vossler}},
-  year = {2023},
+  year = {2024},
   note = {R package version 0.9.1},
   url = {https://github.com/jacobbien/litr-project/tree/main/litr},
 }
diff --git a/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js b/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js new file mode 100644 index 0000000..e8f21f7 --- /dev/null +++ b/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.3.1 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==I(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=P(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function P(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function M(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function j(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const F={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${j(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${j(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=M(t.dataset[n])}return e},getDataAttribute:(t,e)=>M(t.getAttribute(`data-bs-${j(e)}`))};class H{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?F.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?F.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends H{constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),N.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.1"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return n(e)},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;N.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},q=".bs.alert",V=`close${q}`,K=`closed${q}`;class Q extends W{static get NAME(){return"alert"}close(){if(N.trigger(this._element,V).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),N.trigger(this._element,K),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(Q,"close"),m(Q);const X='[data-bs-toggle="button"]';class Y extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=Y.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}N.on(document,"click.bs.button.data-api",X,(t=>{t.preventDefault();const e=t.target.closest(X);Y.getOrCreateInstance(e).toggle()})),m(Y);const U=".bs.swipe",G=`touchstart${U}`,J=`touchmove${U}`,Z=`touchend${U}`,tt=`pointerdown${U}`,et=`pointerup${U}`,it={endCallback:null,leftCallback:null,rightCallback:null},nt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class st extends H{constructor(t,e){super(),this._element=t,t&&st.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return it}static get DefaultType(){return nt}static get NAME(){return"swipe"}dispose(){N.off(this._element,U)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(N.on(this._element,tt,(t=>this._start(t))),N.on(this._element,et,(t=>this._end(t))),this._element.classList.add("pointer-event")):(N.on(this._element,G,(t=>this._start(t))),N.on(this._element,J,(t=>this._move(t))),N.on(this._element,Z,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ot=".bs.carousel",rt=".data-api",at="next",lt="prev",ct="left",ht="right",dt=`slide${ot}`,ut=`slid${ot}`,ft=`keydown${ot}`,pt=`mouseenter${ot}`,mt=`mouseleave${ot}`,gt=`dragstart${ot}`,_t=`load${ot}${rt}`,bt=`click${ot}${rt}`,vt="carousel",yt="active",wt=".active",At=".carousel-item",Et=wt+At,Tt={ArrowLeft:ht,ArrowRight:ct},Ct={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Ot={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class xt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===vt&&this.cycle()}static get Default(){return Ct}static get DefaultType(){return Ot}static get NAME(){return"carousel"}next(){this._slide(at)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(lt)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?N.one(this._element,ut,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,ut,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?at:lt;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&N.on(this._element,ft,(t=>this._keydown(t))),"hover"===this._config.pause&&(N.on(this._element,pt,(()=>this.pause())),N.on(this._element,mt,(()=>this._maybeEnableCycle()))),this._config.touch&&st.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))N.on(t,gt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ct)),rightCallback:()=>this._slide(this._directionToOrder(ht)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new st(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Tt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(wt,this._indicatorsElement);e.classList.remove(yt),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(yt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===at,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>N.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(dt).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(yt),i.classList.remove(yt,c,l),this._isSliding=!1,r(ut)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(Et,this._element)}_getItems(){return z.find(At,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ct?lt:at:t===ct?at:lt}_orderToDirection(t){return p()?t===lt?ct:ht:t===lt?ht:ct}static jQueryInterface(t){return this.each((function(){const e=xt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}N.on(document,bt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(vt))return;t.preventDefault();const i=xt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===F.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),N.on(window,_t,(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)xt.getOrCreateInstance(e)})),m(xt);const kt=".bs.collapse",Lt=`show${kt}`,St=`shown${kt}`,Dt=`hide${kt}`,$t=`hidden${kt}`,It=`click${kt}.data-api`,Nt="show",Pt="collapse",Mt="collapsing",jt=`:scope .${Pt} .${Pt}`,Ft='[data-bs-toggle="collapse"]',Ht={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Bt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(Ft);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Ht}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Bt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(N.trigger(this._element,Lt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Pt),this._element.classList.add(Mt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt,Nt),this._element.style[e]="",N.trigger(this._element,St)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(N.trigger(this._element,Dt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(Mt),this._element.classList.remove(Pt,Nt);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt),N.trigger(this._element,$t)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(Nt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Ft);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(jt,this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Bt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}N.on(document,It,Ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))Bt.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(Bt);var zt="top",Rt="bottom",qt="right",Vt="left",Kt="auto",Qt=[zt,Rt,qt,Vt],Xt="start",Yt="end",Ut="clippingParents",Gt="viewport",Jt="popper",Zt="reference",te=Qt.reduce((function(t,e){return t.concat([e+"-"+Xt,e+"-"+Yt])}),[]),ee=[].concat(Qt,[Kt]).reduce((function(t,e){return t.concat([e,e+"-"+Xt,e+"-"+Yt])}),[]),ie="beforeRead",ne="read",se="afterRead",oe="beforeMain",re="main",ae="afterMain",le="beforeWrite",ce="write",he="afterWrite",de=[ie,ne,se,oe,re,ae,le,ce,he];function ue(t){return t?(t.nodeName||"").toLowerCase():null}function fe(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function pe(t){return t instanceof fe(t).Element||t instanceof Element}function me(t){return t instanceof fe(t).HTMLElement||t instanceof HTMLElement}function ge(t){return"undefined"!=typeof ShadowRoot&&(t instanceof fe(t).ShadowRoot||t instanceof ShadowRoot)}const _e={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];me(s)&&ue(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});me(n)&&ue(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function be(t){return t.split("-")[0]}var ve=Math.max,ye=Math.min,we=Math.round;function Ae(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ee(){return!/^((?!chrome|android).)*safari/i.test(Ae())}function Te(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&me(t)&&(s=t.offsetWidth>0&&we(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&we(n.height)/t.offsetHeight||1);var r=(pe(t)?fe(t):window).visualViewport,a=!Ee()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Ce(t){var e=Te(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Oe(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ge(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function xe(t){return fe(t).getComputedStyle(t)}function ke(t){return["table","td","th"].indexOf(ue(t))>=0}function Le(t){return((pe(t)?t.ownerDocument:t.document)||window.document).documentElement}function Se(t){return"html"===ue(t)?t:t.assignedSlot||t.parentNode||(ge(t)?t.host:null)||Le(t)}function De(t){return me(t)&&"fixed"!==xe(t).position?t.offsetParent:null}function $e(t){for(var e=fe(t),i=De(t);i&&ke(i)&&"static"===xe(i).position;)i=De(i);return i&&("html"===ue(i)||"body"===ue(i)&&"static"===xe(i).position)?e:i||function(t){var e=/firefox/i.test(Ae());if(/Trident/i.test(Ae())&&me(t)&&"fixed"===xe(t).position)return null;var i=Se(t);for(ge(i)&&(i=i.host);me(i)&&["html","body"].indexOf(ue(i))<0;){var n=xe(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ie(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function Ne(t,e,i){return ve(t,ye(e,i))}function Pe(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function Me(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const je={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=be(i.placement),l=Ie(a),c=[Vt,qt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Pe("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:Me(t,Qt))}(s.padding,i),d=Ce(o),u="y"===l?zt:Vt,f="y"===l?Rt:qt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=$e(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=Ne(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Oe(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Fe(t){return t.split("-")[1]}var He={top:"auto",right:"auto",bottom:"auto",left:"auto"};function We(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Vt,y=zt,w=window;if(c){var A=$e(i),E="clientHeight",T="clientWidth";A===fe(i)&&"static"!==xe(A=Le(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===zt||(s===Vt||s===qt)&&o===Yt)&&(y=Rt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Vt&&(s!==zt&&s!==Rt||o!==Yt)||(v=qt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&He),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:we(i*s)/s||0,y:we(n*s)/s||0}}({x:f,y:m},fe(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const Be={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:be(e.placement),variation:Fe(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,We(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,We(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ze={passive:!0};const Re={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=fe(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ze)})),a&&l.addEventListener("resize",i.update,ze),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ze)})),a&&l.removeEventListener("resize",i.update,ze)}},data:{}};var qe={left:"right",right:"left",bottom:"top",top:"bottom"};function Ve(t){return t.replace(/left|right|bottom|top/g,(function(t){return qe[t]}))}var Ke={start:"end",end:"start"};function Qe(t){return t.replace(/start|end/g,(function(t){return Ke[t]}))}function Xe(t){var e=fe(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ye(t){return Te(Le(t)).left+Xe(t).scrollLeft}function Ue(t){var e=xe(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ge(t){return["html","body","#document"].indexOf(ue(t))>=0?t.ownerDocument.body:me(t)&&Ue(t)?t:Ge(Se(t))}function Je(t,e){var i;void 0===e&&(e=[]);var n=Ge(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=fe(n),r=s?[o].concat(o.visualViewport||[],Ue(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Je(Se(r)))}function Ze(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ti(t,e,i){return e===Gt?Ze(function(t,e){var i=fe(t),n=Le(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ee();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ye(t),y:l}}(t,i)):pe(e)?function(t,e){var i=Te(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Ze(function(t){var e,i=Le(t),n=Xe(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ve(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ve(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ye(t),l=-n.scrollTop;return"rtl"===xe(s||i).direction&&(a+=ve(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Le(t)))}function ei(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?be(s):null,r=s?Fe(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case zt:e={x:a,y:i.y-n.height};break;case Rt:e={x:a,y:i.y+i.height};break;case qt:e={x:i.x+i.width,y:l};break;case Vt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ie(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Xt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Yt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ii(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Ut:a,c=i.rootBoundary,h=void 0===c?Gt:c,d=i.elementContext,u=void 0===d?Jt:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Pe("number"!=typeof g?g:Me(g,Qt)),b=u===Jt?Zt:Jt,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Je(Se(t)),i=["absolute","fixed"].indexOf(xe(t).position)>=0&&me(t)?$e(t):t;return pe(i)?e.filter((function(t){return pe(t)&&Oe(t,i)&&"body"!==ue(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=ti(t,i,n);return e.top=ve(s.top,e.top),e.right=ye(s.right,e.right),e.bottom=ye(s.bottom,e.bottom),e.left=ve(s.left,e.left),e}),ti(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(pe(y)?y:y.contextElement||Le(t.elements.popper),l,h,r),A=Te(t.elements.reference),E=ei({reference:A,element:v,strategy:"absolute",placement:s}),T=Ze(Object.assign({},v,E)),C=u===Jt?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Jt&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[qt,Rt].indexOf(t)>=0?1:-1,i=[zt,Rt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function ni(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ee:l,h=Fe(n),d=h?a?te:te.filter((function(t){return Fe(t)===h})):Qt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ii(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[be(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const si={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=be(g),b=l||(_!==g&&p?function(t){if(be(t)===Kt)return[];var e=Ve(t);return[Qe(t),e,Qe(e)]}(g):[Ve(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(be(i)===Kt?ni(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,S=L?"width":"height",D=ii(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?qt:Vt:k?Rt:zt;y[S]>w[S]&&($=Ve($));var I=Ve($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every((function(t){return t}))){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==P(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function oi(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ri(t){return[zt,qt,Rt,Vt].some((function(e){return t[e]>=0}))}const ai={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ii(e,{elementContext:"reference"}),a=ii(e,{altBoundary:!0}),l=oi(r,n),c=oi(a,s,o),h=ri(l),d=ri(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},li={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ee.reduce((function(t,i){return t[i]=function(t,e,i){var n=be(t),s=[Vt,zt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Vt,qt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ci={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ei({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},hi={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ii(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=be(e.placement),b=Fe(e.placement),v=!b,y=Ie(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?zt:Vt,D="y"===y?Rt:qt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],M=f?-T[$]/2:0,j=b===Xt?E[$]:T[$],F=b===Xt?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?Ce(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=Ne(0,E[$],W[$]),V=v?E[$]/2-M-q-z-O.mainAxis:j-q-z-O.mainAxis,K=v?-E[$]/2+M+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&$e(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=Ne(f?ye(N,I+V-Y-X):N,I,f?ve(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?zt:Vt,tt="x"===y?Rt:qt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[zt,Vt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=Ne(t,e,i);return n>i?i:n}(at,et,lt):Ne(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function di(t,e,i){void 0===i&&(i=!1);var n,s,o=me(e),r=me(e)&&function(t){var e=t.getBoundingClientRect(),i=we(e.width)/t.offsetWidth||1,n=we(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=Le(e),l=Te(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==ue(e)||Ue(a))&&(c=(n=e)!==fe(n)&&me(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Xe(n)),me(e)?((h=Te(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ye(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function ui(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var fi={placement:"bottom",modifiers:[],strategy:"absolute"};function pi(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(F.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ti,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(Ni);for(const i of e){const e=qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ei,Ti].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ii)?this:z.prev(this,Ii)[0]||z.next(this,Ii)[0]||z.findOne(Ii,t.delegateTarget.parentNode),o=qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}N.on(document,Si,Ii,qi.dataApiKeydownHandler),N.on(document,Si,Pi,qi.dataApiKeydownHandler),N.on(document,Li,qi.clearMenus),N.on(document,Di,qi.clearMenus),N.on(document,Li,Ii,(function(t){t.preventDefault(),qi.getOrCreateInstance(this).toggle()})),m(qi);const Vi="backdrop",Ki="show",Qi=`mousedown.bs.${Vi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Yi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ui extends H{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return Vi}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(Ki),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(N.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),N.on(t,Qi,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const Gi=".bs.focustrap",Ji=`focusin${Gi}`,Zi=`keydown.tab${Gi}`,tn="backward",en={autofocus:!0,trapElement:null},nn={autofocus:"boolean",trapElement:"element"};class sn extends H{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return en}static get DefaultType(){return nn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),N.off(document,Gi),N.on(document,Ji,(t=>this._handleFocusin(t))),N.on(document,Zi,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,N.off(document,Gi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===tn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?tn:"forward")}}const on=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",rn=".sticky-top",an="padding-right",ln="margin-right";class cn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,an,(e=>e+t)),this._setElementAttributes(on,an,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,an),this._resetElementAttributes(on,an),this._resetElementAttributes(rn,ln)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&F.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=F.getDataAttribute(t,e);null!==i?(F.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const hn=".bs.modal",dn=`hide${hn}`,un=`hidePrevented${hn}`,fn=`hidden${hn}`,pn=`show${hn}`,mn=`shown${hn}`,gn=`resize${hn}`,_n=`click.dismiss${hn}`,bn=`mousedown.dismiss${hn}`,vn=`keydown.dismiss${hn}`,yn=`click${hn}.data-api`,wn="modal-open",An="show",En="modal-static",Tn={backdrop:!0,focus:!0,keyboard:!0},Cn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class On extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new cn,this._addEventListeners()}static get Default(){return Tn}static get DefaultType(){return Cn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||N.trigger(this._element,pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(wn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(N.trigger(this._element,dn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){N.off(window,hn),N.off(this._dialog,hn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ui({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,N.trigger(this._element,mn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){N.on(this._element,vn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),N.on(window,gn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),N.on(this._element,bn,(t=>{N.one(this._element,_n,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(wn),this._resetAdjustments(),this._scrollBar.reset(),N.trigger(this._element,fn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,un).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(En)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(En),this._queueCallback((()=>{this._element.classList.remove(En),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=On.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,yn,'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),N.one(e,pn,(t=>{t.defaultPrevented||N.one(e,fn,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&On.getInstance(i).hide(),On.getOrCreateInstance(e).toggle(this)})),R(On),m(On);const xn=".bs.offcanvas",kn=".data-api",Ln=`load${xn}${kn}`,Sn="show",Dn="showing",$n="hiding",In=".offcanvas.show",Nn=`show${xn}`,Pn=`shown${xn}`,Mn=`hide${xn}`,jn=`hidePrevented${xn}`,Fn=`hidden${xn}`,Hn=`resize${xn}`,Wn=`click${xn}${kn}`,Bn=`keydown.dismiss${xn}`,zn={backdrop:!0,keyboard:!0,scroll:!1},Rn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class qn extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return zn}static get DefaultType(){return Rn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,Nn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new cn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Dn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Sn),this._element.classList.remove(Dn),N.trigger(this._element,Pn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(N.trigger(this._element,Mn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add($n),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Sn,$n),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new cn).reset(),N.trigger(this._element,Fn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ui({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():N.trigger(this._element,jn)}:null})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_addEventListeners(){N.on(this._element,Bn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():N.trigger(this._element,jn))}))}static jQueryInterface(t){return this.each((function(){const e=qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;N.one(e,Fn,(()=>{a(this)&&this.focus()}));const i=z.findOne(In);i&&i!==e&&qn.getInstance(i).hide(),qn.getOrCreateInstance(e).toggle(this)})),N.on(window,Ln,(()=>{for(const t of z.find(In))qn.getOrCreateInstance(t).show()})),N.on(window,Hn,(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&qn.getOrCreateInstance(t).hide()})),R(qn),m(qn);const Vn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Yn={allowList:Vn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Un={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Gn={entry:"(string|element|function|null)",selector:"(string|element)"};class Jn extends H{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Yn}static get DefaultType(){return Un}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Gn)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Zn=new Set(["sanitize","allowList","sanitizeFn"]),ts="fade",es="show",is=".modal",ns="hide.bs.modal",ss="hover",os="focus",rs={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},as={allowList:Vn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ls={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cs extends W{constructor(t,e){if(void 0===vi)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return as}static get DefaultType(){return ls}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),N.off(this._element.closest(is),ns,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=N.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),N.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._queueCallback((()=>{N.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!N.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger[os]=!1,this._activeTrigger[ss]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ts,es),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ts),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Jn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ts)}_isShown(){return this.tip&&this.tip.classList.contains(es)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=rs[e.toUpperCase()];return bi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)N.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ss?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ss?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");N.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?os:ss]=!0,e._enter()})),N.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?os:ss]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(is),ns,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=F.getDataAttributes(this._element);for(const t of Object.keys(e))Zn.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=cs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(cs);const hs={...cs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},ds={...cs.DefaultType,content:"(null|string|element|function)"};class us extends cs{static get Default(){return hs}static get DefaultType(){return ds}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=us.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(us);const fs=".bs.scrollspy",ps=`activate${fs}`,ms=`click${fs}`,gs=`load${fs}.data-api`,_s="active",bs="[href]",vs=".nav-link",ys=`${vs}, .nav-item > ${vs}, .list-group-item`,ws={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Es extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ws}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(N.off(this._config.target,ms),N.on(this._config.target,ms,bs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(bs,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(_s),this._activateParents(t),N.trigger(this._element,ps,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(_s);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,ys))t.classList.add(_s)}_clearActiveClass(t){t.classList.remove(_s);const e=z.find(`${bs}.${_s}`,t);for(const t of e)t.classList.remove(_s)}static jQueryInterface(t){return this.each((function(){const e=Es.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,gs,(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))Es.getOrCreateInstance(t)})),m(Es);const Ts=".bs.tab",Cs=`hide${Ts}`,Os=`hidden${Ts}`,xs=`show${Ts}`,ks=`shown${Ts}`,Ls=`click${Ts}`,Ss=`keydown${Ts}`,Ds=`load${Ts}`,$s="ArrowLeft",Is="ArrowRight",Ns="ArrowUp",Ps="ArrowDown",Ms="Home",js="End",Fs="active",Hs="fade",Ws="show",Bs=":not(.dropdown-toggle)",zs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Rs=`.nav-link${Bs}, .list-group-item${Bs}, [role="tab"]${Bs}, ${zs}`,qs=`.${Fs}[data-bs-toggle="tab"], .${Fs}[data-bs-toggle="pill"], .${Fs}[data-bs-toggle="list"]`;class Vs extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),N.on(this._element,Ss,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?N.trigger(e,Cs,{relatedTarget:t}):null;N.trigger(t,xs,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Fs),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),N.trigger(t,ks,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Hs)))}_deactivate(t,e){t&&(t.classList.remove(Fs),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),N.trigger(t,Os,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Hs)))}_keydown(t){if(![$s,Is,Ns,Ps,Ms,js].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!l(t)));let i;if([Ms,js].includes(t.key))i=e[t.key===Ms?0:e.length-1];else{const n=[Is,Ps].includes(t.key);i=b(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Vs.getOrCreateInstance(i).show())}_getChildren(){return z.find(Rs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",Fs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Fs)}_getInnerElement(t){return t.matches(Rs)?t:z.findOne(Rs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Vs.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,Ls,zs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||Vs.getOrCreateInstance(this).show()})),N.on(window,Ds,(()=>{for(const t of z.find(qs))Vs.getOrCreateInstance(t)})),m(Vs);const Ks=".bs.toast",Qs=`mouseover${Ks}`,Xs=`mouseout${Ks}`,Ys=`focusin${Ks}`,Us=`focusout${Ks}`,Gs=`hide${Ks}`,Js=`hidden${Ks}`,Zs=`show${Ks}`,to=`shown${Ks}`,eo="hide",io="show",no="showing",so={animation:"boolean",autohide:"boolean",delay:"number"},oo={animation:!0,autohide:!0,delay:5e3};class ro extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return oo}static get DefaultType(){return so}static get NAME(){return"toast"}show(){N.trigger(this._element,Zs).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(eo),d(this._element),this._element.classList.add(io,no),this._queueCallback((()=>{this._element.classList.remove(no),N.trigger(this._element,to),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(N.trigger(this._element,Gs).defaultPrevented||(this._element.classList.add(no),this._queueCallback((()=>{this._element.classList.add(eo),this._element.classList.remove(no,io),N.trigger(this._element,Js)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(io),super.dispose()}isShown(){return this._element.classList.contains(io)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){N.on(this._element,Qs,(t=>this._onInteraction(t,!0))),N.on(this._element,Xs,(t=>this._onInteraction(t,!1))),N.on(this._element,Ys,(t=>this._onInteraction(t,!0))),N.on(this._element,Us,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ro.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(ro),m(ro),{Alert:Q,Button:Y,Carousel:xt,Collapse:Bt,Dropdown:qi,Modal:On,Offcanvas:qn,Popover:us,ScrollSpy:Es,Tab:Vs,Toast:ro,Tooltip:cs}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js.map b/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js.map new file mode 100644 index 0000000..3863da8 --- /dev/null +++ b/docs/deps/bootstrap-5.3.1/bootstrap.bundle.min.js.map @@ -0,0 +1 @@ +{"version":3,"names":["elementMap","Map","Data","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","triggerTransitionEnd","dispatchEvent","Event","isElement","object","jquery","nodeType","getElement","length","document","querySelector","isVisible","getClientRects","elementIsVisible","getComputedStyle","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","push","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","Math","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","findHandler","events","callable","delegationSelector","Object","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","call","this","handlers","previousFunction","domElements","querySelectorAll","domElement","hydrateObj","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","toString","JSON","parse","decodeURIComponent","normalizeDataKey","chr","toLowerCase","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","prototype","RegExp","test","TypeError","toUpperCase","BaseComponent","super","_element","_config","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","VERSION","eventName","getSelector","hrefAttribute","trim","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","map","join","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","Alert","close","_destroyElement","each","data","undefined","SELECTOR_DATA_TOGGLE","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","_eventIsPointerPenTouch","clientX","touches","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","DATA_API_KEY","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","EVENT_CLICK_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_ACTIVE","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","KEY_TO_DIRECTION","ArrowLeft","ArrowRight","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","swipeConfig","_directionToOrder","endCallBack","clearTimeout","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","_orderToDirection","isCycling","directionalClassName","orderClassName","completeCallBack","_isAnimated","clearInterval","carousel","slideIndex","carousels","EVENT_SHOW","EVENT_SHOWN","EVENT_HIDE","EVENT_HIDDEN","CLASS_NAME_SHOW","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","CLASS_NAME_DEEPER_CHILDREN","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","activeInstance","dimension","_getDimension","style","scrollSize","complete","getBoundingClientRect","selected","triggerArray","isOpen","top","bottom","right","left","auto","basePlacements","start","end","clippingParents","viewport","popper","reference","variationPlacements","reduce","acc","placement","placements","beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite","modifierPhases","getNodeName","nodeName","getWindow","node","ownerDocument","defaultView","isHTMLElement","HTMLElement","isShadowRoot","applyStyles$1","enabled","phase","_ref","state","elements","forEach","styles","assign","effect","_ref2","initialStyles","position","options","strategy","margin","arrow","hasOwnProperty","attribute","requires","getBasePlacement","round","getUAString","uaData","userAgentData","brands","isArray","item","brand","version","userAgent","isLayoutViewport","includeScale","isFixedStrategy","clientRect","scaleX","scaleY","offsetWidth","width","height","visualViewport","addVisualOffsets","x","offsetLeft","y","offsetTop","getLayoutRect","rootNode","isSameNode","host","isTableElement","getDocumentElement","getParentNode","assignedSlot","getTrueOffsetParent","offsetParent","getOffsetParent","isFirefox","currentNode","css","transform","perspective","contain","willChange","getContainingBlock","getMainAxisFromPlacement","within","mathMax","mathMin","mergePaddingObject","paddingObject","expandToHashMap","hashMap","arrow$1","_state$modifiersData$","arrowElement","popperOffsets","modifiersData","basePlacement","axis","len","padding","rects","toPaddingObject","arrowRect","minProp","maxProp","endDiff","startDiff","arrowOffsetParent","clientSize","clientHeight","clientWidth","centerToReference","center","offset","axisProp","centerOffset","_options$element","requiresIfExists","getVariation","unsetSides","mapToStyles","_Object$assign2","popperRect","variation","offsets","gpuAcceleration","adaptive","roundOffsets","isFixed","_offsets$x","_offsets$y","_ref3","hasX","hasY","sideX","sideY","win","heightProp","widthProp","_Object$assign","commonStyles","_ref4","dpr","devicePixelRatio","roundOffsetsByDPR","computeStyles$1","_ref5","_options$gpuAccelerat","_options$adaptive","_options$roundOffsets","passive","eventListeners","_options$scroll","scroll","_options$resize","resize","scrollParents","scrollParent","update","hash","getOppositePlacement","matched","getOppositeVariationPlacement","getWindowScroll","scrollLeft","pageXOffset","scrollTop","pageYOffset","getWindowScrollBarX","isScrollParent","_getComputedStyle","overflow","overflowX","overflowY","getScrollParent","listScrollParents","_element$ownerDocumen","isBody","updatedList","rectToClientRect","rect","getClientRectFromMixedType","clippingParent","html","layoutViewport","getViewportRect","clientTop","clientLeft","getInnerBoundingClientRect","winScroll","scrollWidth","scrollHeight","getDocumentRect","computeOffsets","commonX","commonY","mainAxis","detectOverflow","_options","_options$placement","_options$strategy","_options$boundary","boundary","_options$rootBoundary","rootBoundary","_options$elementConte","elementContext","_options$altBoundary","altBoundary","_options$padding","altContext","clippingClientRect","mainClippingParents","clipperElement","getClippingParents","firstClippingParent","clippingRect","accRect","getClippingRect","contextElement","referenceClientRect","popperClientRect","elementClientRect","overflowOffsets","offsetData","multiply","computeAutoPlacement","flipVariations","_options$allowedAutoP","allowedAutoPlacements","allPlacements","allowedPlacements","overflows","sort","a","b","flip$1","_skip","_options$mainAxis","checkMainAxis","_options$altAxis","altAxis","checkAltAxis","specifiedFallbackPlacements","fallbackPlacements","_options$flipVariatio","preferredPlacement","oppositePlacement","getExpandedFallbackPlacements","referenceRect","checksMap","makeFallbackChecks","firstFittingPlacement","i","_basePlacement","isStartVariation","isVertical","mainVariationSide","altVariationSide","checks","every","check","_loop","_i","fittingPlacement","reset","getSideOffsets","preventedOffsets","isAnySideFullyClipped","some","side","hide$1","preventOverflow","referenceOverflow","popperAltOverflow","referenceClippingOffsets","popperEscapeOffsets","isReferenceHidden","hasPopperEscaped","offset$1","_options$offset","invertDistance","skidding","distance","distanceAndSkiddingToXY","_data$state$placement","popperOffsets$1","preventOverflow$1","_options$tether","tether","_options$tetherOffset","tetherOffset","isBasePlacement","tetherOffsetValue","normalizedTetherOffsetValue","offsetModifierState","_offsetModifierState$","mainSide","altSide","additive","minLen","maxLen","arrowPaddingObject","arrowPaddingMin","arrowPaddingMax","arrowLen","minOffset","maxOffset","clientOffset","offsetModifierValue","tetherMax","preventedOffset","_offsetModifierState$2","_mainSide","_altSide","_offset","_len","_min","_max","isOriginSide","_offsetModifierValue","_tetherMin","_tetherMax","_preventedOffset","v","withinMaxClamp","getCompositeRect","elementOrVirtualElement","isOffsetParentAnElement","offsetParentIsScaled","isElementScaled","modifiers","visited","result","modifier","dep","depModifier","DEFAULT_OPTIONS","areValidElements","arguments","_key","popperGenerator","generatorOptions","_generatorOptions","_generatorOptions$def","defaultModifiers","_generatorOptions$def2","defaultOptions","pending","orderedModifiers","effectCleanupFns","isDestroyed","setOptions","setOptionsAction","cleanupModifierEffects","merged","orderModifiers","current","existing","m","_ref$options","cleanupFn","forceUpdate","_state$elements","_state$orderedModifie","_state$orderedModifie2","Promise","resolve","then","destroy","onFirstUpdate","createPopper","computeStyles","applyStyles","flip","ARROW_UP_KEY","ARROW_DOWN_KEY","EVENT_KEYDOWN_DATA_API","EVENT_KEYUP_DATA_API","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","autoClose","display","popperConfig","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","_createPopper","focus","_completeHide","Popper","referenceElement","_getPopperConfig","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","_selectMenuItem","clearMenus","openToggles","context","composedPath","isMenuTarget","dataApiKeydownHandler","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","EVENT_FOCUSIN","EVENT_KEYDOWN_TAB","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","shiftKey","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","innerWidth","_disableOverFlow","_setElementAttributes","calculatedValue","_resetElementAttributes","isOverflowing","_saveInitialAttribute","styleProperty","scrollbarWidth","_applyManipulationCallback","setProperty","actualValue","removeProperty","callBack","sel","EVENT_HIDE_PREVENTED","EVENT_RESIZE","EVENT_CLICK_DISMISS","EVENT_MOUSEDOWN_DISMISS","EVENT_KEYDOWN_DISMISS","CLASS_NAME_OPEN","CLASS_NAME_STATIC","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","handleUpdate","modalBody","transitionComplete","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","initialOverflowY","isBodyOverflowing","paddingLeft","paddingRight","showEvent","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","OPEN_SELECTOR","Offcanvas","blur","completeCallback","DefaultAllowlist","area","br","col","code","div","em","hr","h1","h2","h3","h4","h5","h6","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","uriAttributes","SAFE_URL_PATTERN","allowedAttribute","allowedAttributeList","attributeName","nodeValue","attributeRegex","regex","allowList","content","extraClass","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","innerHTML","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","unsafeHtml","sanitizeFunction","createdDocument","DOMParser","parseFromString","elementName","attributeList","allowedAttributes","sanitizeHtml","DISALLOWED_ATTRIBUTES","CLASS_NAME_FADE","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","click","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","isInTheDom","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","prefix","floor","random","getElementById","getUID","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","Popover","_getContent","EVENT_ACTIVATE","EVENT_CLICK","SELECTOR_TARGET_LINKS","SELECTOR_NAV_LINKS","SELECTOR_LINK_ITEMS","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","scrollTo","behavior","IntersectionObserver","_observerCallback","targetElement","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","decodeURI","_activateParents","listGroup","activeNodes","spy","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","HOME_KEY","END_KEY","NOT_SELECTOR_DROPDOWN_TOGGLE","SELECTOR_INNER_ELEM","SELECTOR_DATA_TOGGLE_ACTIVE","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","hideEvent","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","EVENT_MOUSEOVER","EVENT_MOUSEOUT","EVENT_FOCUSOUT","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"sources":["../../js/src/dom/data.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/dom/selector-engine.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../node_modules/@popperjs/core/lib/enums.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../node_modules/@popperjs/core/lib/modifiers/applyStyles.js","../../node_modules/@popperjs/core/lib/utils/getBasePlacement.js","../../node_modules/@popperjs/core/lib/utils/math.js","../../node_modules/@popperjs/core/lib/utils/userAgent.js","../../node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js","../../node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","../../node_modules/@popperjs/core/lib/dom-utils/contains.js","../../node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","../../node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","../../node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","../../node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","../../node_modules/@popperjs/core/lib/utils/within.js","../../node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","../../node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","../../node_modules/@popperjs/core/lib/utils/expandToHashMap.js","../../node_modules/@popperjs/core/lib/modifiers/arrow.js","../../node_modules/@popperjs/core/lib/utils/getVariation.js","../../node_modules/@popperjs/core/lib/modifiers/computeStyles.js","../../node_modules/@popperjs/core/lib/modifiers/eventListeners.js","../../node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","../../node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","../../node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","../../node_modules/@popperjs/core/lib/utils/rectToClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","../../node_modules/@popperjs/core/lib/utils/computeOffsets.js","../../node_modules/@popperjs/core/lib/utils/detectOverflow.js","../../node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","../../node_modules/@popperjs/core/lib/modifiers/flip.js","../../node_modules/@popperjs/core/lib/modifiers/hide.js","../../node_modules/@popperjs/core/lib/modifiers/offset.js","../../node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","../../node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","../../node_modules/@popperjs/core/lib/utils/getAltAxis.js","../../node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","../../node_modules/@popperjs/core/lib/utils/orderModifiers.js","../../node_modules/@popperjs/core/lib/createPopper.js","../../node_modules/@popperjs/core/lib/utils/debounce.js","../../node_modules/@popperjs/core/lib/utils/mergeByName.js","../../node_modules/@popperjs/core/lib/popper-lite.js","../../node_modules/@popperjs/core/lib/popper.js","../../js/src/dropdown.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/util/scrollbar.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`)\n }\n\n return selector\n}\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object))\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getjQuery,\n getNextActiveElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n parseSelector,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index.js'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.1'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible, parseSelector } from '../util/index.js'\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return parseSelector(selector)\n}\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n },\n\n getSelectorFromElement(element) {\n const selector = getSelector(element)\n\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null\n }\n\n return null\n },\n\n getElementFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.findOne(selector) : null\n },\n\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.find(selector) : []\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index.js'\nimport Swipe from './util/swipe.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getElement,\n reflow\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","export default function getUAString() {\n var uaData = navigator.userAgentData;\n\n if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {\n return uaData.brands.map(function (item) {\n return item.brand + \"/\" + item.version;\n }).join(' ');\n }\n\n return navigator.userAgent;\n}","import getUAString from \"../utils/userAgent.js\";\nexport default function isLayoutViewport() {\n return !/^((?!chrome|android).)*safari/i.test(getUAString());\n}","import { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nimport getWindow from \"./getWindow.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n\n var clientRect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (includeScale && isHTMLElement(element)) {\n scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n }\n\n var _ref = isElement(element) ? getWindow(element) : window,\n visualViewport = _ref.visualViewport;\n\n var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n var width = clientRect.width / scaleX;\n var height = clientRect.height / scaleY;\n return {\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: y + height,\n left: x,\n x: x,\n y: y\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getUAString from \"../utils/userAgent.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = /firefox/i.test(getUAString());\n var isIE = /Trident/i.test(getUAString());\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref, win) {\n var x = _ref.x,\n y = _ref.y;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }, getWindow(popper)) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element, strategy) {\n var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent, strategy) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary, strategy) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getViewportRect(element, strategy) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0;\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n var layoutViewport = isLayoutViewport();\n\n if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$strategy = _options.strategy,\n strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n });\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref) {\n var name = _ref.name,\n _ref$options = _ref.options,\n options = _ref$options === void 0 ? {} : _ref$options,\n effect = _ref.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n execute,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute, executeAfterTransition, getElement, reflow } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport { defineJQueryPlugin, isRTL, isVisible, reflow } from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n EventHandler.off(window, EVENT_KEY)\n EventHandler.off(this._dialog, EVENT_KEY)\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin,\n isDisabled,\n isVisible\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n div: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n// js-docs-end allow-list\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'\nimport { execute, getElement, isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '
'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this])\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport { defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index.js'\nimport { DefaultAllowlist } from './util/sanitizer.js'\nimport TemplateFactory from './util/template-factory.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '
' +\n '
' +\n '
' +\n '
',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n this._activeTrigger.click = !this._activeTrigger.click\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper()\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n this._popper = this._createPopper(tip)\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n this._disposePopper()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // TODO: remove this check in v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element])\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element])\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '
' +\n '
' +\n '

' +\n '
' +\n '
',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both

Why write R packages in this way? diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 760178b..0bb315f 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -1,4 +1,4 @@ -pandoc: 3.1.1 +pandoc: 2.19.2 pkgdown: 2.0.7 pkgdown_sha: ~ articles: @@ -6,5 +6,5 @@ articles: faqs: faqs.html package-templates: package-templates.html packages-in-the-wild: packages-in-the-wild.html -last_built: 2023-10-21T18:41Z +last_built: 2024-01-04T20:37Z diff --git a/docs/reference/add_chunk_label_hyperlinks.html b/docs/reference/add_chunk_label_hyperlinks.html index a3acdd9..2e55a99 100644 --- a/docs/reference/add_chunk_label_hyperlinks.html +++ b/docs/reference/add_chunk_label_hyperlinks.html @@ -3,7 +3,7 @@ of the form ###"foo"### and then wraps foo in a span tag with id="foo" and then whenever the chunk label &lt;&lt;foo&gt;&gt; is found it wraps it in a a href="file.html#foo" tag so that it will be a hyperlink to foo's -definition.">Add hyperlinks to embedded chunks — add_chunk_label_hyperlinks • litrAdd hyperlinks to embedded chunks — add_chunk_label_hyperlinks • litrAdd hyperlinks to function definitions — add_function_hyperlinks • litrAdd hyperlinks to function definitions — add_function_hyperlinks • litrAdd a hex sticker to package — add_hex_sticker • litrAdd a hex sticker to package — add_hex_sticker • litr diff --git a/docs/reference/add_pkgdown.html b/docs/reference/add_pkgdown.html index 1b115e0..975c666 100644 --- a/docs/reference/add_pkgdown.html +++ b/docs/reference/add_pkgdown.html @@ -1,7 +1,7 @@ Add a pkgdown site — add_pkgdown • litrAdd a pkgdown site — add_pkgdown • litrAdd README to package — add_readme • litrAdd README to package — add_readme • litrAdd Some Text to a File — add_text_to_file • litrAdd one or more vignettes to package — add_vignettes • litrAdd one or more vignettes to package — add_vignettes • litr diff --git a/docs/reference/check_unedited.html b/docs/reference/check_unedited.html index 776646d..b8ed3ea 100644 --- a/docs/reference/check_unedited.html +++ b/docs/reference/check_unedited.html @@ -1,7 +1,7 @@ Check if package directory is the unedited output of litr::render() — check_unedited • litrCheck if package directory is the unedited output of litr::render() — check_unedited • litrGenerate litr hash field name for DESCRIPTION file — description_litr_hash_field_name • litrGenerate litr hash field name for DESCRIPTION file — description_litr_hash_field_name • litr diff --git a/docs/reference/description_litr_version_field_name.html b/docs/reference/description_litr_version_field_name.html index e0fa701..d9f6838 100644 --- a/docs/reference/description_litr_version_field_name.html +++ b/docs/reference/description_litr_version_field_name.html @@ -1,5 +1,5 @@ -Generate litr version field name for DESCRIPTION file — description_litr_version_field_name • litrGenerate litr version field name for DESCRIPTION file — description_litr_version_field_name • litr diff --git a/docs/reference/do_not_edit_message.html b/docs/reference/do_not_edit_message.html index 06b93aa..3424dc8 100644 --- a/docs/reference/do_not_edit_message.html +++ b/docs/reference/do_not_edit_message.html @@ -1,5 +1,5 @@ -Generate do-not-edit message to put at top of file — do_not_edit_message • litrGenerate do-not-edit message to put at top of file — do_not_edit_message • litr diff --git a/docs/reference/document.html b/docs/reference/document.html index 39fc189..a78016f 100644 --- a/docs/reference/document.html +++ b/docs/reference/document.html @@ -6,7 +6,7 @@ in the outputted Rd files should not say "Please edit documentation in R/file.R" but instead should refer to the Rmd file that generates everything. Second, in the case that Rcpp is being used, it makes some adjustments to ensure -that the compiling of the C++ code should be successful.'>Use roxygen to document a package from within a Rmd file — document • litrUse roxygen to document a package from within a Rmd file — document • litrReplace a function's name with a link to its definition — insert_hrefs • litrReplace a function's name with a link to its definition — insert_hrefs • litrLiterate Programming for Writing R Packages — litr-package • litrLiterate Programming for Writing R Packages — litr-package • litrlitr version of bookdown::gitbook() — litr_gitbook • litrlitr version of rmarkdown::html_document() — litr_html_document • litrlitr version of rmarkdown::html_document() — litr_html_document • litrMake error messages noticeable — make_noticeable • litrMake error messages noticeable — make_noticeable • litrGet the hash of the package from the DESCRIPTION file — read_hash_from_description • litrGet the hash of the package from the DESCRIPTION file — read_hash_from_description • litr diff --git a/docs/reference/remove_rstudio_extras.html b/docs/reference/remove_rstudio_extras.html index 442ae97..cd6e2e1 100644 --- a/docs/reference/remove_rstudio_extras.html +++ b/docs/reference/remove_rstudio_extras.html @@ -1,5 +1,5 @@ -Remove extra files added by usethis — remove_rstudio_extras • litrRemove extra files added by usethis — remove_rstudio_extras • litr diff --git a/docs/reference/render.html b/docs/reference/render.html index abb7571..c29eb18 100644 --- a/docs/reference/render.html +++ b/docs/reference/render.html @@ -4,7 +4,7 @@ .html file when that is the output. In particular, when an .html file is among the outputs, it adds hyperlinks to functions defined within the file to make it easier for someone reading the code to see where different functions are -defined.">Render R markdown file — render • litrRender R markdown file — render • litrReplace ANSI escape sequences with their HTML equivalents — replace_ansi_sequences • litrReplace ANSI escape sequences with their HTML equivalents — replace_ansi_sequences • litr diff --git a/docs/reference/restore_knitr_objects.html b/docs/reference/restore_knitr_objects.html index 5880f0f..2372477 100644 --- a/docs/reference/restore_knitr_objects.html +++ b/docs/reference/restore_knitr_objects.html @@ -1,5 +1,5 @@ -Return the knitr objects to their original state — restore_knitr_objects • litrReturn the knitr objects to their original state — restore_knitr_objects • litr diff --git a/docs/reference/send_to_package.html b/docs/reference/send_to_package.html index be15a28..979c5e8 100644 --- a/docs/reference/send_to_package.html +++ b/docs/reference/send_to_package.html @@ -4,7 +4,7 @@ If so, then it is written to the R/ directory. It also looks for chunks that have one or more lines that start with test_that( or testthat::test_that( (potentially with some leading whitespace). These -chunks are then written to the tests directory of the R package.">A knitr chunk hook for writing R code and tests — send_to_package • litrA knitr chunk hook for writing R code and tests — send_to_package • litrCode for setup chunk — setup • litrCode for setup chunk — setup • litrRun tests for litr itself — test_litr • litrRun tests for litr itself — test_litr • litrAdd litr hash to DESCRIPTION file if error encountered — with_cleanup • litrWrite the hash of the package to the DESCRIPTION file — write_hash_to_description • litrWrite the hash of the package to the DESCRIPTION file — write_hash_to_description • litr diff --git a/docs/reference/write_version_to_description.html b/docs/reference/write_version_to_description.html index 4d12642..9719320 100644 --- a/docs/reference/write_version_to_description.html +++ b/docs/reference/write_version_to_description.html @@ -1,5 +1,5 @@ -Write the version of litr used to the DESCRIPTION file — write_version_to_description • litrWrite the version of litr used to the DESCRIPTION file — write_version_to_description • litr diff --git a/docs/search.json b/docs/search.json index 2969d92..a2ae824 100644 --- a/docs/search.json +++ b/docs/search.json @@ -1 +1 @@ -[{"path":"/LICENSE.html","id":null,"dir":"","previous_headings":"","what":"MIT License","title":"MIT License","text":"Copyright (c) 2023 J. Bien Permission hereby granted, free charge, person obtaining copy software associated documentation files (“Software”), deal Software without restriction, including without limitation rights use, copy, modify, merge, publish, distribute, sublicense, /sell copies Software, permit persons Software furnished , subject following conditions: copyright notice permission notice shall included copies substantial portions Software. SOFTWARE PROVIDED “”, WITHOUT WARRANTY KIND, EXPRESS IMPLIED, INCLUDING LIMITED WARRANTIES MERCHANTABILITY, FITNESS PARTICULAR PURPOSE NONINFRINGEMENT. EVENT SHALL AUTHORS COPYRIGHT HOLDERS LIABLE CLAIM, DAMAGES LIABILITY, WHETHER ACTION CONTRACT, TORT OTHERWISE, ARISING , CONNECTION SOFTWARE USE DEALINGS SOFTWARE.","code":""},{"path":"/articles/basic-example.html","id":"package-setup","dir":"Articles","previous_headings":"","what":"Package setup","title":"A Basic Example","text":"Note: Every R package needs DESCRIPTION file. ’ll start filling relevant information.","code":"usethis::create_package( path = \".\", fields = list( Package = param$package_name, Version = \"0.0.0.9000\", Title = \"Fit Least Squares\", Description = \"A package that fits least squares.\", `Authors@R` = person( given = \"First\", family = \"Last\", email = \"you@gmail.com\", role = c(\"aut\", \"cre\") ) ) ) usethis::use_mit_license(copyright_holder = \"F. Last\")"},{"path":"/articles/basic-example.html","id":"writing-a-function-for-the-package","dir":"Articles","previous_headings":"","what":"Writing a function for the package","title":"A Basic Example","text":"Since R markdown file, can use latex explain code, provide derivations, etc. Suppose response vector \\(y\\\\mathbb R^n\\) data matrix \\(X\\\\mathbb R^{n\\times p}\\). want find solution problem \\[ \\min_{\\beta\\\\mathbb R^p}\\|y-X\\beta\\|^2 \\] ’ll assume \\(X\\) full rank \\(n > p\\). know solution given \\[ \\hat\\beta=(X^TX)^{-1}X^Ty. \\] ’ll write function exactly ! Note: Code chunks whose first line starts #' added package. ’re familiar roxygen2, see . Now ’ve defined do_least_squares(), let’s try ! Note: code chunk start #', added package. Let’s see compares lm’s answer. Compare … Ok, do_least_squares() appears working. Let’s define formal unit test based example . Note: Code chunks one lines starting test_that( testthat::test_that( added package tests.","code":"#' Get the OLS solution #' #' @param y our response, which is an n-vector #' @param X our data matrix, which is n by p #' @export do_least_squares <- function(y, X) { if(nrow(X) != length(y)) stop(\"The number of rows of X must match the length of y.\") as.numeric(solve(crossprod(X), crossprod(X, y))) } set.seed(123) n <- 100 p <- 1 x <- cbind(1, matrix(rnorm(n*p), n, p)) beta_star <- c(2, 0.5) sigma <- 0.1 y <- x %*% beta_star + sigma * rnorm(n) betahat <- do_least_squares(y, x) plot(x[, 2], y) abline(betahat[1], betahat[2], col = 2, lwd=2) fit_lm <- lm(y ~ x[, 2]) fit_lm$coefficients ## (Intercept) x[, 2] ## 1.9897197 0.4947528 betahat ## [1] 1.9897197 0.4947528 testthat::test_that(\"do_least_squares() works\", { set.seed(123) n <- 100 p <- 1 x <- cbind(1, matrix(rnorm(n*p), n, p)) beta_star <- c(2, 0.5) sigma <- 0.1 y <- x %*% beta_star + sigma * rnorm(n) fit_lm <- lm(y ~ x[, 2]) # do lm and our function give the same coefficient vector? testthat::expect_equal(do_least_squares(y, x), as.numeric(fit_lm$coefficients)) # do we get the desired error when there is a length mismatch? testthat::expect_error(do_least_squares(y[-1], x), \"must match\") }) ## Test passed"},{"path":[]},{"path":"/articles/basic-example.html","id":"finer-control-over-where-in-the-package-your-code-is-sent","dir":"Articles","previous_headings":"Some fancier features","what":"Finer control over where in the package your code is sent","title":"A Basic Example","text":"noted , litr detects whether send code chunk package based whether starts #' test_that . However, sometimes ’ll want finer control. case can override behavior explicitly specifying target location. , use code chunk option form send_to=\"R/myfile.R\". add code particular file (either creating need else appending ). two primary use cases feature: () don’t want use roxygen2 document function (b) want several functions appear together .R file.","code":""},{"path":"/articles/basic-example.html","id":"using-a-function-from-a-different-package","dir":"Articles","previous_headings":"Some fancier features","what":"Using a function from a different package","title":"A Basic Example","text":"Imagine wanted actually use function another package . example, perhaps want use lsfit() stats package: also update DESCRIPTION file package dependence: use function another package, simply use pkg:: prefix calling add usethis::use_package(\"pkg\") include package dependency.","code":"#' Get the OLS solution using lsfit() #' #' @param y our response, which is an n-vector #' @param X our data matrix, which is n by p #' @export do_least_squares_with_lsfit <- function(y, X) { fit <- stats::lsfit(x = X, y = y, intercept = FALSE) return(as.numeric(fit$coefficients)) } usethis::use_package(\"stats\")"},{"path":"/articles/basic-example.html","id":"including-a-dataset-in-your-package","dir":"Articles","previous_headings":"Some fancier features","what":"Including a dataset in your package","title":"A Basic Example","text":"’s template .","code":""},{"path":"/articles/basic-example.html","id":"using-rcpp-in-your-package","dir":"Articles","previous_headings":"Some fancier features","what":"Using Rcpp in your package","title":"A Basic Example","text":"’s template .","code":""},{"path":"/articles/basic-example.html","id":"including-a-readme-vignettes-and-pkgdown-site","dir":"Articles","previous_headings":"Some fancier features","what":"Including a README, vignettes, and pkgdown site","title":"A Basic Example","text":"’s template .","code":""},{"path":"/articles/basic-example.html","id":"defining-your-package-with-bookdown","dir":"Articles","previous_headings":"Some fancier features","what":"Defining your package with bookdown","title":"A Basic Example","text":"’re writing large package, may convenient define across multiple .Rmd files. can use bookdown , leads nice looking online book multiple chapters. ’s template .","code":""},{"path":"/articles/basic-example.html","id":"documenting-the-package","dir":"Articles","previous_headings":"","what":"Documenting the package","title":"A Basic Example","text":"end litr document, important call litr::document(), turns royxgen2 traditional documentation files R package.","code":"litr::document() # <-- use instead of devtools::document()"},{"path":"/articles/faqs.html","id":"does-all-code-in-the--rmd-end-up-in-the-r-package","dir":"Articles","previous_headings":"","what":"Does all code in the .Rmd end up in the R package?","title":"FAQs","text":", code chunk sent R/ file R package starts #'. testthat::test_that() used code chunk, chunk added package unit test. example , see create-rhello.Rmd resulting package. want code chunk sent R package doesn’t start roxygen2-style #', can instead add explicit location go using chunk option form send_to=\"R/myfile.R\". can send code multiple code chunks file like.","code":""},{"path":"/articles/faqs.html","id":"do-i-have-to-learn-special-syntax-to-use-litr","dir":"Articles","previous_headings":"","what":"Do I have to learn special syntax to use litr?","title":"FAQs","text":". .Rmd file generates R package litr uses exactly syntax regular .Rmd file. need use roxygen2 documenting functions; however, probably using roxygen2 even weren’t using litr.","code":""},{"path":"/articles/faqs.html","id":"how-does-the-litr--rmd-relate-to-a-vignette","dir":"Articles","previous_headings":"","what":"How does the litr .Rmd relate to a vignette?","title":"FAQs","text":"vignette serves different purpose .Rmd file use litr. goal vignette show potential users package can use . contrast, litr .Rmd file document source code, presented way optimized human readability. audience file future self anyone may eventually wish modify R package understand inner workings. Typical users package need see document.","code":""},{"path":"/articles/faqs.html","id":"for-large-packages-can-i-write-my-package-across-multiple--rmd-files","dir":"Articles","previous_headings":"","what":"For large packages, can I write my package across multiple .Rmd files?","title":"FAQs","text":"Yes! can use bookdown define R package across multiple .Rmd files. leads nice looking online book multiple chapters. render book .Rmd files run single environment, behaves way one enormous .Rmd file. ’s example. easiest way get started work template called make--r-package--bookdown.","code":""},{"path":"/articles/faqs.html","id":"can-i-include-a-readme-for-my-package","dir":"Articles","previous_headings":"","what":"Can I include a README for my package?","title":"FAQs","text":"Yes! can using function litr::add_readme(). See template create-withpkgdown.Rmd example works.","code":""},{"path":"/articles/faqs.html","id":"can-i-include-vignettes-in-my-package","dir":"Articles","previous_headings":"","what":"Can I include vignettes in my package?","title":"FAQs","text":"Yes! can using function litr::add_vignettes(). See template create-withpkgdown.Rmd example works.","code":""},{"path":"/articles/faqs.html","id":"can-i-make-a-pkgdown-site-for-my-package","dir":"Articles","previous_headings":"","what":"Can I make a pkgdown site for my package?","title":"FAQs","text":"Yes! See template create-withpkgdown.Rmd example works.","code":""},{"path":"/articles/faqs.html","id":"is-it-true-that-literate-programming-won-an-academy-award","dir":"Articles","previous_headings":"","what":"Is it true that literate programming won an Academy Award?","title":"FAQs","text":"Almost, quite. Donald Knuth personally thanked Academy Award acceptance speech developing literate programming. See Kristen Bell Michael B. Jordan clapping literate programming .","code":""},{"path":"/articles/faqs.html","id":"did-you-use-litr-to-write-litr","dir":"Articles","previous_headings":"","what":"Did you use litr to write litr?","title":"FAQs","text":"Yes! Version 0.0.1 litr written traditional way (since litr didn’t yet exist…). thereafter, version n+1 litr written using version n litr. creation litr, see . ’re really interested, look literate programming bookdown defines litr.","code":""},{"path":"/authors.html","id":null,"dir":"","previous_headings":"","what":"Authors","title":"Authors and Citation","text":"Jacob Bien. Author, maintainer. Patrick Vossler. Author.","code":""},{"path":"/authors.html","id":"citation","dir":"","previous_headings":"","what":"Citation","title":"Authors and Citation","text":"Bien J, Patrick Vossler (2023). litr: Literate Programming Writing R Packages. R package version 0.9.1, https://github.com/jacobbien/litr-project/tree/main/litr.","code":"@Manual{, title = {litr: Literate Programming for Writing R Packages}, author = {Jacob Bien and {Patrick Vossler}}, year = {2023}, note = {R package version 0.9.1}, url = {https://github.com/jacobbien/litr-project/tree/main/litr}, }"},{"path":[]},{"path":"/index.html","id":"overview","dir":"","previous_headings":"","what":"Overview","title":"Literate Programming for Writing R Packages","text":"litr R package lets write complete R package single R markdown document. enables workflow writing R packages probably different used . litr, knitting creates R package addition .html file.","code":""},{"path":"/index.html","id":"why-write-r-packages-in-this-way","dir":"","previous_headings":"","what":"Why write R packages in this way?","title":"Literate Programming for Writing R Packages","text":"Using litr brings benefits R markdown package development: Record explain every step making R package others can understand every detail years later. Present functions package logical order maximizes human readability. Include derivations latex right next code ends package. Include figures alongside code help explanation. Define unit tests relevant context, .e. directly defining function tested. Furthermore, writing R package actually easier litr without . Just choose one R package templates press “Knit” – ’ll working R package can modify.","code":""},{"path":"/index.html","id":"installation","dir":"","previous_headings":"","what":"Installation","title":"Literate Programming for Writing R Packages","text":"can install latest release litr github following: latest development version, remove @*release .","code":"remotes::install_github(\"jacobbien/litr-project@*release\", subdir = \"litr\")"},{"path":"/index.html","id":"getting-started","dir":"","previous_headings":"","what":"Getting started","title":"Literate Programming for Writing R Packages","text":"Using template best way get started: creates R markdown file called create-rhello.Rmd demonstrates literate programming workflow writing R package. particular, knit create-rhello.Rmd, creates tiny example R package called rhello one function one test function. knit, can either press “Knit” RStudio use following command: creates R package! Now can modify template design package. explore kinds R packages, using Rcpp, see templates page. Also, see section packages wild use litr.","code":"rmarkdown::draft(\"create-rhello.Rmd\", template = \"make-an-r-package\", package = \"litr\") litr::render(\"create-rhello.Rmd\")"},{"path":"/index.html","id":"more-background","dir":"","previous_headings":"","what":"More background","title":"Literate Programming for Writing R Packages","text":"try understand code R package, logic functions relate often obvious. including function documentation, vignettes, unit tests best practices, convey chain logic mind programmer went writing different functions. can difficult know look functions within even well-documented R package. fact functions appear different files functions within files can defined arbitrary order makes unclear approach reading code. Furthermore, tests stored different place functions , making tests harder read. resolved single document goes code tests linear, logical fashion. Rather try construct document fact, idea litr make document actual source code package. R package created act knitting document. want modify anything package, modifying document re-knitting. motivation literate programming, introduced Donald Knuth, direct inspiration fast.ai’s nbdev, available Python. litr package relies heavily number great tools R, especially knitr, rmarkdown, usethis, devtools, testthat. also note Yihui Xie post demonstrates similar idea, although approach appears proof concept. can hear Jeremy Howard Hugo Bowne-Anderson talk literate programming nbdev Vanishing Gradients podcast. discussion inspired litr!","code":""},{"path":"/reference/add_chunk_label_hyperlinks.html","id":null,"dir":"Reference","previous_headings":"","what":"Add hyperlinks to embedded chunks — add_chunk_label_hyperlinks","title":"Add hyperlinks to embedded chunks — add_chunk_label_hyperlinks","text":"Finds chunks referenced html file(s) looking comments form ###\"foo\"### wraps foo span tag id=\"foo\" whenever chunk label <> found wraps href=\"file.html#foo\" tag hyperlink foo's definition.","code":""},{"path":"/reference/add_chunk_label_hyperlinks.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Add hyperlinks to embedded chunks — add_chunk_label_hyperlinks","text":"","code":"add_chunk_label_hyperlinks( html_files, reference_start = \"<<\", reference_end = \">>\" )"},{"path":"/reference/add_chunk_label_hyperlinks.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Add hyperlinks to embedded chunks — add_chunk_label_hyperlinks","text":"html_files Character vector file names html files created Rmd files reference_start delimiter used indicate start chunk label reference_end delimiter used indicate end chunk label","code":""},{"path":"/reference/add_function_hyperlinks.html","id":null,"dir":"Reference","previous_headings":"","what":"Add hyperlinks to function definitions — add_function_hyperlinks","title":"Add hyperlinks to function definitions — add_function_hyperlinks","text":"Finds functions defined html file(s) looking text form foo <- function( wraps foo span tag id=\"foo\" whenever foo found wraps href=\"file.html#foo\" tag hyperlink foo's definition.","code":""},{"path":"/reference/add_function_hyperlinks.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Add hyperlinks to function definitions — add_function_hyperlinks","text":"","code":"add_function_hyperlinks(html_files, pkg_name)"},{"path":"/reference/add_function_hyperlinks.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Add hyperlinks to function definitions — add_function_hyperlinks","text":"html_files Character vector file names html files created Rmd files pkg_name Name package created litr. Taken YAML front matter","code":""},{"path":"/reference/add_hex_sticker.html","id":null,"dir":"Reference","previous_headings":"","what":"Add a hex sticker to package — add_hex_sticker","title":"Add a hex sticker to package — add_hex_sticker","text":"addition calling function, add README.Rmd something like :","code":""},{"path":"/reference/add_hex_sticker.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Add a hex sticker to package — add_hex_sticker","text":"","code":"add_hex_sticker(hex_png_file)"},{"path":"/reference/add_hex_sticker.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Add a hex sticker to package — add_hex_sticker","text":"hex_png_file .png file package's hex sticker","code":""},{"path":"/reference/add_hex_sticker.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Add a hex sticker to package — add_hex_sticker","text":"# -title See .","code":""},{"path":"/reference/add_pkgdown.html","id":null,"dir":"Reference","previous_headings":"","what":"Add a pkgdown site — add_pkgdown","title":"Add a pkgdown site — add_pkgdown","text":"function creates website package. can see locally opening docs/index.html package. get online can copy docs directory website's server.","code":""},{"path":"/reference/add_pkgdown.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Add a pkgdown site — add_pkgdown","text":"","code":"add_pkgdown(config_path = NULL)"},{"path":"/reference/add_pkgdown.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Add a pkgdown site — add_pkgdown","text":"config_path _pkgdown.yml file lives somewhere outside package. NULL, basic default used.","code":""},{"path":"/reference/add_pkgdown.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Add a pkgdown site — add_pkgdown","text":"sure generating .Rmd file called litr::document() called. customize site, may pass customized _pkgdown.yml file described pkgdown vignette.","code":""},{"path":"/reference/add_readme.html","id":null,"dir":"Reference","previous_headings":"","what":"Add README to package — add_readme","title":"Add README to package — add_readme","text":"function takes README.Rmd file, copies package, renders README.md file. also adds two files .Rbuildignore.","code":""},{"path":"/reference/add_readme.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Add README to package — add_readme","text":"","code":"add_readme(rmd_file)"},{"path":"/reference/add_readme.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Add README to package — add_readme","text":"rmd_file path .Rmd file.","code":""},{"path":"/reference/add_text_to_file.html","id":null,"dir":"Reference","previous_headings":"","what":"Add Some Text to a File — add_text_to_file","title":"Add Some Text to a File — add_text_to_file","text":"text added file particular line specified location. first line txt line location modified file. location NULL, text added end file. file exist, created location ignored (unless req_exist TRUE, case error thrown).","code":""},{"path":"/reference/add_text_to_file.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Add Some Text to a File — add_text_to_file","text":"","code":"add_text_to_file( txt, filename, location = NULL, req_exist = FALSE, pad = FALSE, msg = NULL )"},{"path":"/reference/add_text_to_file.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Add Some Text to a File — add_text_to_file","text":"txt Character vector add file filename Name file location Specifies text added. See description . req_exist TRUE, throws error file exist pad TRUE, text added preexisting file, adds newline msg optional message put top file new file","code":""},{"path":"/reference/add_vignettes.html","id":null,"dir":"Reference","previous_headings":"","what":"Add one or more vignettes to package — add_vignettes","title":"Add one or more vignettes to package — add_vignettes","text":"Add one vignettes package","code":""},{"path":"/reference/add_vignettes.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Add one or more vignettes to package — add_vignettes","text":"","code":"add_vignettes(rmd_files, other_files = NULL)"},{"path":"/reference/add_vignettes.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Add one or more vignettes to package — add_vignettes","text":"rmd_files character vector .Rmd files, corresponding vignette other_files character vector files needed vignettes directory (.bib file, images, etc.)","code":""},{"path":"/reference/check_unedited.html","id":null,"dir":"Reference","previous_headings":"","what":"Check if package directory is the unedited output of litr::render() — check_unedited","title":"Check if package directory is the unedited output of litr::render() — check_unedited","text":"Uses hash stored special litr field DESCRIPTION file check current state R package directory identical state time created litr::render().","code":""},{"path":"/reference/check_unedited.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Check if package directory is the unedited output of litr::render() — check_unedited","text":"","code":"check_unedited(package_dir)"},{"path":"/reference/check_unedited.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Check if package directory is the unedited output of litr::render() — check_unedited","text":"package_dir Path package","code":""},{"path":"/reference/description_litr_hash_field_name.html","id":null,"dir":"Reference","previous_headings":"","what":"Generate litr hash field name for DESCRIPTION file — description_litr_hash_field_name","title":"Generate litr hash field name for DESCRIPTION file — description_litr_hash_field_name","text":"Generate litr hash field name DESCRIPTION file","code":""},{"path":"/reference/description_litr_hash_field_name.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Generate litr hash field name for DESCRIPTION file — description_litr_hash_field_name","text":"","code":"description_litr_hash_field_name()"},{"path":"/reference/description_litr_version_field_name.html","id":null,"dir":"Reference","previous_headings":"","what":"Generate litr version field name for DESCRIPTION file — description_litr_version_field_name","title":"Generate litr version field name for DESCRIPTION file — description_litr_version_field_name","text":"Generate litr version field name DESCRIPTION file","code":""},{"path":"/reference/description_litr_version_field_name.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Generate litr version field name for DESCRIPTION file — description_litr_version_field_name","text":"","code":"description_litr_version_field_name()"},{"path":"/reference/do_not_edit_message.html","id":null,"dir":"Reference","previous_headings":"","what":"Generate do-not-edit message to put at top of file — do_not_edit_message","title":"Generate do-not-edit message to put at top of file — do_not_edit_message","text":"Generate --edit message put top file","code":""},{"path":"/reference/do_not_edit_message.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Generate do-not-edit message to put at top of file — do_not_edit_message","text":"","code":"do_not_edit_message(rmd_file, type = c(\"R\", \"man\", \"c\"))"},{"path":"/reference/do_not_edit_message.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Generate do-not-edit message to put at top of file — do_not_edit_message","text":"rmd_file Name Rmd file mention type Whether R/ file, man/ file, c file","code":""},{"path":"/reference/document.html","id":null,"dir":"Reference","previous_headings":"","what":"Use roxygen to document a package from within a Rmd file — document","title":"Use roxygen to document a package from within a Rmd file — document","text":"wrapper devtools::document() function, turn wrapper roxygen2::roxygenize() function. written assuming called within generating Rmd file. purpose litr wrapper two-fold. First, ensures first line outputted Rd files say \"Please edit documentation R/file.R\" instead refer Rmd file generates everything. Second, case Rcpp used, makes adjustments ensure compiling C++ code successful.","code":""},{"path":"/reference/document.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Use roxygen to document a package from within a Rmd file — document","text":"","code":"document(...)"},{"path":"/reference/document.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Use roxygen to document a package from within a Rmd file — document","text":"... Arguments passed devtools::document()","code":""},{"path":"/reference/find_labels.html","id":null,"dir":"Reference","previous_headings":"","what":"Find a .Rmd chunk label in a code chunk — find_labels","title":"Find a .Rmd chunk label in a code chunk — find_labels","text":"Find .Rmd chunk label code chunk","code":""},{"path":"/reference/find_labels.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Find a .Rmd chunk label in a code chunk — find_labels","text":"","code":"find_labels(chunk_code)"},{"path":"/reference/find_labels.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Find a .Rmd chunk label in a code chunk — find_labels","text":"chunk_code Character vector code .Rmd code chunk. element line code chunk.","code":""},{"path":"/reference/find_labels.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Find a .Rmd chunk label in a code chunk — find_labels","text":"List chunk_idx logical vector line chunk corresponding whether chunk label form <

You can also add some extra things to your package here if you like, such as a README, some vignettes, a pkgdown site, etc. See here for an example of how to do this with litr.

- - + - - - - - - - - - - - diff --git a/examples/make-an-r-package-from-bookdown/_book/generalization-to-other-greetings.html b/examples/make-an-r-package-from-bookdown/_book/generalization-to-other-greetings.html index 1bf30ab..17c208f 100644 --- a/examples/make-an-r-package-from-bookdown/_book/generalization-to-other-greetings.html +++ b/examples/make-an-r-package-from-bookdown/_book/generalization-to-other-greetings.html @@ -1,60 +1,33 @@ - - - - 4 Generalization to other greetings | Creating the frombookdown R package - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - @@ -133,18 +106,14 @@
@@ -157,30 +126,22 @@

-
-
-

4 Generalization to other greetings

-

We can now get fancier and define more functions across as many .Rmd files as we like. Hyperlinks work across different .Rmd files. For example, you can click on say_hello() and it will bring you to the definition in the previous chapter.

+
+

+4 Generalization to other greetings +

+

We can now get fancier and define more functions across as many .Rmd files as we like. Hyperlinks work across different .Rmd files. For example, you can click on say_hello() and it will bring you to the definition in the previous chapter.

- -
+

- - - - - - - - - - - diff --git a/examples/make-an-r-package-from-bookdown/_book/index.html b/examples/make-an-r-package-from-bookdown/_book/index.html index 896bab2..95a6a65 100644 --- a/examples/make-an-r-package-from-bookdown/_book/index.html +++ b/examples/make-an-r-package-from-bookdown/_book/index.html @@ -1,60 +1,32 @@ - - - - Creating the frombookdown R package - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - @@ -133,18 +105,14 @@
@@ -157,37 +125,29 @@

-
- +

- - - - - - - - - - - diff --git a/examples/make-an-r-package-from-bookdown/_book/package-setup.html b/examples/make-an-r-package-from-bookdown/_book/package-setup.html index 55326de..59e30cd 100644 --- a/examples/make-an-r-package-from-bookdown/_book/package-setup.html +++ b/examples/make-an-r-package-from-bookdown/_book/package-setup.html @@ -1,60 +1,33 @@ - - - - 2 Package setup | Creating the frombookdown R package - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - @@ -133,18 +106,14 @@
@@ -157,52 +126,44 @@

-
-
-

2 Package setup

+
+

+2 Package setup +

Every R package needs a DESCRIPTION file, so we start by specifying this information:

-
usethis::create_package(
-  path = ".",
-  fields = list(
-    Package = params$package_name,
-    Version = "0.0.0.9000",
-    Title = "A Package Defined in a Bookdown",
-    Description = "This package uses litr to define an R package through writing a bookdown.",
-    `Authors@R` = person(
-      given = "First",
-      family = "Last",
-      email = "you@gmail.com",
-      role = c("aut", "cre")
-      )
-  )
-)
-usethis::use_mit_license(copyright_holder = "F. Last")
+
usethis::create_package(
+  path = ".",
+  fields = list(
+    Package = params$package_name,
+    Version = "0.0.0.9000",
+    Title = "A Package Defined in a Bookdown",
+    Description = "This package uses litr to define an R package through writing a bookdown.",
+    `Authors@R` = person(
+      given = "First",
+      family = "Last",
+      email = "you@gmail.com",
+      role = c("aut", "cre")
+      )
+  )
+)
+usethis::use_mit_license(copyright_holder = "F. Last")

Although it’s not required, it can be nice to add some package-level documentation. This is what will show up when someone types package?<your-package-name> in the console.

-
#' A Package Defined in a Bookdown
-#'
-#' This package uses `litr` to define an R package through writing a `bookdown`.
-#' 
-#' @docType package
+
#' A Package Defined in a Bookdown
+#'
+#' This package uses `litr` to define an R package through writing a `bookdown`.
+#' 
+#' @docType package
- -
+

- - - - - - - - - - - diff --git a/examples/make-an-r-package-from-bookdown/_book/search_index.json b/examples/make-an-r-package-from-bookdown/_book/search_index.json index e1cea57..abb946d 100644 --- a/examples/make-an-r-package-from-bookdown/_book/search_index.json +++ b/examples/make-an-r-package-from-bookdown/_book/search_index.json @@ -1 +1 @@ -[["index.html", "Creating the frombookdown R package 1 Introduction", " Creating the frombookdown R package Jacob Bien 2023-10-21 1 Introduction You can introduce what your R package does here. Note: If you are working in RStudio, you can simply press “Knit” to render this bookdown (and open _book/index.html to see the result). More generally, in a console you can run the following: litr::render("index.Rmd", output_format = litr::litr_gitbook()) "],["package-setup.html", "2 Package setup", " 2 Package setup Every R package needs a DESCRIPTION file, so we start by specifying this information: usethis::create_package( path = ".", fields = list( Package = params$package_name, Version = "0.0.0.9000", Title = "A Package Defined in a Bookdown", Description = "This package uses litr to define an R package through writing a bookdown.", `Authors@R` = person( given = "First", family = "Last", email = "you@gmail.com", role = c("aut", "cre") ) ) ) usethis::use_mit_license(copyright_holder = "F. Last") Although it’s not required, it can be nice to add some package-level documentation. This is what will show up when someone types package?<your-package-name> in the console. #' A Package Defined in a Bookdown #' #' This package uses `litr` to define an R package through writing a `bookdown`. #' #' @docType package "],["the-basics-of-saying-hello.html", "3 The basics of saying hello", " 3 The basics of saying hello Let’s define a function for our R package: #' Say hello to someone #' #' @param name Name of a person #' @param exclamation Whether to include an exclamation mark #' @export say_hello <- function(name, exclamation = TRUE) { paste0("Hello ", name, ifelse(exclamation, "!", ".")) } Code chunks whose first line starts with #' are added to the package. We can try running it. say_hello("Jacob") ## [1] "Hello Jacob!" That code chunk does not start with #', so it is not added to the package. Let’s write some tests to make sure the function behaves as desired: testthat::test_that("say_hello works", { testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!") testthat::expect_equal(say_hello("Jacob", exclamation = FALSE), "Hello Jacob.") }) ## Test passed Code chunks that have one or more lines starting with test_that( (or testthat::test_that() are added to the package as tests. "],["generalization-to-other-greetings.html", "4 Generalization to other greetings", " 4 Generalization to other greetings We can now get fancier and define more functions across as many .Rmd files as we like. Hyperlinks work across different .Rmd files. For example, you can click on say_hello() and it will bring you to the definition in the previous chapter. "],["conclude.html", "5 Conclusion", " 5 Conclusion When you are done defining the package, it remains to convert the Roxygen to documentation. litr::document() # <-- use instead of devtools::document() ## ℹ Updating frombookdown documentation ## ℹ Loading frombookdown ## Writing 'NAMESPACE' ## Writing 'frombookdown-package.Rd' ## Writing 'say_hello.Rd' You can also add some extra things to your package here if you like, such as a README, some vignettes, a pkgdown site, etc. See here for an example of how to do this with litr. "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]] +[["index.html", "Creating the frombookdown R package 1 Introduction", " Creating the frombookdown R package Jacob Bien 2024-01-04 1 Introduction You can introduce what your R package does here. Note: If you are working in RStudio, you can simply press “Knit” to render this bookdown (and open _book/index.html to see the result). More generally, in a console you can run the following: litr::render("index.Rmd", output_format = litr::litr_gitbook()) "],["package-setup.html", "2 Package setup", " 2 Package setup Every R package needs a DESCRIPTION file, so we start by specifying this information: usethis::create_package( path = ".", fields = list( Package = params$package_name, Version = "0.0.0.9000", Title = "A Package Defined in a Bookdown", Description = "This package uses litr to define an R package through writing a bookdown.", `Authors@R` = person( given = "First", family = "Last", email = "you@gmail.com", role = c("aut", "cre") ) ) ) usethis::use_mit_license(copyright_holder = "F. Last") Although it’s not required, it can be nice to add some package-level documentation. This is what will show up when someone types package?<your-package-name> in the console. #' A Package Defined in a Bookdown #' #' This package uses `litr` to define an R package through writing a `bookdown`. #' #' @docType package "],["the-basics-of-saying-hello.html", "3 The basics of saying hello", " 3 The basics of saying hello Let’s define a function for our R package: #' Say hello to someone #' #' @param name Name of a person #' @param exclamation Whether to include an exclamation mark #' @export say_hello <- function(name, exclamation = TRUE) { paste0("Hello ", name, ifelse(exclamation, "!", ".")) } Code chunks whose first line starts with #' are added to the package. We can try running it. say_hello("Jacob") ## [1] "Hello Jacob!" That code chunk does not start with #', so it is not added to the package. Let’s write some tests to make sure the function behaves as desired: testthat::test_that("say_hello works", { testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!") testthat::expect_equal(say_hello("Jacob", exclamation = FALSE), "Hello Jacob.") }) ## Test passed Code chunks that have one or more lines starting with test_that( (or testthat::test_that() are added to the package as tests. "],["generalization-to-other-greetings.html", "4 Generalization to other greetings", " 4 Generalization to other greetings We can now get fancier and define more functions across as many .Rmd files as we like. Hyperlinks work across different .Rmd files. For example, you can click on say_hello() and it will bring you to the definition in the previous chapter. "],["conclude.html", "5 Conclusion", " 5 Conclusion When you are done defining the package, it remains to convert the Roxygen to documentation. litr::document() # <-- use instead of devtools::document() ## ℹ Updating frombookdown documentation ## ℹ Loading frombookdown ## Writing 'NAMESPACE' ## Writing 'frombookdown-package.Rd' ## Writing 'say_hello.Rd' You can also add some extra things to your package here if you like, such as a README, some vignettes, a pkgdown site, etc. See here for an example of how to do this with litr. "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]] diff --git a/examples/make-an-r-package-from-bookdown/_book/the-basics-of-saying-hello.html b/examples/make-an-r-package-from-bookdown/_book/the-basics-of-saying-hello.html index a4a01f7..951f0c9 100644 --- a/examples/make-an-r-package-from-bookdown/_book/the-basics-of-saying-hello.html +++ b/examples/make-an-r-package-from-bookdown/_book/the-basics-of-saying-hello.html @@ -1,60 +1,33 @@ - - - - 3 The basics of saying hello | Creating the frombookdown R package - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - @@ -133,18 +106,14 @@
@@ -157,50 +126,42 @@

-
-
-

3 The basics of saying hello

+
+

+3 The basics of saying hello +

Let’s define a function for our R package:

-
#' Say hello to someone
-#' 
-#' @param name Name of a person
-#' @param exclamation Whether to include an exclamation mark
-#' @export 
-say_hello <- function(name, exclamation = TRUE) {
-  paste0("Hello ", name, ifelse(exclamation, "!", "."))
-}
+
#' Say hello to someone
+#' 
+#' @param name Name of a person
+#' @param exclamation Whether to include an exclamation mark
+#' @export 
+say_hello <- function(name, exclamation = TRUE) {
+  paste0("Hello ", name, ifelse(exclamation, "!", "."))
+}

Code chunks whose first line starts with #' are added to the package.

We can try running it.

-
say_hello("Jacob")
-
## [1] "Hello Jacob!"
+
say_hello("Jacob")
+
## [1] "Hello Jacob!"

That code chunk does not start with #', so it is not added to the package.

Let’s write some tests to make sure the function behaves as desired:

-
testthat::test_that("say_hello works", {
-  testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!")
-  testthat::expect_equal(say_hello("Jacob", exclamation = FALSE), "Hello Jacob.")
-})
+
testthat::test_that("say_hello works", {
+  testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!")
+  testthat::expect_equal(say_hello("Jacob", exclamation = FALSE), "Hello Jacob.")
+})
## Test passed

Code chunks that have one or more lines starting with test_that( (or testthat::test_that() are added to the package as tests.

- -
+

- - - - - - - - - - - diff --git a/examples/make-an-r-package-from-bookdown/frombookdown/DESCRIPTION b/examples/make-an-r-package-from-bookdown/frombookdown/DESCRIPTION index 7dd714d..f4a5999 100644 --- a/examples/make-an-r-package-from-bookdown/frombookdown/DESCRIPTION +++ b/examples/make-an-r-package-from-bookdown/frombookdown/DESCRIPTION @@ -13,4 +13,4 @@ Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 LitrVersionUsed: 0.9.1 -LitrId: 6644f51747d716dffaea375ff7ee0252 +LitrId: 610d416503c8b51447c9212e7b832c38 diff --git a/examples/make-an-r-package-from-bookdown/frombookdown/LICENSE b/examples/make-an-r-package-from-bookdown/frombookdown/LICENSE index 2bb6bed..9be16ff 100644 --- a/examples/make-an-r-package-from-bookdown/frombookdown/LICENSE +++ b/examples/make-an-r-package-from-bookdown/frombookdown/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2023 +YEAR: 2024 COPYRIGHT HOLDER: F. Last diff --git a/examples/make-an-r-package-from-bookdown/frombookdown/LICENSE.md b/examples/make-an-r-package-from-bookdown/frombookdown/LICENSE.md index bde1333..b67cbfc 100644 --- a/examples/make-an-r-package-from-bookdown/frombookdown/LICENSE.md +++ b/examples/make-an-r-package-from-bookdown/frombookdown/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2023 F. Last +Copyright (c) 2024 F. Last Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/make-an-r-package-with-armadillo/create-witharmadillo.html b/examples/make-an-r-package-with-armadillo/create-witharmadillo.html index a3ef639..58722a7 100644 --- a/examples/make-an-r-package-with-armadillo/create-witharmadillo.html +++ b/examples/make-an-r-package-with-armadillo/create-witharmadillo.html @@ -1,19 +1,12 @@ - - - - - - - - - - - + + + + + Creating the witharmadillo R package - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - @@ -364,21 +329,21 @@

Package setup

We start by specifying the information needed in the DESCRIPTION file of the R package.

usethis::create_package(
-  path = ".",
+  path = ".",
   fields = list(
     Package = params$package_name,
-    Version = "0.0.0.9000",
-    Title = "A Package That Uses RcppArmadillo Code",
-    Description = "This package uses RcppArmadillo.",
+    Version = "0.0.0.9000",
+    Title = "A Package That Uses RcppArmadillo Code",
+    Description = "This package uses RcppArmadillo.",
     `Authors@R` = person(
-      given = "First",
-      family = "Last",
-      email = "you@gmail.com",
-      role = c("aut", "cre")
+      given = "First",
+      family = "Last",
+      email = "you@gmail.com",
+      role = c("aut", "cre")
       )
   )
 )
-usethis::use_mit_license(copyright_holder = "F. Last")
+usethis::use_mit_license(copyright_holder = "F. Last")

Now to the package itself

@@ -391,21 +356,21 @@

Now to the package itself

#include <RcppArmadillo.h> using namespace arma; -//' Cholesky decomposition -//' -//' @param X A positive definite matrix -//' @export +//' Cholesky decomposition +//' +//' @param X A positive definite matrix +//' @export // [[Rcpp::export]] arma::mat my_chol(arma::mat& X) { return arma::chol(X); }

After at least one Rcpp code chunk,1 we next call the following usethis command:

-
usethis::use_rcpp_armadillo(name = "code")
-
## ✔ Leaving 'src/code.cpp' unchanged
-## • Edit 'src/code.cpp'
-## ✔ Adding 'RcppArmadillo' to LinkingTo field in DESCRIPTION
-## ✔ Created 'src/Makevars' and 'src/Makevars.win' with requested compilation settings.
+
usethis::use_rcpp_armadillo(name = "code")
+
## ✔ Leaving 'src/code.cpp' unchanged
+## • Edit 'src/code.cpp'
+## ✔ Adding 'RcppArmadillo' to LinkingTo field in DESCRIPTION
+## ✔ Created 'src/Makevars' and 'src/Makevars.win' with requested compilation settings.

And that’s it. If we like, we may call the function that we defined within the Rmarkdown file:

x <- matrix(0, 3, 3)
@@ -420,7 +385,7 @@ 

Now to the package itself

process a bit (and the code will still be included in the package).

Let’s write some tests to make sure the function behaves as desired:

-
testthat::test_that("my_chol works", {
+
testthat::test_that("my_chol works", {
   x <- matrix(0, 3, 3)
   x[upper.tri(x, diag = TRUE)] <- 1:6
   xchol <- my_chol(crossprod(x))
@@ -442,13 +407,13 @@ 

Documenting the package and building

## ℹ Loading witharmadillo ## ℹ Re-compiling witharmadillo (debug build) ## -## Writing 'NAMESPACE' -## Writing 'NAMESPACE' -## Writing 'my_chol.Rd' -## Writing 'witharmadillo-package.Rd'
+## Writing 'NAMESPACE' +## Writing 'NAMESPACE' +## Writing 'my_chol.Rd' +## Writing 'witharmadillo-package.Rd'
-
+
  1. If we had included a package_doc code chunk, then the location of this command would not actually matter.↩︎

  2. @@ -471,11 +436,7 @@

    Documenting the package and building

    }); - - - - - - - - - - - - diff --git a/examples/make-an-r-package-with-armadillo/witharmadillo/DESCRIPTION b/examples/make-an-r-package-with-armadillo/witharmadillo/DESCRIPTION index b4801c5..eee276f 100644 --- a/examples/make-an-r-package-with-armadillo/witharmadillo/DESCRIPTION +++ b/examples/make-an-r-package-with-armadillo/witharmadillo/DESCRIPTION @@ -17,4 +17,4 @@ Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 LitrVersionUsed: 0.9.1 -LitrId: 98e6ae0c7e7c02d146bac3ff955fd0a6 +LitrId: 696856b062e814802490e96a2dcbe966 diff --git a/examples/make-an-r-package-with-armadillo/witharmadillo/LICENSE b/examples/make-an-r-package-with-armadillo/witharmadillo/LICENSE index 2bb6bed..9be16ff 100644 --- a/examples/make-an-r-package-with-armadillo/witharmadillo/LICENSE +++ b/examples/make-an-r-package-with-armadillo/witharmadillo/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2023 +YEAR: 2024 COPYRIGHT HOLDER: F. Last diff --git a/examples/make-an-r-package-with-armadillo/witharmadillo/LICENSE.md b/examples/make-an-r-package-with-armadillo/witharmadillo/LICENSE.md index bde1333..b67cbfc 100644 --- a/examples/make-an-r-package-with-armadillo/witharmadillo/LICENSE.md +++ b/examples/make-an-r-package-with-armadillo/witharmadillo/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2023 F. Last +Copyright (c) 2024 F. Last Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/make-an-r-package-with-data/create-rhasdata.html b/examples/make-an-r-package-with-data/create-rhasdata.html index 4928206..97efd4c 100644 --- a/examples/make-an-r-package-with-data/create-rhasdata.html +++ b/examples/make-an-r-package-with-data/create-rhasdata.html @@ -1,19 +1,12 @@ - - - - - - - - - - - + + + + + Creating the rhasdata R package - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - @@ -364,21 +329,21 @@

    Package setup

    We start by specifying the information needed in the DESCRIPTION file of the R package.

    usethis::create_package(
    -  path = ".",
    +  path = ".",
       fields = list(
         Package = params$package_name,
    -    Version = "0.0.0.9000",
    -    Title = "A Package That Has Data",
    -    Description = "This package has a dataset.  It could also have some functions, but for now all it has is some data.",
    +    Version = "0.0.0.9000",
    +    Title = "A Package That Has Data",
    +    Description = "This package has a dataset.  It could also have some functions, but for now all it has is some data.",
         `Authors@R` = person(
    -      given = "First",
    -      family = "Last",
    -      email = "you@gmail.com",
    -      role = c("aut", "cre")
    +      given = "First",
    +      family = "Last",
    +      email = "you@gmail.com",
    +      role = c("aut", "cre")
           )
       )
     )
    -usethis::use_mit_license(copyright_holder = "F. Last")
    +usethis::use_mit_license(copyright_holder = "F. Last")

Now to the package itself

@@ -393,25 +358,25 @@

Create a dataset

mydata <- data.frame(x = x, y = y)

Let’s have a look:

plot(y ~ x, data = mydata)
-

+

Now, let’s send this off to the package:

usethis::use_data(mydata)
-
## ✔ Adding 'R' to Depends field in DESCRIPTION
-## ✔ Creating 'data/'
-## ✔ Setting LazyData to 'true' in 'DESCRIPTION'
-## ✔ Saving 'mydata' to 'data/mydata.rda'
-## • Document your data (see 'https://r-pkgs.org/data.html')
+
## ✔ Adding 'R' to Depends field in DESCRIPTION
+## ✔ Creating 'data/'
+## ✔ Setting LazyData to 'true' in 'DESCRIPTION'
+## ✔ Saving 'mydata' to 'data/mydata.rda'
+## • Document your data (see 'https://r-pkgs.org/data.html')

And we’ll need to document the dataset as well:

-
#' A regression data set
-#' 
-#' @format A data frame with 100 rows and 2 variables:
-#' \describe{
-#' \item{x}{a predictor variable}
-#' \item{y}{a response variable}
-#' }
-#' @source This data was simulated.  But usually we might cite a source here.
-"mydata"
-
## [1] "mydata"
+
#' A regression data set
+#' 
+#' @format A data frame with 100 rows and 2 variables:
+#' \describe{
+#' \item{x}{a predictor variable}
+#' \item{y}{a response variable}
+#' }
+#' @source This data was simulated.  But usually we might cite a source here.
+"mydata"
+
## [1] "mydata"
@@ -425,7 +390,7 @@

Documenting the package and building

# devtools::check(document = FALSE)
## ℹ Updating rhasdata documentation
 ## ℹ Loading rhasdata
-## Writing 'mydata.Rd'
+## Writing 'mydata.Rd'
@@ -444,11 +409,7 @@

Documenting the package and building

}); - - - - - - - - - - - - diff --git a/examples/make-an-r-package-with-data/rhasdata/DESCRIPTION b/examples/make-an-r-package-with-data/rhasdata/DESCRIPTION index 39e6fc5..1dbb7b6 100644 --- a/examples/make-an-r-package-with-data/rhasdata/DESCRIPTION +++ b/examples/make-an-r-package-with-data/rhasdata/DESCRIPTION @@ -13,4 +13,4 @@ Depends: R (>= 2.10) LazyData: true LitrVersionUsed: 0.9.1 -LitrId: 368ad9fcb7f572ff5bccecaeac9ead2f +LitrId: fb3a1a452a2b78378d71b674486d6673 diff --git a/examples/make-an-r-package-with-data/rhasdata/LICENSE b/examples/make-an-r-package-with-data/rhasdata/LICENSE index 2bb6bed..9be16ff 100644 --- a/examples/make-an-r-package-with-data/rhasdata/LICENSE +++ b/examples/make-an-r-package-with-data/rhasdata/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2023 +YEAR: 2024 COPYRIGHT HOLDER: F. Last diff --git a/examples/make-an-r-package-with-data/rhasdata/LICENSE.md b/examples/make-an-r-package-with-data/rhasdata/LICENSE.md index bde1333..b67cbfc 100644 --- a/examples/make-an-r-package-with-data/rhasdata/LICENSE.md +++ b/examples/make-an-r-package-with-data/rhasdata/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2023 F. Last +Copyright (c) 2024 F. Last Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/make-an-r-package-with-data/rhasdata/data/mydata.rda b/examples/make-an-r-package-with-data/rhasdata/data/mydata.rda index f689c7048230dbff211325cff03a27e58b44a5c3..32ac6c2f1c0e01de16ce5bb2f6c8a4cf0be708b5 100644 GIT binary patch delta 2010 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 - - - - - - - - - - - + + + + + Creating the withpkgdown R package - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - @@ -364,18 +329,18 @@

Package setup

We start by specifying the information needed in the DESCRIPTION file of the R package.

usethis::create_package(
-  path = ".",
+  path = ".",
   fields = list(
     Package = params$package_name,
-    Version = "0.0.0.9000",
-    Title = "A Package With a README, Vignette, and Pkgdown Site",
-    Description = "This package's purpose is to show how to have a README, vignette, and a pkgdown website using litr.",
-    `Authors@R` = person("First", "Last", email = "you@gmail.com",
-                         role = c("aut", "cre")
+    Version = "0.0.0.9000",
+    Title = "A Package With a README, Vignette, and Pkgdown Site",
+    Description = "This package's purpose is to show how to have a README, vignette, and a pkgdown website using litr.",
+    `Authors@R` = person("First", "Last", email = "you@gmail.com",
+                         role = c("aut", "cre")
       )
   )
 )
-usethis::use_mit_license(copyright_holder = "F. Last")
+usethis::use_mit_license(copyright_holder = "F. Last")

Now to the package itself

@@ -385,22 +350,22 @@

Now to the package itself

Define two functions

Let’s define functions that say hello and hi:

-
#' Say hello to someone
-#' 
-#' @param name Name of a person
-#' @param exclamation Whether to include an exclamation mark
-#' @export 
-say_hello <- function(name, exclamation = TRUE) {
-  paste0("Hello ", name, ifelse(exclamation, "!", "."))
+
#' Say hello to someone
+#' 
+#' @param name Name of a person
+#' @param exclamation Whether to include an exclamation mark
+#' @export 
+say_hello <- function(name, exclamation = TRUE) {
+  paste0("Hello ", name, ifelse(exclamation, "!", "."))
 }
 
-#' Say hi to someone
-#' 
-#' @param name Name of a person
-#' @param exclamation Whether to include an exclamation mark
-#' @export 
-say_hi <- function(name, exclamation = TRUE) {
-  paste0("Hi ", name, ifelse(exclamation, "!", "."))
+#' Say hi to someone
+#' 
+#' @param name Name of a person
+#' @param exclamation Whether to include an exclamation mark
+#' @export 
+say_hi <- function(name, exclamation = TRUE) {
+  paste0("Hi ", name, ifelse(exclamation, "!", "."))
 }
@@ -410,9 +375,9 @@

Document the package

litr::document()
## ℹ Updating withpkgdown documentation
 ## ℹ Loading withpkgdown
-## Writing 'NAMESPACE'
-## Writing 'say_hello.Rd'
-## Writing 'say_hi.Rd'
+## Writing 'NAMESPACE' +## Writing 'say_hello.Rd' +## Writing 'say_hi.Rd'

Add extra items

@@ -434,7 +399,7 @@

Add a hex sticker

You’d put it in the source-files directory. We’ll use the litr hex for this example, even though technically we should have created a “withpkgdown” sticker for this example.

-
litr::add_hex_sticker("../source-files/litr-hex.png")
+
litr::add_hex_sticker("../source-files/litr-hex.png")

Add a README

@@ -442,33 +407,33 @@

Add a README

described here, if we have a hex sticker, we’ll add something like the following to the level-one header at the top of the README:

-
# withpkgdown: An Example Package <img src="man/figures/logo.png" align="right" height="139" />
+
# withpkgdown: An Example Package <img src="man/figures/logo.png" align="right" height="139" />

We add README.Rmd to the package and then generate the README.md based on it:

-
litr::add_readme("../source-files/README.Rmd")
-
## ✔ Writing 'README.Rmd'
-## ✔ Adding '^README\\.Rmd$' to '.Rbuildignore'
-## ✔ Creating '.git/hooks/'
-## ✔ Writing '.git/hooks/pre-commit'
+
litr::add_readme("../source-files/README.Rmd")
+
## ✔ Writing 'README.Rmd'
+## ✔ Adding '^README\\.Rmd$' to '.Rbuildignore'
+## ✔ Creating '.git/hooks/'
+## ✔ Writing '.git/hooks/pre-commit'

Add a vignette

-
litr::add_vignettes("../source-files/using-package.Rmd")
+
litr::add_vignettes("../source-files/using-package.Rmd")

Add a pkgdown site

First, let’s include the github link of our package as the URL so that we can have a link to it on our pkgdown site.

desc::desc_set(
-  "URL",
-  "https://github.com/jacobbien/litr-project/tree/main/examples/make-an-r-package-with-extras/withpkgdown"
+  "URL",
+  "https://github.com/jacobbien/litr-project/tree/main/examples/make-an-r-package-with-extras/withpkgdown"
   )
## Package: withpkgdown
 ## Title: A Package With a README, Vignette, and Pkgdown Site
 ## Version: 0.0.0.9000
 ## Authors@R (parsed):
 ##     * First Last <you@gmail.com> [aut, cre]
-## Description: This package's purpose is to show how to have a README,
+## Description: This package's purpose is to show how to have a README,
 ##     vignette, and a pkgdown website using litr.
 ## License: MIT + file LICENSE
 ## URL:
@@ -483,52 +448,52 @@ 

Add a pkgdown site

## RoxygenNote: 7.2.3

Be sure that this next command appears after litr::document() has been called in this file.

-
litr::add_pkgdown("../source-files/_pkgdown.yml")
-
## ✔ Adding '^_pkgdown\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore'
+
litr::add_pkgdown("../source-files/_pkgdown.yml")
+
## ✔ Adding '^_pkgdown\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore'
## -- Installing package into temporary library -----------------------------------
 ## == Building pkgdown site =======================================================
-## Reading from: '/Users/jacobbien/Documents/GitHub/litr-project/examples/withpkgdown'
-## Writing to:   '/Users/jacobbien/Documents/GitHub/litr-project/examples/docs'
+## Reading from: '/Users/vossler/litr-project/examples/withpkgdown'
+## Writing to:   '/Users/vossler/litr-project/examples/docs'
 ## -- Initialising site -----------------------------------------------------------
-## Copying '../../../../../Library/R/arm64/4.3/library/pkgdown/BS5/assets/link.svg' to 'link.svg'
-## Copying '../../../../../Library/R/arm64/4.3/library/pkgdown/BS5/assets/pkgdown.js' to 'pkgdown.js'
+## Copying '../../../../../Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library/pkgdown/BS5/assets/link.svg' to 'link.svg'
+## Copying '../../../../../Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library/pkgdown/BS5/assets/pkgdown.js' to 'pkgdown.js'
 ## -- Building favicons -----------------------------------------------------------
 ## Building favicons with realfavicongenerator.net...
-## Copying 'pkgdown/favicon/apple-touch-icon-120x120.png' to 'apple-touch-icon-120x120.png'
-## Copying 'pkgdown/favicon/apple-touch-icon-152x152.png' to 'apple-touch-icon-152x152.png'
-## Copying 'pkgdown/favicon/apple-touch-icon-180x180.png' to 'apple-touch-icon-180x180.png'
-## Copying 'pkgdown/favicon/apple-touch-icon-60x60.png' to 'apple-touch-icon-60x60.png'
-## Copying 'pkgdown/favicon/apple-touch-icon-76x76.png' to 'apple-touch-icon-76x76.png'
-## Copying 'pkgdown/favicon/apple-touch-icon.png' to 'apple-touch-icon.png'
-## Copying 'pkgdown/favicon/favicon-16x16.png' to 'favicon-16x16.png'
-## Copying 'pkgdown/favicon/favicon-32x32.png' to 'favicon-32x32.png'
-## Copying 'pkgdown/favicon/favicon.ico' to 'favicon.ico'
-## Copying 'man/figures/logo.png' to 'logo.png'
+## Copying 'pkgdown/favicon/apple-touch-icon-120x120.png' to 'apple-touch-icon-120x120.png'
+## Copying 'pkgdown/favicon/apple-touch-icon-152x152.png' to 'apple-touch-icon-152x152.png'
+## Copying 'pkgdown/favicon/apple-touch-icon-180x180.png' to 'apple-touch-icon-180x180.png'
+## Copying 'pkgdown/favicon/apple-touch-icon-60x60.png' to 'apple-touch-icon-60x60.png'
+## Copying 'pkgdown/favicon/apple-touch-icon-76x76.png' to 'apple-touch-icon-76x76.png'
+## Copying 'pkgdown/favicon/apple-touch-icon.png' to 'apple-touch-icon.png'
+## Copying 'pkgdown/favicon/favicon-16x16.png' to 'favicon-16x16.png'
+## Copying 'pkgdown/favicon/favicon-32x32.png' to 'favicon-32x32.png'
+## Copying 'pkgdown/favicon/favicon.ico' to 'favicon.ico'
+## Copying 'man/figures/logo.png' to 'logo.png'
 ## -- Building home ---------------------------------------------------------------
-## Writing 'authors.html'
-## Reading 'LICENSE.md'
-## Writing 'LICENSE.html'
-## Writing 'LICENSE-text.html'
-## Copying 'man/figures/logo.png' to 'reference/figures/logo.png'
-## Writing '404.html'
+## Writing 'authors.html'
+## Reading 'LICENSE.md'
+## Writing 'LICENSE.html'
+## Writing 'LICENSE-text.html'
+## Copying 'man/figures/logo.png' to 'reference/figures/logo.png'
+## Writing '404.html'
 ## -- Building function reference -------------------------------------------------
-## Writing 'reference/index.html'
-## Reading 'man/say_hello.Rd'
-## Writing 'reference/say_hello.html'
-## Reading 'man/say_hi.Rd'
-## Writing 'reference/say_hi.html'
+## Writing 'reference/index.html'
+## Reading 'man/say_hello.Rd'
+## Writing 'reference/say_hello.html'
+## Reading 'man/say_hi.Rd'
+## Writing 'reference/say_hi.html'
 ## -- Building articles -----------------------------------------------------------
-## Writing 'articles/index.html'
-## Reading 'vignettes/using-package.Rmd'
-## Writing 'articles/using-package.html'
-## Writing 'sitemap.xml'
+## Writing 'articles/index.html'
+## Reading 'vignettes/using-package.Rmd'
+## Writing 'articles/using-package.html'
+## Writing 'sitemap.xml'
 ## -- Building search index -------------------------------------------------------
 ## == DONE ========================================================================

You can call add_pkgdown() without the argument; however, here we are providing a customized _pkgdown.yml file. See this pkgdown vignette for how to customize your site. Here is the contents of the _pkgdown.yml that was used:

-
cat(readLines("../source-files/_pkgdown.yml"), sep = '\n')
+
cat(readLines("../source-files/_pkgdown.yml"), sep = '\n')
destination: ../docs/
 
 url: ~
@@ -546,7 +511,7 @@ 

Add a pkgdown site

-
+
  1. You can make it in R with this tool!↩︎

@@ -568,11 +533,7 @@

Add a pkgdown site

}); - - - - - - - - - - - - diff --git a/examples/make-an-r-package-with-extras/docs/404.html b/examples/make-an-r-package-with-extras/docs/404.html index 9ef0d29..fda769d 100644 --- a/examples/make-an-r-package-with-extras/docs/404.html +++ b/examples/make-an-r-package-with-extras/docs/404.html @@ -13,8 +13,8 @@ - - + + diff --git a/examples/make-an-r-package-with-extras/docs/LICENSE-text.html b/examples/make-an-r-package-with-extras/docs/LICENSE-text.html index 3536c09..4eefc4a 100644 --- a/examples/make-an-r-package-with-extras/docs/LICENSE-text.html +++ b/examples/make-an-r-package-with-extras/docs/LICENSE-text.html @@ -1,5 +1,5 @@ -License • withpkgdownLicense • withpkgdown @@ -47,7 +47,7 @@
-
YEAR: 2023
+
YEAR: 2024
 COPYRIGHT HOLDER: F. Last
 
diff --git a/examples/make-an-r-package-with-extras/docs/LICENSE.html b/examples/make-an-r-package-with-extras/docs/LICENSE.html index 7e3b3fa..ffb0cd3 100644 --- a/examples/make-an-r-package-with-extras/docs/LICENSE.html +++ b/examples/make-an-r-package-with-extras/docs/LICENSE.html @@ -1,5 +1,5 @@ -MIT License • withpkgdownMIT License • withpkgdown @@ -49,7 +49,7 @@
-

Copyright (c) 2023 F. Last

+

Copyright (c) 2024 F. Last

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

diff --git a/examples/make-an-r-package-with-extras/docs/apple-touch-icon-120x120.png b/examples/make-an-r-package-with-extras/docs/apple-touch-icon-120x120.png index ea09a719e5c190c2ec421fa97ce6b6c8d475f84a..75434770d5a97357f0b166b9ad1caf5c52d9af42 100644 GIT binary patch delta 101 zcmZ4KveIQj1=kBk77-PR+N}RyHa5Lg@HWvkFw`|L2{AITGB&d^GSN0Lure^1Aa-H} m0|SFRc?MtW?ChKk%19*4G+BwL?-W2%m)B>93HX& delta 101 zcmZ4KveIQj1=n*fQ6UY{qX(EzZftt1;BBmHXrOCk7-DE)Wny4uWTzm7AZEnO4bQWMG6{L)n!?+sUk&`2bew9j^cY delta 101 zcmewo^d)FQ1=n*fQ6UYnn3F-p8=EXNyp44Y4RnnRLkumfObo1yjI<35tPBh~b}Y+h lU|>)!ag8WRNi0dV%FR#7OsixtGBCof;pbDi%ad6(^8rwg9*qD1 diff --git a/examples/make-an-r-package-with-extras/docs/apple-touch-icon-180x180.png b/examples/make-an-r-package-with-extras/docs/apple-touch-icon-180x180.png index 0cd462743f9a0095f54ab0242f2163853539f776..abbdc98eee29abab596b890f0f0668d3643a79d1 100644 GIT binary patch delta 101 zcmaEs{49Aw1=kBk77-QcC&}ylHa4v?@HWvkFw`|L2{AITGB&d^GS@aRure@M`0tV_ m0|SFRc?MtW?ChKk%19*4Hp(?bxtlf%m)CX%pbe} delta 101 zcmaEs{49Aw1=n*fQ6UX+?Q=;_H#V&@@HW;pG|)9N3^BB@GBL0+GS)UQure^vJHPfe m0|SFRc?MtW?ChKk%19*4e^si(Rc?MtW?ChKk%19*4JvQ^W=%dLoDTqnd>zm7AZEnO4bQWMG6{!z@$nmy^p4^8tdW9=rem diff --git a/examples/make-an-r-package-with-extras/docs/articles/index.html b/examples/make-an-r-package-with-extras/docs/articles/index.html index c2235f2..b7e63ca 100644 --- a/examples/make-an-r-package-with-extras/docs/articles/index.html +++ b/examples/make-an-r-package-with-extras/docs/articles/index.html @@ -1,5 +1,5 @@ -Articles • withpkgdownArticles • withpkgdown diff --git a/examples/make-an-r-package-with-extras/docs/articles/using-package.html b/examples/make-an-r-package-with-extras/docs/articles/using-package.html index 47ac6ad..dc98142 100644 --- a/examples/make-an-r-package-with-extras/docs/articles/using-package.html +++ b/examples/make-an-r-package-with-extras/docs/articles/using-package.html @@ -14,8 +14,8 @@ - - + + @@ -78,7 +78,7 @@

Using withpkgdown Package

Your Name

-

2023-10-21

+

2024-01-04

Source: vignettes/using-package.Rmd
using-package.Rmd
diff --git a/examples/make-an-r-package-with-extras/docs/authors.html b/examples/make-an-r-package-with-extras/docs/authors.html index 3c7e551..da32715 100644 --- a/examples/make-an-r-package-with-extras/docs/authors.html +++ b/examples/make-an-r-package-with-extras/docs/authors.html @@ -1,5 +1,5 @@ -Authors and Citation • withpkgdownAuthors and Citation • withpkgdown @@ -59,14 +59,14 @@

Authors

Citation

Source: DESCRIPTION

-

Last F (2023). +

Last F (2024). withpkgdown: A Package With a README, Vignette, and Pkgdown Site. R package version 0.0.0.9000, https://github.com/jacobbien/litr-project/tree/main/examples/make-an-r-package-with-extras/withpkgdown.

@Manual{,
   title = {withpkgdown: A Package With a README, Vignette, and Pkgdown Site},
   author = {First Last},
-  year = {2023},
+  year = {2024},
   note = {R package version 0.0.0.9000},
   url = {https://github.com/jacobbien/litr-project/tree/main/examples/make-an-r-package-with-extras/withpkgdown},
 }
diff --git a/examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js b/examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js deleted file mode 100644 index 1d13886..0000000 --- a/examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v5.2.2 (https://getbootstrap.com/) - * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},m=t=>{"function"==typeof t&&t()},_=(e,i,n=!0)=>{if(!n)return void m(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),m(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=N(t);return C.has(o)||(o=t),[n,s,o]}function D(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return j(s,{delegateTarget:r}),n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return j(n,{delegateTarget:t}),i.oneOff&&P.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function S(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function I(t,e,i,n){const s=e[i]||{};for(const o of Object.keys(s))if(o.includes(n)){const n=s[o];S(t,e,i,n.callable,n.delegationSelector)}}function N(t){return t=t.replace(y,""),T[t]||t}const P={on(t,e,i,n){D(t,e,i,n,!1)},one(t,e,i,n){D(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))I(t,l,i,e.slice(1));for(const i of Object.keys(c)){const n=i.replace(w,"");if(!a||e.includes(n)){const e=c[i];S(t,l,r,e.callable,e.delegationSelector)}}}else{if(!Object.keys(c).length)return;S(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==N(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());let l=new Event(e,{bubbles:o,cancelable:!0});return l=j(l,i),a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function j(t,e){for(const[i,n]of Object.entries(e||{}))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}const M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};function $(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function W(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const B={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${W(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${W(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=$(t.dataset[n])}return e},getDataAttribute:(t,e)=>$(t.getAttribute(`data-bs-${W(e)}`))};class F{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?B.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?B.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const n of Object.keys(e)){const s=e[n],r=t[n],a=o(r)?"element":null==(i=r)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(a))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}var i}}class z extends F{constructor(t,e){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(e),H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),P.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.2.2"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const q=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;P.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class R extends z{static get NAME(){return"alert"}close(){if(P.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),P.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=R.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}q(R,"close"),g(R);const V='[data-bs-toggle="button"]';class K extends z{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=K.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}P.on(document,"click.bs.button.data-api",V,(t=>{t.preventDefault();const e=t.target.closest(V);K.getOrCreateInstance(e).toggle()})),g(K);const Q={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))}},X={endCallback:null,leftCallback:null,rightCallback:null},Y={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class U extends F{constructor(t,e){super(),this._element=t,t&&U.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return X}static get DefaultType(){return Y}static get NAME(){return"swipe"}dispose(){P.off(this._element,".bs.swipe")}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),m(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&m(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(P.on(this._element,"pointerdown.bs.swipe",(t=>this._start(t))),P.on(this._element,"pointerup.bs.swipe",(t=>this._end(t))),this._element.classList.add("pointer-event")):(P.on(this._element,"touchstart.bs.swipe",(t=>this._start(t))),P.on(this._element,"touchmove.bs.swipe",(t=>this._move(t))),P.on(this._element,"touchend.bs.swipe",(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const G="next",J="prev",Z="left",tt="right",et="slid.bs.carousel",it="carousel",nt="active",st={ArrowLeft:tt,ArrowRight:Z},ot={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},rt={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class at extends z{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=Q.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===it&&this.cycle()}static get Default(){return ot}static get DefaultType(){return rt}static get NAME(){return"carousel"}next(){this._slide(G)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(J)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?P.one(this._element,et,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void P.one(this._element,et,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?G:J;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&P.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(P.on(this._element,"mouseenter.bs.carousel",(()=>this.pause())),P.on(this._element,"mouseleave.bs.carousel",(()=>this._maybeEnableCycle()))),this._config.touch&&U.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of Q.find(".carousel-item img",this._element))P.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(Z)),rightCallback:()=>this._slide(this._directionToOrder(tt)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new U(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=st[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=Q.findOne(".active",this._indicatorsElement);e.classList.remove(nt),e.removeAttribute("aria-current");const i=Q.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(nt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===G,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>P.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r("slide.bs.carousel").defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(nt),i.classList.remove(nt,c,l),this._isSliding=!1,r(et)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return Q.findOne(".active.carousel-item",this._element)}_getItems(){return Q.find(".carousel-item",this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===Z?J:G:t===Z?G:J}_orderToDirection(t){return p()?t===J?Z:tt:t===J?tt:Z}static jQueryInterface(t){return this.each((function(){const e=at.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}P.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",(function(t){const e=n(this);if(!e||!e.classList.contains(it))return;t.preventDefault();const i=at.getOrCreateInstance(e),s=this.getAttribute("data-bs-slide-to");return s?(i.to(s),void i._maybeEnableCycle()):"next"===B.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),P.on(window,"load.bs.carousel.data-api",(()=>{const t=Q.find('[data-bs-ride="carousel"]');for(const e of t)at.getOrCreateInstance(e)})),g(at);const lt="show",ct="collapse",ht="collapsing",dt='[data-bs-toggle="collapse"]',ut={parent:null,toggle:!0},ft={parent:"(null|element)",toggle:"boolean"};class pt extends z{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const n=Q.find(dt);for(const t of n){const e=i(t),n=Q.find(e).filter((t=>t===this._element));null!==e&&n.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return ut}static get DefaultType(){return ft}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>pt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(P.trigger(this._element,"show.bs.collapse").defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[e]="",P.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(P.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);for(const t of this._triggerArray){const e=n(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),P.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(dt);for(const e of t){const t=n(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=Q.find(":scope .collapse .collapse",this._config.parent);return Q.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}P.on(document,"click.bs.collapse.data-api",dt,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this),n=Q.find(e);for(const t of n)pt.getOrCreateInstance(t,{toggle:!1}).toggle()})),g(pt);var gt="top",mt="bottom",_t="right",bt="left",vt="auto",yt=[gt,mt,_t,bt],wt="start",At="end",Et="clippingParents",Tt="viewport",Ct="popper",Ot="reference",xt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+At])}),[]),kt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+At])}),[]),Lt="beforeRead",Dt="read",St="afterRead",It="beforeMain",Nt="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",$t=[Lt,Dt,St,It,Nt,Pt,jt,Mt,Ht];function Wt(t){return t?(t.nodeName||"").toLowerCase():null}function Bt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function Ft(t){return t instanceof Bt(t).Element||t instanceof Element}function zt(t){return t instanceof Bt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Bt(t).ShadowRoot||t instanceof ShadowRoot)}const Rt={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Wt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Wt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Vt(t){return t.split("-")[0]}var Kt=Math.max,Qt=Math.min,Xt=Math.round;function Yt(){var t=navigator.userAgentData;return null!=t&&t.brands?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ut(){return!/^((?!chrome|android).)*safari/i.test(Yt())}function Gt(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&zt(t)&&(s=t.offsetWidth>0&&Xt(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&Xt(n.height)/t.offsetHeight||1);var r=(Ft(t)?Bt(t):window).visualViewport,a=!Ut()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Jt(t){var e=Gt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Zt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function te(t){return Bt(t).getComputedStyle(t)}function ee(t){return["table","td","th"].indexOf(Wt(t))>=0}function ie(t){return((Ft(t)?t.ownerDocument:t.document)||window.document).documentElement}function ne(t){return"html"===Wt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||ie(t)}function se(t){return zt(t)&&"fixed"!==te(t).position?t.offsetParent:null}function oe(t){for(var e=Bt(t),i=se(t);i&&ee(i)&&"static"===te(i).position;)i=se(i);return i&&("html"===Wt(i)||"body"===Wt(i)&&"static"===te(i).position)?e:i||function(t){var e=/firefox/i.test(Yt());if(/Trident/i.test(Yt())&&zt(t)&&"fixed"===te(t).position)return null;var i=ne(t);for(qt(i)&&(i=i.host);zt(i)&&["html","body"].indexOf(Wt(i))<0;){var n=te(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function re(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function ae(t,e,i){return Kt(t,Qt(e,i))}function le(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ce(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const he={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Vt(i.placement),l=re(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return le("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ce(t,yt))}(s.padding,i),d=Jt(o),u="y"===l?gt:bt,f="y"===l?mt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],g=r[l]-i.rects.reference[l],m=oe(o),_=m?"y"===l?m.clientHeight||0:m.clientWidth||0:0,b=p/2-g/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=ae(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Zt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function de(t){return t.split("-")[1]}var ue={top:"auto",right:"auto",bottom:"auto",left:"auto"};function fe(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,g=void 0===p?0:p,m="function"==typeof h?h({x:f,y:g}):{x:f,y:g};f=m.x,g=m.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=bt,y=gt,w=window;if(c){var A=oe(i),E="clientHeight",T="clientWidth";A===Bt(i)&&"static"!==te(A=ie(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===gt||(s===bt||s===_t)&&o===At)&&(y=mt,g-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,g*=l?1:-1),s!==bt&&(s!==gt&&s!==mt||o!==At)||(v=_t,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&ue),x=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:Xt(e*n)/n||0,y:Xt(i*n)/n||0}}({x:f,y:g}):{x:f,y:g};return f=x.x,g=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+g+"px)":"translate3d("+f+"px, "+g+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?g+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const pe={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Vt(e.placement),variation:de(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,fe(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,fe(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ge={passive:!0};const me={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Bt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ge)})),a&&l.addEventListener("resize",i.update,ge),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ge)})),a&&l.removeEventListener("resize",i.update,ge)}},data:{}};var _e={left:"right",right:"left",bottom:"top",top:"bottom"};function be(t){return t.replace(/left|right|bottom|top/g,(function(t){return _e[t]}))}var ve={start:"end",end:"start"};function ye(t){return t.replace(/start|end/g,(function(t){return ve[t]}))}function we(t){var e=Bt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ae(t){return Gt(ie(t)).left+we(t).scrollLeft}function Ee(t){var e=te(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Te(t){return["html","body","#document"].indexOf(Wt(t))>=0?t.ownerDocument.body:zt(t)&&Ee(t)?t:Te(ne(t))}function Ce(t,e){var i;void 0===e&&(e=[]);var n=Te(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Bt(n),r=s?[o].concat(o.visualViewport||[],Ee(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ce(ne(r)))}function Oe(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function xe(t,e,i){return e===Tt?Oe(function(t,e){var i=Bt(t),n=ie(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ut();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ae(t),y:l}}(t,i)):Ft(e)?function(t,e){var i=Gt(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Oe(function(t){var e,i=ie(t),n=we(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=Kt(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=Kt(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ae(t),l=-n.scrollTop;return"rtl"===te(s||i).direction&&(a+=Kt(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(ie(t)))}function ke(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Vt(s):null,r=s?de(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case gt:e={x:a,y:i.y-n.height};break;case mt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?re(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case At:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Et:a,c=i.rootBoundary,h=void 0===c?Tt:c,d=i.elementContext,u=void 0===d?Ct:d,f=i.altBoundary,p=void 0!==f&&f,g=i.padding,m=void 0===g?0:g,_=le("number"!=typeof m?m:ce(m,yt)),b=u===Ct?Ot:Ct,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Ce(ne(t)),i=["absolute","fixed"].indexOf(te(t).position)>=0&&zt(t)?oe(t):t;return Ft(i)?e.filter((function(t){return Ft(t)&&Zt(t,i)&&"body"!==Wt(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=xe(t,i,n);return e.top=Kt(s.top,e.top),e.right=Qt(s.right,e.right),e.bottom=Qt(s.bottom,e.bottom),e.left=Kt(s.left,e.left),e}),xe(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(Ft(y)?y:y.contextElement||ie(t.elements.popper),l,h,r),A=Gt(t.elements.reference),E=ke({reference:A,element:v,strategy:"absolute",placement:s}),T=Oe(Object.assign({},v,E)),C=u===Ct?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Ct&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[_t,mt].indexOf(t)>=0?1:-1,i=[gt,mt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function De(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?kt:l,h=de(n),d=h?a?xt:xt.filter((function(t){return de(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=Le(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Vt(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const Se={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,g=i.allowedAutoPlacements,m=e.options.placement,_=Vt(m),b=l||(_!==m&&p?function(t){if(Vt(t)===vt)return[];var e=be(t);return[ye(t),e,ye(e)]}(m):[be(m)]),v=[m].concat(b).reduce((function(t,i){return t.concat(Vt(i)===vt?De(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:g}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,D=L?"width":"height",S=Le(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),I=L?k?_t:bt:k?mt:gt;y[D]>w[D]&&(I=be(I));var N=be(I),P=[];if(o&&P.push(S[x]<=0),a&&P.push(S[I]<=0,S[N]<=0),P.every((function(t){return t}))){T=O,E=!1;break}A.set(O,P)}if(E)for(var j=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function Ie(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Ne(t){return[gt,_t,mt,bt].some((function(e){return t[e]>=0}))}const Pe={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=Le(e,{elementContext:"reference"}),a=Le(e,{altBoundary:!0}),l=Ie(r,n),c=Ie(a,s,o),h=Ne(l),d=Ne(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},je={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=kt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Vt(t),s=[bt,gt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Me={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ke({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},He={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,g=void 0===p?0:p,m=Le(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Vt(e.placement),b=de(e.placement),v=!b,y=re(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof g?g(Object.assign({},e.rects,{placement:e.placement})):g,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,D="y"===y?gt:bt,S="y"===y?mt:_t,I="y"===y?"height":"width",N=A[y],P=N+m[D],j=N-m[S],M=f?-T[I]/2:0,H=b===wt?E[I]:T[I],$=b===wt?-T[I]:-E[I],W=e.elements.arrow,B=f&&W?Jt(W):{width:0,height:0},F=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=F[D],q=F[S],R=ae(0,E[I],B[I]),V=v?E[I]/2-M-R-z-O.mainAxis:H-R-z-O.mainAxis,K=v?-E[I]/2+M+R+q+O.mainAxis:$+R+q+O.mainAxis,Q=e.elements.arrow&&oe(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=N+K-Y,G=ae(f?Qt(P,N+V-Y-X):P,N,f?Kt(j,U):j);A[y]=G,k[y]=G-N}if(a){var J,Z="x"===y?gt:bt,tt="x"===y?mt:_t,et=A[w],it="y"===w?"height":"width",nt=et+m[Z],st=et-m[tt],ot=-1!==[gt,bt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=ae(t,e,i);return n>i?i:n}(at,et,lt):ae(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function $e(t,e,i){void 0===i&&(i=!1);var n,s,o=zt(e),r=zt(e)&&function(t){var e=t.getBoundingClientRect(),i=Xt(e.width)/t.offsetWidth||1,n=Xt(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=ie(e),l=Gt(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==Wt(e)||Ee(a))&&(c=(n=e)!==Bt(n)&&zt(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:we(n)),zt(e)?((h=Gt(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ae(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function We(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Fe(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(B.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=Q.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=Q.find(ti);for(const i of e){const e=hi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Xe,Ye].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ze)?this:Q.prev(this,Ze)[0]||Q.next(this,Ze)[0]||Q.findOne(Ze,t.delegateTarget.parentNode),o=hi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}P.on(document,Ge,Ze,hi.dataApiKeydownHandler),P.on(document,Ge,ei,hi.dataApiKeydownHandler),P.on(document,Ue,hi.clearMenus),P.on(document,"keyup.bs.dropdown.data-api",hi.clearMenus),P.on(document,Ue,Ze,(function(t){t.preventDefault(),hi.getOrCreateInstance(this).toggle()})),g(hi);const di=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",ui=".sticky-top",fi="padding-right",pi="margin-right";class gi{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,fi,(e=>e+t)),this._setElementAttributes(di,fi,(e=>e+t)),this._setElementAttributes(ui,pi,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,fi),this._resetElementAttributes(di,fi),this._resetElementAttributes(ui,pi)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&B.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=B.getDataAttribute(t,e);null!==i?(B.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of Q.find(t,this._element))e(i)}}const mi="show",_i="mousedown.bs.backdrop",bi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},vi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class yi extends F{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return bi}static get DefaultType(){return vi}static get NAME(){return"backdrop"}show(t){if(!this._config.isVisible)return void m(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(mi),this._emulateAnimation((()=>{m(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(mi),this._emulateAnimation((()=>{this.dispose(),m(t)}))):m(t)}dispose(){this._isAppended&&(P.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),P.on(t,_i,(()=>{m(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const wi=".bs.focustrap",Ai="backward",Ei={autofocus:!0,trapElement:null},Ti={autofocus:"boolean",trapElement:"element"};class Ci extends F{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return Ei}static get DefaultType(){return Ti}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),P.off(document,wi),P.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),P.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,P.off(document,wi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=Q.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===Ai?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ai:"forward")}}const Oi="hidden.bs.modal",xi="show.bs.modal",ki="modal-open",Li="show",Di="modal-static",Si={backdrop:!0,focus:!0,keyboard:!0},Ii={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ni extends z{constructor(t,e){super(t,e),this._dialog=Q.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new gi,this._addEventListeners()}static get Default(){return Si}static get DefaultType(){return Ii}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||P.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(ki),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(P.trigger(this._element,"hide.bs.modal").defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Li),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){for(const t of[window,this._dialog])P.off(t,".bs.modal");this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new yi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ci({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=Q.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(Li),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,P.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){P.on(this._element,"keydown.dismiss.bs.modal",(t=>{if("Escape"===t.key)return this._config.keyboard?(t.preventDefault(),void this.hide()):void this._triggerBackdropTransition()})),P.on(window,"resize.bs.modal",(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),P.on(this._element,"mousedown.dismiss.bs.modal",(t=>{P.one(this._element,"click.dismiss.bs.modal",(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(ki),this._resetAdjustments(),this._scrollBar.reset(),P.trigger(this._element,Oi)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(P.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Di)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Di),this._queueCallback((()=>{this._element.classList.remove(Di),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Ni.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}P.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),P.one(e,xi,(t=>{t.defaultPrevented||P.one(e,Oi,(()=>{a(this)&&this.focus()}))}));const i=Q.findOne(".modal.show");i&&Ni.getInstance(i).hide(),Ni.getOrCreateInstance(e).toggle(this)})),q(Ni),g(Ni);const Pi="show",ji="showing",Mi="hiding",Hi=".offcanvas.show",$i="hidePrevented.bs.offcanvas",Wi="hidden.bs.offcanvas",Bi={backdrop:!0,keyboard:!0,scroll:!1},Fi={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class zi extends z{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Bi}static get DefaultType(){return Fi}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||P.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new gi).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(ji),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Pi),this._element.classList.remove(ji),P.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(P.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Mi),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Pi,Mi),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new gi).reset(),P.trigger(this._element,Wi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new yi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():P.trigger(this._element,$i)}:null})}_initializeFocusTrap(){return new Ci({trapElement:this._element})}_addEventListeners(){P.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():P.trigger(this._element,$i))}))}static jQueryInterface(t){return this.each((function(){const e=zi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}P.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;P.one(e,Wi,(()=>{a(this)&&this.focus()}));const i=Q.findOne(Hi);i&&i!==e&&zi.getInstance(i).hide(),zi.getOrCreateInstance(e).toggle(this)})),P.on(window,"load.bs.offcanvas.data-api",(()=>{for(const t of Q.find(Hi))zi.getOrCreateInstance(t).show()})),P.on(window,"resize.bs.offcanvas",(()=>{for(const t of Q.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&zi.getOrCreateInstance(t).hide()})),q(zi),g(zi);const qi=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Ri=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Vi=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Ki=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!qi.has(i)||Boolean(Ri.test(t.nodeValue)||Vi.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Qi={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Xi={allowList:Qi,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Yi={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Ui={entry:"(string|element|function|null)",selector:"(string|element)"};class Gi extends F{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Ui)}_setContent(t,e,i){const n=Q.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Ki(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return"function"==typeof t?t(this):t}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Ji=new Set(["sanitize","allowList","sanitizeFn"]),Zi="fade",tn="show",en=".modal",nn="hide.bs.modal",sn="hover",on="focus",rn={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},an={allowList:Qi,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,0],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ln={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cn extends z{constructor(t,e){if(void 0===Ke)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return an}static get DefaultType(){return ln}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),P.off(this._element.closest(en),nn,this._hideModalHandler),this.tip&&this.tip.remove(),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=P.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this.tip&&(this.tip.remove(),this.tip=null);const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),P.trigger(this._element,this.constructor.eventName("inserted"))),this._popper?this._popper.update():this._popper=this._createPopper(i),i.classList.add(tn),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.on(t,"mouseover",h);this._queueCallback((()=>{P.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(!this._isShown())return;if(P.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented)return;const t=this._getTipElement();if(t.classList.remove(tn),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||t.remove(),this._element.removeAttribute("aria-describedby"),P.trigger(this._element,this.constructor.eventName("hidden")),this._disposePopper())}),this.tip,this._isAnimated())}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(Zi,tn),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(Zi),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Gi({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(Zi)}_isShown(){return this.tip&&this.tip.classList.contains(tn)}_createPopper(t){const e="function"==typeof this._config.placement?this._config.placement.call(this,t,this._element):this._config.placement,i=rn[e.toUpperCase()];return Ve(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)P.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===sn?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===sn?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");P.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?on:sn]=!0,e._enter()})),P.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?on:sn]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},P.on(this._element.closest(en),nn,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=B.getDataAttributes(this._element);for(const t of Object.keys(e))Ji.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=cn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(cn);const hn={...cn.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},dn={...cn.DefaultType,content:"(null|string|element|function)"};class un extends cn{static get Default(){return hn}static get DefaultType(){return dn}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn="click.bs.scrollspy",pn="active",gn="[href]",mn={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},_n={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class bn extends z{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return mn}static get DefaultType(){return _n}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(P.off(this._config.target,fn),P.on(this._config.target,fn,gn,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=Q.find(gn,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=Q.findOne(e.hash,this._element);a(t)&&(this._targetLinks.set(e.hash,e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(pn),this._activateParents(t),P.trigger(this._element,"activate.bs.scrollspy",{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))Q.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(pn);else for(const e of Q.parents(t,".nav, .list-group"))for(const t of Q.prev(e,".nav-link, .nav-item > .nav-link, .list-group-item"))t.classList.add(pn)}_clearActiveClass(t){t.classList.remove(pn);const e=Q.find("[href].active",t);for(const t of e)t.classList.remove(pn)}static jQueryInterface(t){return this.each((function(){const e=bn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(window,"load.bs.scrollspy.data-api",(()=>{for(const t of Q.find('[data-bs-spy="scroll"]'))bn.getOrCreateInstance(t)})),g(bn);const vn="ArrowLeft",yn="ArrowRight",wn="ArrowUp",An="ArrowDown",En="active",Tn="fade",Cn="show",On='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',xn=`.nav-link:not(.dropdown-toggle), .list-group-item:not(.dropdown-toggle), [role="tab"]:not(.dropdown-toggle), ${On}`;class kn extends z{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),P.on(this._element,"keydown.bs.tab",(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?P.trigger(e,"hide.bs.tab",{relatedTarget:t}):null;P.trigger(t,"show.bs.tab",{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(En),this._activate(n(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),P.trigger(t,"shown.bs.tab",{relatedTarget:e})):t.classList.add(Cn)}),t,t.classList.contains(Tn)))}_deactivate(t,e){t&&(t.classList.remove(En),t.blur(),this._deactivate(n(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),P.trigger(t,"hidden.bs.tab",{relatedTarget:e})):t.classList.remove(Cn)}),t,t.classList.contains(Tn)))}_keydown(t){if(![vn,yn,wn,An].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=[yn,An].includes(t.key),i=b(this._getChildren().filter((t=>!l(t))),t.target,e,!0);i&&(i.focus({preventScroll:!0}),kn.getOrCreateInstance(i).show())}_getChildren(){return Q.find(xn,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=n(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`#${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=Q.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",En),n(".dropdown-menu",Cn),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(En)}_getInnerElement(t){return t.matches(xn)?t:Q.findOne(xn,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=kn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(document,"click.bs.tab",On,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||kn.getOrCreateInstance(this).show()})),P.on(window,"load.bs.tab",(()=>{for(const t of Q.find('.active[data-bs-toggle="tab"], .active[data-bs-toggle="pill"], .active[data-bs-toggle="list"]'))kn.getOrCreateInstance(t)})),g(kn);const Ln="hide",Dn="show",Sn="showing",In={animation:"boolean",autohide:"boolean",delay:"number"},Nn={animation:!0,autohide:!0,delay:5e3};class Pn extends z{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return Nn}static get DefaultType(){return In}static get NAME(){return"toast"}show(){P.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Ln),d(this._element),this._element.classList.add(Dn,Sn),this._queueCallback((()=>{this._element.classList.remove(Sn),P.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(P.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(Sn),this._queueCallback((()=>{this._element.classList.add(Ln),this._element.classList.remove(Sn,Dn),P.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(Dn),super.dispose()}isShown(){return this._element.classList.contains(Dn)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){P.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),P.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),P.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),P.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Pn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return q(Pn),g(Pn),{Alert:R,Button:K,Carousel:at,Collapse:pt,Dropdown:hi,Modal:Ni,Offcanvas:zi,Popover:un,ScrollSpy:bn,Tab:kn,Toast:Pn,Tooltip:cn}})); -//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js.map b/examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js.map deleted file mode 100644 index 69926c2..0000000 --- a/examples/make-an-r-package-with-extras/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"names":["TRANSITION_END","getSelector","element","selector","getAttribute","hrefAttribute","includes","startsWith","split","trim","getSelectorFromElement","document","querySelector","getElementFromSelector","triggerTransitionEnd","dispatchEvent","Event","isElement","object","jquery","nodeType","getElement","length","isVisible","getClientRects","elementIsVisible","getComputedStyle","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","window","jQuery","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","push","execute","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","Math","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","findHandler","events","callable","delegationSelector","Object","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","has","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","call","this","handlers","previousFunction","replace","domElements","querySelectorAll","domElement","hydrateObj","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","keys","on","one","inNamespace","isNamespace","elementEvent","slice","keyHandlers","trigger","args","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","key","value","entries","_unused","defineProperty","configurable","get","elementMap","Map","Data","set","instance","instanceMap","size","console","error","Array","from","remove","delete","normalizeData","toString","JSON","parse","decodeURIComponent","normalizeDataKey","chr","toLowerCase","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","prototype","match","RegExp","test","TypeError","toUpperCase","BaseComponent","super","_element","_config","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","static","getInstance","VERSION","enableDismissTrigger","component","method","clickEvent","tagName","getOrCreateInstance","Alert","close","_destroyElement","each","data","undefined","SELECTOR_DATA_TOGGLE","Button","toggle","button","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","map","join","el","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","_eventIsPointerPenTouch","clientX","touches","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLID","CLASS_NAME_CAROUSEL","CLASS_NAME_ACTIVE","KEY_TO_DIRECTION","ArrowLeft","ArrowRight","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","swipeConfig","_directionToOrder","clearTimeout","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","eventName","_orderToDirection","isCycling","directionalClassName","orderClassName","_isAnimated","SELECTOR_ACTIVE","clearInterval","carousel","slideIndex","carousels","CLASS_NAME_SHOW","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","activeInstance","dimension","_getDimension","style","scrollSize","getBoundingClientRect","selected","triggerArray","isOpen","selectorElements","top","bottom","right","left","auto","basePlacements","start","end","clippingParents","viewport","popper","reference","variationPlacements","reduce","acc","placement","placements","beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite","modifierPhases","getNodeName","nodeName","getWindow","node","ownerDocument","defaultView","isHTMLElement","HTMLElement","isShadowRoot","applyStyles$1","enabled","phase","_ref","state","elements","forEach","styles","assign","effect","_ref2","initialStyles","position","options","strategy","margin","arrow","hasOwnProperty","attribute","requires","getBasePlacement","round","getUAString","uaData","userAgentData","brands","item","brand","version","userAgent","isLayoutViewport","includeScale","isFixedStrategy","clientRect","scaleX","scaleY","offsetWidth","width","height","visualViewport","addVisualOffsets","x","offsetLeft","y","offsetTop","getLayoutRect","rootNode","isSameNode","host","isTableElement","getDocumentElement","getParentNode","assignedSlot","getTrueOffsetParent","offsetParent","getOffsetParent","isFirefox","currentNode","css","transform","perspective","contain","willChange","getContainingBlock","getMainAxisFromPlacement","within","mathMax","mathMin","mergePaddingObject","paddingObject","expandToHashMap","hashMap","arrow$1","_state$modifiersData$","arrowElement","popperOffsets","modifiersData","basePlacement","axis","len","padding","rects","toPaddingObject","arrowRect","minProp","maxProp","endDiff","startDiff","arrowOffsetParent","clientSize","clientHeight","clientWidth","centerToReference","center","offset","axisProp","centerOffset","_options$element","requiresIfExists","getVariation","unsetSides","mapToStyles","_Object$assign2","popperRect","variation","offsets","gpuAcceleration","adaptive","roundOffsets","isFixed","_offsets$x","_offsets$y","_ref3","hasX","hasY","sideX","sideY","win","heightProp","widthProp","_Object$assign","commonStyles","_ref4","dpr","devicePixelRatio","roundOffsetsByDPR","computeStyles$1","_ref5","_options$gpuAccelerat","_options$adaptive","_options$roundOffsets","passive","eventListeners","_options$scroll","scroll","_options$resize","resize","scrollParents","scrollParent","update","hash","getOppositePlacement","matched","getOppositeVariationPlacement","getWindowScroll","scrollLeft","pageXOffset","scrollTop","pageYOffset","getWindowScrollBarX","isScrollParent","_getComputedStyle","overflow","overflowX","overflowY","getScrollParent","listScrollParents","_element$ownerDocumen","isBody","updatedList","rectToClientRect","rect","getClientRectFromMixedType","clippingParent","html","layoutViewport","getViewportRect","clientTop","clientLeft","getInnerBoundingClientRect","winScroll","scrollWidth","scrollHeight","getDocumentRect","computeOffsets","commonX","commonY","mainAxis","detectOverflow","_options","_options$placement","_options$strategy","_options$boundary","boundary","_options$rootBoundary","rootBoundary","_options$elementConte","elementContext","_options$altBoundary","altBoundary","_options$padding","altContext","clippingClientRect","mainClippingParents","clipperElement","getClippingParents","firstClippingParent","clippingRect","accRect","getClippingRect","contextElement","referenceClientRect","popperClientRect","elementClientRect","overflowOffsets","offsetData","multiply","computeAutoPlacement","flipVariations","_options$allowedAutoP","allowedAutoPlacements","allPlacements","allowedPlacements","overflows","sort","a","b","flip$1","_skip","_options$mainAxis","checkMainAxis","_options$altAxis","altAxis","checkAltAxis","specifiedFallbackPlacements","fallbackPlacements","_options$flipVariatio","preferredPlacement","oppositePlacement","getExpandedFallbackPlacements","referenceRect","checksMap","makeFallbackChecks","firstFittingPlacement","i","_basePlacement","isStartVariation","isVertical","mainVariationSide","altVariationSide","checks","every","check","_loop","_i","fittingPlacement","reset","getSideOffsets","preventedOffsets","isAnySideFullyClipped","some","side","hide$1","preventOverflow","referenceOverflow","popperAltOverflow","referenceClippingOffsets","popperEscapeOffsets","isReferenceHidden","hasPopperEscaped","offset$1","_options$offset","invertDistance","skidding","distance","distanceAndSkiddingToXY","_data$state$placement","popperOffsets$1","preventOverflow$1","_options$tether","tether","_options$tetherOffset","tetherOffset","isBasePlacement","tetherOffsetValue","normalizedTetherOffsetValue","offsetModifierState","_offsetModifierState$","mainSide","altSide","additive","minLen","maxLen","arrowPaddingObject","arrowPaddingMin","arrowPaddingMax","arrowLen","minOffset","maxOffset","clientOffset","offsetModifierValue","tetherMax","preventedOffset","_offsetModifierState$2","_mainSide","_altSide","_offset","_len","_min","_max","isOriginSide","_offsetModifierValue","_tetherMin","_tetherMax","_preventedOffset","v","withinMaxClamp","getCompositeRect","elementOrVirtualElement","isOffsetParentAnElement","offsetParentIsScaled","isElementScaled","modifiers","visited","result","modifier","dep","depModifier","DEFAULT_OPTIONS","areValidElements","arguments","_key","popperGenerator","generatorOptions","_generatorOptions","_generatorOptions$def","defaultModifiers","_generatorOptions$def2","defaultOptions","pending","orderedModifiers","effectCleanupFns","isDestroyed","setOptions","setOptionsAction","cleanupModifierEffects","merged","orderModifiers","current","existing","m","_ref3$options","cleanupFn","forceUpdate","_state$elements","_state$orderedModifie","_state$orderedModifie2","Promise","resolve","then","destroy","onFirstUpdate","createPopper","computeStyles","applyStyles","flip","ARROW_UP_KEY","ARROW_DOWN_KEY","EVENT_CLICK_DATA_API","EVENT_KEYDOWN_DATA_API","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","autoClose","display","popperConfig","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","_createPopper","focus","_completeHide","Popper","referenceElement","_getPopperConfig","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","_selectMenuItem","openToggles","context","composedPath","isMenuTarget","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","dataApiKeydownHandler","clearMenus","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","innerWidth","_disableOverFlow","_setElementAttributes","calculatedValue","_resetElementAttributes","isOverflowing","_saveInitialAttribute","styleProperty","scrollbarWidth","_applyManipulationCallback","setProperty","actualValue","removeProperty","callBack","sel","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","shiftKey","EVENT_HIDDEN","EVENT_SHOW","CLASS_NAME_OPEN","CLASS_NAME_STATIC","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","htmlElement","handleUpdate","modalBody","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","initialOverflowY","isBodyOverflowing","paddingLeft","paddingRight","showEvent","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","OPEN_SELECTOR","EVENT_HIDE_PREVENTED","Offcanvas","blur","uriAttributes","SAFE_URL_PATTERN","DATA_URL_PATTERN","allowedAttribute","allowedAttributeList","attributeName","nodeValue","attributeRegex","regex","DefaultAllowlist","area","br","col","code","div","em","hr","h1","h2","h3","h4","h5","h6","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","allowList","content","extraClass","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","innerHTML","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","unsafeHtml","sanitizeFunction","createdDocument","DOMParser","parseFromString","elementName","attributeList","allowedAttributes","sanitizeHtml","DISALLOWED_ATTRIBUTES","CLASS_NAME_FADE","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","click","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","isInTheDom","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","prefix","floor","random","getElementById","getUID","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","Popover","_getContent","EVENT_CLICK","SELECTOR_TARGET_LINKS","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","scrollTo","behavior","IntersectionObserver","_observerCallback","targetElement","id","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","_activateParents","listGroup","activeNodes","spy","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","SELECTOR_INNER_ELEM","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","hideEvent","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"sources":["../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/data.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/dom/selector-engine.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../node_modules/@popperjs/core/lib/enums.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../node_modules/@popperjs/core/lib/modifiers/applyStyles.js","../../node_modules/@popperjs/core/lib/utils/getBasePlacement.js","../../node_modules/@popperjs/core/lib/utils/math.js","../../node_modules/@popperjs/core/lib/utils/userAgent.js","../../node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js","../../node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","../../node_modules/@popperjs/core/lib/dom-utils/contains.js","../../node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","../../node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","../../node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","../../node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","../../node_modules/@popperjs/core/lib/utils/within.js","../../node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","../../node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","../../node_modules/@popperjs/core/lib/utils/expandToHashMap.js","../../node_modules/@popperjs/core/lib/modifiers/arrow.js","../../node_modules/@popperjs/core/lib/utils/getVariation.js","../../node_modules/@popperjs/core/lib/modifiers/computeStyles.js","../../node_modules/@popperjs/core/lib/modifiers/eventListeners.js","../../node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","../../node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","../../node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","../../node_modules/@popperjs/core/lib/utils/rectToClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","../../node_modules/@popperjs/core/lib/utils/computeOffsets.js","../../node_modules/@popperjs/core/lib/utils/detectOverflow.js","../../node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","../../node_modules/@popperjs/core/lib/modifiers/flip.js","../../node_modules/@popperjs/core/lib/modifiers/hide.js","../../node_modules/@popperjs/core/lib/modifiers/offset.js","../../node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","../../node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","../../node_modules/@popperjs/core/lib/utils/getAltAxis.js","../../node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","../../node_modules/@popperjs/core/lib/utils/orderModifiers.js","../../node_modules/@popperjs/core/lib/createPopper.js","../../node_modules/@popperjs/core/lib/utils/debounce.js","../../node_modules/@popperjs/core/lib/utils/mergeByName.js","../../node_modules/@popperjs/core/lib/popper-lite.js","../../node_modules/@popperjs/core/lib/popper.js","../../js/src/dropdown.js","../../js/src/util/scrollbar.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return selector\n}\n\nconst getSelectorFromElement = element => {\n const selector = getSelector(element)\n\n if (selector) {\n return document.querySelector(selector) ? selector : null\n }\n\n return null\n}\n\nconst getElementFromSelector = element => {\n const selector = getSelector(element)\n\n return selector ? document.querySelector(selector) : null\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(object)\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = callback => {\n if (typeof callback === 'function') {\n callback()\n }\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getElementFromSelector,\n getjQuery,\n getNextActiveElement,\n getSelectorFromElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // todo: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const handlerKey of Object.keys(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n const event = storeElementEvent[handlerKey]\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const keyHandlers of Object.keys(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n const event = storeElementEvent[keyHandlers]\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n let evt = new Event(event, { bubbles, cancelable: true })\n evt = hydrateObj(evt, args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta) {\n for (const [key, value] of Object.entries(meta || {})) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isElement, toType } from './index'\nimport Manipulator from '../dom/manipulator'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const property of Object.keys(configTypes)) {\n const expectedTypes = configTypes[property]\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data'\nimport { executeAfterTransition, getElement } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Config from './util/config'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.2.2'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport { getElementFromSelector, isDisabled } from './index'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\nimport { enableDismissTrigger } from './util/component-functions'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible } from '../util/index'\n\n/**\n * Constants\n */\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Config from './config'\nimport EventHandler from '../dom/event-handler'\nimport { execute } from './index'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElementFromSelector,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport Swipe from './util/swipe'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // todo: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElement,\n getElementFromSelector,\n getSelectorFromElement,\n reflow\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n const selector = getSelectorFromElement(this)\n const selectorElements = SelectorEngine.find(selector)\n\n for (const element of selectorElements) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","export default function getUAString() {\n var uaData = navigator.userAgentData;\n\n if (uaData != null && uaData.brands) {\n return uaData.brands.map(function (item) {\n return item.brand + \"/\" + item.version;\n }).join(' ');\n }\n\n return navigator.userAgent;\n}","import getUAString from \"../utils/userAgent.js\";\nexport default function isLayoutViewport() {\n return !/^((?!chrome|android).)*safari/i.test(getUAString());\n}","import { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nimport getWindow from \"./getWindow.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n\n var clientRect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (includeScale && isHTMLElement(element)) {\n scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n }\n\n var _ref = isElement(element) ? getWindow(element) : window,\n visualViewport = _ref.visualViewport;\n\n var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n var width = clientRect.width / scaleX;\n var height = clientRect.height / scaleY;\n return {\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: y + height,\n left: x,\n x: x,\n y: y\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getUAString from \"../utils/userAgent.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = /firefox/i.test(getUAString());\n var isIE = /Trident/i.test(getUAString());\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n if (!isHTMLElement(arrowElement)) {\n console.error(['Popper: \"arrow\" element must be an HTMLElement (not an SVGElement).', 'To use an SVG arrow, wrap it in an HTMLElement that will be used as', 'the arrow.'].join(' '));\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(['Popper: \"arrow\" modifier\\'s `element` must be a child of the popper', 'element.'].join(' '));\n }\n\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref) {\n var x = _ref.x,\n y = _ref.y;\n var win = window;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n\n if (process.env.NODE_ENV !== \"production\") {\n var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';\n\n if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {\n return transitionProperty.indexOf(property) >= 0;\n })) {\n console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: \"transform\", \"top\", \"right\", \"bottom\", \"left\".', '\\n\\n', 'Disable the \"computeStyles\" modifier\\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\\n\\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));\n }\n }\n\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element, strategy) {\n var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent, strategy) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary, strategy) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getViewportRect(element, strategy) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0;\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n var layoutViewport = isLayoutViewport();\n\n if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$strategy = _options.strategy,\n strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.error(['Popper: The `allowedAutoPlacements` option did not allow any', 'placements. Ensure the `placement` option matches the variation', 'of the allowed placements.', 'For example, \"auto\" cannot be used to allow \"bottom-start\".', 'Use \"auto-start\" instead.'].join(' '));\n }\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport getComputedStyle from \"./dom-utils/getComputedStyle.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport validateModifiers from \"./utils/validateModifiers.js\";\nimport uniqueBy from \"./utils/uniqueBy.js\";\nimport getBasePlacement from \"./utils/getBasePlacement.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nimport { auto } from \"./enums.js\";\nvar INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';\nvar INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n }); // Validate the provided modifiers so that the consumer will get warned\n // if one of the modifiers is invalid for any reason\n\n if (process.env.NODE_ENV !== \"production\") {\n var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {\n var name = _ref.name;\n return name;\n });\n validateModifiers(modifiers);\n\n if (getBasePlacement(state.options.placement) === auto) {\n var flipModifier = state.orderedModifiers.find(function (_ref2) {\n var name = _ref2.name;\n return name === 'flip';\n });\n\n if (!flipModifier) {\n console.error(['Popper: \"auto\" placements require the \"flip\" modifier be', 'present and enabled to work.'].join(' '));\n }\n }\n\n var _getComputedStyle = getComputedStyle(popper),\n marginTop = _getComputedStyle.marginTop,\n marginRight = _getComputedStyle.marginRight,\n marginBottom = _getComputedStyle.marginBottom,\n marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can\n // cause bugs with positioning, so we'll warn the consumer\n\n\n if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {\n return parseFloat(margin);\n })) {\n console.warn(['Popper: CSS \"margin\" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));\n }\n }\n\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(INVALID_ELEMENT_ERROR);\n }\n\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n var __debug_loops__ = 0;\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (process.env.NODE_ENV !== \"production\") {\n __debug_loops__ += 1;\n\n if (__debug_loops__ > 100) {\n console.error(INFINITE_LOOP_ERROR);\n break;\n }\n }\n\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(INVALID_ELEMENT_ERROR);\n }\n\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref3) {\n var name = _ref3.name,\n _ref3$options = _ref3.options,\n options = _ref3$options === void 0 ? {} : _ref3$options,\n effect = _ref3.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport {\n defineJQueryPlugin,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // todo:v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine'\nimport Manipulator from '../dom/manipulator'\nimport { isElement } from './index'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport { execute, executeAfterTransition, getElement, reflow } from './index'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport SelectorEngine from '../dom/selector-engine'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport SelectorEngine from './dom/selector-engine'\nimport ScrollBarHelper from './util/scrollbar'\nimport BaseComponent from './base-component'\nimport Backdrop from './util/backdrop'\nimport FocusTrap from './util/focustrap'\nimport { enableDismissTrigger } from './util/component-functions'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n for (const htmlElement of [window, this._dialog]) {\n EventHandler.off(htmlElement, EVENT_KEY)\n }\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n event.preventDefault()\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElementFromSelector,\n isDisabled,\n isVisible\n} from './util/index'\nimport ScrollBarHelper from './util/scrollbar'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\nimport SelectorEngine from './dom/selector-engine'\nimport Backdrop from './util/backdrop'\nimport FocusTrap from './util/focustrap'\nimport { enableDismissTrigger } from './util/component-functions'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (!this._config.keyboard) {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\n/**\n * A pattern that recognizes a commonly useful subset of URLs that are safe.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n */\nconst SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i\n\n/**\n * A pattern that matches safe data URLs. Only matches image, video and audio types.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n */\nconst DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[\\d+/a-z]+=*$/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n div: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer'\nimport { getElement, isElement } from '../util/index'\nimport SelectorEngine from '../dom/selector-engine'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '
'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return typeof arg === 'function' ? arg(this) : arg\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport { defineJQueryPlugin, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index'\nimport { DefaultAllowlist } from './util/sanitizer'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport BaseComponent from './base-component'\nimport TemplateFactory from './util/template-factory'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 0],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '
' +\n '
' +\n '
' +\n '
',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n this._activeTrigger.click = !this._activeTrigger.click\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this.tip) {\n this.tip.remove()\n }\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // todo v6 remove this OR make it optional\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n if (this._popper) {\n this._popper.update()\n } else {\n this._popper = this._createPopper(tip)\n }\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n tip.remove()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n\n this._disposePopper()\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // todo: remove this check on v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // todo: on v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = typeof this._config.placement === 'function' ?\n this._config.placement.call(this, tip, this._element) :\n this._config.placement\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return typeof arg === 'function' ? arg.call(this._element) : arg\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const key in this._config) {\n if (this.constructor.Default[key] !== this._config[key]) {\n config[key] = this._config[key]\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport Tooltip from './tooltip'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '
' +\n '
' +\n '

' +\n '
' +\n '
',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(anchor.hash, this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(anchor.hash, anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both

Add some package-level documentation if you want to (or delete the chunk entirely). This is what will show up when someone types package?withrcpp in the console.

-
#' My great package
-#'
-#' Here's an overview of the package.
-#' 
-#' @docType package
-#' @seealso \code{\link{alternate_signs}}
+
#' My great package
+#'
+#' Here's an overview of the package.
+#' 
+#' @docType package
+#' @seealso \code{\link{alternate_signs}}

Now to the package itself

@@ -398,16 +363,16 @@

Define a function

chunk starting with {R, it instead starts with {Rcpp. The code in this chunk will be placed in the proper place in the R package. Use Roxygen2 to document this as you would an R -function, except instead of #' at the start of each line, -use //'. Including the line +function, except instead of #' at the start of each line, +use //'. Including the line // [[Rcpp::export]] is important for this to work.

#include <Rcpp.h>
 using namespace Rcpp;
 
-//' Alternately flip the signs of elements of a vector
-//' 
-//' @param v A numerical vector
-//' @export
+//' Alternately flip the signs of elements of a vector
+//' 
+//' @param v A numerical vector
+//' @export
 // [[Rcpp::export]]
 NumericVector alternate_signs(NumericVector v) {
   int n = v.size();
@@ -431,7 +396,7 @@ 

Define a function

package).

Let’s write some tests to make sure the function behaves as desired:

-
testthat::test_that("alternate_signs works", {
+
testthat::test_that("alternate_signs works", {
   testthat::expect_equal(alternate_signs(1:3), c(1, -2, 3))
   testthat::expect_equal(alternate_signs(c(-4, 2)), c(-4, -2))
 })
@@ -452,29 +417,26 @@

Documenting the package and building

# devtools::check(document = FALSE)
## ℹ Updating withrcpp documentation
 ## ℹ Loading withrcpp
-
## Exports from /Users/jacobbien/Documents/GitHub/litr-project/examples/withrcpp/src/code.cpp:
+
## Exports from /Users/vossler/litr-project/examples/withrcpp/src/code.cpp:
 ##    NumericVector alternate_signs(NumericVector v)
 ## 
-## /Users/jacobbien/Documents/GitHub/litr-project/examples/withrcpp/src/RcppExports.cpp updated.
-## /Users/jacobbien/Documents/GitHub/litr-project/examples/withrcpp/R/RcppExports.R updated.
+## /Users/vossler/litr-project/examples/withrcpp/src/RcppExports.cpp updated. +## /Users/vossler/litr-project/examples/withrcpp/R/RcppExports.R updated.
## ℹ Re-compiling withrcpp (debug build)
## ── R CMD INSTALL ───────────────────────────────────────────────────────────────
 ## * installing *source* package ‘withrcpp’ ...
 ## ** using staged installation
 ## ** libs
-## using C++ compiler: ‘Apple clang version 14.0.0 (clang-1400.0.29.202)’
-## using SDK: ‘’
-## clang++ -arch arm64 -std=gnu++17 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I'/Users/jacobbien/Library/R/arm64/4.3/library/Rcpp/include' -I/opt/R/arm64/include    -fPIC  -falign-functions=64 -Wall -g -O2  -UNDEBUG -Wall -pedantic -g -O0 -c RcppExports.cpp -o RcppExports.o
-## clang++ -arch arm64 -std=gnu++17 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I'/Users/jacobbien/Library/R/arm64/4.3/library/Rcpp/include' -I/opt/R/arm64/include    -fPIC  -falign-functions=64 -Wall -g -O2  -UNDEBUG -Wall -pedantic -g -O0 -c code.cpp -o code.o
-## clang++ -arch arm64 -std=gnu++17 -dynamiclib -Wl,-headerpad_max_install_names -undefined dynamic_lookup -single_module -multiply_defined suppress -L/Library/Frameworks/R.framework/Resources/lib -L/opt/R/arm64/lib -o withrcpp.so RcppExports.o code.o -F/Library/Frameworks/R.framework/.. -framework R -Wl,-framework -Wl,CoreFoundation
-## ld: warning: -undefined dynamic_lookup may not work with chained fixups
-## installing to /private/var/folders/wk/ysb32prj0gqdt1q08l3cwxlm0000gp/T/RtmpR3PX9F/devtools_install_2bde11469d16/00LOCK-withrcpp/00new/withrcpp/libs
+## clang++ -arch arm64 -std=gnu++14 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I'/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library/Rcpp/include' -I/opt/R/arm64/include   -fPIC  -falign-functions=64 -Wall -g -O2  -DSTAN_THREADS -pthread -UNDEBUG -Wall -pedantic -g -O0 -c RcppExports.cpp -o RcppExports.o
+## clang++ -arch arm64 -std=gnu++14 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I'/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library/Rcpp/include' -I/opt/R/arm64/include   -fPIC  -falign-functions=64 -Wall -g -O2  -DSTAN_THREADS -pthread -UNDEBUG -Wall -pedantic -g -O0 -c code.cpp -o code.o
+## clang++ -arch arm64 -std=gnu++14 -dynamiclib -Wl,-headerpad_max_install_names -undefined dynamic_lookup -single_module -multiply_defined suppress -L/Library/Frameworks/R.framework/Resources/lib -L/opt/R/arm64/lib -o withrcpp.so RcppExports.o code.o -F/Library/Frameworks/R.framework/.. -framework R -Wl,-framework -Wl,CoreFoundation
+## installing to /private/var/folders/x8/5qdk28wd0lj649bpd3k64c9w0000gp/T/Rtmp4mM0RJ/devtools_install_16c676c1fc8f/00LOCK-withrcpp/00new/withrcpp/libs
 ## ** checking absolute paths in shared objects and dynamic libraries
 ## * DONE (withrcpp)
-
## Writing 'NAMESPACE'
-## Writing 'NAMESPACE'
-## Writing 'alternate_signs.Rd'
-## Writing 'withrcpp-package.Rd'
+
## Writing 'NAMESPACE'
+## Writing 'NAMESPACE'
+## Writing 'alternate_signs.Rd'
+## Writing 'withrcpp-package.Rd'
@@ -493,11 +455,7 @@

Documenting the package and building

}); - - - - - - - - - - - - diff --git a/examples/make-an-r-package-with-rcpp/withrcpp/DESCRIPTION b/examples/make-an-r-package-with-rcpp/withrcpp/DESCRIPTION index 462c81c..8ad5f3e 100644 --- a/examples/make-an-r-package-with-rcpp/withrcpp/DESCRIPTION +++ b/examples/make-an-r-package-with-rcpp/withrcpp/DESCRIPTION @@ -16,4 +16,4 @@ Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 LitrVersionUsed: 0.9.1 -LitrId: 5858afc198042c5c5dbe4c805c1dd526 +LitrId: 7d3722abb053943f0ff4eaddeba00c1e diff --git a/examples/make-an-r-package-with-rcpp/withrcpp/LICENSE b/examples/make-an-r-package-with-rcpp/withrcpp/LICENSE index 2bb6bed..9be16ff 100644 --- a/examples/make-an-r-package-with-rcpp/withrcpp/LICENSE +++ b/examples/make-an-r-package-with-rcpp/withrcpp/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2023 +YEAR: 2024 COPYRIGHT HOLDER: F. Last diff --git a/examples/make-an-r-package-with-rcpp/withrcpp/LICENSE.md b/examples/make-an-r-package-with-rcpp/withrcpp/LICENSE.md index bde1333..b67cbfc 100644 --- a/examples/make-an-r-package-with-rcpp/withrcpp/LICENSE.md +++ b/examples/make-an-r-package-with-rcpp/withrcpp/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2023 F. Last +Copyright (c) 2024 F. Last Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/make-an-r-package/create-rhello.html b/examples/make-an-r-package/create-rhello.html index da3bdc0..6731a8e 100644 --- a/examples/make-an-r-package/create-rhello.html +++ b/examples/make-an-r-package/create-rhello.html @@ -1,19 +1,12 @@ - - - - - - - - - - - + + + + + Creating the rhello R package - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - @@ -364,47 +329,47 @@

Package setup

We start by specifying the information needed in the DESCRIPTION file of the R package.

usethis::create_package(
-  path = ".",
+  path = ".",
   fields = list(
     Package = params$package_name,
-    Version = "0.0.0.9000",
-    Title = "A Package That Says Hello",
-    Description = "This package says hello.  But its actual purpose is to show how an R package can be completely coded in a single R markdown file.",
+    Version = "0.0.0.9000",
+    Title = "A Package That Says Hello",
+    Description = "This package says hello.  But its actual purpose is to show how an R package can be completely coded in a single R markdown file.",
     `Authors@R` = person(
-      given = "First",
-      family = "Last",
-      email = "you@gmail.com",
-      role = c("aut", "cre")
+      given = "First",
+      family = "Last",
+      email = "you@gmail.com",
+      role = c("aut", "cre")
       )
   )
 )
-usethis::use_mit_license(copyright_holder = "F. Last")
+usethis::use_mit_license(copyright_holder = "F. Last")

Now to the package itself

Define a function

Let’s define a function for our R package:

-
#' Say hello to someone
-#' 
-#' @param name Name of a person
-#' @param exclamation Whether to include an exclamation mark
-#' @export 
-say_hello <- function(name, exclamation = TRUE) {
-  paste0("Hello ", name, ifelse(exclamation, "!", "."))
+
#' Say hello to someone
+#' 
+#' @param name Name of a person
+#' @param exclamation Whether to include an exclamation mark
+#' @export 
+say_hello <- function(name, exclamation = TRUE) {
+  paste0("Hello ", name, ifelse(exclamation, "!", "."))
 }
-

Code chunks whose first line starts with #' are added to +

Code chunks whose first line starts with #' are added to the package.

We can try running it.

-
say_hello("Jacob")
-
## [1] "Hello Jacob!"
-

That code chunk does not start with #', so it is not +

say_hello("Jacob")
+
## [1] "Hello Jacob!"
+

That code chunk does not start with #', so it is not added to the package.

Let’s write some tests to make sure the function behaves as desired:

-
testthat::test_that("say_hello works", {
-  testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!")
-  testthat::expect_equal(say_hello("Jacob", exclamation = FALSE), "Hello Jacob.")
+
testthat::test_that("say_hello works", {
+  testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!")
+  testthat::expect_equal(say_hello("Jacob", exclamation = FALSE), "Hello Jacob.")
 })
## Test passed

Code chunks that have one or more lines starting with @@ -423,8 +388,8 @@

Documenting the package and building

# devtools::check(document = FALSE)
## ℹ Updating rhello documentation
 ## ℹ Loading rhello
-## Writing 'NAMESPACE'
-## Writing 'say_hello.Rd'
+## Writing 'NAMESPACE' +## Writing 'say_hello.Rd'
@@ -443,11 +408,7 @@

Documenting the package and building

}); - - - - - - - - - - - - diff --git a/examples/make-an-r-package/rhello/DESCRIPTION b/examples/make-an-r-package/rhello/DESCRIPTION index 097cda9..c2a3e2f 100644 --- a/examples/make-an-r-package/rhello/DESCRIPTION +++ b/examples/make-an-r-package/rhello/DESCRIPTION @@ -13,4 +13,4 @@ Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 LitrVersionUsed: 0.9.1 -LitrId: 06ac511b723fee1ee04ce7b9829bd87c +LitrId: e58fee9a91f4c05182af3a6cbe4b9bc5 diff --git a/examples/make-an-r-package/rhello/LICENSE b/examples/make-an-r-package/rhello/LICENSE index 2bb6bed..9be16ff 100644 --- a/examples/make-an-r-package/rhello/LICENSE +++ b/examples/make-an-r-package/rhello/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2023 +YEAR: 2024 COPYRIGHT HOLDER: F. Last diff --git a/examples/make-an-r-package/rhello/LICENSE.md b/examples/make-an-r-package/rhello/LICENSE.md index bde1333..b67cbfc 100644 --- a/examples/make-an-r-package/rhello/LICENSE.md +++ b/examples/make-an-r-package/rhello/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2023 F. Last +Copyright (c) 2024 F. Last Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/litr/DESCRIPTION b/litr/DESCRIPTION index b7b73d6..942f629 100644 --- a/litr/DESCRIPTION +++ b/litr/DESCRIPTION @@ -25,6 +25,7 @@ Imports: stringr, usethis, xfun, + xml2, yaml Suggests: dplyr, @@ -34,4 +35,4 @@ Config/testthat/edition: 3 VignetteBuilder: knitr URL: https://github.com/jacobbien/litr-project/tree/main/litr LitrVersionUsed: 0.9.1 -LitrId: 1883c8af5554c5f6c61b6f9ca3a8c8b3 +LitrId: 35636597ca8bb590fd7b0088c6865899 diff --git a/litr/LICENSE b/litr/LICENSE index 81a81ef..c69b1b4 100644 --- a/litr/LICENSE +++ b/litr/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2023 +YEAR: 2024 COPYRIGHT HOLDER: J. Bien diff --git a/litr/LICENSE.md b/litr/LICENSE.md index 8f3f298..13abae1 100644 --- a/litr/LICENSE.md +++ b/litr/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2023 J. Bien +Copyright (c) 2024 J. Bien Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/litr/R/render.R b/litr/R/render.R index 6526653..2a33267 100644 --- a/litr/R/render.R +++ b/litr/R/render.R @@ -294,12 +294,12 @@ litr_html_document <- function(minimal_eval = FALSE, ...) { # modify post_processor new$post_processor = function(metadata, input_file, output_file, ...) { out <- old$post_processor(metadata, input_file, output_file, ...) - html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$") + # html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$") # add hyperlinks within html output to make it easier to navigate: - add_function_hyperlinks(html_files, metadata$params$package_name) - add_chunk_label_hyperlinks(html_files) + add_function_hyperlinks(output_file, metadata$params$package_name) + add_chunk_label_hyperlinks(output_file) # replace ANSI sequences with HTML tag equivalents - replace_ansi_sequences(html_files) + replace_ansi_sequences(output_file) out } new @@ -327,7 +327,10 @@ litr_gitbook <- function(minimal_eval = FALSE, ...) { # modify post_processor new$post_processor = function(metadata, input_file, output_file, ...) { out <- old$post_processor(metadata, input_file, output_file, ...) - html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$") + out_dir <- fs::path_dir(out) + file_stems <- readLines(file.path(out_dir, "reference-keys.txt")) + html_files <- file.path(out_dir, paste0(file_stems, ".html")) + html_files <- unique(intersect(c(out, html_files), fs::dir_ls(out_dir))) # add hyperlinks within html output to make it easier to navigate: add_function_hyperlinks(html_files, metadata$params$package_name) add_chunk_label_hyperlinks(html_files) @@ -533,8 +536,10 @@ add_chunk_label_hyperlinks <- function(html_files, pattern, stringr::str_glue("###"\\1"###") ) - # and keep track of it for later: - chunk_names <- c(chunk_names, chunk_name) + # and keep track of it for later. + # we're using setNames here to make sure that we keep the name of file + # where the chunk name is defined + chunk_names <- setNames(c(chunk_names, chunk_name), c(names(chunk_names), html_file)) } list(chunk_names = chunk_names, txt = txt) } @@ -594,7 +599,41 @@ add_chunk_label_hyperlinks <- function(html_files, } ) - writeLines(txt, con = html_files[i]) + parsed_html <- xml2::read_html(paste(txt,collapse="\n")) + # get all possible chunk names in this file. + chunk_names <- all_chunk_names[which(names(all_chunk_names) == html_files[i])] + + if(length(chunk_names) > 0){ + for(j in seq_along(chunk_names)){ + span_node <- xml2::xml_find_first(parsed_html, stringr::str_glue('(.//span[@id="{chunk_names[j]}"])')) + span_node_path <- stringr::str_split(xml2::xml_path(span_node),"/") + + pre_path <- paste(span_node_path[[1]][1:(max(which(stringr::str_detect(span_node_path[[1]], "pre"))))],collapse="/") + if(nchar(pre_path) == 0){ + next() + } + pre_parent <- xml2::xml_find_first(parsed_html, pre_path) + if(is.na(pre_parent)){ + next() + } + xml2::xml_add_parent(pre_parent + , xml2::read_xml(stringr::str_glue('
'))) + xml2::xml_add_sibling(pre_parent, xml2::read_xml(stringr::str_glue('{chunk_names[j]}')), where="before") + # xml2::xml_add_parent(pre_parent, xml2::read_xml(stringr::str_glue('
{chunk_names[j]}
'))) + xml2::xml_remove(span_node) + # remove the extra line break that is left over from removing the span + code_node <- xml2::xml_child(pre_parent) + changed_txt <- stringr::str_remove(paste(as.character(xml2::xml_contents(code_node)),collapse=""), '\n') + xml2::xml_replace(code_node, xml2::read_xml(stringr::str_glue('{changed_txt}'))) + } + } + # last thing is to insert an additional style node in the head with our CSS so we have a standard style whether we are using bookdown or Rmarkdown + css_string <- "fieldset.chunkfield {border:4px solid black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em} + legend.chunklegend {padding:0;font-size:21px;width:auto;border:0; border-bottom: none; margin-bottom:0} + " + head_node <- xml2::xml_find_first(parsed_html, ".//head") + xml2::xml_add_child(head_node, xml2::read_xml(stringr::str_glue(""))) + txt <- xml2::write_html(parsed_html, html_files[i]) } } diff --git a/litr/README.md b/litr/README.md index 1d60eff..8bf62e3 100644 --- a/litr/README.md +++ b/litr/README.md @@ -12,12 +12,8 @@ The `litr` R package lets you write a complete R package in a single R markdown document. This enables a workflow for writing R packages that is probably very different from what you are used to. -
- - -
+![*With litr, knitting creates an R package in addition to the .html +file.*](man/figures/diagram3.png) ## Why write R packages in this way? diff --git a/litr/pkgdown/favicon/apple-touch-icon-120x120.png b/litr/pkgdown/favicon/apple-touch-icon-120x120.png index b8f3cff3fd486c34b324c811f5207fa8270a223a..4689f32ac061c5db08094b55ccaee9f7683759f8 100644 GIT binary patch delta 101 zcmZ4KveIQj1=kBk77?-Kc18=KxLcpK{)8t57sh8S8{nHX4^nrj;vSQ!{xaBaBA lz`&qd;u=wsl30>zm7AZEnO4bQWMG6{!$Jv*+{wEX^8q)M9mW6v diff --git a/litr/pkgdown/favicon/apple-touch-icon-152x152.png b/litr/pkgdown/favicon/apple-touch-icon-152x152.png index a322fe00c9162bb64d246683862d9546222972d8..36cc6569277c54ff40069368cdc03f38d5889c6f 100644 GIT binary patch delta 101 zcmewo^d)FQ1=kBk77?-Kc18=EXNyp44Y4RnnRLkumfObo0{&9w~-tPBh;xHep5 lU|>)!ag8WRNi0dV%FR#7OsixtGBCofVWEUY?qpWYd;mj#9UcGx diff --git a/litr/pkgdown/favicon/apple-touch-icon-180x180.png b/litr/pkgdown/favicon/apple-touch-icon-180x180.png index 415515a0bbd55fe0b33c80f83f5229f3fe440cda..55799f6b5f257d343ba401801a9d1328b4777b5d 100644 GIT binary patch delta 101 zcmaEs{49Aw1=kBk77g)9FqV5 delta 101 zcmaEs{49Aw1=n*fQ6UYhJVCn&8=F=ccpK{)8t57sh8S8{nHX4^T4);>SQ!|o@qVyi lU|>)!ag8WRNi0dV%FR#7OsixtGBCofAw0mNWpcS;J^&PX9LfLy diff --git a/litr/pkgdown/favicon/apple-touch-icon-60x60.png b/litr/pkgdown/favicon/apple-touch-icon-60x60.png index 5b53766abd2b8704dcafa46104119025f00ebda2..d524f5bf3e3325da3a1592a53bd21b90345feecc 100644 GIT binary patch delta 83 zcmew$|3Q921=kBk77mFw`|L2{AITGB&p|GSfCNure@s;k{`; m0|SFRc?MtW?ChKk%19*4Ku~Km?xhR&IbTqTOGgv delta 101 zcmdn3v0Gz81=n*fQ6UYB2Ko3s8=L+Mc^m5*8t57sh8S8{nHX4^nrRytSQ!|U8Fi;J lFfgc=xJHzuB$lLF<>sekrd2W+85m*Lz@Be>bMhJCd;kbZ9UlMy diff --git a/litr/pkgdown/favicon/apple-touch-icon.png b/litr/pkgdown/favicon/apple-touch-icon.png index 055f0fb09917f030642298d12e8dc7ed9a8a1204..95b6b56efc0801b4a95f12ae9a5b79daddf56a72 100644 GIT binary patch delta 101 zcmaEs{49Aw1=kBk77zm7AZEnO4bQWMG6{!@|#ceUr-#^8rDb9%BFi delta 101 zcmaEs{49Aw1=n*fQ6UX$NijaQjZLczyp44Y4RnnRLkumfObo0{Ewv2{tPBhqs($w{ lFfgc=xJHzuB$lLF<>sekrd2W+85m*L@KN2_U~;)(J^=lJ9RUCU diff --git a/litr/pkgdown/favicon/favicon-16x16.png b/litr/pkgdown/favicon/favicon-16x16.png index 8634fa9f86581690338892433ac40703b2bed089..883c7d354262f0ecea366afce60c08dadacfbdec 100644 GIT binary patch delta 84 zcmaFQ@t$Kt7ZcYDMivoORU65sC7Y)(DKbi#=o%R68kmF_8CV&c1Ch3Yft7(l)3lva SC!c2)$07asG^6GumV5yBWfxTd delta 84 zcmaFQ@t$Kt7ZcZWE>R&(W)1%Ko13RFDKbhK>lzy98X1NdT3DGFS{WE?8yHv_7&L1f ToH6-4vp5dvPo Date: Mon, 15 Jan 2024 11:41:31 -0800 Subject: [PATCH 2/2] change border and font size --- .../_book/adding-extras-to-an-r-package.html | 168 +- create-litr/_book/combining-.r-files.html | 100 +- create-litr/_book/document.html | 74 +- .../documenting-the-package-and-testing.html | 144 +- .../functionality-to-facilitate-workflow.html | 120 +- create-litr/_book/generating-package.html | 786 ++++----- create-litr/_book/hash.html | 130 +- .../_book/including-extras-for-litr.html | 34 +- create-litr/_book/including-templates.html | 196 +-- create-litr/_book/index.html | 6 +- create-litr/_book/package-setup.html | 64 +- create-litr/_book/rendering.html | 1405 ++++++++--------- create-litr/_book/search_index.json | 2 +- create-litr/_book/tests.html | 670 ++++---- create-litr/index.Rmd | 5 +- docs/apple-touch-icon-120x120.png | Bin 8745 -> 8745 bytes docs/apple-touch-icon-152x152.png | Bin 10612 -> 10612 bytes docs/apple-touch-icon-180x180.png | Bin 12774 -> 12774 bytes docs/apple-touch-icon-60x60.png | Bin 4080 -> 4080 bytes docs/apple-touch-icon-76x76.png | Bin 5179 -> 5179 bytes docs/apple-touch-icon.png | Bin 12774 -> 12774 bytes docs/favicon-16x16.png | Bin 1135 -> 1135 bytes docs/favicon-32x32.png | Bin 2097 -> 2097 bytes docs/index.html | 5 +- docs/pkgdown.yml | 4 +- .../_book/404.html | 2 +- .../_book/conclude.html | 8 +- .../generalization-to-other-greetings.html | 6 +- .../_book/index.html | 10 +- .../_book/package-setup.html | 38 +- .../_book/search_index.json | 2 +- .../_book/the-basics-of-saying-hello.html | 32 +- .../frombookdown/DESCRIPTION | 4 +- .../create-witharmadillo.html | 6 +- .../witharmadillo/DESCRIPTION | 4 +- .../witharmadillo/R/RcppExports.R | 2 +- .../create-rhasdata.html | 4 +- .../rhasdata/DESCRIPTION | 4 +- .../rhasdata/data/mydata.rda | Bin 2030 -> 2029 bytes .../create-withpkgdown.html | 14 +- .../docs/apple-touch-icon-120x120.png | Bin 8745 -> 8745 bytes .../docs/apple-touch-icon-152x152.png | Bin 10612 -> 10612 bytes .../docs/apple-touch-icon-180x180.png | Bin 12774 -> 12774 bytes .../docs/apple-touch-icon-60x60.png | Bin 4080 -> 4080 bytes .../docs/apple-touch-icon-76x76.png | Bin 5179 -> 5179 bytes .../docs/apple-touch-icon.png | Bin 12774 -> 12774 bytes .../docs/articles/using-package.html | 2 +- .../docs/favicon-16x16.png | Bin 1135 -> 1135 bytes .../docs/favicon-32x32.png | Bin 2097 -> 2097 bytes .../docs/pkgdown.yml | 4 +- .../withpkgdown/DESCRIPTION | 4 +- .../favicon/apple-touch-icon-120x120.png | Bin 8745 -> 8745 bytes .../favicon/apple-touch-icon-152x152.png | Bin 10612 -> 10612 bytes .../favicon/apple-touch-icon-180x180.png | Bin 12774 -> 12774 bytes .../favicon/apple-touch-icon-60x60.png | Bin 4080 -> 4080 bytes .../favicon/apple-touch-icon-76x76.png | Bin 5179 -> 5179 bytes .../pkgdown/favicon/apple-touch-icon.png | Bin 12774 -> 12774 bytes .../pkgdown/favicon/favicon-16x16.png | Bin 1135 -> 1135 bytes .../pkgdown/favicon/favicon-32x32.png | Bin 2097 -> 2097 bytes .../create-withrcpp.html | 23 +- .../withrcpp/DESCRIPTION | 4 +- .../withrcpp/R/RcppExports.R | 2 +- examples/make-an-r-package/create-rhello.html | 4 +- examples/make-an-r-package/rhello/DESCRIPTION | 4 +- litr/DESCRIPTION | 4 +- litr/R/render.R | 5 +- litr/README.md | 8 +- .../favicon/apple-touch-icon-120x120.png | Bin 8745 -> 8745 bytes .../favicon/apple-touch-icon-152x152.png | Bin 10612 -> 10612 bytes .../favicon/apple-touch-icon-180x180.png | Bin 12774 -> 12774 bytes .../favicon/apple-touch-icon-60x60.png | Bin 4080 -> 4080 bytes .../favicon/apple-touch-icon-76x76.png | Bin 5179 -> 5179 bytes litr/pkgdown/favicon/apple-touch-icon.png | Bin 12774 -> 12774 bytes litr/pkgdown/favicon/favicon-16x16.png | Bin 1135 -> 1135 bytes litr/pkgdown/favicon/favicon-32x32.png | Bin 2097 -> 2097 bytes 75 files changed, 2058 insertions(+), 2055 deletions(-) diff --git a/create-litr/_book/adding-extras-to-an-r-package.html b/create-litr/_book/adding-extras-to-an-r-package.html index af346f5..74ce7eb 100644 --- a/create-litr/_book/adding-extras-to-an-r-package.html +++ b/create-litr/_book/adding-extras-to-an-r-package.html @@ -211,70 +211,70 @@

9 Adding extras to an R package

9.1 Adding a README

We define a helper function that takes an externally defined README.Rmd and puts it into the package, creates a README.md, and makes sure that these will be added to the .Rbuildignore.

-
#' Add README to package
-#' 
-#' This function takes a README.Rmd file, copies it into the package, and then
-#' renders it to a README.md file.  It also adds these two files to the
-#' .Rbuildignore.
-#' 
-#' @param rmd_file The path to a .Rmd file.
-#' @export
-add_readme <- function(rmd_file) {
-  usethis::use_readme_rmd(open = FALSE)
-  fs::file_copy(rmd_file, new_path = "README.Rmd", overwrite = TRUE)
-  out <- xfun::Rscript_call(
-    rmarkdown::render,
-    args = list(
-      input = "README.Rmd",
-      output_options = list(html_preview = "false")
-    )
-  )
-}
+
#' Add README to package
+#' 
+#' This function takes a README.Rmd file, copies it into the package, and then
+#' renders it to a README.md file.  It also adds these two files to the
+#' .Rbuildignore.
+#' 
+#' @param rmd_file The path to a .Rmd file.
+#' @export
+add_readme <- function(rmd_file) {
+  usethis::use_readme_rmd(open = FALSE)
+  fs::file_copy(rmd_file, new_path = "README.Rmd", overwrite = TRUE)
+  out <- xfun::Rscript_call(
+    rmarkdown::render,
+    args = list(
+      input = "README.Rmd",
+      output_options = list(html_preview = "false")
+    )
+  )
+}

9.2 Adding a hex sticker

We define a helper function that takes an externally defined hex sticker (.png file) and puts it into the package under man/figures. The suggestion for storing it in this directory came from here.

-
#' Add a hex sticker to package
-#' 
-#' In addition to calling this function, you should add to your README.Rmd something like this:
-#' 
-#' `# your-title <img src="man/figures/logo.png" align="right" height="139" />`
-#' 
-#' See [here](https://pkgdown.r-lib.org/reference/build_home.html#package-logo) 
-#' for more.
-#' 
-#' @param hex_png_file The .png file with your package's hex sticker
-#' @export
-add_hex_sticker <- function(hex_png_file) {
-  figures_dir <- file.path("man", "figures")
-  fs::dir_create(figures_dir)
-  fs::file_copy(path = hex_png_file, 
-                new_path = file.path(figures_dir, "logo.png"), 
-                overwrite = TRUE)
-}
+
#' Add a hex sticker to package
+#' 
+#' In addition to calling this function, you should add to your README.Rmd something like this:
+#' 
+#' `# your-title <img src="man/figures/logo.png" align="right" height="139" />`
+#' 
+#' See [here](https://pkgdown.r-lib.org/reference/build_home.html#package-logo) 
+#' for more.
+#' 
+#' @param hex_png_file The .png file with your package's hex sticker
+#' @export
+add_hex_sticker <- function(hex_png_file) {
+  figures_dir <- file.path("man", "figures")
+  fs::dir_create(figures_dir)
+  fs::file_copy(path = hex_png_file, 
+                new_path = file.path(figures_dir, "logo.png"), 
+                overwrite = TRUE)
+}

9.3 Adding vignettes

We next define a helper function for adding vignettes to the package. This mimics usethis::use_vignette(). We couldn’t directly use that function because we want the project file to live outside of the package directory, which confuses usethis.

-
#' Add one or more vignettes to package
-#' 
-#' @param rmd_files A character vector of .Rmd files, each corresponding to 
-#' a vignette
-#' @param other_files A character vector of any other files needed in the 
-#' vignettes directory (.bib file, images, etc.)
-#' @export
-add_vignettes <- function(rmd_files, other_files = NULL) {
-  fs::dir_create("vignettes")
-  for (fn in c(rmd_files, other_files)) fs::file_copy(fn, "vignettes")
-  
-  # update DESCRIPTION file:
-  deps <- desc::desc_get_deps()$package
-  if (!("knitr" %in% deps))
-    desc::desc_set_dep("knitr", type = "Suggests")
-  if (!("rmarkdown" %in% deps))
-    desc::desc_set_dep("rmarkdown", type = "Suggests")
-  out <- desc::desc_set("VignetteBuilder", "knitr")
-}
+
#' Add one or more vignettes to package
+#' 
+#' @param rmd_files A character vector of .Rmd files, each corresponding to 
+#' a vignette
+#' @param other_files A character vector of any other files needed in the 
+#' vignettes directory (.bib file, images, etc.)
+#' @export
+add_vignettes <- function(rmd_files, other_files = NULL) {
+  fs::dir_create("vignettes")
+  for (fn in c(rmd_files, other_files)) fs::file_copy(fn, "vignettes")
+  
+  # update DESCRIPTION file:
+  deps <- desc::desc_get_deps()$package
+  if (!("knitr" %in% deps))
+    desc::desc_set_dep("knitr", type = "Suggests")
+  if (!("rmarkdown" %in% deps))
+    desc::desc_set_dep("rmarkdown", type = "Suggests")
+  out <- desc::desc_set("VignetteBuilder", "knitr")
+}

9.4 Add a pkgdown site

@@ -283,36 +283,36 @@

9.4 Add a pkgdown site
#' Add a pkgdown site
-#' 
-#' This function creates a website for your package.  You can see it locally by
-#' opening `docs/index.html` in your package.  To get it online you can copy the
-#' `docs` directory to your website's server.
-#' 
-#' Be sure that in the generating .Rmd file this is called *after*
-#' `litr::document()` has been called.  To customize the site, you may pass a
-#' customized `_pkgdown.yml` file as described in [this `pkgdown` vignette](https://pkgdown.r-lib.org/articles/customise.html).
-#' 
-#' @param config_path The _pkgdown.yml file that lives somewhere outside of your package.  If NULL, then a basic default will be used.
-#' @export
-add_pkgdown <- function(config_path = NULL) {
-  config_file <- "_pkgdown.yml"
-  destdir <- "docs"
-  usethis::use_build_ignore(c(config_file, destdir, "pkgdown"))
-  if (is.null(config_path)) {
-    # create a new config file (note it lives outside of package)
-    config <- usethis:::pkgdown_config(destdir)
-    usethis::write_over(config_file, yaml::as.yaml(config))
-  } else {
-    # copy the one that already exists:
-    fs::file_copy(config_path, config_file)  
-  }
-  pkgdown::build_site()
-}

+
#' Add a pkgdown site
+#' 
+#' This function creates a website for your package.  You can see it locally by
+#' opening `docs/index.html` in your package.  To get it online you can copy the
+#' `docs` directory to your website's server.
+#' 
+#' Be sure that in the generating .Rmd file this is called *after*
+#' `litr::document()` has been called.  To customize the site, you may pass a
+#' customized `_pkgdown.yml` file as described in [this `pkgdown` vignette](https://pkgdown.r-lib.org/articles/customise.html).
+#' 
+#' @param config_path The _pkgdown.yml file that lives somewhere outside of your package.  If NULL, then a basic default will be used.
+#' @export
+add_pkgdown <- function(config_path = NULL) {
+  config_file <- "_pkgdown.yml"
+  destdir <- "docs"
+  usethis::use_build_ignore(c(config_file, destdir, "pkgdown"))
+  if (is.null(config_path)) {
+    # create a new config file (note it lives outside of package)
+    config <- usethis:::pkgdown_config(destdir)
+    usethis::write_over(config_file, yaml::as.yaml(config))
+  } else {
+    # copy the one that already exists:
+    fs::file_copy(config_path, config_file)  
+  }
+  pkgdown::build_site()
+}

After this step, you can locally see the site by opening docs/index.html in the browser. You can then copy the docs directory to your website’s server and you’re done.

Since the above function uses pkgdown and yaml, we include these in our package:

-
usethis::use_package("pkgdown")
-usethis::use_package("yaml")
+
usethis::use_package("pkgdown")
+usethis::use_package("yaml")
## ✔ Adding 'pkgdown' to Imports field in DESCRIPTION
 ## • Refer to functions with `pkgdown::fun()`
## ✔ Adding 'yaml' to Imports field in DESCRIPTION
diff --git a/create-litr/_book/combining-.r-files.html b/create-litr/_book/combining-.r-files.html
index 338810d..950ec49 100644
--- a/create-litr/_book/combining-.r-files.html
+++ b/create-litr/_book/combining-.r-files.html
@@ -207,56 +207,56 @@ 

10 Combining .R files

This section should eventually be removed but for now I’m doing this to convince myself that the package generated by this .Rmd file really matches the initial version created without the package.

-
library(magrittr)
-library(purrr)
-rfiles <- fs::dir_ls("R")
-code <- rfiles %>% 
-  map(readLines) %>% 
-  set_names(
-    rfiles %>% stringr::str_remove("^.*/") %>% stringr::str_remove(".R$")
-    )
-hash_functions <- c("hash_package_directory",
-                    "description_litr_hash_field_name",
-                    "write_hash_to_description",
-                    "read_hash_from_description",
-                    "check_unedited")
-render_functions <- c("render",
-                      "with_cleanup",
-                      "litrify_output_format",
-                      "litr_pdf_document",
-                      "litr_html_document",
-                      "litr_gitbook",
-                      "replace_ansi_sequences",
-                      "add_function_hyperlinks",
-                      "insert_hrefs",
-                      "add_chunk_label_hyperlinks",
-                      "restore_knitr_objects",
-                      "remove_rstudio_extras",
-                      "get_params_used",
-                      "get_package_directory",
-                      "do_not_edit_message",
-                      "description_litr_version_field_name",
-                      "write_version_to_description",
-                      "document",
-                      "load_all")
-setup_functions <- c("setup",
-                     "make_noticeable",
-                     "send_to_package",
-                     "add_text_to_file",
-                     "find_labels")
-extras_functions <- c("add_readme",
-                      "add_hex_sticker",
-                      "add_vignettes",
-                      "add_pkgdown")
-remove_initial_lines <- function(code_list) {
-  # drop first line of each list element except for the first list element
-  c(code_list[1], map(code_list[-1], ~ .x[-1]))
-}
-fs::file_delete(setdiff(rfiles, "R/litr-package.R"))
-writeLines(unlist(remove_initial_lines(code[hash_functions])), "R/hash.R")
-writeLines(unlist(remove_initial_lines(code[render_functions])), "R/render.R")
-writeLines(unlist(remove_initial_lines(code[setup_functions])), "R/setup.R")
-writeLines(unlist(remove_initial_lines(code[extras_functions])), "R/extras.R")
+
library(magrittr)
+library(purrr)
+rfiles <- fs::dir_ls("R")
+code <- rfiles %>% 
+  map(readLines) %>% 
+  set_names(
+    rfiles %>% stringr::str_remove("^.*/") %>% stringr::str_remove(".R$")
+    )
+hash_functions <- c("hash_package_directory",
+                    "description_litr_hash_field_name",
+                    "write_hash_to_description",
+                    "read_hash_from_description",
+                    "check_unedited")
+render_functions <- c("render",
+                      "with_cleanup",
+                      "litrify_output_format",
+                      "litr_pdf_document",
+                      "litr_html_document",
+                      "litr_gitbook",
+                      "replace_ansi_sequences",
+                      "add_function_hyperlinks",
+                      "insert_hrefs",
+                      "add_chunk_label_hyperlinks",
+                      "restore_knitr_objects",
+                      "remove_rstudio_extras",
+                      "get_params_used",
+                      "get_package_directory",
+                      "do_not_edit_message",
+                      "description_litr_version_field_name",
+                      "write_version_to_description",
+                      "document",
+                      "load_all")
+setup_functions <- c("setup",
+                     "make_noticeable",
+                     "send_to_package",
+                     "add_text_to_file",
+                     "find_labels")
+extras_functions <- c("add_readme",
+                      "add_hex_sticker",
+                      "add_vignettes",
+                      "add_pkgdown")
+remove_initial_lines <- function(code_list) {
+  # drop first line of each list element except for the first list element
+  c(code_list[1], map(code_list[-1], ~ .x[-1]))
+}
+fs::file_delete(setdiff(rfiles, "R/litr-package.R"))
+writeLines(unlist(remove_initial_lines(code[hash_functions])), "R/hash.R")
+writeLines(unlist(remove_initial_lines(code[render_functions])), "R/render.R")
+writeLines(unlist(remove_initial_lines(code[setup_functions])), "R/setup.R")
+writeLines(unlist(remove_initial_lines(code[extras_functions])), "R/extras.R")
## 
 ## Attaching package: 'purrr'
## The following object is masked from 'package:magrittr':
diff --git a/create-litr/_book/document.html b/create-litr/_book/document.html
index 98e0804..d3a5ad3 100644
--- a/create-litr/_book/document.html
+++ b/create-litr/_book/document.html
@@ -207,44 +207,44 @@ 

6 Wrapper to devtools::document()

This function is nearly identical to devtools::document() except that it changes the roxygen2 message that says “Please edit documentation in R/[…].R” to instead mention the generating .Rmd file. When Rcpp is used, it also makes sure that #include <RcppArmadillo.h> comes before #include <Rcpp.h>.

-
#' Use roxygen to document a package from within a Rmd file
-#' 
-#' This is a wrapper for the `devtools::document()` function, which in turn is a
-#' wrapper for the `roxygen2::roxygenize()` function.  It is written assuming that
-#' it is being called from within a generating Rmd file.  The purpose for `litr` 
-#' having this wrapper is two-fold.  First, it ensures that the first line
-#' in the outputted `Rd` files should not say "Please edit documentation in 
-#' R/file.R" but instead should refer to the Rmd file that generates everything. 
-#' Second, in the case that Rcpp is being used, it makes some adjustments to ensure
-#' that the compiling of the C++ code should be successful.
-#' 
-#' @param ... Arguments to be passed to `devtools::document()`
-#' @export
-document <- function(...) {
-  # prepare Rcpp code for compiling
-  if (fs::file_exists("src/code.cpp")) {
-    # make sure that #include <RcppArmadillo.h> if it exists
-    # comes *before* (or instead of) <Rcpp.h>
-    txt <- readLines("src/code.cpp")
-    loc <- stringr::str_which(txt, r"(#include <RcppArmadillo.h>)")
-    if (length(loc) > 0) {
-      include_arma_line <- txt[loc[1]]
-      txt <- c(include_arma_line, txt[-loc])
-      writeLines(txt, "src/code.cpp")
-    }
-  }
-  
-  devtools::document(...)
-  # remove the line of the following form in each man/*.Rd file:
-  pattern <- "% Please edit documentation in .*$"
-  msg <- do_not_edit_message(knitr::current_input(), type = "man")
-  for (fname in fs::dir_ls("man")) {
-    txt <- stringr::str_replace(readLines(fname), pattern, msg)
-    cat(paste(txt, collapse = "\n"), file = fname)
-  }
-}
+
#' Use roxygen to document a package from within a Rmd file
+#' 
+#' This is a wrapper for the `devtools::document()` function, which in turn is a
+#' wrapper for the `roxygen2::roxygenize()` function.  It is written assuming that
+#' it is being called from within a generating Rmd file.  The purpose for `litr` 
+#' having this wrapper is two-fold.  First, it ensures that the first line
+#' in the outputted `Rd` files should not say "Please edit documentation in 
+#' R/file.R" but instead should refer to the Rmd file that generates everything. 
+#' Second, in the case that Rcpp is being used, it makes some adjustments to ensure
+#' that the compiling of the C++ code should be successful.
+#' 
+#' @param ... Arguments to be passed to `devtools::document()`
+#' @export
+document <- function(...) {
+  # prepare Rcpp code for compiling
+  if (fs::file_exists("src/code.cpp")) {
+    # make sure that #include <RcppArmadillo.h> if it exists
+    # comes *before* (or instead of) <Rcpp.h>
+    txt <- readLines("src/code.cpp")
+    loc <- stringr::str_which(txt, r"(#include <RcppArmadillo.h>)")
+    if (length(loc) > 0) {
+      include_arma_line <- txt[loc[1]]
+      txt <- c(include_arma_line, txt[-loc])
+      writeLines(txt, "src/code.cpp")
+    }
+  }
+  
+  devtools::document(...)
+  # remove the line of the following form in each man/*.Rd file:
+  pattern <- "% Please edit documentation in .*$"
+  msg <- do_not_edit_message(knitr::current_input(), type = "man")
+  for (fname in fs::dir_ls("man")) {
+    txt <- stringr::str_replace(readLines(fname), pattern, msg)
+    cat(paste(txt, collapse = "\n"), file = fname)
+  }
+}

We used devtools, so let’s import it:

-
usethis::use_package("devtools")
+
usethis::use_package("devtools")
## ✔ Adding 'devtools' to Imports field in DESCRIPTION
 ## • Refer to functions with `devtools::fun()`
diff --git a/create-litr/_book/documenting-the-package-and-testing.html b/create-litr/_book/documenting-the-package-and-testing.html index d65859a..d3b108b 100644 --- a/create-litr/_book/documenting-the-package-and-testing.html +++ b/create-litr/_book/documenting-the-package-and-testing.html @@ -209,12 +209,12 @@

13 Documenting the package and te

We finish by running commands that will document and test litr.

The formatting of the test output does not print out very neatly.

We download the latest release:

-
litr::document()
-install_old <- function() {
-  remotes::install_github("jacobbien/litr-project@*release", subdir = "litr")
-}
-xfun::Rscript_call(test_litr,
-                   list(install_old = install_old, location_of_new = "."))
+
litr::document()
+install_old <- function() {
+  remotes::install_github("jacobbien/litr-project@*release", subdir = "litr")
+}
+xfun::Rscript_call(test_litr,
+                   list(install_old = install_old, location_of_new = "."))
## ℹ Updating litr documentation
 ## ℹ Loading litr
 ## Writing 'NAMESPACE'
@@ -264,15 +264,15 @@ 

13 Documenting the package and te ## 8 tests.R tests Rendering with minimal_eval=TRUE works 2 0 FALSE ## 9 tests.R tests templates can be knit 12 0 FALSE ## error warning user system real passed -## 1 FALSE 0 0.049 0.004 0.053 10 -## 2 FALSE 0 0.003 0.000 0.003 2 -## 3 FALSE 0 0.262 0.019 2.354 1 -## 4 FALSE 0 0.097 0.039 1.880 11 -## 5 FALSE 0 0.007 0.001 0.008 3 -## 6 FALSE 0 0.020 0.003 1.884 1 -## 7 FALSE 0 0.443 0.043 12.580 12 -## 8 FALSE 0 0.314 0.034 5.271 2 -## 9 FALSE 0 0.128 0.077 58.672 12 +## 1 FALSE 0 0.084 0.005 0.090 10 +## 2 FALSE 0 0.004 0.001 0.004 2 +## 3 FALSE 0 0.194 0.018 1.208 1 +## 4 FALSE 0 0.050 0.007 1.104 11 +## 5 FALSE 0 0.006 0.001 0.006 3 +## 6 FALSE 0 0.019 0.002 1.181 1 +## 7 FALSE 0 0.353 0.022 7.571 12 +## 8 FALSE 0 0.209 0.015 3.131 2 +## 9 FALSE 0 0.120 0.023 37.485 12 ## result ## 1 , 10, 3, 10, 77, 3, 77, 10, 10, testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, req_exist = TRUE), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, fs::file_exists(myfile) is not TRUE\n\n, 16, 3, 16, 48, 3, 48, 16, 16, 45, 47, add_text_to_file() works, `sometxt` (`actual`) not equal to readLines(myfile) (`expected`).\n\n, 17, 3, 17, 52, 3, 52, 17, 17, 45, 47, add_text_to_file() works, c(sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\n\n, 22, 3, 22, 64, 3, 64, 22, 22, 45, 47, add_text_to_file() works, , 25, 3, 25, 62, 3, 62, 25, 25, testthat::expect_error(add_text_to_file(sometxt, myfile, 0)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, 0), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, , 26, 3, 26, 63, 3, 63, 26, 26, testthat::expect_error(add_text_to_file(sometxt, myfile, -1)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, -1), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, , 27, 3, 27, 62, 3, 62, 27, 27, testthat::expect_error(add_text_to_file(sometxt, myfile, 5)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, 5), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, c(moretxt2, sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\n\n, 32, 3, 32, 74, 3, 74, 32, 32, 45, 47, add_text_to_file() works, c(moretxt2, moretxt3, sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\n\n, 37, 3, 38, 43, 3, 43, 37, 38, 45, 47, add_text_to_file() works, c(moretxt2, moretxt3, sometxt, moretxt, moretxt4) (`actual`) not equal to readLines(myfile) (`expected`).\n\n, 43, 3, 44, 43, 3, 43, 43, 44, 45, 47, add_text_to_file() works ## 2 get_package_directory(".", "mypkg", input) (`actual`) not equal to file.path("inputdir", "mypkg") (`expected`).\n\n, 50, 3, 53, 3, 3, 3, 50, 53, 45, 47, get_package_directory() works, get_package_directory("..", "mypkg", input) (`actual`) not equal to file.path("inputdir", "..", "mypkg") (`expected`).\n\n, 54, 3, 57, 3, 3, 3, 54, 57, 45, 47, get_package_directory() works @@ -286,63 +286,63 @@

13 Documenting the package and te

13.1 Add examples folder with the output of knitting each example

In this section, we will litr-knit each template and put the outputs in an examples directory that lives outside of the litr R package. These examples are linked to in README.Rmd.

-
build_all_templates <- function(install_old, location_of_new) {
-  devtools::install(location_of_new)
-
-  example_dir <- file.path("..", "examples")
-  if (fs::dir_exists(example_dir)) fs::dir_delete(example_dir)
-  fs::dir_create(example_dir)
-  
-  templates <- fs::path_file(fs::dir_ls("inst/rmarkdown/templates"))
-  templates_bookdown <- stringr::str_subset(templates, "bookdown")
-  templates_nonbookdown <- setdiff(templates, templates_bookdown)
-  for (template in templates_nonbookdown) {
-    tmp_file <- file.path(example_dir, "temp.Rmd")
-    rmarkdown::draft(tmp_file, template, package = "litr", edit = FALSE)
-    pkg_name <- rmarkdown::yaml_front_matter(tmp_file)$params$package_name
-    rmd_file <- file.path(example_dir, paste0("create-", pkg_name, ".Rmd"))
-    fs::file_move(tmp_file, rmd_file)
-    render(rmd_file)
-    # move to a template-specific directory:
-    template_dir <- file.path(example_dir, template)
-    fs::dir_create(template_dir)
-    fs::file_move(fs::dir_ls(example_dir, regexp = pkg_name), template_dir)
-    # move the source-files directory
-    if (fs::dir_exists(file.path(example_dir, "source-files"))) {
-      fs::dir_create(file.path(example_dir, template, "source-files"))
-      fs::dir_copy(file.path(example_dir, "source-files"),
-                   file.path(example_dir, template))
-      fs::dir_delete(file.path(example_dir, "source-files"))
-    }
-    # move the docs directory (when pkgdown creates one)
-    if (fs::dir_exists(file.path(example_dir, "docs"))) {
-      fs::dir_create(file.path(example_dir, template, "docs"))
-      fs::dir_copy(file.path(example_dir, "docs"),
-                   file.path(example_dir, template))
-      fs::dir_delete(file.path(example_dir, "docs"))
-    }
-  }
-
-  for (template in templates_bookdown) {
-    tmp_dir <- file.path(example_dir, "temp")
-    rmarkdown::draft(file.path(example_dir, "temp.Rmd"),
-                     template,
-                     package = "litr",
-                     edit = FALSE)
-    prev_dir <- getwd()
-    setwd(tmp_dir)
-    fs::file_delete("temp.Rmd")
-    render("index.Rmd")
-    # move to a template-specific directory:
-    setwd(prev_dir)
-    fs::dir_copy(tmp_dir, file.path(example_dir, template))
-    fs::dir_delete(tmp_dir)
-  }
-
-  install_old()
-}
-
xfun::Rscript_call(build_all_templates,
-                   list(install_old = install_old, location_of_new = "."))
+
build_all_templates <- function(install_old, location_of_new) {
+  devtools::install(location_of_new)
+
+  example_dir <- file.path("..", "examples")
+  if (fs::dir_exists(example_dir)) fs::dir_delete(example_dir)
+  fs::dir_create(example_dir)
+  
+  templates <- fs::path_file(fs::dir_ls("inst/rmarkdown/templates"))
+  templates_bookdown <- stringr::str_subset(templates, "bookdown")
+  templates_nonbookdown <- setdiff(templates, templates_bookdown)
+  for (template in templates_nonbookdown) {
+    tmp_file <- file.path(example_dir, "temp.Rmd")
+    rmarkdown::draft(tmp_file, template, package = "litr", edit = FALSE)
+    pkg_name <- rmarkdown::yaml_front_matter(tmp_file)$params$package_name
+    rmd_file <- file.path(example_dir, paste0("create-", pkg_name, ".Rmd"))
+    fs::file_move(tmp_file, rmd_file)
+    render(rmd_file)
+    # move to a template-specific directory:
+    template_dir <- file.path(example_dir, template)
+    fs::dir_create(template_dir)
+    fs::file_move(fs::dir_ls(example_dir, regexp = pkg_name), template_dir)
+    # move the source-files directory
+    if (fs::dir_exists(file.path(example_dir, "source-files"))) {
+      fs::dir_create(file.path(example_dir, template, "source-files"))
+      fs::dir_copy(file.path(example_dir, "source-files"),
+                   file.path(example_dir, template))
+      fs::dir_delete(file.path(example_dir, "source-files"))
+    }
+    # move the docs directory (when pkgdown creates one)
+    if (fs::dir_exists(file.path(example_dir, "docs"))) {
+      fs::dir_create(file.path(example_dir, template, "docs"))
+      fs::dir_copy(file.path(example_dir, "docs"),
+                   file.path(example_dir, template))
+      fs::dir_delete(file.path(example_dir, "docs"))
+    }
+  }
+
+  for (template in templates_bookdown) {
+    tmp_dir <- file.path(example_dir, "temp")
+    rmarkdown::draft(file.path(example_dir, "temp.Rmd"),
+                     template,
+                     package = "litr",
+                     edit = FALSE)
+    prev_dir <- getwd()
+    setwd(tmp_dir)
+    fs::file_delete("temp.Rmd")
+    render("index.Rmd")
+    # move to a template-specific directory:
+    setwd(prev_dir)
+    fs::dir_copy(tmp_dir, file.path(example_dir, template))
+    fs::dir_delete(tmp_dir)
+  }
+
+  install_old()
+}
+
xfun::Rscript_call(build_all_templates,
+                   list(install_old = install_old, location_of_new = "."))
## [1] "litr"

diff --git a/create-litr/_book/functionality-to-facilitate-workflow.html b/create-litr/_book/functionality-to-facilitate-workflow.html index d9e4056..1fc0a43 100644 --- a/create-litr/_book/functionality-to-facilitate-workflow.html +++ b/create-litr/_book/functionality-to-facilitate-workflow.html @@ -211,67 +211,67 @@

8 Functionality to facilitate wor
  • Litr-knit the .Rmd file with minimal_eval=TRUE to some temporary location (since the idea of load_all() is to leave the .html file alone) and then delete the .html file.

  • Run devtools::load_all() on the output.

  • -
    #' Load complete package
    -#' 
    -#' This is a litr wrapper to `devtools::load_all()`.  It first calls
    -#' `litr::render()` with `minimal_eval=TRUE`, then it calls
    -#' `devtools::load_all()` on the generated package.
    -#' 
    -#' @param input The input file to be rendered (see `rmarkdown::render`)
    -#' @param output_dir By default (and in typical usage) this is NULL, meaning
    -#' that no .html/bookdown/.pdf will result.  However, when a directory is given,
    -#' the result of the litr-knitting will be saved to this location.
    -#' @param ... Additional parameters to be passed to `devtools::load_all()`
    -#' @export
    -load_all <- function(input, output_dir = NULL, ...) {
    -  no_output <- is.null(output_dir)
    -  if (no_output) {
    -    output_dir <- tempfile()
    -    if (fs::file_exists(output_dir)) fs::file_delete(output_dir)
    -    fs::dir_create(output_dir)
    -  }
    -  
    -  # let's copy over everything from input directory to output directory
    -  fs::dir_copy(fs::path_dir(input), output_dir, overwrite = TRUE)
    -  input_path <- fs::path_split(input)[[1]]
    -  moved_input <- file.path(output_dir, fs::path_file(input))
    -  
    -  # get package directory
    -  params <- get_params_used(moved_input, list())
    -  package_dir <- get_package_directory(
    -    params$package_parent_dir,
    -    params$package_name,
    -    moved_input
    -  )
    -  
    -  # but if a package directory was copied here, let's remove it before
    -  # calling render to avoid a potential error
    -  if (fs::dir_exists(package_dir)) fs::dir_delete(package_dir)
    -  
    -  litr::render(moved_input, minimal_eval = TRUE, output_dir = output_dir,
    -               quiet = TRUE)
    -  
    -  new_package_dir <- file.path(fs::path_dir(input), params$package_name)
    -  fs::dir_copy(package_dir, new_package_dir, overwrite = TRUE)
    -  if (no_output) fs::dir_delete(output_dir)
    -  
    -  devtools::load_all(new_package_dir)
    -}
    +
    #' Load complete package
    +#' 
    +#' This is a litr wrapper to `devtools::load_all()`.  It first calls
    +#' `litr::render()` with `minimal_eval=TRUE`, then it calls
    +#' `devtools::load_all()` on the generated package.
    +#' 
    +#' @param input The input file to be rendered (see `rmarkdown::render`)
    +#' @param output_dir By default (and in typical usage) this is NULL, meaning
    +#' that no .html/bookdown/.pdf will result.  However, when a directory is given,
    +#' the result of the litr-knitting will be saved to this location.
    +#' @param ... Additional parameters to be passed to `devtools::load_all()`
    +#' @export
    +load_all <- function(input, output_dir = NULL, ...) {
    +  no_output <- is.null(output_dir)
    +  if (no_output) {
    +    output_dir <- tempfile()
    +    if (fs::file_exists(output_dir)) fs::file_delete(output_dir)
    +    fs::dir_create(output_dir)
    +  }
    +  
    +  # let's copy over everything from input directory to output directory
    +  fs::dir_copy(fs::path_dir(input), output_dir, overwrite = TRUE)
    +  input_path <- fs::path_split(input)[[1]]
    +  moved_input <- file.path(output_dir, fs::path_file(input))
    +  
    +  # get package directory
    +  params <- get_params_used(moved_input, list())
    +  package_dir <- get_package_directory(
    +    params$package_parent_dir,
    +    params$package_name,
    +    moved_input
    +  )
    +  
    +  # but if a package directory was copied here, let's remove it before
    +  # calling render to avoid a potential error
    +  if (fs::dir_exists(package_dir)) fs::dir_delete(package_dir)
    +  
    +  litr::render(moved_input, minimal_eval = TRUE, output_dir = output_dir,
    +               quiet = TRUE)
    +  
    +  new_package_dir <- file.path(fs::path_dir(input), params$package_name)
    +  fs::dir_copy(package_dir, new_package_dir, overwrite = TRUE)
    +  if (no_output) fs::dir_delete(output_dir)
    +  
    +  devtools::load_all(new_package_dir)
    +}

    Let’s test that this works. In particular, we’ll call load_all() and then try to use one of the functions from the package.

    -
    testthat::test_that('load_all() works', {
    -  # setup files for tests:
    -  dir <- tempfile()
    -  if (fs::file_exists(dir)) fs::file_delete(dir)
    -  fs::dir_create(dir)
    -  rmd_file <- file.path(dir, 'create-pkg.Rmd')
    -  fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file)
    -  html_file <- file.path(dir, "create-pkg.html")
    -
    -  load_all(rmd_file)
    -  testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!")
    -  
    -  fs::dir_delete(dir)
    -})
    +
    testthat::test_that('load_all() works', {
    +  # setup files for tests:
    +  dir <- tempfile()
    +  if (fs::file_exists(dir)) fs::file_delete(dir)
    +  fs::dir_create(dir)
    +  rmd_file <- file.path(dir, 'create-pkg.Rmd')
    +  fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file)
    +  html_file <- file.path(dir, "create-pkg.html")
    +
    +  load_all(rmd_file)
    +  testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!")
    +  
    +  fs::dir_delete(dir)
    +})
    diff --git a/create-litr/_book/generating-package.html b/create-litr/_book/generating-package.html index 2c7fd6a..ce25d0f 100644 --- a/create-litr/_book/generating-package.html +++ b/create-litr/_book/generating-package.html @@ -220,221 +220,221 @@

    4.1 Sending code chunks to the pa
    #include <Rcpp.h>
     using namespace Rcpp;

    and we only want this to appear once in code.cpp, so we do a bit of work to remove that if it appears in the code chunk.

    -
    #' A knitr chunk hook for writing R code and tests
    -#' 
    -#' This chunk hook detects whether a chunk is defining a function or dataset
    -#' to be included in the R package (looks for the `roxygen2` comment format `#' `).
    -#' If so, then it is written to the `R/` directory.  It also looks for chunks 
    -#' that have one or more lines that start with `test_that(` or 
    -#' `testthat::test_that(` (potentially with some leading whitespace).  These 
    -#' chunks are then written to the `tests` directory of the R package.
    -#' 
    -#' When the `send_to` option is used, this chunk hook instead simply writes the
    -#' code chunk to the file specified.
    -#' 
    -#' @param before Indicates whether this is being called before or after the 
    -#' chunk code is executed
    -#' @param options Has information from the chunk
    -#' @param envir Environment
    -#' @keywords internal
    -send_to_package <- function(before, options, envir) {
    -  msg <- do_not_edit_message(knitr::current_input(), type = "R")
    -  if (before == FALSE) {
    -    # Don't do anything after the code chunk has been executed.
    -    return()
    -  }
    -  package_dir <- knitr::opts_knit$get("root.dir")
    -  package_name <- fs::path_file(package_dir)
    -  if (!is.null(options$send_to)) {
    -    # the user has defined an option that indicates where in the package this
    -    # code should be written
    -    file <- file.path(package_dir, options$send_to)
    -    add_text_to_file(options$code, file, pad = TRUE, msg = msg)
    -    return()
    -  }
    -  if (stringr::str_detect(options$code[1], "^#' ")) {
    -    # starts with roxygen2, so let's assume this chunk is defining an R function
    -    # or dataset that belongs in the package
    -    non_comment <- stringr::str_subset(options$code, "^#", negate = TRUE)
    -    if (length(non_comment) > 0) {
    -      if (stringr::str_detect(non_comment[1], "<-")) {
    -        # a function is being defined
    -        objname <- stringr::str_match(non_comment[1], "^(.*)\\s*<-\\s*function")[, 2]
    -        objname <- stringr::str_trim(objname)
    -      } else if (stringr::str_detect(non_comment[1], '^".+"$')) {
    -        # a dataset is being documented
    -        objname <- stringr::str_sub(non_comment[1], start = 2, end = -2)
    -      } else {
    -        # Roxygen2 comment wasn't followed by anything recognized, so do not 
    -        # send this to package
    -        return()
    -      }
    -      file <- file.path(package_dir, "R", stringr::str_glue("{objname}.R"))
    -      cat(paste(c(msg, "", options$code, ""), collapse = "\n"), file = file)
    -    }
    -  }
    -  else if (any(stringr::str_detect(options$code,
    -                                   "^\\s*(testthat::)?test_that\\("))) {
    -    # This chunk is inferred to be a test
    -    test_dir <- file.path(package_dir, "tests", "testthat")
    -    test_file <- file.path(test_dir, "tests.R")
    -    if (!file.exists(test_file)) {
    -      # It's the first chunk with tests
    -      if (!dir.exists(test_dir)) usethis::use_testthat()
    -      cat(c(msg, ""), collapse = "\n", file = test_file)
    -    }
    -    cat(
    -      paste(c(options$code, "", ""), collapse = "\n"),
    -      file = test_file,
    -      append = TRUE
    -    )
    -  } else if (options$engine == "Rcpp") {
    -    # To add Rcpp code, we need the package documentation file to exist 
    -    if (!file.exists(file.path(
    -      package_dir,
    -      "R",
    -      paste0(package_name, "-package.R"))
    -      )) {
    -      usethis::use_package_doc(open = FALSE)
    -    }
    -    cpp_file <- file.path(package_dir, "src", "code.cpp")
    -    if (!file.exists(cpp_file)) {
    -      # set up package for Rcpp
    -      # these next few lines are taken from usethis::use_rcpp()
    -      # it approximates a call to usethis::use_rcpp(name = "code")
    -      usethis:::use_dependency("Rcpp", "LinkingTo")
    -      usethis:::use_dependency("Rcpp", "Imports")
    -      usethis:::roxygen_ns_append("@importFrom Rcpp sourceCpp")
    -      usethis:::use_src()
    -      usethis::use_template("code.cpp", save_as = "src/code.cpp")
    -
    -      msg <- do_not_edit_message(knitr::current_input(), type = "c")
    -      cat(msg, file = cpp_file, append = TRUE)
    -    }
    -    # append code to code.cpp, but remove lines that are `#include <Rcpp.h>`
    -    # or `using namespace Rcpp;` since this already appears at top of file
    -    cat(paste(c(
    -      "",
    -      stringr::str_subset(
    -        options$code,
    -        r"(^#include <Rcpp.h>$|^using namespace Rcpp;$)",
    -        negate = TRUE),
    -      ""), collapse = "\n"), 
    -        file = cpp_file,
    -        append = TRUE)
    -  }
    -  return()
    -}
    +
    #' A knitr chunk hook for writing R code and tests
    +#' 
    +#' This chunk hook detects whether a chunk is defining a function or dataset
    +#' to be included in the R package (looks for the `roxygen2` comment format `#' `).
    +#' If so, then it is written to the `R/` directory.  It also looks for chunks 
    +#' that have one or more lines that start with `test_that(` or 
    +#' `testthat::test_that(` (potentially with some leading whitespace).  These 
    +#' chunks are then written to the `tests` directory of the R package.
    +#' 
    +#' When the `send_to` option is used, this chunk hook instead simply writes the
    +#' code chunk to the file specified.
    +#' 
    +#' @param before Indicates whether this is being called before or after the 
    +#' chunk code is executed
    +#' @param options Has information from the chunk
    +#' @param envir Environment
    +#' @keywords internal
    +send_to_package <- function(before, options, envir) {
    +  msg <- do_not_edit_message(knitr::current_input(), type = "R")
    +  if (before == FALSE) {
    +    # Don't do anything after the code chunk has been executed.
    +    return()
    +  }
    +  package_dir <- knitr::opts_knit$get("root.dir")
    +  package_name <- fs::path_file(package_dir)
    +  if (!is.null(options$send_to)) {
    +    # the user has defined an option that indicates where in the package this
    +    # code should be written
    +    file <- file.path(package_dir, options$send_to)
    +    add_text_to_file(options$code, file, pad = TRUE, msg = msg)
    +    return()
    +  }
    +  if (stringr::str_detect(options$code[1], "^#' ")) {
    +    # starts with roxygen2, so let's assume this chunk is defining an R function
    +    # or dataset that belongs in the package
    +    non_comment <- stringr::str_subset(options$code, "^#", negate = TRUE)
    +    if (length(non_comment) > 0) {
    +      if (stringr::str_detect(non_comment[1], "<-")) {
    +        # a function is being defined
    +        objname <- stringr::str_match(non_comment[1], "^(.*)\\s*<-\\s*function")[, 2]
    +        objname <- stringr::str_trim(objname)
    +      } else if (stringr::str_detect(non_comment[1], '^".+"$')) {
    +        # a dataset is being documented
    +        objname <- stringr::str_sub(non_comment[1], start = 2, end = -2)
    +      } else {
    +        # Roxygen2 comment wasn't followed by anything recognized, so do not 
    +        # send this to package
    +        return()
    +      }
    +      file <- file.path(package_dir, "R", stringr::str_glue("{objname}.R"))
    +      cat(paste(c(msg, "", options$code, ""), collapse = "\n"), file = file)
    +    }
    +  }
    +  else if (any(stringr::str_detect(options$code,
    +                                   "^\\s*(testthat::)?test_that\\("))) {
    +    # This chunk is inferred to be a test
    +    test_dir <- file.path(package_dir, "tests", "testthat")
    +    test_file <- file.path(test_dir, "tests.R")
    +    if (!file.exists(test_file)) {
    +      # It's the first chunk with tests
    +      if (!dir.exists(test_dir)) usethis::use_testthat()
    +      cat(c(msg, ""), collapse = "\n", file = test_file)
    +    }
    +    cat(
    +      paste(c(options$code, "", ""), collapse = "\n"),
    +      file = test_file,
    +      append = TRUE
    +    )
    +  } else if (options$engine == "Rcpp") {
    +    # To add Rcpp code, we need the package documentation file to exist 
    +    if (!file.exists(file.path(
    +      package_dir,
    +      "R",
    +      paste0(package_name, "-package.R"))
    +      )) {
    +      usethis::use_package_doc(open = FALSE)
    +    }
    +    cpp_file <- file.path(package_dir, "src", "code.cpp")
    +    if (!file.exists(cpp_file)) {
    +      # set up package for Rcpp
    +      # these next few lines are taken from usethis::use_rcpp()
    +      # it approximates a call to usethis::use_rcpp(name = "code")
    +      usethis:::use_dependency("Rcpp", "LinkingTo")
    +      usethis:::use_dependency("Rcpp", "Imports")
    +      usethis:::roxygen_ns_append("@importFrom Rcpp sourceCpp")
    +      usethis:::use_src()
    +      usethis::use_template("code.cpp", save_as = "src/code.cpp")
    +
    +      msg <- do_not_edit_message(knitr::current_input(), type = "c")
    +      cat(msg, file = cpp_file, append = TRUE)
    +    }
    +    # append code to code.cpp, but remove lines that are `#include <Rcpp.h>`
    +    # or `using namespace Rcpp;` since this already appears at top of file
    +    cat(paste(c(
    +      "",
    +      stringr::str_subset(
    +        options$code,
    +        r"(^#include <Rcpp.h>$|^using namespace Rcpp;$)",
    +        negate = TRUE),
    +      ""), collapse = "\n"), 
    +        file = cpp_file,
    +        append = TRUE)
    +  }
    +  return()
    +}

    The above code makes use of a number of functions from the stringr and usethis packages, so we’ll need to add those packages to the Imports section of the DESCRIPTION file:

    -
    usethis::use_package("stringr")
    -usethis::use_package("usethis")
    +
    usethis::use_package("stringr")
    +usethis::use_package("usethis")
    ## ✔ Adding 'stringr' to Imports field in DESCRIPTION
     ## • Refer to functions with `stringr::fun()`
    ## ✔ Adding 'usethis' to Imports field in DESCRIPTION
     ## • Refer to functions with `usethis::fun()`

    The code also calls the function do_not_edit_message(), which adds a line at the top of the files sent to the R package reminding the user that these are not source files to be edited but rather output of the generating .Rmd file. There are two variations on this message.

    -
    #' Generate do-not-edit message to put at top of file
    -#' 
    -#' @param rmd_file Name of the Rmd file to mention
    -#' @param type Whether this is a R/ file, man/ file, or a c file
    -#' @keywords internal
    -do_not_edit_message <- function(rmd_file, type = c("R", "man", "c")) {
    -  if (type[1] == "R")
    -    return(stringr::str_glue("# Generated from {rmd_file}: do not edit by hand"))
    -  else if (type[1] == "man")
    -    return(stringr::str_glue("% Please edit documentation in {rmd_file}."))
    -  else if (type[1] == "c")
    -    return(stringr::str_glue("// Generated from {rmd_file}: do not edit by hand"))
    -  else
    -    stop("type must be either 'R', 'man', or 'c'.")
    -}
    +
    #' Generate do-not-edit message to put at top of file
    +#' 
    +#' @param rmd_file Name of the Rmd file to mention
    +#' @param type Whether this is a R/ file, man/ file, or a c file
    +#' @keywords internal
    +do_not_edit_message <- function(rmd_file, type = c("R", "man", "c")) {
    +  if (type[1] == "R")
    +    return(stringr::str_glue("# Generated from {rmd_file}: do not edit by hand"))
    +  else if (type[1] == "man")
    +    return(stringr::str_glue("% Please edit documentation in {rmd_file}."))
    +  else if (type[1] == "c")
    +    return(stringr::str_glue("// Generated from {rmd_file}: do not edit by hand"))
    +  else
    +    stop("type must be either 'R', 'man', or 'c'.")
    +}

    This function will also be used with type = "man" by litr::document().

    The above also makes use of a simple helper function that inserts text into a specified location of a file (or creates that file if it doesn’t exist). Actually currently it doesn’t, but we can replace cat() in the above with calls to add_text_to_file().

    -
    #' Add Some Text to a File
    -#' 
    -#' The text will be added to the file at a particular line specified by
    -#' `location`.  The first line of `txt` will be on line `location` of the
    -#' modified file.  If `location` is NULL, then text is added to end of file.
    -#' If file does not exist, it is created and `location` is ignored (unless 
    -#' `req_exist` is `TRUE`, in which case an error is thrown).
    -#' 
    -#' @param txt Character vector to add to file
    -#' @param filename Name of file
    -#' @param location Specifies where text should be added. See description for more.
    -#' @param req_exist If TRUE, then throws an error if file doesn't exist
    -#' @param pad If TRUE, then when text is being added to a preexisting file, it adds a newline
    -#' @param msg An optional message to put at top of file if this is a new file
    -#' @keywords internal
    -add_text_to_file <- function(txt, filename, location = NULL, req_exist = FALSE,
    -                             pad = FALSE, msg = NULL) {
    -  if (!file.exists(filename)) {
    -    if (req_exist) stop(stringr::str_glue("Cannot find file {filename}."))
    -    if (!is.null(msg)) txt <- c(msg, "", txt)
    -    writeLines(txt, con = filename)
    -    return()
    -  }
    -  if (pad) txt <- c("", txt)
    -  filetxt <- readLines(filename)
    -  if (is.null(location) || location == length(filetxt) + 1) {
    -    filetxt <- c(filetxt, txt)
    -  }
    -  else if (location > length(filetxt) + 1 | location < 1) 
    -    stop("Invalid location")
    -  else if (location == 1) {
    -    filetxt <- c(txt, filetxt)
    -  } else {
    -    # location is somewhere in middle
    -    filetxt <- c(filetxt[1:(location - 1)],
    -                 txt,
    -                 filetxt[location:length(filetxt)])
    -  }
    -  writeLines(filetxt, con = filename)
    -}
    -
    testthat::test_that("add_text_to_file() works", {
    -  dir <- tempfile()
    -  if (fs::file_exists(dir)) fs::file_delete(dir)
    -  fs::dir_create(dir)
    -  
    -  # should throw error when file does not exist and req_exist is TRUE:
    -  myfile <- file.path(dir, "file.txt")
    -  sometxt <- c("hello", "there")
    -  testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE))
    -
    -  # should create a new file where one does not exist:
    -  myfile <- file.path(dir, "file.txt")
    -  sometxt <- c("hello", "there")
    -  add_text_to_file(sometxt, myfile)
    -  testthat::expect_true(fs::file_exists(myfile))
    -  testthat::expect_equal(sometxt, readLines(myfile))
    -  
    -  # should append to end of file by default
    -  moretxt <- "world"
    -  add_text_to_file(moretxt, myfile)
    -  testthat::expect_equal(c(sometxt, moretxt), readLines(myfile))
    -   
    -  # should throw error for invalid locations:
    -  testthat::expect_error(add_text_to_file(sometxt, myfile, 0))
    -  testthat::expect_error(add_text_to_file(sometxt, myfile, -1))
    -  testthat::expect_error(add_text_to_file(sometxt, myfile, 5))
    -
    -  # should add to specified line:
    -  moretxt2 <- "hi"
    -  add_text_to_file(moretxt2, myfile, 1)
    -  testthat::expect_equal(c(moretxt2, sometxt, moretxt), readLines(myfile))
    -
    -  # should add to specified line:
    -  moretxt3 <- "hi2"
    -  add_text_to_file(moretxt3, myfile, 2)
    -  testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt),
    -                         readLines(myfile))
    -
    -  # should add to specified line:
    -  moretxt4 <- "hi3"
    -  add_text_to_file(moretxt4, myfile, 6)
    -  testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt, moretxt4),
    -                         readLines(myfile))
    -  fs::dir_delete(dir)
    -})
    +
    #' Add Some Text to a File
    +#' 
    +#' The text will be added to the file at a particular line specified by
    +#' `location`.  The first line of `txt` will be on line `location` of the
    +#' modified file.  If `location` is NULL, then text is added to end of file.
    +#' If file does not exist, it is created and `location` is ignored (unless 
    +#' `req_exist` is `TRUE`, in which case an error is thrown).
    +#' 
    +#' @param txt Character vector to add to file
    +#' @param filename Name of file
    +#' @param location Specifies where text should be added. See description for more.
    +#' @param req_exist If TRUE, then throws an error if file doesn't exist
    +#' @param pad If TRUE, then when text is being added to a preexisting file, it adds a newline
    +#' @param msg An optional message to put at top of file if this is a new file
    +#' @keywords internal
    +add_text_to_file <- function(txt, filename, location = NULL, req_exist = FALSE,
    +                             pad = FALSE, msg = NULL) {
    +  if (!file.exists(filename)) {
    +    if (req_exist) stop(stringr::str_glue("Cannot find file {filename}."))
    +    if (!is.null(msg)) txt <- c(msg, "", txt)
    +    writeLines(txt, con = filename)
    +    return()
    +  }
    +  if (pad) txt <- c("", txt)
    +  filetxt <- readLines(filename)
    +  if (is.null(location) || location == length(filetxt) + 1) {
    +    filetxt <- c(filetxt, txt)
    +  }
    +  else if (location > length(filetxt) + 1 | location < 1) 
    +    stop("Invalid location")
    +  else if (location == 1) {
    +    filetxt <- c(txt, filetxt)
    +  } else {
    +    # location is somewhere in middle
    +    filetxt <- c(filetxt[1:(location - 1)],
    +                 txt,
    +                 filetxt[location:length(filetxt)])
    +  }
    +  writeLines(filetxt, con = filename)
    +}
    +
    testthat::test_that("add_text_to_file() works", {
    +  dir <- tempfile()
    +  if (fs::file_exists(dir)) fs::file_delete(dir)
    +  fs::dir_create(dir)
    +  
    +  # should throw error when file does not exist and req_exist is TRUE:
    +  myfile <- file.path(dir, "file.txt")
    +  sometxt <- c("hello", "there")
    +  testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE))
    +
    +  # should create a new file where one does not exist:
    +  myfile <- file.path(dir, "file.txt")
    +  sometxt <- c("hello", "there")
    +  add_text_to_file(sometxt, myfile)
    +  testthat::expect_true(fs::file_exists(myfile))
    +  testthat::expect_equal(sometxt, readLines(myfile))
    +  
    +  # should append to end of file by default
    +  moretxt <- "world"
    +  add_text_to_file(moretxt, myfile)
    +  testthat::expect_equal(c(sometxt, moretxt), readLines(myfile))
    +   
    +  # should throw error for invalid locations:
    +  testthat::expect_error(add_text_to_file(sometxt, myfile, 0))
    +  testthat::expect_error(add_text_to_file(sometxt, myfile, -1))
    +  testthat::expect_error(add_text_to_file(sometxt, myfile, 5))
    +
    +  # should add to specified line:
    +  moretxt2 <- "hi"
    +  add_text_to_file(moretxt2, myfile, 1)
    +  testthat::expect_equal(c(moretxt2, sometxt, moretxt), readLines(myfile))
    +
    +  # should add to specified line:
    +  moretxt3 <- "hi2"
    +  add_text_to_file(moretxt3, myfile, 2)
    +  testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt),
    +                         readLines(myfile))
    +
    +  # should add to specified line:
    +  moretxt4 <- "hi3"
    +  add_text_to_file(moretxt4, myfile, 6)
    +  testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt, moretxt4),
    +                         readLines(myfile))
    +  fs::dir_delete(dir)
    +})
    ## Test passed
    @@ -460,195 +460,195 @@

    4.2 Setting up the R package crea
    1. Define a package_doc engine which allows users to define package-level documentation.
    -
    #' Code for setup chunk
    -#' 
    -#' * Creates directory where package will be. (Deletes what is currently there as 
    -#' long as it appears to have been created by litr and does not have any 
    -#' subsequent manual edits.)
    -#' * Sets the root directory to this directory
    -#' * Sets up the main chunk hook `litr::send_to_package()` that sends code to the 
    -#' R package directory.
    -#' * In the case that `minimal_eval=TRUE`, sets up an options hook for `eval` so
    -#'   chunks are only evaluated if there is a `usethis` or `litr::document()`
    -#'   command
    -#' * Deactivates an internal function of the `usethis` package
    -#' * Redefines the document output hook to handle chunk references differently  
    -#' * Sets up a [custom language engine](https://bookdown.org/yihui/rmarkdown-cookbook/custom-engine.html) called
    -#' `package_doc` that creates a package documentation file and then inserts
    -#' whatever the user puts in the chunk.
    -#' 
    -#' Returns the original state of the knitr objects that have been modified in 
    -#' setup.  This allows us to return things to the previous state after we are
    -#' finished.  This is relevant in the case where litr-knitting occurs in the 
    -#' current session and we don't want to leave things in a permanently modified
    -#' state.
    -#' 
    -#' @param package_dir Directory where R package will be created
    -<<param-minimal_eval>>
    -#' @keywords internal
    -setup <- function(package_dir, minimal_eval) {
    -  if (file.exists(package_dir)) {
    -    unedited <- tryCatch(check_unedited(package_dir),
    -                         error = function(e) {
    -                           # contents of package_dir does not resemble
    -                           # a litr package
    -                           return(FALSE)
    -                         })
    -    if (!unedited) {
    -      stop(make_noticeable(paste(
    -        stringr::str_glue("The directory {normalizePath(package_dir)}"),
    -        "already exists and either was not created by litr or may have manual",
    -        "edits. In either case, please rename that directory (or delete it)", 
    -        "and then try again.", 
    -        sep = "\n")))
    -    }
    -    unlink(package_dir, recursive = TRUE)
    -  }
    -  fs::dir_create(package_dir)
    -  usethis:::proj_set_(usethis:::proj_path_prep(package_dir))
    -
    -  # let's keep a version of the knitr objects before modifying them:
    -  original_knitr <- list(opts_knit = knitr::opts_knit$get(),
    -                         knit_hooks = knitr::knit_hooks$get(),
    -                         opts_chunk = knitr::opts_chunk$get(),
    -                         opts_hooks = knitr::opts_hooks$get(),
    -                         knit_engines = knitr::knit_engines$get()
    -                         )
    -  
    -  knitr::opts_knit$set(root.dir = package_dir) # sets wd of future chunks
    -  knitr::knit_hooks$set(send_to_package = send_to_package)
    -  knitr::opts_chunk$set(send_to_package = TRUE)
    -  if (minimal_eval) {
    -    # only evaluate chunks that appear to include usethis commands or 
    -    # a call to litr::document() but if someone has specifically set eval=FALSE
    -    # in a particular chunk, do honor that
    -    usethis_exports <- getNamespaceExports("usethis")
    -    patterns <- paste(c("usethis::", usethis_exports, "litr::document\\("), collapse = "|")
    -    knitr::opts_hooks$set(eval = function(options) {
    -      if (options$eval)
    -        options$eval <- any(stringr::str_detect(options$code, patterns))
    -      return(options)
    -    })
    -  }
    -  
    -  
    -  # change usethis:::challenge_nested_project so that it will not complain
    -  # about creating a nested project (e.g. if this is called within a git 
    -  # subdirectory)
    -  utils::assignInNamespace("challenge_nested_project", function(...) NULL, ns = "usethis")
    -
    -  # define document hook to handle chunk references:
    -  knitr::knit_hooks$set(document = function(x) {
    -    # get the indices of x corresponding to code chunks
    -    chunk_start <- "^(\n```+[a-zA-Z0-9_]+\n)"
    -    idx_block <- stringr::str_which(x, chunk_start)
    -    original_code <- knitr::knit_code$get()
    -    # We first get indices of skipped chunks in original_code list
    -    skipped_chunks <- which(sapply(original_code, function(x){
    -      return(isFALSE(attr(x, "chunk_opts")$echo) || isFALSE(attr(x, "chunk_opts")$include))
    -    }))
    -
    -    # Next we remove the indices of skipped chunks
    -    original_code_idx_fixed <- setdiff(seq(length(original_code)), skipped_chunks)
    -    
    -    labels <- names(original_code)
    -    # replace each x[i] that has code in it with the original code
    -    for (i in seq_along(idx_block)) {
    -      # break code into multiple lines:
    -      chunk <- strsplit(x[idx_block[i]], "\n")[[1]]
    -      # get the fence used (in case it's more than three ticks):
    -      i_start <- stringr::str_which(chunk, "^```+[a-zA-Z0-9_]+")
    -      fence <- stringr::str_replace(chunk[i_start[1]],
    -                                    "^(```+)[a-zA-Z0-9_]+", "\\1")
    -      i_fences <- stringr::str_which(chunk, paste0("^", fence))
    -      # there can be multiple code and output chunks strung together 
    -      # within a single x[i] if results are not held to end
    -      i_all_code <- c()
    -      for (j in seq_along(i_start)) {
    -        # get the elements corresponding the j-th code chunk within chunk
    -        i_code_end <- i_fences[which(i_fences == i_start[j]) + 1]
    -        i_all_code <- c(i_all_code, i_start[j]:i_code_end)
    -      }
    -      i_all_code <- setdiff(i_all_code, i_start[1])
    -      chunk_no_code <- chunk[-i_all_code]
    -      chunk <- c(chunk_no_code[1:i_start[1]],
    -                 original_code[original_code_idx_fixed[i]][[1]],
    -                 # insert the original version, accounting for skipped chunks
    -                 fence)
    -      if (i_start[1] < length(chunk_no_code))
    -        chunk <- c(chunk, chunk_no_code[(i_start[1] + 1):length(chunk_no_code)])
    -        x[idx_block[i]] <- paste(chunk, collapse = "\n")
    -    }
    -    
    -    # replace code chunks with the original code
    -    # (so we'll still have <<label>> chunk references)
    -    refs <- c() # labels that get referred to
    -    for (label in labels) {
    -      refs <- c(refs, find_labels(original_code[[label]])$chunk_ids)
    -    }
    -    refs <- unique(refs)
    -    adj_labels <- labels[!labels %in% names(skipped_chunks)]
    -    ref_id <- match(refs, adj_labels)
    -    if (any(is.na(ref_id))) {
    -      stop(make_noticeable(paste(
    -        stringr::str_glue("The chunk reference <<{refs[is.na(ref_id)][1]}>> ",
    -        "is used, but there is no chunk with that label.", 
    -        sep = "\n"))))
    -      }
    -    to_insert <- paste0('###"', adj_labels[ref_id], '"###\n')
    -    x[idx_block[ref_id]] <- stringr::str_replace(x[idx_block[ref_id]],
    -                                                 chunk_start,
    -                                                 paste0("\\1", to_insert))
    -    x
    -  })
    -  
    -  # setup package_doc engine
    -  knitr::knit_engines$set(package_doc = function(options) {
    -    # create package_doc
    -    usethis::use_package_doc(open = FALSE)
    -    
    -    # insert the contents of the code chunk into the package_doc
    -    pkgdoc <- file.path("R", paste0(fs::path_file(package_dir), "-package.R"))
    -    add_text_to_file(options$code, filename = pkgdoc, location = 1)
    -    
    -    # now treat this as if it were standard R code with eval=FALSE
    -    r_engine <- knitr::knit_engines$get("R")
    -    options[["eval"]] <- FALSE
    -    return(r_engine(options))
    -  })
    -  return(original_knitr)
    -}
    +
    #' Code for setup chunk
    +#' 
    +#' * Creates directory where package will be. (Deletes what is currently there as 
    +#' long as it appears to have been created by litr and does not have any 
    +#' subsequent manual edits.)
    +#' * Sets the root directory to this directory
    +#' * Sets up the main chunk hook `litr::send_to_package()` that sends code to the 
    +#' R package directory.
    +#' * In the case that `minimal_eval=TRUE`, sets up an options hook for `eval` so
    +#'   chunks are only evaluated if there is a `usethis` or `litr::document()`
    +#'   command
    +#' * Deactivates an internal function of the `usethis` package
    +#' * Redefines the document output hook to handle chunk references differently  
    +#' * Sets up a [custom language engine](https://bookdown.org/yihui/rmarkdown-cookbook/custom-engine.html) called
    +#' `package_doc` that creates a package documentation file and then inserts
    +#' whatever the user puts in the chunk.
    +#' 
    +#' Returns the original state of the knitr objects that have been modified in 
    +#' setup.  This allows us to return things to the previous state after we are
    +#' finished.  This is relevant in the case where litr-knitting occurs in the 
    +#' current session and we don't want to leave things in a permanently modified
    +#' state.
    +#' 
    +#' @param package_dir Directory where R package will be created
    +<<param-minimal_eval>>
    +#' @keywords internal
    +setup <- function(package_dir, minimal_eval) {
    +  if (file.exists(package_dir)) {
    +    unedited <- tryCatch(check_unedited(package_dir),
    +                         error = function(e) {
    +                           # contents of package_dir does not resemble
    +                           # a litr package
    +                           return(FALSE)
    +                         })
    +    if (!unedited) {
    +      stop(make_noticeable(paste(
    +        stringr::str_glue("The directory {normalizePath(package_dir)}"),
    +        "already exists and either was not created by litr or may have manual",
    +        "edits. In either case, please rename that directory (or delete it)", 
    +        "and then try again.", 
    +        sep = "\n")))
    +    }
    +    unlink(package_dir, recursive = TRUE)
    +  }
    +  fs::dir_create(package_dir)
    +  usethis:::proj_set_(usethis:::proj_path_prep(package_dir))
    +
    +  # let's keep a version of the knitr objects before modifying them:
    +  original_knitr <- list(opts_knit = knitr::opts_knit$get(),
    +                         knit_hooks = knitr::knit_hooks$get(),
    +                         opts_chunk = knitr::opts_chunk$get(),
    +                         opts_hooks = knitr::opts_hooks$get(),
    +                         knit_engines = knitr::knit_engines$get()
    +                         )
    +  
    +  knitr::opts_knit$set(root.dir = package_dir) # sets wd of future chunks
    +  knitr::knit_hooks$set(send_to_package = send_to_package)
    +  knitr::opts_chunk$set(send_to_package = TRUE)
    +  if (minimal_eval) {
    +    # only evaluate chunks that appear to include usethis commands or 
    +    # a call to litr::document() but if someone has specifically set eval=FALSE
    +    # in a particular chunk, do honor that
    +    usethis_exports <- getNamespaceExports("usethis")
    +    patterns <- paste(c("usethis::", usethis_exports, "litr::document\\("), collapse = "|")
    +    knitr::opts_hooks$set(eval = function(options) {
    +      if (options$eval)
    +        options$eval <- any(stringr::str_detect(options$code, patterns))
    +      return(options)
    +    })
    +  }
    +  
    +  
    +  # change usethis:::challenge_nested_project so that it will not complain
    +  # about creating a nested project (e.g. if this is called within a git 
    +  # subdirectory)
    +  utils::assignInNamespace("challenge_nested_project", function(...) NULL, ns = "usethis")
    +
    +  # define document hook to handle chunk references:
    +  knitr::knit_hooks$set(document = function(x) {
    +    # get the indices of x corresponding to code chunks
    +    chunk_start <- "^(\n```+[a-zA-Z0-9_]+\n)"
    +    idx_block <- stringr::str_which(x, chunk_start)
    +    original_code <- knitr::knit_code$get()
    +    # We first get indices of skipped chunks in original_code list
    +    skipped_chunks <- which(sapply(original_code, function(x){
    +      return(isFALSE(attr(x, "chunk_opts")$echo) || isFALSE(attr(x, "chunk_opts")$include))
    +    }))
    +
    +    # Next we remove the indices of skipped chunks
    +    original_code_idx_fixed <- setdiff(seq(length(original_code)), skipped_chunks)
    +    
    +    labels <- names(original_code)
    +    # replace each x[i] that has code in it with the original code
    +    for (i in seq_along(idx_block)) {
    +      # break code into multiple lines:
    +      chunk <- strsplit(x[idx_block[i]], "\n")[[1]]
    +      # get the fence used (in case it's more than three ticks):
    +      i_start <- stringr::str_which(chunk, "^```+[a-zA-Z0-9_]+")
    +      fence <- stringr::str_replace(chunk[i_start[1]],
    +                                    "^(```+)[a-zA-Z0-9_]+", "\\1")
    +      i_fences <- stringr::str_which(chunk, paste0("^", fence))
    +      # there can be multiple code and output chunks strung together 
    +      # within a single x[i] if results are not held to end
    +      i_all_code <- c()
    +      for (j in seq_along(i_start)) {
    +        # get the elements corresponding the j-th code chunk within chunk
    +        i_code_end <- i_fences[which(i_fences == i_start[j]) + 1]
    +        i_all_code <- c(i_all_code, i_start[j]:i_code_end)
    +      }
    +      i_all_code <- setdiff(i_all_code, i_start[1])
    +      chunk_no_code <- chunk[-i_all_code]
    +      chunk <- c(chunk_no_code[1:i_start[1]],
    +                 original_code[original_code_idx_fixed[i]][[1]],
    +                 # insert the original version, accounting for skipped chunks
    +                 fence)
    +      if (i_start[1] < length(chunk_no_code))
    +        chunk <- c(chunk, chunk_no_code[(i_start[1] + 1):length(chunk_no_code)])
    +        x[idx_block[i]] <- paste(chunk, collapse = "\n")
    +    }
    +    
    +    # replace code chunks with the original code
    +    # (so we'll still have <<label>> chunk references)
    +    refs <- c() # labels that get referred to
    +    for (label in labels) {
    +      refs <- c(refs, find_labels(original_code[[label]])$chunk_ids)
    +    }
    +    refs <- unique(refs)
    +    adj_labels <- labels[!labels %in% names(skipped_chunks)]
    +    ref_id <- match(refs, adj_labels)
    +    if (any(is.na(ref_id))) {
    +      stop(make_noticeable(paste(
    +        stringr::str_glue("The chunk reference <<{refs[is.na(ref_id)][1]}>> ",
    +        "is used, but there is no chunk with that label.", 
    +        sep = "\n"))))
    +      }
    +    to_insert <- paste0('###"', adj_labels[ref_id], '"###\n')
    +    x[idx_block[ref_id]] <- stringr::str_replace(x[idx_block[ref_id]],
    +                                                 chunk_start,
    +                                                 paste0("\\1", to_insert))
    +    x
    +  })
    +  
    +  # setup package_doc engine
    +  knitr::knit_engines$set(package_doc = function(options) {
    +    # create package_doc
    +    usethis::use_package_doc(open = FALSE)
    +    
    +    # insert the contents of the code chunk into the package_doc
    +    pkgdoc <- file.path("R", paste0(fs::path_file(package_dir), "-package.R"))
    +    add_text_to_file(options$code, filename = pkgdoc, location = 1)
    +    
    +    # now treat this as if it were standard R code with eval=FALSE
    +    r_engine <- knitr::knit_engines$get("R")
    +    options[["eval"]] <- FALSE
    +    return(r_engine(options))
    +  })
    +  return(original_knitr)
    +}

    In our new document output hook defined above, we call a function find_labels(). It takes a block of code and returns both a logical vector of which lines contained chunk labels and another vector containing the labels of those referenced chunks. We define it here:

    -
    #' Find a .Rmd chunk label in a code chunk
    -#' 
    -#' @param chunk_code Character vector of code from a .Rmd code chunk. Each element is a line of the code chunk.
    -#' @return List where chunk_idx is a logical vector for each line of the chunk corresponding to whether a chunk label of the form `<<label>>` was found and chunk_ids is a character vector of chunk label was found in that chunk.
    -#' @keywords internal
    -find_labels <- function(chunk_code) {
    -  rc <- knitr::all_patterns$md$ref.chunk
    -  chunk_idx <- any(idx = grepl(rc, chunk_code))
    -  chunk_ids <- stringr::str_trim(sub(rc, "\\1", chunk_code[grepl(rc, chunk_code)]))
    -  return(list(chunk_idx = chunk_idx, chunk_ids = chunk_ids))
    -}
    +
    #' Find a .Rmd chunk label in a code chunk
    +#' 
    +#' @param chunk_code Character vector of code from a .Rmd code chunk. Each element is a line of the code chunk.
    +#' @return List where chunk_idx is a logical vector for each line of the chunk corresponding to whether a chunk label of the form `<<label>>` was found and chunk_ids is a character vector of chunk label was found in that chunk.
    +#' @keywords internal
    +find_labels <- function(chunk_code) {
    +  rc <- knitr::all_patterns$md$ref.chunk
    +  chunk_idx <- any(idx = grepl(rc, chunk_code))
    +  chunk_ids <- stringr::str_trim(sub(rc, "\\1", chunk_code[grepl(rc, chunk_code)]))
    +  return(list(chunk_idx = chunk_idx, chunk_ids = chunk_ids))
    +}

    The setup() function also uses a small function, make_noticeable(), which we define here:

    -
    #' Make error messages noticeable
    -#' 
    -#' Since litr error messages are amid a lot of output from knitting, we'd like 
    -#' the litr ones to be eye-catching.
    -#' 
    -#' @param msg Error message
    -#' @keywords internal
    -make_noticeable <- function(msg) {
    -  paste("",
    -        "======",
    -        "Please read your friendly litr error message here:",
    -        paste("> ", msg),
    -        "======",
    -        sep = "\n")
    -}
    +
    #' Make error messages noticeable
    +#' 
    +#' Since litr error messages are amid a lot of output from knitting, we'd like 
    +#' the litr ones to be eye-catching.
    +#' 
    +#' @param msg Error message
    +#' @keywords internal
    +make_noticeable <- function(msg) {
    +  paste("",
    +        "======",
    +        "Please read your friendly litr error message here:",
    +        paste("> ", msg),
    +        "======",
    +        sep = "\n")
    +}

    The code in this section used the fs and knitr packages, so we import those:

    -
    usethis::use_package("fs")
    -usethis::use_package("knitr")
    +
    usethis::use_package("fs")
    +usethis::use_package("knitr")
    ## ✔ Adding 'fs' to Imports field in DESCRIPTION
     ## • Refer to functions with `fs::fun()`
    ## ✔ Adding 'knitr' to Imports field in DESCRIPTION
    diff --git a/create-litr/_book/hash.html b/create-litr/_book/hash.html
    index 266007a..98729b2 100644
    --- a/create-litr/_book/hash.html
    +++ b/create-litr/_book/hash.html
    @@ -208,81 +208,81 @@ 

    5 Not overwriting a manually edited R package

    As described in the previous section, the function setup() will only overwrite a directory if it is the unedited output from using litr. The basic idea is that the function litr::render() when creating a new package finishes by adding a hash to the DESCRIPTION file. (And likewise when rmarkdown::render() is used with a litr output format, as described here.) This hash is a function of everything in the package, so if anything about the package changes (any file is modified, added, or removed) then the function check_unedited() will be able to detect that by recomputing the hash and seeing that it doesn’t match the hash in the DESCRIPTION file.

    Let’s start by defining the function hash_package_directory() that does the hashing. The hash is a function of everything in the outputted package except for that special line in the DESCRIPTION file with the hash. We use tools::md5sum() and digest::digest() to do the hashing.

    -
    #' Hash package directory
    -#' 
    -#' Gets an identifier that can be used to uniquely (whp) identify the current 
    -#' state of the package. This is formed by ignoring the `LitrId` field of the
    -#' DESCRIPTION file, which is the location where the output of this function is 
    -#' stored when `litr::render` generates the package.
    -#' 
    -#' @param package_dir Path to package
    -#' @keywords internal
    -hash_package_directory <- function(package_dir) {
    -  pkg_files <- fs::dir_ls(package_dir, recurse = TRUE, all = TRUE, type = "file")
    -  pkg_files <- stringr::str_subset(pkg_files, ".DS_Store$", negate = TRUE)
    -  pkg_files <- normalizePath(pkg_files)
    -  descr_file <- normalizePath(file.path(package_dir, "DESCRIPTION"))
    -  i_descr <- which(pkg_files == descr_file)
    -  if (length(i_descr) == 0) stop("Cannot find DESCRIPTION file.")
    -  txt_descr <- readLines(pkg_files[i_descr])
    -  txt_descr_mod <- stringr::str_subset(
    -    txt_descr, 
    -    stringr::str_glue("{description_litr_hash_field_name()}: .+$"),
    -    negate = TRUE)
    -  hashes <- as.character(tools::md5sum(pkg_files[-i_descr]))
    -  digest::digest(c(hashes, list(txt_descr_mod)))
    -}
    +
    #' Hash package directory
    +#' 
    +#' Gets an identifier that can be used to uniquely (whp) identify the current 
    +#' state of the package. This is formed by ignoring the `LitrId` field of the
    +#' DESCRIPTION file, which is the location where the output of this function is 
    +#' stored when `litr::render` generates the package.
    +#' 
    +#' @param package_dir Path to package
    +#' @keywords internal
    +hash_package_directory <- function(package_dir) {
    +  pkg_files <- fs::dir_ls(package_dir, recurse = TRUE, all = TRUE, type = "file")
    +  pkg_files <- stringr::str_subset(pkg_files, ".DS_Store$", negate = TRUE)
    +  pkg_files <- normalizePath(pkg_files)
    +  descr_file <- normalizePath(file.path(package_dir, "DESCRIPTION"))
    +  i_descr <- which(pkg_files == descr_file)
    +  if (length(i_descr) == 0) stop("Cannot find DESCRIPTION file.")
    +  txt_descr <- readLines(pkg_files[i_descr])
    +  txt_descr_mod <- stringr::str_subset(
    +    txt_descr, 
    +    stringr::str_glue("{description_litr_hash_field_name()}: .+$"),
    +    negate = TRUE)
    +  hashes <- as.character(tools::md5sum(pkg_files[-i_descr]))
    +  digest::digest(c(hashes, list(txt_descr_mod)))
    +}

    We used digest, so let’s import it:

    -
    usethis::use_package("digest")
    +
    usethis::use_package("digest")
    ## ✔ Adding 'digest' to Imports field in DESCRIPTION
     ## • Refer to functions with `digest::fun()`

    We will store this hash in a special field within the DESCRIPTION file. Let’s call this field LitrId. However, in case we ever decide to change the name of this field, it’s better that we only define it in one place. So we do this with the following function:

    -
    #' Generate litr hash field name for DESCRIPTION file
    -#' @keywords internal
    -description_litr_hash_field_name <- function() return("LitrId")
    +
    #' Generate litr hash field name for DESCRIPTION file
    +#' @keywords internal
    +description_litr_hash_field_name <- function() return("LitrId")

    Ok, now let’s write the function that litr::render() will call that will take the generated R package and add a line that puts the hash in the DESCRIPTION file under that special litr field:

    -
    #' Write the hash of the package to the DESCRIPTION file
    -#' 
    -#' @param package_dir Path to package
    -#' @keywords internal
    -write_hash_to_description <- function(package_dir) {
    -  desc_file <- file.path(package_dir, "DESCRIPTION")
    -  if (!file.exists(desc_file)) file.create(desc_file)
    -  hash <- hash_package_directory(package_dir)
    -  desc::desc_set(description_litr_hash_field_name(), hash, file = desc_file)
    -}
    +
    #' Write the hash of the package to the DESCRIPTION file
    +#' 
    +#' @param package_dir Path to package
    +#' @keywords internal
    +write_hash_to_description <- function(package_dir) {
    +  desc_file <- file.path(package_dir, "DESCRIPTION")
    +  if (!file.exists(desc_file)) file.create(desc_file)
    +  hash <- hash_package_directory(package_dir)
    +  desc::desc_set(description_litr_hash_field_name(), hash, file = desc_file)
    +}

    Let’s include the desc package, which helps us manipulate DESCRIPTION files.

    -
    usethis::use_package("desc")
    +
    usethis::use_package("desc")
    ## ✔ Adding 'desc' to Imports field in DESCRIPTION
     ## • Refer to functions with `desc::fun()`

    And of course we’ll need a function that can read the value of that field as well:

    -
    #' Get the hash of the package from the DESCRIPTION file
    -#' 
    -#' @param package_dir Path to package
    -#' @keywords internal
    -read_hash_from_description <- function(package_dir) {
    -  descr <- file.path(package_dir, "DESCRIPTION")
    -  if (!file.exists(descr)) stop("Cannot find DESCRIPTION file.")
    -  txt <- stringr::str_subset(
    -    readLines(descr), 
    -    stringr::str_glue("{description_litr_hash_field_name()}: .+$"))
    -  if (length(txt) > 1) stop("More than one hash found in DESCRIPTION.")
    -  if (length(txt) == 0) stop("No hash found in DESCRIPTION.")
    -  stringr::str_extract(txt, "\\S+$")
    -}
    +
    #' Get the hash of the package from the DESCRIPTION file
    +#' 
    +#' @param package_dir Path to package
    +#' @keywords internal
    +read_hash_from_description <- function(package_dir) {
    +  descr <- file.path(package_dir, "DESCRIPTION")
    +  if (!file.exists(descr)) stop("Cannot find DESCRIPTION file.")
    +  txt <- stringr::str_subset(
    +    readLines(descr), 
    +    stringr::str_glue("{description_litr_hash_field_name()}: .+$"))
    +  if (length(txt) > 1) stop("More than one hash found in DESCRIPTION.")
    +  if (length(txt) == 0) stop("No hash found in DESCRIPTION.")
    +  stringr::str_extract(txt, "\\S+$")
    +}

    With all this hash functionality in place, the function check_unedited() is actually quite simple to define:

    -
    #' Check if package directory is the unedited output of litr::render()
    -#' 
    -#' Uses hash stored in a special `litr` field of DESCRIPTION file to check that 
    -#' the current state of the R package directory is identical to its state at the
    -#' time that it was created by `litr::render()`.
    -#' 
    -#' @param package_dir Path to package
    -#' @keywords internal
    -check_unedited <- function(package_dir) {
    -  hash <- hash_package_directory(package_dir)
    -  hash == read_hash_from_description(package_dir)
    -}
    +
    #' Check if package directory is the unedited output of litr::render()
    +#' 
    +#' Uses hash stored in a special `litr` field of DESCRIPTION file to check that 
    +#' the current state of the R package directory is identical to its state at the
    +#' time that it was created by `litr::render()`.
    +#' 
    +#' @param package_dir Path to package
    +#' @keywords internal
    +check_unedited <- function(package_dir) {
    +  hash <- hash_package_directory(package_dir)
    +  hash == read_hash_from_description(package_dir)
    +}

    It simply computes the hash of the current package and checks whether that hash is the same as what was originally written to the DESCRIPTION file by litr::render().

    diff --git a/create-litr/_book/including-extras-for-litr.html b/create-litr/_book/including-extras-for-litr.html index 110fc3e..0563a20 100644 --- a/create-litr/_book/including-extras-for-litr.html +++ b/create-litr/_book/including-extras-for-litr.html @@ -209,26 +209,26 @@

    14 Including extras for lit

    14.1 README with hex sticker

    We include a README.Rmd and then generate the README.md based on it:

    -
    add_readme(file.path("..", "source-files", "README.Rmd"))
    +
    add_readme(file.path("..", "source-files", "README.Rmd"))
    ## ✔ Writing 'README.Rmd'
     ## ✔ Adding '^README\\.Rmd$' to '.Rbuildignore'
     ## ✔ Creating '.git/hooks/'
     ## ✔ Writing '.git/hooks/pre-commit'

    Let’s add the litr hex sticker too (which is referred to in the README).

    -
    add_hex_sticker(file.path("..", "source-files", "litr-hex.png"))
    +
    add_hex_sticker(file.path("..", "source-files", "litr-hex.png"))

    Let’s also add a figure we include in the README.

    -
    fs::file_copy(file.path("..", "source-files", "diagram3.png"),
    -              file.path("man", "figures"))
    +
    fs::file_copy(file.path("..", "source-files", "diagram3.png"),
    +              file.path("man", "figures"))

    14.2 Vignettes

    -
    add_vignettes(c(file.path("..", "source-files", "package-templates.Rmd"),
    -                file.path("..", "source-files", "packages-in-the-wild.Rmd"),
    -                file.path("..", "source-files", "faqs.Rmd"),
    -                file.path("..", "source-files", "basic-example.Rmd")))
    +
    add_vignettes(c(file.path("..", "source-files", "package-templates.Rmd"),
    +                file.path("..", "source-files", "packages-in-the-wild.Rmd"),
    +                file.path("..", "source-files", "faqs.Rmd"),
    +                file.path("..", "source-files", "basic-example.Rmd")))

    The templates vignette uses dplyr and stringr, so we add them as “Suggests” in the DESCRIPTION file:

    -
    usethis::use_package("dplyr", type = "Suggests")
    -usethis::use_package("stringr", type = "Suggests")
    +
    usethis::use_package("dplyr", type = "Suggests")
    +usethis::use_package("stringr", type = "Suggests")
    ## ✔ Adding 'dplyr' to Suggests field in DESCRIPTION
     ## • Use `requireNamespace("dplyr", quietly = TRUE)` to test if package is installed
     ## • Then directly refer to functions with `dplyr::fun()`
    @@ -238,7 +238,7 @@

    14.2 Vignettes

    14.3 A pkgdown site

    We’ll first add the github url to the DESCRIPTION file.

    -
    desc::desc_set("URL", "https://github.com/jacobbien/litr-project/tree/main/litr")
    +
    desc::desc_set("URL", "https://github.com/jacobbien/litr-project/tree/main/litr")
    ## Package: litr
     ## Title: Literate Programming for Writing R Packages
     ## Version: 0.9.1
    @@ -274,15 +274,15 @@ 

    14.3 A pkgdown site
    pkgdown_yml <- file.path("..", "source-files", "_pkgdown.yml")
    -add_pkgdown(pkgdown_yml)

    +
    pkgdown_yml <- file.path("..", "source-files", "_pkgdown.yml")
    +add_pkgdown(pkgdown_yml)
    ## ✔ Adding '^_pkgdown\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore'
    ## -- Installing package into temporary library -----------------------------------
     ## == Building pkgdown site =======================================================
    -## Reading from: '/Users/vossler/litr-project/litr'
    -## Writing to:   '/Users/vossler/litr-project/docs'
    +## Reading from: '/Users/jacobbien/Documents/GitHub/litr-project/litr'
    +## Writing to:   '/Users/jacobbien/Documents/GitHub/litr-project/docs'
     ## -- Initialising site -----------------------------------------------------------
     ## -- Building favicons -----------------------------------------------------------
     ## Building favicons with realfavicongenerator.net...
    @@ -342,7 +342,7 @@ 

    14.3 A pkgdown sitethis pkgdown vignette in our customizations. Here is the contents of the _pkgdown.yml that was used:

    -
    cat(readLines("../source-files/_pkgdown.yml"), sep = '\n')
    +
    cat(readLines("../source-files/_pkgdown.yml"), sep = '\n')
    destination: ../docs/
     
     url: ~
    diff --git a/create-litr/_book/including-templates.html b/create-litr/_book/including-templates.html
    index 3c9410c..0d4c17c 100644
    --- a/create-litr/_book/including-templates.html
    +++ b/create-litr/_book/including-templates.html
    @@ -208,124 +208,124 @@ 

    11 Including templates

    We now add the .Rmd templates to the package. We have the skeleton.Rmd defined in source-files. Note that paths are relative to the outputted package’s location.

    The first template is the simplest imaginable package with a single function:

    -
    usethis::use_rmarkdown_template(
    -  template_name = "Template To Make an R Package",
    -  template_dir = "make-an-r-package",
    -  template_description = "Template for an Rmd file for writing an R package using literate programming.",
    -  template_create_dir = FALSE
    -)
    -fs::file_copy(
    -  path = file.path(
    -    "..", "source-files", "make-an-r-package", "skeleton.Rmd"
    -    ), 
    -  new_path = file.path(
    -    "inst", "rmarkdown", "templates", "make-an-r-package", "skeleton"
    -    ), 
    -  overwrite = TRUE
    -)
    +
    usethis::use_rmarkdown_template(
    +  template_name = "Template To Make an R Package",
    +  template_dir = "make-an-r-package",
    +  template_description = "Template for an Rmd file for writing an R package using literate programming.",
    +  template_create_dir = FALSE
    +)
    +fs::file_copy(
    +  path = file.path(
    +    "..", "source-files", "make-an-r-package", "skeleton.Rmd"
    +    ), 
    +  new_path = file.path(
    +    "inst", "rmarkdown", "templates", "make-an-r-package", "skeleton"
    +    ), 
    +  overwrite = TRUE
    +)
    ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package/skeleton/'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package/template.yaml'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package/skeleton/skeleton.Rmd'

    The second template shows how to create a package with a dataset:

    -
    usethis::use_rmarkdown_template(
    -  template_name = "Template To Make an R Package With a Dataset",
    -  template_dir = "make-an-r-package-with-data",
    -  template_description = "Template for an Rmd file for writing an R package with a dataset using literate programming.",
    -  template_create_dir = FALSE
    -)
    -fs::file_copy(
    -  path = file.path(
    -    "..", "source-files", "make-an-r-package-with-data", "skeleton.Rmd"
    -    ), 
    -  new_path = file.path(
    -    "inst", "rmarkdown", "templates", "make-an-r-package-with-data", "skeleton"
    -    ), 
    -  overwrite = TRUE
    -)
    +
    usethis::use_rmarkdown_template(
    +  template_name = "Template To Make an R Package With a Dataset",
    +  template_dir = "make-an-r-package-with-data",
    +  template_description = "Template for an Rmd file for writing an R package with a dataset using literate programming.",
    +  template_create_dir = FALSE
    +)
    +fs::file_copy(
    +  path = file.path(
    +    "..", "source-files", "make-an-r-package-with-data", "skeleton.Rmd"
    +    ), 
    +  new_path = file.path(
    +    "inst", "rmarkdown", "templates", "make-an-r-package-with-data", "skeleton"
    +    ), 
    +  overwrite = TRUE
    +)
    ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-data/skeleton/'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-data/template.yaml'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-data/skeleton/skeleton.Rmd'

    The third template shows how to create a package that uses Rcpp:

    -
    usethis::use_rmarkdown_template(
    -  template_name = "Template To Make an R Package With Rcpp",
    -  template_dir = "make-an-r-package-with-rcpp",
    -  template_description = "Template for an Rmd file for writing an R package that makes use of Rcpp while using literate programming.",
    -  template_create_dir = FALSE
    -)
    -fs::file_copy(
    -  path = file.path(
    -    "..", "source-files", "make-an-r-package-with-rcpp", "skeleton.Rmd"
    -    ),
    -  new_path = file.path(
    -    "inst", "rmarkdown", "templates", "make-an-r-package-with-rcpp", "skeleton"
    -    ),
    -  overwrite = TRUE
    -)
    +
    usethis::use_rmarkdown_template(
    +  template_name = "Template To Make an R Package With Rcpp",
    +  template_dir = "make-an-r-package-with-rcpp",
    +  template_description = "Template for an Rmd file for writing an R package that makes use of Rcpp while using literate programming.",
    +  template_create_dir = FALSE
    +)
    +fs::file_copy(
    +  path = file.path(
    +    "..", "source-files", "make-an-r-package-with-rcpp", "skeleton.Rmd"
    +    ),
    +  new_path = file.path(
    +    "inst", "rmarkdown", "templates", "make-an-r-package-with-rcpp", "skeleton"
    +    ),
    +  overwrite = TRUE
    +)
    ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/skeleton/'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/template.yaml'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/skeleton/skeleton.Rmd'

    The fourth template shows how to create a package with “extras” such as a README, a vignette, and a pkgdown site:

    -
    usethis::use_rmarkdown_template(
    -  template_name = "Template To Make an R Package With a README, Vignette, and Pkgdown Site",
    -  template_dir = "make-an-r-package-with-extras",
    -  template_description = "Template for an Rmd file for writing an R package that has a README, vignette, and pkgdown site while using literate programming.",
    -  template_create_dir = FALSE
    -)
    -fs::file_copy(
    -  path = file.path(
    -    "..", "source-files", "make-an-r-package-with-extras", "skeleton.Rmd"
    -    ),
    -  new_path = file.path(
    -    "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton"
    -    ),
    -  overwrite = TRUE
    -)
    -fs::dir_copy(
    -  path = file.path(
    -    "..", "source-files", "make-an-r-package-with-extras", "source-files"
    -    ),
    -  new_path = file.path(
    -    "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton",
    -    "source-files"
    -    ),
    -  overwrite = TRUE
    -)
    +
    usethis::use_rmarkdown_template(
    +  template_name = "Template To Make an R Package With a README, Vignette, and Pkgdown Site",
    +  template_dir = "make-an-r-package-with-extras",
    +  template_description = "Template for an Rmd file for writing an R package that has a README, vignette, and pkgdown site while using literate programming.",
    +  template_create_dir = FALSE
    +)
    +fs::file_copy(
    +  path = file.path(
    +    "..", "source-files", "make-an-r-package-with-extras", "skeleton.Rmd"
    +    ),
    +  new_path = file.path(
    +    "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton"
    +    ),
    +  overwrite = TRUE
    +)
    +fs::dir_copy(
    +  path = file.path(
    +    "..", "source-files", "make-an-r-package-with-extras", "source-files"
    +    ),
    +  new_path = file.path(
    +    "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton",
    +    "source-files"
    +    ),
    +  overwrite = TRUE
    +)
    ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-extras/skeleton/'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-extras/template.yaml'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-extras/skeleton/skeleton.Rmd'

    The fifth template shows how to create a package from a bookdown site, i.e. instead of having just a single create-pkg.Rmd, we can have a series of .Rmd files that together create a bookdown:

    -
    usethis::use_rmarkdown_template(
    -  template_name = "Template To Make an R Package From a Bookdown",
    -  template_dir = "make-an-r-package-from-bookdown",
    -  template_description = "Template for a bookdown that defines an R package using literate programming.",
    -  template_create_dir = TRUE
    -)
    -fs::dir_copy(
    -  path = file.path("..", "source-files", "make-an-r-package-from-bookdown"),
    -  new_path = file.path(
    -    "inst", "rmarkdown", "templates", "make-an-r-package-from-bookdown", "skeleton"
    -    ),
    -  overwrite = TRUE
    -)
    +
    usethis::use_rmarkdown_template(
    +  template_name = "Template To Make an R Package From a Bookdown",
    +  template_dir = "make-an-r-package-from-bookdown",
    +  template_description = "Template for a bookdown that defines an R package using literate programming.",
    +  template_create_dir = TRUE
    +)
    +fs::dir_copy(
    +  path = file.path("..", "source-files", "make-an-r-package-from-bookdown"),
    +  new_path = file.path(
    +    "inst", "rmarkdown", "templates", "make-an-r-package-from-bookdown", "skeleton"
    +    ),
    +  overwrite = TRUE
    +)
    ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/skeleton/'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/template.yaml'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/skeleton/skeleton.Rmd'

    The sixth template shows how to create a package that uses RcppArmadillo:

    -
    usethis::use_rmarkdown_template(
    -  template_name = "Template To Make an R Package With RcppArmadillo",
    -  template_dir = "make-an-r-package-with-armadillo",
    -  template_description = "Template for an Rmd file for writing an R package that makes use of RcppArmadillo while using literate programming.",
    -  template_create_dir = FALSE
    -)
    -fs::file_copy(
    -  path = file.path(
    -    "..", "source-files", "make-an-r-package-with-armadillo", "skeleton.Rmd"
    -    ),
    -  new_path = file.path(
    -    "inst", "rmarkdown", "templates", "make-an-r-package-with-armadillo", "skeleton"
    -    ),
    -  overwrite = TRUE
    -)
    +
    usethis::use_rmarkdown_template(
    +  template_name = "Template To Make an R Package With RcppArmadillo",
    +  template_dir = "make-an-r-package-with-armadillo",
    +  template_description = "Template for an Rmd file for writing an R package that makes use of RcppArmadillo while using literate programming.",
    +  template_create_dir = FALSE
    +)
    +fs::file_copy(
    +  path = file.path(
    +    "..", "source-files", "make-an-r-package-with-armadillo", "skeleton.Rmd"
    +    ),
    +  new_path = file.path(
    +    "inst", "rmarkdown", "templates", "make-an-r-package-with-armadillo", "skeleton"
    +    ),
    +  overwrite = TRUE
    +)
    ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/skeleton/'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/template.yaml'
     ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/skeleton/skeleton.Rmd'
    diff --git a/create-litr/_book/index.html b/create-litr/_book/index.html index d1d45ae..8cf32ae 100644 --- a/create-litr/_book/index.html +++ b/create-litr/_book/index.html @@ -214,9 +214,9 @@

    1 Preambleliterate programming to define the litr R package. This means that all source code for litr is contained and presented in this document alongside explanation. The litr R package gets created as an output when one runs a piece of code that takes this document as input. To modify the litr package, one should modify the code in this document (and the explanation, as needed) and then regenerate the package. But what piece of code do we use to create an R package from this document?

    Well… it turns out that this is precisely what the litr package is for. But how can we use litr to create litr? We actually use version \(n-1\) of litr to create version \(n\). When an R markdown file is rendered with litr, an R package is created in addition to the usual html/pdf/etc document. To learn more about using litr, please visit the litr website. This document is not intended for people who wish to use litr. Rather, it is intended for people who want to know how it works and (possibly) modify it.

    This document used litr version 0.9.0 to define the current version of litr. In particular, it was generated by running the following command in an R console from the working directory containing index.Rmd:

    -
    litr::render("index.Rmd")
    -fs::dir_copy("_book", "../docs/create", overwrite = TRUE)
    -fs::dir_delete("_book")
    +
    litr::render("index.Rmd")
    +fs::dir_copy("_book", "../docs/create", overwrite = TRUE)
    +fs::dir_delete("_book")

    The second and third lines move the generated bookdown to the docs/ directory so that it will render nicely on github by visiting https://jacobbien.github.io/litr-project/create/.

    diff --git a/create-litr/_book/package-setup.html b/create-litr/_book/package-setup.html index e408fdc..2e78a51 100644 --- a/create-litr/_book/package-setup.html +++ b/create-litr/_book/package-setup.html @@ -207,23 +207,23 @@

    3 Package setup

    We start by specifying the information needed in the DESCRIPTION file of the R package. We mostly follow the R Packages book’s description of version numbering. Releases 0.0.1, 0.0.2, and 0.0.3 should really have been 0.1.0, 0.2.0, and 0.3.0, because these were not just patches. Rather, each added quite substantial new functionality. For this reason, we have gone straight from 0.0.3 to 0.4.0. We will keep the leftmost number (“major release”) at 0 until we feel like the package is complete with all the intended features.

    -
    usethis::create_package(
    -  path = ".",
    -  fields = list(
    -    Package = params$package_name,
    -    Version = "0.9.1",
    -    Title = "Literate Programming for Writing R Packages",
    -    Description = "Allows one to fully create an R package in a single .Rmd 
    -    file.  Includes functionality and .Rmd templates for a literate programming
    -    approach to R package development.",
    -    `Authors@R` = c(
    -      person("Jacob", "Bien", email = "jbien@usc.edu", role = c("aut", "cre")),
    -      person("Patrick Vossler", role = "aut")
    -      )
    -   )
    - )
    -
    -usethis::use_mit_license(copyright_holder = "J. Bien")
    +
    usethis::create_package(
    +  path = ".",
    +  fields = list(
    +    Package = params$package_name,
    +    Version = "0.9.1",
    +    Title = "Literate Programming for Writing R Packages",
    +    Description = "Allows one to fully create an R package in a single .Rmd 
    +    file.  Includes functionality and .Rmd templates for a literate programming
    +    approach to R package development.",
    +    `Authors@R` = c(
    +      person("Jacob", "Bien", email = "jbien@usc.edu", role = c("aut", "cre")),
    +      person("Patrick Vossler", role = "aut")
    +      )
    +   )
    + )
    +
    +usethis::use_mit_license(copyright_holder = "J. Bien")

    Let’s add some package-level documentation. This is what will show up when someone types package?litr in the console.

    #' Literate Programming for Writing R Packages
     #'
    @@ -249,21 +249,21 @@ 

    3.1 A note on circularityKeeping track of the version of litr used is particularly important when using litr to develop litr. There is a tendency to want to use the new functionality that we are creating in create-litr.Rmd itself as soon as we have defined it. However, this is circular and thus must be avoided. To see why we need to be careful, let’s consider an actual example that arose when working on litr version 0.0.3. After release v0.0.2, one of the new features we added is a new language engine called package_doc that allows us to have a special kind of code block defining the package documentation. In trying out this feature and making sure it works on skeleton.Rmd, we would most likely install version 0.0.3. Now that version 0.0.3 is installed, there will be a tendency to want to add a package_doc block to create-litr.Rmd, and it will appear to work. However, this is circular, because we have used version 0.0.3 to create version 0.0.3! In particular, if we remove litr and re-install it from github, we will get version 0.0.2 so that when we attempt to create the package using litr::render("create-litr.Rmd"), we will get an error telling us that it doesn’t have a language engine named package_doc.

    The code chunk in this section is for preventing this from happening.

    For more on circularity, see the section on testing litr.

    -
    install_version_of_litr <- utils::packageVersion("litr")
    -remote <- remotes::github_remote(
    -  repo = "jacobbien/litr-project@*release",
    -  subdir = "litr"
    -  )
    -version_of_latest_release <- stringr::str_remove(remote$ref, "v")
    -if (install_version_of_litr != version_of_latest_release)
    -  stop(stringr::str_glue(
    -    "You should be using the version of litr from the latest release (version",
    -    " {version_of_latest_release}),\n but you are using version",
    -    " {install_version_of_litr}.\n",
    -    "You can install the release version of litr from GitHub by running the",
    -    " the following command:\n",
    -    "remotes::install_github(repo='jacobbien/litr-project@*release', subdir = 'litr')"
    -  ))
    +
    install_version_of_litr <- utils::packageVersion("litr")
    +remote <- remotes::github_remote(
    +  repo = "jacobbien/litr-project@*release",
    +  subdir = "litr"
    +  )
    +version_of_latest_release <- stringr::str_remove(remote$ref, "v")
    +if (install_version_of_litr != version_of_latest_release)
    +  stop(stringr::str_glue(
    +    "You should be using the version of litr from the latest release (version",
    +    " {version_of_latest_release}),\n but you are using version",
    +    " {install_version_of_litr}.\n",
    +    "You can install the release version of litr from GitHub by running the",
    +    " the following command:\n",
    +    "remotes::install_github(repo='jacobbien/litr-project@*release', subdir = 'litr')"
    +  ))

    diff --git a/create-litr/_book/rendering.html b/create-litr/_book/rendering.html index f1c4a4b..6c75372 100644 --- a/create-litr/_book/rendering.html +++ b/create-litr/_book/rendering.html @@ -215,11 +215,11 @@

    7 Altering the rendering process<

    Although these are presented as two separate approaches, we have written litr::render() so that if a user passes one of the litr output formats to litr::render(), it will still work.

    We encourage users to use litr::render()1 rather than rmarkdown::render() since in litr::render() we’re able to wrap the call to rmarkdown::render() in the function with_cleanup(). This ensures that, if an error occurs during the knitting process, the special litr hash will still be created. This is desirable since it means that the next time we try to litr-knit, we will not get an error about overwriting a manually edited package directory.

    When coding an R package with litr, sometimes there are code chunks that can take a while to evaluate (e.g., tests), which slows down the coding process. We therefore provide an argument (to both litr::render() and the various litr output formats) that allows for “minimal eval” to occur. The goal is to allow the R package to be updated completely but without any of the code chunks being evaluated, except those whose involving usethis or a call to litr::document(), since these commands lead to changes in the R package itself. Here is the documentation associated with the minimal_eval parameter, which is an argument to a number of functions in this section:

    -
    ###"param-minimal_eval"###
    -#' @param minimal_eval If `TRUE`, then only chunks with `litr::document()` or 
    -#' `usethis` commands will be evaluated.  This can be convenient in coding when 
    -#' you just want to quickly update the R package without having to wait for long
    -#' evaluations to occur.
    +
    ###"param-minimal_eval"###
    +#' @param minimal_eval If `TRUE`, then only chunks with `litr::document()` or 
    +#' `usethis` commands will be evaluated.  This can be convenient in coding when 
    +#' you just want to quickly update the R package without having to wait for long
    +#' evaluations to occur.

    7.1 Defining litr output formats

    The function rmarkdown::render() allows for customizable behavior through the use of custom output formats.

    @@ -230,81 +230,81 @@

    7.1 Defining litr ou
  • It adds a marker (litr_format <- TRUE) that will help litr::render() know when a litr output format is being passed to it.

  • We present the function and then describe the details of the new pre_knit() and post_processor() functions below.

    -
    #' Modify an existing output format to have `litr` behavior
    -#' 
    -#' This function modifies the `pre_knit()` and `post_processor()` functions of a
    -#' preexisting output format so that it will have the `litr` behavior (meaning that an R package will be created when `rmarkdown::render()` is called).
    -#' 
    -#' @param base_format a preexisting, non-litr output format such as `rmarkdown::html_document`
    -<<param-minimal_eval>>
    -#' @export
    -litrify_output_format <- function(base_format = rmarkdown::html_document,
    -                                  minimal_eval = FALSE) {
    -  force(base_format) # I think using force here is advisable?
    -  force(minimal_eval) # https://adv-r.hadley.nz/function-factories.html
    -  function(...) {
    -    old <- base_format(...)
    -    new <- old
    -    new$original_knitr_objects <- list()
    -    new$pre_knit <- function(...) {
    -      args <- list(...)
    -      input <- args$input
    -      params <- knitr::knit_params(readLines(input))
    -      package_dir <- get_package_directory(
    -        params$package_parent_dir$value,
    -        params$package_name$value,
    -        input)
    -      new$original_knitr_objects <<- litr:::setup(package_dir, minimal_eval)
    -      if (!is.null(old$pre_knit)) old$pre_knit(...)
    -    }
    -
    -    new$post_processor <- function(metadata, input_file, output_file, ...) {
    -      # typically the post_processor function returns the output file path
    -      # if old$post_processor is NULL, as in the case of pdf_document,
    -      # then R will throw an error when trying to call old$post_processor
    -      # if we only add a check for non null old$post_processor and otherwise
    -      # set out <- NULL then R will throw an error later in rmarkdown::render
    -      # since output_file is set to the output of the post_processor if 
    -      # output_format$post_processor is not null (See line 478 in rmarkdown::render)
    -      # Therefore, our solution is to set out to the output_file path if old$post_process is null.
    -      if (!is.null(old$post_processor)){
    -        out <- old$post_processor(metadata, input_file, output_file, ...)  
    -      } else {
    -        out <- output_file 
    -      }
    -      package_dir <- get_package_directory(
    -        metadata$params$package_parent_dir,
    -        metadata$params$package_name,
    -        input_file
    -      )
    -      # remove .Rproj and .gitignore if usethis::create_package() added these
    -      remove_rstudio_extras(package_dir)
    -
    -      # add to DESCRIPTION file the version of litr used to create package:
    -      write_version_to_description(package_dir)
    -
    -      # add litr hash so we can tell later if package files were manually edited:
    -      write_hash_to_description(package_dir)
    -      
    -      out
    -    }
    -    
    -    new$on_exit <- function() {
    -      old$on_exit()
    -      
    -      # restore knitr to its original state
    -      restore_knitr_objects(new$original_knitr_objects)
    -    }
    -    
    -    # mark this as a litr_format
    -    new$litr_format <- TRUE
    -    
    -    # litr formats have minimal_eval as an option
    -    new$minimal_eval <- minimal_eval
    -
    -    new
    -  }
    -}
    +
    #' Modify an existing output format to have `litr` behavior
    +#' 
    +#' This function modifies the `pre_knit()` and `post_processor()` functions of a
    +#' preexisting output format so that it will have the `litr` behavior (meaning that an R package will be created when `rmarkdown::render()` is called).
    +#' 
    +#' @param base_format a preexisting, non-litr output format such as `rmarkdown::html_document`
    +<<param-minimal_eval>>
    +#' @export
    +litrify_output_format <- function(base_format = rmarkdown::html_document,
    +                                  minimal_eval = FALSE) {
    +  force(base_format) # I think using force here is advisable?
    +  force(minimal_eval) # https://adv-r.hadley.nz/function-factories.html
    +  function(...) {
    +    old <- base_format(...)
    +    new <- old
    +    new$original_knitr_objects <- list()
    +    new$pre_knit <- function(...) {
    +      args <- list(...)
    +      input <- args$input
    +      params <- knitr::knit_params(readLines(input))
    +      package_dir <- get_package_directory(
    +        params$package_parent_dir$value,
    +        params$package_name$value,
    +        input)
    +      new$original_knitr_objects <<- litr:::setup(package_dir, minimal_eval)
    +      if (!is.null(old$pre_knit)) old$pre_knit(...)
    +    }
    +
    +    new$post_processor <- function(metadata, input_file, output_file, ...) {
    +      # typically the post_processor function returns the output file path
    +      # if old$post_processor is NULL, as in the case of pdf_document,
    +      # then R will throw an error when trying to call old$post_processor
    +      # if we only add a check for non null old$post_processor and otherwise
    +      # set out <- NULL then R will throw an error later in rmarkdown::render
    +      # since output_file is set to the output of the post_processor if 
    +      # output_format$post_processor is not null (See line 478 in rmarkdown::render)
    +      # Therefore, our solution is to set out to the output_file path if old$post_process is null.
    +      if (!is.null(old$post_processor)){
    +        out <- old$post_processor(metadata, input_file, output_file, ...)  
    +      } else {
    +        out <- output_file 
    +      }
    +      package_dir <- get_package_directory(
    +        metadata$params$package_parent_dir,
    +        metadata$params$package_name,
    +        input_file
    +      )
    +      # remove .Rproj and .gitignore if usethis::create_package() added these
    +      remove_rstudio_extras(package_dir)
    +
    +      # add to DESCRIPTION file the version of litr used to create package:
    +      write_version_to_description(package_dir)
    +
    +      # add litr hash so we can tell later if package files were manually edited:
    +      write_hash_to_description(package_dir)
    +      
    +      out
    +    }
    +    
    +    new$on_exit <- function() {
    +      old$on_exit()
    +      
    +      # restore knitr to its original state
    +      restore_knitr_objects(new$original_knitr_objects)
    +    }
    +    
    +    # mark this as a litr_format
    +    new$litr_format <- TRUE
    +    
    +    # litr formats have minimal_eval as an option
    +    new$minimal_eval <- minimal_eval
    +
    +    new
    +  }
    +}
    1. The pre_knit() function is modified so that setup() is called before the preexisting output format’s pre_knit() function is called. As the name suggests, this is a function that gets called before knitting. The purpose of the call to setup() is to create the R package directory and make it so that when we knit the file using rmarkdown::render(), a lot of special things will happen, such as code being sent to the R package directory. The function setup() returns the state of the knitr settings before any changes were made. This previous state of the knitr settings will be restored at the end of the rendering process in on_exit().

    2. The post_processor() function is modified so that the DESCRIPTION file gets marked with the version of litr used and with the litr hash (as already described here). Some special care is taken for the case that the original output format doesn’t have a post processor (e.g., this is the case for the pdf_document output format). The particulars of this are given in a comment in the code chunk above.

    3. @@ -312,455 +312,454 @@

      7.1 Defining litr ou

    We use the above function to create some litr versions of common output formats, as seen in the next few subsections.

    Before proceeding, we define the function write_version_to_description() that is called above.

    -
    #' Generate litr version field name for DESCRIPTION file
    -#' @keywords internal
    -description_litr_version_field_name <- function() return("LitrVersionUsed")
    -
    #' Write the version of litr used to the DESCRIPTION file
    -#' 
    -#' @param package_dir Path to package
    -#' @keywords internal
    -write_version_to_description <- function(package_dir) {
    -  ver <- as.character(utils::packageVersion("litr"))
    -  add_text_to_file(
    -    txt = stringr::str_glue("{description_litr_version_field_name()}: {ver}"),
    -    filename = file.path(package_dir, "DESCRIPTION"),
    -    req_exist = TRUE
    -    )
    -}
    +
    #' Generate litr version field name for DESCRIPTION file
    +#' @keywords internal
    +description_litr_version_field_name <- function() return("LitrVersionUsed")
    +
    #' Write the version of litr used to the DESCRIPTION file
    +#' 
    +#' @param package_dir Path to package
    +#' @keywords internal
    +write_version_to_description <- function(package_dir) {
    +  ver <- as.character(utils::packageVersion("litr"))
    +  add_text_to_file(
    +    txt = stringr::str_glue("{description_litr_version_field_name()}: {ver}"),
    +    filename = file.path(package_dir, "DESCRIPTION"),
    +    req_exist = TRUE
    +    )
    +}

    Also, we made use of a small function for getting the package directory based on the input file’s location and the parameters that are being used in the rendering process. We define it here:

    -
    #' Get package directory
    -#' 
    -#' @param package_parent_dir The directory of where the package should go (relative to the input directory)
    -#' @param package_name The name of the package
    -#' @param input The file name of the input
    -#' @keywords internal
    -get_package_directory <- function(package_parent_dir, package_name, input) {
    -  if (package_parent_dir == ".")
    -    return(file.path(dirname(input), package_name))
    -  file.path(dirname(input), package_parent_dir, package_name)
    -}
    +
    #' Get package directory
    +#' 
    +#' @param package_parent_dir The directory of where the package should go (relative to the input directory)
    +#' @param package_name The name of the package
    +#' @param input The file name of the input
    +#' @keywords internal
    +get_package_directory <- function(package_parent_dir, package_name, input) {
    +  if (package_parent_dir == ".")
    +    return(file.path(dirname(input), package_name))
    +  file.path(dirname(input), package_parent_dir, package_name)
    +}

    Let’s write some tests to make sure it’s behaving as expected:

    -
    testthat::test_that("get_package_directory() works", {
    -  input <- file.path("inputdir", "input.Rmd")
    -  testthat::expect_equal(
    -    get_package_directory(".", "mypkg", input),
    -    file.path("inputdir", "mypkg") # inputdir/mypkg
    -  )
    -  testthat::expect_equal(
    -    get_package_directory("..", "mypkg", input),
    -    file.path("inputdir", "..", "mypkg") # inputdir/../mypkg
    -  )
    -})
    +
    testthat::test_that("get_package_directory() works", {
    +  input <- file.path("inputdir", "input.Rmd")
    +  testthat::expect_equal(
    +    get_package_directory(".", "mypkg", input),
    +    file.path("inputdir", "mypkg") # inputdir/mypkg
    +  )
    +  testthat::expect_equal(
    +    get_package_directory("..", "mypkg", input),
    +    file.path("inputdir", "..", "mypkg") # inputdir/../mypkg
    +  )
    +})
    ## Test passed

    7.1.1 .pdf output format

    We want our .pdf documents to accurately display the “logging” output from functions in packages such as devtools that use special ANSI escape codes for displaying information in the terminal. Unfortunately, these codes use escape characters which cause problems when creating .pdf versions of our documents. While it is relatively straightforward to map ANSI escape codes to HTML tags, as we will see in the .html output format section, converting these escape codes to Latex commands is more complicated. As a result, we define a post_knit function in our litr_pdf_document format to avoid this issue by stripping out all escape codes in the file before it is converted into a .tex file and then compiled into a .pdf document.

    Specifically, the post_knit function modifies the intermediate .knit.md file, which contains both the .Rmd file, as well as the output of each code chunk. Thus, we can inspect the output text of each code chunk and remove any ANSI escape codes before the .knit.md is converted to a .tex file and then a .pdf document. We rely upon two internal functions from the fansi R package to remove all possible escape codes and return a clean character vector.

    -
    #' litr version of `rmarkdown::pdf_document()`
    -#' 
    -#' This behaves exactly like `rmarkdown::pdf_document()` except it creates an 
    -#' R package.
    -#' 
    -<<param-minimal_eval>>
    -#' @param ... Parameters to be passed to `rmarkdown::pdf_document()` 
    -#' @export
    -litr_pdf_document <- function(minimal_eval = FALSE, ...) {
    -  litr_pdf_document_ <- litrify_output_format(rmarkdown::pdf_document,
    -                                              minimal_eval = minimal_eval)
    -  old <- litr_pdf_document_(...)
    -  new <- old
    -
    -  # post_knit
    -  new$post_knit = function(...){
    -    args = list(...)
    -    input_filename <- args[[2]]
    -    knitted_filename <- fs::path_ext_set(input_filename, ".knit.md")
    -    knitted_output <- readLines(knitted_filename)
    -    cleaned_output <- sapply(1:length(knitted_output), function(i){
    -      test_str <- knitted_output[i]
    -      fansi:::VAL_IN_ENV(x=test_str, ctl="all", warn=TRUE, warn.mask=fansi:::get_warn_mangled())
    -      .Call(fansi:::FANSI_strip_csi, test_str, CTL.INT, WARN.INT)
    -    })
    -    writeLines(cleaned_output, knitted_filename)
    -  }
    -  new
    -}
    +
    #' litr version of `rmarkdown::pdf_document()`
    +#' 
    +#' This behaves exactly like `rmarkdown::pdf_document()` except it creates an 
    +#' R package.
    +#' 
    +<<param-minimal_eval>>
    +#' @param ... Parameters to be passed to `rmarkdown::pdf_document()` 
    +#' @export
    +litr_pdf_document <- function(minimal_eval = FALSE, ...) {
    +  litr_pdf_document_ <- litrify_output_format(rmarkdown::pdf_document,
    +                                              minimal_eval = minimal_eval)
    +  old <- litr_pdf_document_(...)
    +  new <- old
    +
    +  # post_knit
    +  new$post_knit = function(...){
    +    args = list(...)
    +    input_filename <- args[[2]]
    +    knitted_filename <- fs::path_ext_set(input_filename, ".knit.md")
    +    knitted_output <- readLines(knitted_filename)
    +    cleaned_output <- sapply(1:length(knitted_output), function(i){
    +      test_str <- knitted_output[i]
    +      fansi:::VAL_IN_ENV(x=test_str, ctl="all", warn=TRUE, warn.mask=fansi:::get_warn_mangled())
    +      .Call(fansi:::FANSI_strip_csi, test_str, CTL.INT, WARN.INT)
    +    })
    +    writeLines(cleaned_output, knitted_filename)
    +  }
    +  new
    +}

    Since the above section uses the fansi package for handling ANSI escape sequences, we include it in our package:

    -
    usethis::use_package("fansi")
    +
    usethis::use_package("fansi")
    ## ✔ Adding 'fansi' to Imports field in DESCRIPTION
     ## • Refer to functions with `fansi::fun()`

    7.1.2 .html output format

    For .html documents, we’d like to add a bit more in the postprocessing step. In particular, we include some special function and chunk hyperlinking behavior described below. The function add_function_hyperlinks() processes the outputted .html file(s), making it so that one can easily navigate to function definitions. (This function is described lower in this section.) We likewise call a function add_chunk_label_hyperlinks(), which makes chunk references into clickable links. In particular, the chunk reference <<my-chunk>> within a code chunk would link to a chunk named “my-chunk” that begins with ###"my-chunk"###. The ###"my-chunk"### line is added by a document hook defined in setup(). Finally, we replace ANSI sequences with HTML tag equivalents (the need for this is explained in the section on the .pdf output format).

    -
    #' litr version of `rmarkdown::html_document()`
    -#' 
    -#' This behaves like `rmarkdown::html_document()` with a few differences:
    -#' - It creates an R package.
    -#' - It adds hyperlinks to function definitions whenever a function is used
    -#' elsewhere in the document.
    -#' - It does "Knuth-style" chunk referencing with hyperlinks.
    -#' 
    -<<param-minimal_eval>>
    -#' @param ... Parameters to be passed to `rmarkdown::pdf_document()` 
    -#' @export
    -litr_html_document <- function(minimal_eval = FALSE, ...) {
    -  litr_html_document_ <- litrify_output_format(rmarkdown::html_document,
    -                                               minimal_eval = minimal_eval)
    -  old <- litr_html_document_(...)
    -  new <- old
    -  # modify post_processor
    -  new$post_processor = function(metadata, input_file, output_file, ...) {
    -    out <- old$post_processor(metadata, input_file, output_file, ...)
    -    # html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$")
    -    # add hyperlinks within html output to make it easier to navigate:
    -    add_function_hyperlinks(output_file, metadata$params$package_name)
    -    add_chunk_label_hyperlinks(output_file)
    -    # replace ANSI sequences with HTML tag equivalents
    -    replace_ansi_sequences(output_file)
    -    out
    -  }
    -  new
    -}
    +
    #' litr version of `rmarkdown::html_document()`
    +#' 
    +#' This behaves like `rmarkdown::html_document()` with a few differences:
    +#' - It creates an R package.
    +#' - It adds hyperlinks to function definitions whenever a function is used
    +#' elsewhere in the document.
    +#' - It does "Knuth-style" chunk referencing with hyperlinks.
    +#' 
    +<<param-minimal_eval>>
    +#' @param ... Parameters to be passed to `rmarkdown::pdf_document()` 
    +#' @export
    +litr_html_document <- function(minimal_eval = FALSE, ...) {
    +  litr_html_document_ <- litrify_output_format(rmarkdown::html_document,
    +                                               minimal_eval = minimal_eval)
    +  old <- litr_html_document_(...)
    +  new <- old
    +  # modify post_processor
    +  new$post_processor = function(metadata, input_file, output_file, ...) {
    +    out <- old$post_processor(metadata, input_file, output_file, ...)
    +    # html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$")
    +    # add hyperlinks within html output to make it easier to navigate:
    +    add_function_hyperlinks(output_file, metadata$params$package_name)
    +    add_chunk_label_hyperlinks(output_file)
    +    # replace ANSI sequences with HTML tag equivalents
    +    replace_ansi_sequences(output_file)
    +    out
    +  }
    +  new
    +}

    We describe these two add_*_hyperlinks() functions next.

    The function add_function_hyperlinks() looks for foo followed by <- function( and then wraps foo in a span tag with id="foo"; whenever foo is found elsewhere in the document, it calls the insert_hrefs() function to wrap a a href="file.html#foo" tag (where file.html is the file where foo is defined), so that it will be a hyperlink to foo’s definition.

    -
    #' Add hyperlinks to function definitions
    -#' 
    -#' Finds functions that are defined in the html file(s) by looking for text of the 
    -#' form `foo <- function(` and then wraps `foo` in a `span` tag with `id="foo"` 
    -#' and then whenever `foo` is found it wraps a `a href="file.html#foo"` tag so 
    -#' that it will be a hyperlink to `foo`'s definition.
    -#' 
    -#' @param html_files Character vector of file names of html files that were created
    -#' from Rmd files
    -#' @param pkg_name Name of the package created by litr. Taken from YAML front matter
    -#' @keywords internal
    -add_function_hyperlinks <- function(html_files, pkg_name) {
    -  find_function_defs <- function(html_file) {
    -    txt <- readLines(html_file)
    -    start_line <- which(txt == "<body>")
    -    pattern1 <- '([a-zA-Z0-9_.]+)(\\s*&lt;-\\s*function)'
    -    pattern2 <- stringr::str_replace(pattern1,
    -                                     '&lt;-',
    -                                     '<span class="ot">&lt;-</span>')
    -    pattern2 <- stringr::str_replace(pattern2,
    -                                     'function',
    -                                     '<span class="cf">function</span>')
    -    # find functions that are defined in this file:
    -    function_names <- character(0)
    -    for (pattern in c(pattern1, pattern2)) {
    -      for (i in seq(start_line + 1, length(txt))) {
    -        fn_name <- stringr::str_match(txt[i], pattern)[, 2]
    -        if(is.na(fn_name)) next
    -        # a function was defined in this line, so put a span around it
    -        txt[i] <- stringr::str_replace(
    -          txt[i],
    -          pattern,
    -          stringr::str_glue("<span id='{fn_name}'>\\1</span>\\2")
    -        )
    -        # and keep track of it for later:
    -        function_names <- c(function_names, fn_name)
    -      }
    -    }
    -    list(function_names = function_names, txt = txt)
    -  }
    -  fdefs <- lapply(html_files, find_function_defs)
    -  all_function_names <- unlist(lapply(fdefs, function(lst) lst$function_names))
    -  # if a function is defined multiple times, then it's ambiguous where to link to
    -  # so let's not try linking to it (this can occur when a function is defined 
    -  # within a function, such as `new$post_processor()`)
    -  repeated <- names(which(table(all_function_names) > 1))
    -  all_function_names <- setdiff(all_function_names, repeated)
    -  if (length(all_function_names) == 0) {
    -    # no functions defined in package, so nothing more to be done here
    -    return()
    -  }
    -  num_per_file <- unlist(lapply(fdefs, 
    -                                function(lst) {
    -                                  length(setdiff(lst$function_names, repeated))
    -                                }))
    -  where_defined <- rep(fs::path_file(html_files), times = num_per_file)
    -  defined_functions_pattern <- paste0("(::)?",all_function_names, "\\(", collapse = "|")
    -  # There's also this case: <span class="fu">myfunction</span>
    -  defined_functions_pattern2 <- paste0(
    -    '<span class="fu">', all_function_names, '</span>\\(',
    -    collapse = "|")
    -  
    -  for (i in seq_along(html_files)) {
    -    # whenever one of the defined functions is named, link to its definition
    -    # using the format `file_where_foo_is_defined.html#foo`
    -    modified_txt <- insert_hrefs(fdefs[[i]]$txt, defined_functions_pattern,
    -                                 where_defined, all_function_names, pkg_name)
    -    modified_txt <- insert_hrefs(modified_txt, defined_functions_pattern2,
    -                                 where_defined, all_function_names, pkg_name, remove_span=TRUE)
    -    writeLines(modified_txt, con = html_files[i])
    -  }
    -}
    +
    #' Add hyperlinks to function definitions
    +#' 
    +#' Finds functions that are defined in the html file(s) by looking for text of the 
    +#' form `foo <- function(` and then wraps `foo` in a `span` tag with `id="foo"` 
    +#' and then whenever `foo` is found it wraps a `a href="file.html#foo"` tag so 
    +#' that it will be a hyperlink to `foo`'s definition.
    +#' 
    +#' @param html_files Character vector of file names of html files that were created
    +#' from Rmd files
    +#' @param pkg_name Name of the package created by litr. Taken from YAML front matter
    +#' @keywords internal
    +add_function_hyperlinks <- function(html_files, pkg_name) {
    +  find_function_defs <- function(html_file) {
    +    txt <- readLines(html_file)
    +    start_line <- which(txt == "<body>")
    +    pattern1 <- '([a-zA-Z0-9_.]+)(\\s*&lt;-\\s*function)'
    +    pattern2 <- stringr::str_replace(pattern1,
    +                                     '&lt;-',
    +                                     '<span class="ot">&lt;-</span>')
    +    pattern2 <- stringr::str_replace(pattern2,
    +                                     'function',
    +                                     '<span class="cf">function</span>')
    +    # find functions that are defined in this file:
    +    function_names <- character(0)
    +    for (pattern in c(pattern1, pattern2)) {
    +      for (i in seq(start_line + 1, length(txt))) {
    +        fn_name <- stringr::str_match(txt[i], pattern)[, 2]
    +        if(is.na(fn_name)) next
    +        # a function was defined in this line, so put a span around it
    +        txt[i] <- stringr::str_replace(
    +          txt[i],
    +          pattern,
    +          stringr::str_glue("<span id='{fn_name}'>\\1</span>\\2")
    +        )
    +        # and keep track of it for later:
    +        function_names <- c(function_names, fn_name)
    +      }
    +    }
    +    list(function_names = function_names, txt = txt)
    +  }
    +  fdefs <- lapply(html_files, find_function_defs)
    +  all_function_names <- unlist(lapply(fdefs, function(lst) lst$function_names))
    +  # if a function is defined multiple times, then it's ambiguous where to link to
    +  # so let's not try linking to it (this can occur when a function is defined 
    +  # within a function, such as `new$post_processor()`)
    +  repeated <- names(which(table(all_function_names) > 1))
    +  all_function_names <- setdiff(all_function_names, repeated)
    +  if (length(all_function_names) == 0) {
    +    # no functions defined in package, so nothing more to be done here
    +    return()
    +  }
    +  num_per_file <- unlist(lapply(fdefs, 
    +                                function(lst) {
    +                                  length(setdiff(lst$function_names, repeated))
    +                                }))
    +  where_defined <- rep(fs::path_file(html_files), times = num_per_file)
    +  defined_functions_pattern <- paste0("(::)?",all_function_names, "\\(", collapse = "|")
    +  # There's also this case: <span class="fu">myfunction</span>
    +  defined_functions_pattern2 <- paste0(
    +    '<span class="fu">', all_function_names, '</span>\\(',
    +    collapse = "|")
    +  
    +  for (i in seq_along(html_files)) {
    +    # whenever one of the defined functions is named, link to its definition
    +    # using the format `file_where_foo_is_defined.html#foo`
    +    modified_txt <- insert_hrefs(fdefs[[i]]$txt, defined_functions_pattern,
    +                                 where_defined, all_function_names, pkg_name)
    +    modified_txt <- insert_hrefs(modified_txt, defined_functions_pattern2,
    +                                 where_defined, all_function_names, pkg_name, remove_span=TRUE)
    +    writeLines(modified_txt, con = html_files[i])
    +  }
    +}

    We define next the helper function insert_hrefs(), which was called in the previous function. If the function foo() is defined in the .Rmd file that defines a package named pkg, then whenever foo() or pkg::foo() appears in the .Rmd, a link will be added; however, if other_pkg::foo() appears, then no link will be added.

    -
    #' Replace a function's name with a link to its definition
    -#' 
    -#' A helper function for `add_function_hyperlinks` that wraps references to a 
    -#' function in an anchor tag with a link to the function's definition.
    -#' 
    -#' @param txt Character vector where each element is a row of the knitted HTML file.
    -#' @param function_pattern Regular Expression passed from `add_function_hyperlinks` that contains all referenced functions in the document.
    -#' @param where_defined Character vector that contains the name of the file in which a function was defined.
    -#' @param all_function_names Character vector of all referenced functions in the document.
    -#' @param pkg_name Name of the package created by litr. Taken from YAML front matter.
    -#' @param remove_span Boolean argument for removing span tags. Used for minimizing code duplication.
    -#' @keywords internal
    -insert_hrefs <- function(txt, function_pattern, where_defined,
    -                         all_function_names, pkg_name, remove_span=FALSE){
    -  # filter down matches of defined_functions_pattern
    -  has_fn_name <- which(stringr::str_detect(txt, function_pattern))
    -  has_colon_prefix <- which(stringr::str_detect(txt, paste0("::", all_function_names, "\\(", collapse = "|")))
    -  has_only_fn_name <- setdiff(has_fn_name, has_colon_prefix)
    -  has_pkg_colon_prefix <- which(stringr::str_detect(txt, paste0(stringr::str_glue("{pkg_name}::"))))
    -  
    -  # define different replacement functions for colon prefix cases and regular cases
    -  colon_pref_replace_fn <- function(x){
    -    if(remove_span){
    -      fn_name <- stringr::str_remove(x, "</span>\\(")
    -      fn_name <- stringr::str_remove(fn_name, '<span class="fu">')
    -    } else{
    -      fn_name <- stringr::str_remove(x, "\\(")
    -    }
    -    fn_name <- stringr::str_remove(fn_name, stringr::str_glue('{pkg_name}::'))
    -    # implicitly assuming that a function is not redefined in another file
    -    def_file <- where_defined[all_function_names == fn_name]
    -    return(stringr::str_glue("{pkg_name}::<a href='{def_file}#{fn_name}'>{fn_name}</a>("))
    -    
    -  }
    -  regular_replace_fn <- function(x){
    -    if(remove_span){
    -      fn_name <- stringr::str_remove(x, '</span>\\(')
    -      fn_name <- stringr::str_remove(fn_name, '<span class="fu">')  
    -    } else {
    -      fn_name <- stringr::str_remove(x, "\\(")
    -    }
    -    # implicitly assuming that a function is not redefined in another file
    -    def_file <- where_defined[all_function_names == fn_name]
    -    stringr::str_glue("<a href='{def_file}#{fn_name}'>{fn_name}</a>(")
    -  }  
    -  
    -  colon_prefix_function_pattern <- paste0(stringr::str_glue("{pkg_name}::"),all_function_names, "\\(", collapse = "|")
    -  colon_prefix_refs <- stringr::str_replace_all(
    -    txt[has_pkg_colon_prefix],
    -    colon_prefix_function_pattern,
    -    colon_pref_replace_fn
    -  )
    -  
    -  regular_refs <- stringr::str_replace_all(
    -    txt[has_only_fn_name],
    -    function_pattern,
    -    regular_replace_fn
    -  )
    -  # now put back in the changed lines
    -  txt[has_pkg_colon_prefix] <- colon_prefix_refs
    -  txt[has_only_fn_name] <- regular_refs
    -  txt
    -}
    +
    #' Replace a function's name with a link to its definition
    +#' 
    +#' A helper function for `add_function_hyperlinks` that wraps references to a 
    +#' function in an anchor tag with a link to the function's definition.
    +#' 
    +#' @param txt Character vector where each element is a row of the knitted HTML file.
    +#' @param function_pattern Regular Expression passed from `add_function_hyperlinks` that contains all referenced functions in the document.
    +#' @param where_defined Character vector that contains the name of the file in which a function was defined.
    +#' @param all_function_names Character vector of all referenced functions in the document.
    +#' @param pkg_name Name of the package created by litr. Taken from YAML front matter.
    +#' @param remove_span Boolean argument for removing span tags. Used for minimizing code duplication.
    +#' @keywords internal
    +insert_hrefs <- function(txt, function_pattern, where_defined,
    +                         all_function_names, pkg_name, remove_span=FALSE){
    +  # filter down matches of defined_functions_pattern
    +  has_fn_name <- which(stringr::str_detect(txt, function_pattern))
    +  has_colon_prefix <- which(stringr::str_detect(txt, paste0("::", all_function_names, "\\(", collapse = "|")))
    +  has_only_fn_name <- setdiff(has_fn_name, has_colon_prefix)
    +  has_pkg_colon_prefix <- which(stringr::str_detect(txt, paste0(stringr::str_glue("{pkg_name}::"))))
    +  
    +  # define different replacement functions for colon prefix cases and regular cases
    +  colon_pref_replace_fn <- function(x){
    +    if(remove_span){
    +      fn_name <- stringr::str_remove(x, "</span>\\(")
    +      fn_name <- stringr::str_remove(fn_name, '<span class="fu">')
    +    } else{
    +      fn_name <- stringr::str_remove(x, "\\(")
    +    }
    +    fn_name <- stringr::str_remove(fn_name, stringr::str_glue('{pkg_name}::'))
    +    # implicitly assuming that a function is not redefined in another file
    +    def_file <- where_defined[all_function_names == fn_name]
    +    return(stringr::str_glue("{pkg_name}::<a href='{def_file}#{fn_name}'>{fn_name}</a>("))
    +    
    +  }
    +  regular_replace_fn <- function(x){
    +    if(remove_span){
    +      fn_name <- stringr::str_remove(x, '</span>\\(')
    +      fn_name <- stringr::str_remove(fn_name, '<span class="fu">')  
    +    } else {
    +      fn_name <- stringr::str_remove(x, "\\(")
    +    }
    +    # implicitly assuming that a function is not redefined in another file
    +    def_file <- where_defined[all_function_names == fn_name]
    +    stringr::str_glue("<a href='{def_file}#{fn_name}'>{fn_name}</a>(")
    +  }  
    +  
    +  colon_prefix_function_pattern <- paste0(stringr::str_glue("{pkg_name}::"),all_function_names, "\\(", collapse = "|")
    +  colon_prefix_refs <- stringr::str_replace_all(
    +    txt[has_pkg_colon_prefix],
    +    colon_prefix_function_pattern,
    +    colon_pref_replace_fn
    +  )
    +  
    +  regular_refs <- stringr::str_replace_all(
    +    txt[has_only_fn_name],
    +    function_pattern,
    +    regular_replace_fn
    +  )
    +  # now put back in the changed lines
    +  txt[has_pkg_colon_prefix] <- colon_prefix_refs
    +  txt[has_only_fn_name] <- regular_refs
    +  txt
    +}

    In addition to adding hyperlinks to function definitions, we also want to add hyperlinks for chunk references of the form <<chunk-name>> that link to user-defined chunk names that take the form ###chunk-name###. We want these user-defined chunk names to stand out to readers and in their original form as code comments they might be harder to notice. To solve this issue, we modify the formatting of these chunks to add a border with the name of the chunk set into the border, mimicking the look of a <fieldset> tag. The additional formatting requires inserting additional HTML tags as well as css into the knitted HTML file so we use the xml2 package to parse and manipulate the knitted HTML file.

    -
    #' Add hyperlinks to embedded chunks
    -#' 
    -#' Finds chunks that are referenced in the html file(s) by looking for comments
    -#' of the form `###"foo"###` and then wraps `foo` in a `span` tag with `id="foo"` 
    -#' and then whenever the chunk label `<<foo>>` is found it wraps it in a 
    -#' `a href="file.html#foo"` tag so that it will be a hyperlink to `foo`'s 
    -#' definition.
    -#' 
    -#' @param html_files Character vector of file names of html files that were created
    -#' from Rmd files
    -#' @param reference_start The delimiter used to indicate the start of a chunk label 
    -#' @param reference_end The delimiter used to indicate the end of a chunk label 
    -#' @keywords internal
    -add_chunk_label_hyperlinks <- function(html_files,
    -                                       reference_start = "&lt;&lt;",
    -                                       reference_end = "&gt;&gt;"){
    -  find_chunk_defs <- function(html_file) {
    -    txt <- readLines(html_file)
    -    start_line <- which(txt == "<body>")
    -    pattern <- '###&quot;([a-zA-Z0-9-_.]+)&quot;###'
    -    # find chunks that are defined in this file:
    -    chunk_names <- character(0)
    -    for (i in seq(start_line + 1, length(txt))) {
    -      chunk_name <- stringr::str_match(txt[i], pattern)[, 2]
    -      if(is.na(chunk_name)) next
    -      # a chunk was defined in this line, so put a span around it
    -      txt[i] <- stringr::str_replace(
    -        txt[i],
    -        pattern,
    -        stringr::str_glue("<span id='{chunk_name}'>###&quot;\\1&quot;###</span>")
    -      )
    -      # and keep track of it for later.
    -      # we're using setNames here to make sure that we keep the name of file
    -      # where the chunk name is defined 
    -      chunk_names <- setNames(c(chunk_names, chunk_name), c(names(chunk_names), html_file))
    -    }
    -    list(chunk_names = chunk_names, txt = txt)
    -  }
    -  
    -  cdefs <- lapply(html_files, find_chunk_defs)
    -  all_chunk_names <- unlist(lapply(cdefs, function(lst) lst$chunk_names))
    -  num_per_file <- unlist(lapply(cdefs, function(lst) length(lst$chunk_names)))
    -  where_defined <- rep(fs::path_file(html_files), times = num_per_file)
    -  
    -  defined_chunks_pattern <- paste0(reference_start, all_chunk_names, reference_end, 
    -                                   collapse = "|")
    -  ref_start <- '<span class="sc">&lt;</span><span class="er">&lt;</span>'
    -  ref_start_alt <- '<span class=\"er\">&lt;&lt;</span>'
    -  ref_end <- '<span class="sc">&gt;</span><span class="er">&gt;</span>'
    -  hyphen_with_extras <- '<span class="sc">-</span>'
    -  all_chunk_names2 <- stringr::str_replace_all(all_chunk_names, "-", hyphen_with_extras)
    -  defined_chunks_pattern2 <- paste0(
    -    ref_start, all_chunk_names2, ref_end, collapse = "|"
    -    )
    -  defined_chunks_pattern2_alt <- paste0(
    -    ref_start_alt, all_chunk_names2, ref_end, collapse = "|"
    -    )
    -  defined_chunks_pattern2 <- paste(
    -    defined_chunks_pattern2, defined_chunks_pattern2_alt, sep = "|"
    -    )
    -
    -  for (i in seq_along(html_files)) {
    -    # whenever one of these named chunks is referenced, link to its definition
    -    # using the format `file_where_chunk_is_defined.html#chunkname`
    -    txt <- stringr::str_replace_all(
    -      cdefs[[i]]$txt,
    -      defined_chunks_pattern,
    -      function(x) {
    -        cname <- stringr::str_remove_all(
    -          x,
    -          paste(reference_start, reference_end, sep = "|")
    -        )
    -        def_file <- where_defined[all_chunk_names == cname]
    -        stringr::str_glue(
    -          "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>"
    -          )
    -      }
    -    )
    -    txt <- stringr::str_replace_all(
    -      txt,
    -      defined_chunks_pattern2,
    -      function(x) {
    -        cname <- stringr::str_remove_all(
    -          x,
    -          paste(ref_start, ref_start_alt, ref_end, sep = "|")
    -        )
    -        def_file <- where_defined[all_chunk_names2 == cname]
    -        cname <- stringr::str_replace_all(cname, hyphen_with_extras, "-")
    -        stringr::str_glue(
    -          "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>"
    -          )
    -      }
    -    )
    -
    -    parsed_html <- xml2::read_html(paste(txt,collapse="\n"))
    -    # get all possible chunk names in this file.
    -    chunk_names <- all_chunk_names[which(names(all_chunk_names) == html_files[i])]
    -    
    -    if(length(chunk_names) > 0){
    -      for(j in seq_along(chunk_names)){
    -        span_node <- xml2::xml_find_first(parsed_html, stringr::str_glue('(.//span[@id="{chunk_names[j]}"])'))
    -        span_node_path <- stringr::str_split(xml2::xml_path(span_node),"/")
    -        
    -        pre_path <- paste(span_node_path[[1]][1:(max(which(stringr::str_detect(span_node_path[[1]], "pre"))))],collapse="/")
    -        if(nchar(pre_path) == 0){
    -          next()
    -        }
    -        pre_parent <- xml2::xml_find_first(parsed_html, pre_path)
    -        if(is.na(pre_parent)){
    -          next()
    -        }
    -        xml2::xml_add_parent(pre_parent
    -                             , xml2::read_xml(stringr::str_glue('<fieldset id="{chunk_names[j]}" class="chunkfield"> </fieldset>')))
    -        xml2::xml_add_sibling(pre_parent, xml2::read_xml(stringr::str_glue('<legend class="chunklegend">{chunk_names[j]}</legend>')), where="before")
    -        # xml2::xml_add_parent(pre_parent, xml2::read_xml(stringr::str_glue('<fieldset id="{chunk_names[j]}" style="border:4px solid black"> <legend>{chunk_names[j]}</legend> </div>')))
    -        xml2::xml_remove(span_node)
    -        # remove the extra line break that is left over from removing the span
    -        code_node <- xml2::xml_child(pre_parent)
    -        changed_txt <- stringr::str_remove(paste(as.character(xml2::xml_contents(code_node)),collapse=""), '\n')
    -        xml2::xml_replace(code_node, xml2::read_xml(stringr::str_glue('<code>{changed_txt}</code>')))
    -      }
    -    }
    -    # last thing is to insert an additional style node in the head with our CSS so we have a standard style whether we are using bookdown or Rmarkdown
    -    css_string <- "fieldset.chunkfield {border:4px solid black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em}
    -    legend.chunklegend {padding:0;font-size:21px;width:auto;border:0; border-bottom: none; margin-bottom:0}
    -    "
    -    head_node <- xml2::xml_find_first(parsed_html, ".//head")
    -    xml2::xml_add_child(head_node, xml2::read_xml(stringr::str_glue("<style type='text/css'>{css_string}</style>")))
    -    txt <- xml2::write_html(parsed_html, html_files[i])
    -  }
    -}
    +
    #' Add hyperlinks to embedded chunks
    +#' 
    +#' Finds chunks that are referenced in the html file(s) by looking for comments
    +#' of the form `###"foo"###` and then wraps `foo` in a `span` tag with `id="foo"` 
    +#' and then whenever the chunk label `<<foo>>` is found it wraps it in a 
    +#' `a href="file.html#foo"` tag so that it will be a hyperlink to `foo`'s 
    +#' definition.
    +#' 
    +#' @param html_files Character vector of file names of html files that were created
    +#' from Rmd files
    +#' @param reference_start The delimiter used to indicate the start of a chunk label 
    +#' @param reference_end The delimiter used to indicate the end of a chunk label 
    +#' @keywords internal
    +add_chunk_label_hyperlinks <- function(html_files,
    +                                       reference_start = "&lt;&lt;",
    +                                       reference_end = "&gt;&gt;"){
    +  find_chunk_defs <- function(html_file) {
    +    txt <- readLines(html_file)
    +    start_line <- which(txt == "<body>")
    +    pattern <- '###&quot;([a-zA-Z0-9-_.]+)&quot;###'
    +    # find chunks that are defined in this file:
    +    chunk_names <- character(0)
    +    for (i in seq(start_line + 1, length(txt))) {
    +      chunk_name <- stringr::str_match(txt[i], pattern)[, 2]
    +      if(is.na(chunk_name)) next
    +      # a chunk was defined in this line, so put a span around it
    +      txt[i] <- stringr::str_replace(
    +        txt[i],
    +        pattern,
    +        stringr::str_glue("<span id='{chunk_name}'>###&quot;\\1&quot;###</span>")
    +      )
    +      # and keep track of it for later.
    +      # we're using setNames here to make sure that we keep the name of file
    +      # where the chunk name is defined 
    +      chunk_names <- setNames(c(chunk_names, chunk_name), c(names(chunk_names), html_file))
    +    }
    +    list(chunk_names = chunk_names, txt = txt)
    +  }
    +  
    +  cdefs <- lapply(html_files, find_chunk_defs)
    +  all_chunk_names <- unlist(lapply(cdefs, function(lst) lst$chunk_names))
    +  num_per_file <- unlist(lapply(cdefs, function(lst) length(lst$chunk_names)))
    +  where_defined <- rep(fs::path_file(html_files), times = num_per_file)
    +  
    +  defined_chunks_pattern <- paste0(reference_start, all_chunk_names, reference_end, 
    +                                   collapse = "|")
    +  ref_start <- '<span class="sc">&lt;</span><span class="er">&lt;</span>'
    +  ref_start_alt <- '<span class=\"er\">&lt;&lt;</span>'
    +  ref_end <- '<span class="sc">&gt;</span><span class="er">&gt;</span>'
    +  hyphen_with_extras <- '<span class="sc">-</span>'
    +  all_chunk_names2 <- stringr::str_replace_all(all_chunk_names, "-", hyphen_with_extras)
    +  defined_chunks_pattern2 <- paste0(
    +    ref_start, all_chunk_names2, ref_end, collapse = "|"
    +    )
    +  defined_chunks_pattern2_alt <- paste0(
    +    ref_start_alt, all_chunk_names2, ref_end, collapse = "|"
    +    )
    +  defined_chunks_pattern2 <- paste(
    +    defined_chunks_pattern2, defined_chunks_pattern2_alt, sep = "|"
    +    )
    +
    +  for (i in seq_along(html_files)) {
    +    # whenever one of these named chunks is referenced, link to its definition
    +    # using the format `file_where_chunk_is_defined.html#chunkname`
    +    txt <- stringr::str_replace_all(
    +      cdefs[[i]]$txt,
    +      defined_chunks_pattern,
    +      function(x) {
    +        cname <- stringr::str_remove_all(
    +          x,
    +          paste(reference_start, reference_end, sep = "|")
    +        )
    +        def_file <- where_defined[all_chunk_names == cname]
    +        stringr::str_glue(
    +          "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>"
    +          )
    +      }
    +    )
    +    txt <- stringr::str_replace_all(
    +      txt,
    +      defined_chunks_pattern2,
    +      function(x) {
    +        cname <- stringr::str_remove_all(
    +          x,
    +          paste(ref_start, ref_start_alt, ref_end, sep = "|")
    +        )
    +        def_file <- where_defined[all_chunk_names2 == cname]
    +        cname <- stringr::str_replace_all(cname, hyphen_with_extras, "-")
    +        stringr::str_glue(
    +          "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>"
    +          )
    +      }
    +    )
    +
    +    parsed_html <- xml2::read_html(paste(txt,collapse="\n"))
    +    # get all possible chunk names in this file.
    +    chunk_names <- all_chunk_names[which(names(all_chunk_names) == html_files[i])]
    +    
    +    if(length(chunk_names) > 0){
    +      for(j in seq_along(chunk_names)){
    +        span_node <- xml2::xml_find_first(parsed_html, stringr::str_glue('(.//span[@id="{chunk_names[j]}"])'))
    +        span_node_path <- stringr::str_split(xml2::xml_path(span_node),"/")
    +        
    +        pre_path <- paste(span_node_path[[1]][1:(max(which(stringr::str_detect(span_node_path[[1]], "pre"))))],collapse="/")
    +        if(nchar(pre_path) == 0){
    +          next()
    +        }
    +        pre_parent <- xml2::xml_find_first(parsed_html, pre_path)
    +        if(is.na(pre_parent)){
    +          next()
    +        }
    +        xml2::xml_add_parent(pre_parent
    +                             , xml2::read_xml(stringr::str_glue('<fieldset id="{chunk_names[j]}" class="chunkfield"> </fieldset>')))
    +        xml2::xml_add_sibling(pre_parent, xml2::read_xml(stringr::str_glue('<legend class="chunklegend">{chunk_names[j]}</legend>')), where="before")
    +        xml2::xml_remove(span_node)
    +        # remove the extra line break that is left over from removing the span
    +        code_node <- xml2::xml_child(pre_parent)
    +        changed_txt <- stringr::str_remove(paste(as.character(xml2::xml_contents(code_node)),collapse=""), '\n')
    +        xml2::xml_replace(code_node, xml2::read_xml(stringr::str_glue('<code>{changed_txt}</code>')))
    +      }
    +    }
    +    # last thing is to insert an additional style node in the head with our CSS so we have a standard style whether we are using bookdown or Rmarkdown
    +    css_string <- "fieldset.chunkfield {border:1px dotted black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em}
    +    legend.chunklegend {padding:0;width:auto;border:0; border-bottom: none; margin-bottom:0}
    +    "
    +    head_node <- xml2::xml_find_first(parsed_html, ".//head")
    +    xml2::xml_add_child(head_node, xml2::read_xml(stringr::str_glue("<style type='text/css'>{css_string}</style>")))
    +    txt <- xml2::write_html(parsed_html, html_files[i])
    +  }
    +}

    Since we rely on the xml2 package to add in the extra label formatting, let’s import it:

    -
    usethis::use_package("xml2")
    +
    usethis::use_package("xml2")
    ## ✔ Adding 'xml2' to Imports field in DESCRIPTION
     ## • Refer to functions with `xml2::fun()`

    Finally, we want to replace the ANSI escape sequences used by packages such as testthat and devtools with their HTML equivalents so the output matches what we see in the terminal.

    -
    #' Replace ANSI escape sequences with their HTML equivalents
    -#' 
    -#' Finds ANSI escape sequences and replaces them with HTML tags using the `fansi` package
    -#' 
    -#' @param html_files Character vector of file names of html files that were created
    -#' from Rmd files
    -#' @keywords internal
    -replace_ansi_sequences <- function(html_files) {
    -  for (i in seq_along(html_files)) {
    -    file_lines <- readLines(html_files[i])
    -    # look for lines with escape sequences for URLs and remove the URL
    -    # escape sequences before we convert to HTML
    -    url_code_regex <- "\\033]8;;.*\\a(.*?)\\033]8;;\\a"
    -    url_seq_idx <- which(stringr::str_detect(file_lines, url_code_regex))
    -    file_lines[url_seq_idx] <- sapply(url_seq_idx, function(idx){
    -      line <- file_lines[idx]
    -      stringr::str_replace(line, url_code_regex, stringr::str_glue("\\1"))  
    -    })
    -    
    -    txt <-
    -      fansi::sgr_to_html(x = file_lines,
    -                         warn = FALSE,
    -                         term.cap = "256")
    -    writeLines(txt, con = html_files[i])
    -  }
    -}
    +
    #' Replace ANSI escape sequences with their HTML equivalents
    +#' 
    +#' Finds ANSI escape sequences and replaces them with HTML tags using the `fansi` package
    +#' 
    +#' @param html_files Character vector of file names of html files that were created
    +#' from Rmd files
    +#' @keywords internal
    +replace_ansi_sequences <- function(html_files) {
    +  for (i in seq_along(html_files)) {
    +    file_lines <- readLines(html_files[i])
    +    # look for lines with escape sequences for URLs and remove the URL
    +    # escape sequences before we convert to HTML
    +    url_code_regex <- "\\033]8;;.*\\a(.*?)\\033]8;;\\a"
    +    url_seq_idx <- which(stringr::str_detect(file_lines, url_code_regex))
    +    file_lines[url_seq_idx] <- sapply(url_seq_idx, function(idx){
    +      line <- file_lines[idx]
    +      stringr::str_replace(line, url_code_regex, stringr::str_glue("\\1"))  
    +    })
    +    
    +    txt <-
    +      fansi::sgr_to_html(x = file_lines,
    +                         warn = FALSE,
    +                         term.cap = "256")
    +    writeLines(txt, con = html_files[i])
    +  }
    +}

    7.1.3 bookdown output format

    It turns out that our modification to the bookdown::gitbook() format’s postprocessor is identical to the above. This suggests that we should probably reuse code more effectively. But for now I will leave it how it is:

    -
    #' litr version of `bookdown::gitbook()`
    -#' 
    -#' This behaves like `bookdown::gitbook()` with a few differences:
    -#' - It creates an R package.
    -#' - It adds hyperlinks to function definitions whenever a function is used
    -#' elsewhere in the document.
    -#' - It does "Knuth-style" chunk referencing with hyperlinks.
    -#' 
    -<<param-minimal_eval>>
    -#' @param ... Parameters to be passed to `bookdown::gitbook()` 
    -#' @export
    -litr_gitbook <- function(minimal_eval = FALSE, ...) {
    -  litr_gitbook_ <- litrify_output_format(bookdown::gitbook,
    -                                         minimal_eval = minimal_eval)
    -  old <- litr_gitbook_(...)
    -  new <- old
    -  # modify post_processor
    -  new$post_processor = function(metadata, input_file, output_file, ...) {
    -    out <- old$post_processor(metadata, input_file, output_file, ...)
    -    out_dir <- fs::path_dir(out)
    -    file_stems <- readLines(file.path(out_dir, "reference-keys.txt"))
    -    html_files <- file.path(out_dir, paste0(file_stems, ".html"))
    -    html_files <- unique(intersect(c(out, html_files), fs::dir_ls(out_dir)))
    -    # add hyperlinks within html output to make it easier to navigate:
    -    add_function_hyperlinks(html_files, metadata$params$package_name)
    -    add_chunk_label_hyperlinks(html_files)
    -    # replace ANSI sequences with HTML tag equivalents
    -    replace_ansi_sequences(html_files)
    -    out
    -  }
    -  new
    -}
    +
    #' litr version of `bookdown::gitbook()`
    +#' 
    +#' This behaves like `bookdown::gitbook()` with a few differences:
    +#' - It creates an R package.
    +#' - It adds hyperlinks to function definitions whenever a function is used
    +#' elsewhere in the document.
    +#' - It does "Knuth-style" chunk referencing with hyperlinks.
    +#' 
    +<<param-minimal_eval>>
    +#' @param ... Parameters to be passed to `bookdown::gitbook()` 
    +#' @export
    +litr_gitbook <- function(minimal_eval = FALSE, ...) {
    +  litr_gitbook_ <- litrify_output_format(bookdown::gitbook,
    +                                         minimal_eval = minimal_eval)
    +  old <- litr_gitbook_(...)
    +  new <- old
    +  # modify post_processor
    +  new$post_processor = function(metadata, input_file, output_file, ...) {
    +    out <- old$post_processor(metadata, input_file, output_file, ...)
    +    out_dir <- fs::path_dir(out)
    +    file_stems <- readLines(file.path(out_dir, "reference-keys.txt"))
    +    html_files <- file.path(out_dir, paste0(file_stems, ".html"))
    +    html_files <- unique(intersect(c(out, html_files), fs::dir_ls(out_dir)))
    +    # add hyperlinks within html output to make it easier to navigate:
    +    add_function_hyperlinks(html_files, metadata$params$package_name)
    +    add_chunk_label_hyperlinks(html_files)
    +    # replace ANSI sequences with HTML tag equivalents
    +    replace_ansi_sequences(html_files)
    +    out
    +  }
    +  new
    +}

    To use this output format, one would use bookdown::render_book() instead of rmarkdown::render(). In particular:

    -
    bookdown::render_book(output_format = litr::litr_gitbook())
    +
    bookdown::render_book(output_format = litr::litr_gitbook())

    The preamble in index.Rmd would look something like this:

    ---
     title: "A `litr` Book"
    @@ -776,7 +775,7 @@ 

    7.1.3 bookdown output format

    This first line makes it so that in RStudio when you press “Knit”, it calls litr::render(), and the second line makes it so that it will use the special litr bookdown output format.

    Since the above function uses bookdown, we include it in our package:

    -
    usethis::use_package("bookdown")
    +
    usethis::use_package("bookdown")
    ## ✔ Adding 'bookdown' to Imports field in DESCRIPTION
     ## • Refer to functions with `bookdown::fun()`
    @@ -789,218 +788,218 @@

    7.2 Defining litr::

    To render a .Rmd with a litr output format (including the litr_gitbook() format).

    The second use case might seem unnecessary in that

    -
    rmarkdown::render("create-pkg.Rmd", output_format = litr::litr_html_document())
    +
    rmarkdown::render("create-pkg.Rmd", output_format = litr::litr_html_document())

    or

    -
    bookdown::render_book("index.Rmd", output_format = litr::litr_gitbook())
    +
    bookdown::render_book("index.Rmd", output_format = litr::litr_gitbook())

    would do what we want. However, a reason to still prefer litr::render() is that this function ensures the identical behavior to when one clicks the “Knit” in RStudio. It does this by opening a fresh R session (when fresh_session=TRUE, which is the default) in which rmarkdown::render() (or bookdown::render_book()) is called. This is based on the description in the Rmarkdown Cookbook. Another reason to prefer litr::render() is that if there is an error in the rendering process, the special litr hash will still be written to the DESCRIPTION file. This means that after fixing that error when one calls litr::render(), one will not get the error telling the user to delete the partially generated package directory. We accomplish this with the function with_cleanup() defined below.

    In the first use case, litr::render() is responsible for ensuring all the special litr things happen (like setup() being called before knitting, the litr-hash being written afterwards, and hyperlinking occurs). The details of what it does is very similar to what is described in the output formats section, especially the one on the html output format.

    One thing that is different is that we need a function get_params_used(), defined at the end of this section, that gets the actual parameters that are used so that the location of the outputted package can be found.

    -
    #' Render R markdown file
    -#' 
    -#' Wrapper to `rmarkdown::render()` that produces an R package as output in 
    -#' addition to the standard output document.  It does some post-processing on the 
    -#' .html file when that is the output.  In particular, when an .html file is among
    -#' the outputs, it adds hyperlinks to functions defined within the file to make 
    -#' it easier for someone reading the code to see where different functions are
    -#' defined.
    -#' 
    -#' @param input The input file to be rendered (see `rmarkdown::render`)
    -<<param-minimal_eval>>
    -#' @param fresh_session Whether to call `rmarkdown::render` from a fresh R 
    -#' session. By default TRUE, so that it matches the behavior of pressing "Knitr"
    -#' in RStudio.  However, for debugging it can be useful to set this to FALSE so 
    -#' that functions like `debug()` and `browser()` will work.
    -#' @param ... Additional parameters to pass to `rmarkdown::render`
    -#' @export
    -render <- function(input, minimal_eval, fresh_session = TRUE, ...) {
    -  # call rmarkdown::render in a new environment so it behaves the same as 
    -  # pressing the knit button in RStudio:
    -  # https://bookdown.org/yihui/rmarkdown-cookbook/rmarkdown-render.html
    -  args <- list(...)
    -
    -  # let's determine if the output format being used is a litr format.
    -  # If it is, then we'll simply want to call rmarkdown::render() since the 
    -  # special litr behavior will be attained through the output format.
    -  litr_format <- FALSE
    -  bookdown_format <- FALSE
    -  output_format_arg <- FALSE
    -  if ("output_format" %in% names(args)) {
    -    output_format_arg <- TRUE
    -    if ("litr_format" %in% names(args$output_format)) {
    -      litr_format <- TRUE
    -    }
    -    if ("bookdown_output_format" %in% names(args$output_format)) {
    -      bookdown_format <- TRUE
    -    }
    -  } else {
    -    frontmatter <- rmarkdown::yaml_front_matter(input)
    -    if ("output" %in% names(frontmatter)) {
    -      formats <- ifelse(is.list(frontmatter$output),
    -                        names(frontmatter$output),
    -                        frontmatter$output)
    -      if (any(stringr::str_detect(formats, "litr::"))) {
    -        litr_format <- TRUE
    -      }
    -      if (any(stringr::str_detect(formats, "litr::litr_gitbook"))) {
    -        bookdown_format <- TRUE
    -      }
    -    }
    -  }
    -
    -  # get package_directory
    -  params <- get_params_used(input, args$params)
    -  package_dir <- get_package_directory(
    -    params$package_parent_dir,
    -    params$package_name,
    -    input
    -    )
    -  
    -  # if minimal_eval was passed to render, add this to the output_options
    -  # argument that will be passed to rmarkdown::render
    -  if (is.null(args$output_options)) args$output_options <- list()
    -  if (!missing(minimal_eval)) args$output_options$minimal_eval <- minimal_eval
    -  
    -  # determine whether a new R session will be created when we run the rendering 
    -  # function of rmarkdown/bookdown
    -  if (fresh_session)
    -    run_function <- xfun::Rscript_call
    -  else
    -    run_function <- do.call
    -  
    -  if (litr_format) {
    -    # this uses a litr output format, so we don't need to do anything litr-specific
    -    # here because it will happen through the output format
    -    
    -    if (output_format_arg & !missing(minimal_eval)) {
    -      # the output format was passed through the output_format argument rather 
    -      # than through the metadata
    -      if (minimal_eval) {
    -        stop(make_noticeable(paste(
    -          "When passing a litr output format using the output_format argument,",
    -          "you should not pass minimal_eval = TRUE directly to render.",
    -          "Instead, pass it to the litr output format function.  For example,",
    -          "litr::litr_html_document(minimal_eval = TRUE).",
    -          collapse = " "
    -          )))
    -      }
    -    }
    -    
    -    if (bookdown_format) {
    -      if (fs::is_file(input)) input <- fs::path_dir(input)
    -      return(invisible(run_function(with_cleanup(bookdown::render_book,
    -                                                 package_dir),
    -                                    c(input = input, args))))
    -    }
    -    else
    -      return(invisible(run_function(with_cleanup(rmarkdown::render,
    -                                                 package_dir),
    -                                    c(input = input, args))))
    -  }
    -  
    -  # the output format being used is not a litr-specific one, so we need to make
    -  # sure that all the special litr things happen
    -  args$package_dir <- package_dir
    -
    -  render_ <- function(input, package_dir, minimal_eval, ...) {
    -    knitr_objects <- litr:::setup(package_dir, minimal_eval)
    -    out <- rmarkdown::render(input, ...)
    -    restore_knitr_objects(knitr_objects)
    -    # remove .Rproj and .gitignore if usethis::create_package() added these
    -    remove_rstudio_extras(package_dir)
    -    return(out)
    -  }
    -
    -  if (missing(minimal_eval)) minimal_eval <- FALSE
    -  out <- run_function(with_cleanup(render_, package_dir),
    -                      c(input = input, minimal_eval = minimal_eval, args))
    -
    -
    -  # add hyperlinks within html output to make it easier to navigate:
    -  if (any(stringr::str_detect(out, "html$"))) {
    -    html_file <- stringr::str_subset(out, "html$")
    -    add_function_hyperlinks(html_file, params$package_name)
    -    add_chunk_label_hyperlinks(html_file)
    -  }
    -  
    -  # add to DESCRIPTION file the version of litr used to create package:
    -  write_version_to_description(package_dir)
    -  
    -  # add litr hash so we can tell later if package files were manually edited:
    -  write_hash_to_description(package_dir)
    -}
    +
    #' Render R markdown file
    +#' 
    +#' Wrapper to `rmarkdown::render()` that produces an R package as output in 
    +#' addition to the standard output document.  It does some post-processing on the 
    +#' .html file when that is the output.  In particular, when an .html file is among
    +#' the outputs, it adds hyperlinks to functions defined within the file to make 
    +#' it easier for someone reading the code to see where different functions are
    +#' defined.
    +#' 
    +#' @param input The input file to be rendered (see `rmarkdown::render`)
    +<<param-minimal_eval>>
    +#' @param fresh_session Whether to call `rmarkdown::render` from a fresh R 
    +#' session. By default TRUE, so that it matches the behavior of pressing "Knitr"
    +#' in RStudio.  However, for debugging it can be useful to set this to FALSE so 
    +#' that functions like `debug()` and `browser()` will work.
    +#' @param ... Additional parameters to pass to `rmarkdown::render`
    +#' @export
    +render <- function(input, minimal_eval, fresh_session = TRUE, ...) {
    +  # call rmarkdown::render in a new environment so it behaves the same as 
    +  # pressing the knit button in RStudio:
    +  # https://bookdown.org/yihui/rmarkdown-cookbook/rmarkdown-render.html
    +  args <- list(...)
    +
    +  # let's determine if the output format being used is a litr format.
    +  # If it is, then we'll simply want to call rmarkdown::render() since the 
    +  # special litr behavior will be attained through the output format.
    +  litr_format <- FALSE
    +  bookdown_format <- FALSE
    +  output_format_arg <- FALSE
    +  if ("output_format" %in% names(args)) {
    +    output_format_arg <- TRUE
    +    if ("litr_format" %in% names(args$output_format)) {
    +      litr_format <- TRUE
    +    }
    +    if ("bookdown_output_format" %in% names(args$output_format)) {
    +      bookdown_format <- TRUE
    +    }
    +  } else {
    +    frontmatter <- rmarkdown::yaml_front_matter(input)
    +    if ("output" %in% names(frontmatter)) {
    +      formats <- ifelse(is.list(frontmatter$output),
    +                        names(frontmatter$output),
    +                        frontmatter$output)
    +      if (any(stringr::str_detect(formats, "litr::"))) {
    +        litr_format <- TRUE
    +      }
    +      if (any(stringr::str_detect(formats, "litr::litr_gitbook"))) {
    +        bookdown_format <- TRUE
    +      }
    +    }
    +  }
    +
    +  # get package_directory
    +  params <- get_params_used(input, args$params)
    +  package_dir <- get_package_directory(
    +    params$package_parent_dir,
    +    params$package_name,
    +    input
    +    )
    +  
    +  # if minimal_eval was passed to render, add this to the output_options
    +  # argument that will be passed to rmarkdown::render
    +  if (is.null(args$output_options)) args$output_options <- list()
    +  if (!missing(minimal_eval)) args$output_options$minimal_eval <- minimal_eval
    +  
    +  # determine whether a new R session will be created when we run the rendering 
    +  # function of rmarkdown/bookdown
    +  if (fresh_session)
    +    run_function <- xfun::Rscript_call
    +  else
    +    run_function <- do.call
    +  
    +  if (litr_format) {
    +    # this uses a litr output format, so we don't need to do anything litr-specific
    +    # here because it will happen through the output format
    +    
    +    if (output_format_arg & !missing(minimal_eval)) {
    +      # the output format was passed through the output_format argument rather 
    +      # than through the metadata
    +      if (minimal_eval) {
    +        stop(make_noticeable(paste(
    +          "When passing a litr output format using the output_format argument,",
    +          "you should not pass minimal_eval = TRUE directly to render.",
    +          "Instead, pass it to the litr output format function.  For example,",
    +          "litr::litr_html_document(minimal_eval = TRUE).",
    +          collapse = " "
    +          )))
    +      }
    +    }
    +    
    +    if (bookdown_format) {
    +      if (fs::is_file(input)) input <- fs::path_dir(input)
    +      return(invisible(run_function(with_cleanup(bookdown::render_book,
    +                                                 package_dir),
    +                                    c(input = input, args))))
    +    }
    +    else
    +      return(invisible(run_function(with_cleanup(rmarkdown::render,
    +                                                 package_dir),
    +                                    c(input = input, args))))
    +  }
    +  
    +  # the output format being used is not a litr-specific one, so we need to make
    +  # sure that all the special litr things happen
    +  args$package_dir <- package_dir
    +
    +  render_ <- function(input, package_dir, minimal_eval, ...) {
    +    knitr_objects <- litr:::setup(package_dir, minimal_eval)
    +    out <- rmarkdown::render(input, ...)
    +    restore_knitr_objects(knitr_objects)
    +    # remove .Rproj and .gitignore if usethis::create_package() added these
    +    remove_rstudio_extras(package_dir)
    +    return(out)
    +  }
    +
    +  if (missing(minimal_eval)) minimal_eval <- FALSE
    +  out <- run_function(with_cleanup(render_, package_dir),
    +                      c(input = input, minimal_eval = minimal_eval, args))
    +
    +
    +  # add hyperlinks within html output to make it easier to navigate:
    +  if (any(stringr::str_detect(out, "html$"))) {
    +    html_file <- stringr::str_subset(out, "html$")
    +    add_function_hyperlinks(html_file, params$package_name)
    +    add_chunk_label_hyperlinks(html_file)
    +  }
    +  
    +  # add to DESCRIPTION file the version of litr used to create package:
    +  write_version_to_description(package_dir)
    +  
    +  # add litr hash so we can tell later if package files were manually edited:
    +  write_hash_to_description(package_dir)
    +}

    We used the package xfun, so let’s import it:

    -
    usethis::use_package("xfun")
    +
    usethis::use_package("xfun")
    ## ✔ Adding 'xfun' to Imports field in DESCRIPTION
     ## • Refer to functions with `xfun::fun()`

    When litr::render() encounters an error, it can leave the output directory partially modified. We want to make sure the litr hash still gets written to the DESCRIPTION file. Otherwise, the next time one calls litr::render() it does not allow this directory to be overwritten. We do this by using withCallingHandlers(). This function, explained here, is similar to tryCatch(), but with the advantage that it lets the function to continue to run normally, meaning that we will still get the error message as it would appear if we hadn’t done the condition handling.

    -
    #' Add litr hash to DESCRIPTION file if error encountered
    -#' 
    -#' This creates a function that calls the passed function within the context of
    -#' a try-catch.  If an error is encountered, the litr hash is still added to
    -#' the DESCRIPTION file so that future calls to `litr::render()` will recognize
    -#' that it can safely overwrite the package directory (i.e., no manual editing
    -#' occurred).
    -#' 
    -#' @param fun function being called
    -#' @param package_dir directory where package is being written to
    -#' @param ... arguments to be passed to `fun`
    -#' @keywords internal
    -with_cleanup <- function(fun, package_dir) {
    -  return(function(...) {
    -    withCallingHandlers(
    -      fun(...),
    -      error = function(e) {
    -        # add litr hash so we can tell later if package files were manually edited:
    -        write_hash_to_description(package_dir)
    -      })
    -  })
    -}
    +
    #' Add litr hash to DESCRIPTION file if error encountered
    +#' 
    +#' This creates a function that calls the passed function within the context of
    +#' a try-catch.  If an error is encountered, the litr hash is still added to
    +#' the DESCRIPTION file so that future calls to `litr::render()` will recognize
    +#' that it can safely overwrite the package directory (i.e., no manual editing
    +#' occurred).
    +#' 
    +#' @param fun function being called
    +#' @param package_dir directory where package is being written to
    +#' @param ... arguments to be passed to `fun`
    +#' @keywords internal
    +with_cleanup <- function(fun, package_dir) {
    +  return(function(...) {
    +    withCallingHandlers(
    +      fun(...),
    +      error = function(e) {
    +        # add litr hash so we can tell later if package files were manually edited:
    +        write_hash_to_description(package_dir)
    +      })
    +  })
    +}

    In setup(), we modified the knitr objects (e.g., adding hooks, engines, etc.). We call the function restore_knitr_objects() after we’re done, to put things back how they were:

    -
    #' Return the knitr objects to their original state
    -#' 
    -#' @param original_knitr_objects As returned by `setup()`
    -#' @keywords internal
    -restore_knitr_objects <- function(original_knitr_objects) {
    -  knitr::opts_knit$restore(original_knitr_objects$opts_knit)
    -  knitr::knit_hooks$restore(original_knitr_objects$knit_hooks)
    -  knitr::opts_chunk$restore(original_knitr_objects$opts_chunk)
    -  knitr::opts_hooks$restore(original_knitr_objects$opts_hooks)
    -  knitr::knit_engines$restore(original_knitr_objects$knit_engines)
    -}
    +
    #' Return the knitr objects to their original state
    +#' 
    +#' @param original_knitr_objects As returned by `setup()`
    +#' @keywords internal
    +restore_knitr_objects <- function(original_knitr_objects) {
    +  knitr::opts_knit$restore(original_knitr_objects$opts_knit)
    +  knitr::knit_hooks$restore(original_knitr_objects$knit_hooks)
    +  knitr::opts_chunk$restore(original_knitr_objects$opts_chunk)
    +  knitr::opts_hooks$restore(original_knitr_objects$opts_hooks)
    +  knitr::knit_engines$restore(original_knitr_objects$knit_engines)
    +}

    Another thing we want to do at the end of the rendering process is to remove two files that might have been created by usethis: .Rproj and .gitignore. These are created by usethis::create_package() when rstudio = TRUE. We don’t want these files created since this would suggest to a user that the R package should be worked on from within it rather than from the generating .Rmd file.

    -
    #' Remove extra files added by usethis
    -#' 
    -#' Remove .Rproj and .gitignore files if they are in the package directory.
    -#' 
    -#' @param package_dir Path to package
    -#' @keywords internal
    -remove_rstudio_extras <- function(package_dir) {
    -  extras <- fs::dir_ls(package_dir,
    -                       all = TRUE,
    -                       regexp = "[.]Rproj$|[.]gitignore$")
    -  rbuildignore <- file.path(package_dir, ".Rbuildignore")
    -  txt <- readLines(rbuildignore)
    -  txt <- stringr::str_subset(txt, "^.*Rproj.*$", negate = TRUE)
    -  writeLines(txt, con = rbuildignore)
    -  for (extra in extras) fs::file_delete(extra)
    -}
    +
    #' Remove extra files added by usethis
    +#' 
    +#' Remove .Rproj and .gitignore files if they are in the package directory.
    +#' 
    +#' @param package_dir Path to package
    +#' @keywords internal
    +remove_rstudio_extras <- function(package_dir) {
    +  extras <- fs::dir_ls(package_dir,
    +                       all = TRUE,
    +                       regexp = "[.]Rproj$|[.]gitignore$")
    +  rbuildignore <- file.path(package_dir, ".Rbuildignore")
    +  txt <- readLines(rbuildignore)
    +  txt <- stringr::str_subset(txt, "^.*Rproj.*$", negate = TRUE)
    +  writeLines(txt, con = rbuildignore)
    +  for (extra in extras) fs::file_delete(extra)
    +}

    As described earlier, the function get_params_used() combines the parameters from the YAML but allows for those values to be overridden through arguments passed to render().

    -
    #' Get parameter values used in rendering
    -#' 
    -#' When the `params` argument of `rmarkdown::render()` is explicitly used, this
    -#' overrides the default that appears in `input`.
    -#' @param input The input file to be rendered (see `rmarkdown::render`)
    -#' @param passed_params The list of parameters that were passed to `render`.
    -#' @keywords internal
    -get_params_used <- function(input, passed_params) {
    -  params <- rmarkdown::yaml_front_matter(input)$params
    -  for (param in names(passed_params)) {
    -    params[[param]] <- passed_params[[param]]
    -  }
    -  params
    -}
    +
    #' Get parameter values used in rendering
    +#' 
    +#' When the `params` argument of `rmarkdown::render()` is explicitly used, this
    +#' overrides the default that appears in `input`.
    +#' @param input The input file to be rendered (see `rmarkdown::render`)
    +#' @param passed_params The list of parameters that were passed to `render`.
    +#' @keywords internal
    +get_params_used <- function(input, passed_params) {
    +  params <- rmarkdown::yaml_front_matter(input)$params
    +  for (param in names(passed_params)) {
    +    params[[param]] <- passed_params[[param]]
    +  }
    +  params
    +}

    We used the package rmarkdown, so let’s import it:

    -
    usethis::use_package("rmarkdown")
    +
    usethis::use_package("rmarkdown")
    ## ✔ Adding 'rmarkdown' to Imports field in DESCRIPTION
     ## • Refer to functions with `rmarkdown::fun()`

    diff --git a/create-litr/_book/search_index.json b/create-litr/_book/search_index.json index c2a993b..1a8e324 100644 --- a/create-litr/_book/search_index.json +++ b/create-litr/_book/search_index.json @@ -1 +1 @@ -[["index.html", "Creating the litr R package 1 Preamble", " Creating the litr R package Jacob Bien May 27, 2022 1 Preamble This document uses literate programming to define the litr R package. This means that all source code for litr is contained and presented in this document alongside explanation. The litr R package gets created as an output when one runs a piece of code that takes this document as input. To modify the litr package, one should modify the code in this document (and the explanation, as needed) and then regenerate the package. But what piece of code do we use to create an R package from this document? Well… it turns out that this is precisely what the litr package is for. But how can we use litr to create litr? We actually use version \\(n-1\\) of litr to create version \\(n\\). When an R markdown file is rendered with litr, an R package is created in addition to the usual html/pdf/etc document. To learn more about using litr, please visit the litr website. This document is not intended for people who wish to use litr. Rather, it is intended for people who want to know how it works and (possibly) modify it. This document used litr version 0.9.0 to define the current version of litr. In particular, it was generated by running the following command in an R console from the working directory containing index.Rmd: litr::render("index.Rmd") fs::dir_copy("_book", "../docs/create", overwrite = TRUE) fs::dir_delete("_book") The second and third lines move the generated bookdown to the docs/ directory so that it will render nicely on github by visiting https://jacobbien.github.io/litr-project/create/. "],["overview.html", "2 Overview", " 2 Overview The litr package consists of (a) document templates that users can use as the basis for creating an R package, (b) code needed so that when such a R markdown file is knitted, it will generate not just an .html (or some other output format) but also an R package. The code has the following components: Functionality for generating the R package when the .Rmd file is knitted. In particular, we define a knitr chunk hook, which we call send_to_package() that identifies code chunks in the .Rmd file that should be included in the package. To make it so that this chunk hook will be active, we also have a function called setup() that is called at the start of litr::render() right before rmarkdown::render() is called. The setup() function makes it so that when the .Rmd file is knitted, the chunk hook send_to_package() will be invoked on each code chunk. It is also responsible for other preliminaries, such as defining a new knitr language engine that can interpret package-level documentation. Functionality for making sure the R package outputted will not overwrite a manually edited R package. Our approach here is to use a hash that will make it clear whether something has been modified. Wrapper to devtools::document() The reason we write a wrapper for devtools::document() is because we want it to behave slightly differently. In particular, devtools::document() reminds the reader to edit the roxygen in the R/ files, whereas in our case, we want to make sure they edit the original .Rmd file, not the R/ files. Functionality to alter the rendering process The focus here is to make it so that all the special litr functionality will be active when the .Rmd is rendered. We implement two ways this can happen. First, we have several custom output formats, such as litr_html_document(). When rmarkdown::render() is called with one of these litr output formats, setup() gets called before knitting occurs and some other litr-specific post-processing occurs as well. The second way is through the function litr::render(). If a user calls litr::render() with a non-litr output format, e.g. rmarkdown::html_document(), then setup() gets called before rmarkdown::render() and also post-processing can again happen. Another thing of note about litr::render() is that it renders the document in a fresh environment, which ensures identical behavior to when a user presses “Knit” in RStudio. "],["package-setup.html", "3 Package setup 3.1 A note on circularity", " 3 Package setup We start by specifying the information needed in the DESCRIPTION file of the R package. We mostly follow the R Packages book’s description of version numbering. Releases 0.0.1, 0.0.2, and 0.0.3 should really have been 0.1.0, 0.2.0, and 0.3.0, because these were not just patches. Rather, each added quite substantial new functionality. For this reason, we have gone straight from 0.0.3 to 0.4.0. We will keep the leftmost number (“major release”) at 0 until we feel like the package is complete with all the intended features. usethis::create_package( path = ".", fields = list( Package = params$package_name, Version = "0.9.1", Title = "Literate Programming for Writing R Packages", Description = "Allows one to fully create an R package in a single .Rmd file. Includes functionality and .Rmd templates for a literate programming approach to R package development.", `Authors@R` = c( person("Jacob", "Bien", email = "jbien@usc.edu", role = c("aut", "cre")), person("Patrick Vossler", role = "aut") ) ) ) usethis::use_mit_license(copyright_holder = "J. Bien") Let’s add some package-level documentation. This is what will show up when someone types package?litr in the console. #' Literate Programming for Writing R Packages #' #' Allows one to fully create an R package in a single .Rmd file. Includes #' functionality and .Rmd templates for a literate programming approach to R #' package development. #' #' @examples #' # Make a file create-rhello.Rmd based on a template #' \\dontrun{ #' rmarkdown::draft("create-rhello.Rmd", #' template = "make-an-r-package", #' package = "litr", #' edit = FALSE) #' # Now call litr::render (or press Knit if in RStudio) to generate not just #' # create-rhello.html, but also an R package called `rhello`. #' litr::render("create-rhello.Rmd") #' } #' @docType package #' @seealso \\code{\\link{render}} 3.1 A note on circularity Keeping track of the version of litr used is particularly important when using litr to develop litr. There is a tendency to want to use the new functionality that we are creating in create-litr.Rmd itself as soon as we have defined it. However, this is circular and thus must be avoided. To see why we need to be careful, let’s consider an actual example that arose when working on litr version 0.0.3. After release v0.0.2, one of the new features we added is a new language engine called package_doc that allows us to have a special kind of code block defining the package documentation. In trying out this feature and making sure it works on skeleton.Rmd, we would most likely install version 0.0.3. Now that version 0.0.3 is installed, there will be a tendency to want to add a package_doc block to create-litr.Rmd, and it will appear to work. However, this is circular, because we have used version 0.0.3 to create version 0.0.3! In particular, if we remove litr and re-install it from github, we will get version 0.0.2 so that when we attempt to create the package using litr::render(\"create-litr.Rmd\"), we will get an error telling us that it doesn’t have a language engine named package_doc. The code chunk in this section is for preventing this from happening. For more on circularity, see the section on testing litr. install_version_of_litr <- utils::packageVersion("litr") remote <- remotes::github_remote( repo = "jacobbien/litr-project@*release", subdir = "litr" ) version_of_latest_release <- stringr::str_remove(remote$ref, "v") if (install_version_of_litr != version_of_latest_release) stop(stringr::str_glue( "You should be using the version of litr from the latest release (version", " {version_of_latest_release}),\\n but you are using version", " {install_version_of_litr}.\\n", "You can install the release version of litr from GitHub by running the", " the following command:\\n", "remotes::install_github(repo='jacobbien/litr-project@*release', subdir = 'litr')" )) "],["generating-package.html", "4 Generating the R package 4.1 Sending code chunks to the package 4.2 Setting up the R package creation", " 4 Generating the R package 4.1 Sending code chunks to the package We start by defining a chunk hook, which is a function that runs both before and after each code chunk is run in the knitting process. In this case, the function (called send_to_package) is responsible for determining whether the code chunk looks like something that should be exported to the R package. We don’t want all code sent off to our R package. For example, sometimes we’ll want to demonstrate in the Rmd file how a certain function we’ve just created is used by running it on an example or making a plot. That bit of example code would not be included in the package. We start by making sure that code is only sent to the R package once (arbitrarily we have code outputted to the package before and not after the chunk is run). The function then checks if this code chunk is code that should be put into the package. There are four specific cases it considers: If the special option send_to is used in this code chunk, then things are very simple in that the user has explicitly told us where this code should be added. For example, if send_to=\"R/file.R\", then the code in this chunk will be appended to R/file.R in the R package (and if that file doesn’t yet exist, it will be created). Is it a piece of code to be sent to the R/ directory? In particular, it checks to see if the code chunk begins with the characteristic roxygen2 characters #'. If it does, then the name of the object being documented is identified (could be a function, a dataset, an S4 object, etc.) and then we write the code chunk to the file R/<objectname>.R. If the code chunk does not start with #', then we check if it has any line starting with test_that( or testthat::test_that(. If so, then this whole code chunk is appended to tests/testthat/tests.R (and this file is created the first time a test chunk is sent to the package). Next, it checks if the language engine is Rcpp. This occurs when the code chunk starts with {Rcpp, rather than the usual {r (or alternatively when the engine=\"Rcpp\" option is used). We then set things up appropriately for the use of Rcpp within the package (by adapting some code from within the usethis::use_rcpp() function). Finally, we write the code chunk to src/code.cpp. There is a common header used, #include <Rcpp.h> using namespace Rcpp; and we only want this to appear once in code.cpp, so we do a bit of work to remove that if it appears in the code chunk. #' A knitr chunk hook for writing R code and tests #' #' This chunk hook detects whether a chunk is defining a function or dataset #' to be included in the R package (looks for the `roxygen2` comment format `#' `). #' If so, then it is written to the `R/` directory. It also looks for chunks #' that have one or more lines that start with `test_that(` or #' `testthat::test_that(` (potentially with some leading whitespace). These #' chunks are then written to the `tests` directory of the R package. #' #' When the `send_to` option is used, this chunk hook instead simply writes the #' code chunk to the file specified. #' #' @param before Indicates whether this is being called before or after the #' chunk code is executed #' @param options Has information from the chunk #' @param envir Environment #' @keywords internal send_to_package <- function(before, options, envir) { msg <- do_not_edit_message(knitr::current_input(), type = "R") if (before == FALSE) { # Don't do anything after the code chunk has been executed. return() } package_dir <- knitr::opts_knit$get("root.dir") package_name <- fs::path_file(package_dir) if (!is.null(options$send_to)) { # the user has defined an option that indicates where in the package this # code should be written file <- file.path(package_dir, options$send_to) add_text_to_file(options$code, file, pad = TRUE, msg = msg) return() } if (stringr::str_detect(options$code[1], "^#' ")) { # starts with roxygen2, so let's assume this chunk is defining an R function # or dataset that belongs in the package non_comment <- stringr::str_subset(options$code, "^#", negate = TRUE) if (length(non_comment) > 0) { if (stringr::str_detect(non_comment[1], "<-")) { # a function is being defined objname <- stringr::str_match(non_comment[1], "^(.*)\\\\s*<-\\\\s*function")[, 2] objname <- stringr::str_trim(objname) } else if (stringr::str_detect(non_comment[1], '^".+"$')) { # a dataset is being documented objname <- stringr::str_sub(non_comment[1], start = 2, end = -2) } else { # Roxygen2 comment wasn't followed by anything recognized, so do not # send this to package return() } file <- file.path(package_dir, "R", stringr::str_glue("{objname}.R")) cat(paste(c(msg, "", options$code, ""), collapse = "\\n"), file = file) } } else if (any(stringr::str_detect(options$code, "^\\\\s*(testthat::)?test_that\\\\("))) { # This chunk is inferred to be a test test_dir <- file.path(package_dir, "tests", "testthat") test_file <- file.path(test_dir, "tests.R") if (!file.exists(test_file)) { # It's the first chunk with tests if (!dir.exists(test_dir)) usethis::use_testthat() cat(c(msg, ""), collapse = "\\n", file = test_file) } cat( paste(c(options$code, "", ""), collapse = "\\n"), file = test_file, append = TRUE ) } else if (options$engine == "Rcpp") { # To add Rcpp code, we need the package documentation file to exist if (!file.exists(file.path( package_dir, "R", paste0(package_name, "-package.R")) )) { usethis::use_package_doc(open = FALSE) } cpp_file <- file.path(package_dir, "src", "code.cpp") if (!file.exists(cpp_file)) { # set up package for Rcpp # these next few lines are taken from usethis::use_rcpp() # it approximates a call to usethis::use_rcpp(name = "code") usethis:::use_dependency("Rcpp", "LinkingTo") usethis:::use_dependency("Rcpp", "Imports") usethis:::roxygen_ns_append("@importFrom Rcpp sourceCpp") usethis:::use_src() usethis::use_template("code.cpp", save_as = "src/code.cpp") msg <- do_not_edit_message(knitr::current_input(), type = "c") cat(msg, file = cpp_file, append = TRUE) } # append code to code.cpp, but remove lines that are `#include <Rcpp.h>` # or `using namespace Rcpp;` since this already appears at top of file cat(paste(c( "", stringr::str_subset( options$code, r"(^#include <Rcpp.h>$|^using namespace Rcpp;$)", negate = TRUE), ""), collapse = "\\n"), file = cpp_file, append = TRUE) } return() } The above code makes use of a number of functions from the stringr and usethis packages, so we’ll need to add those packages to the Imports section of the DESCRIPTION file: usethis::use_package("stringr") usethis::use_package("usethis") ## ✔ Adding 'stringr' to Imports field in DESCRIPTION ## • Refer to functions with `stringr::fun()` ## ✔ Adding 'usethis' to Imports field in DESCRIPTION ## • Refer to functions with `usethis::fun()` The code also calls the function do_not_edit_message(), which adds a line at the top of the files sent to the R package reminding the user that these are not source files to be edited but rather output of the generating .Rmd file. There are two variations on this message. #' Generate do-not-edit message to put at top of file #' #' @param rmd_file Name of the Rmd file to mention #' @param type Whether this is a R/ file, man/ file, or a c file #' @keywords internal do_not_edit_message <- function(rmd_file, type = c("R", "man", "c")) { if (type[1] == "R") return(stringr::str_glue("# Generated from {rmd_file}: do not edit by hand")) else if (type[1] == "man") return(stringr::str_glue("% Please edit documentation in {rmd_file}.")) else if (type[1] == "c") return(stringr::str_glue("// Generated from {rmd_file}: do not edit by hand")) else stop("type must be either 'R', 'man', or 'c'.") } This function will also be used with type = \"man\" by litr::document(). The above also makes use of a simple helper function that inserts text into a specified location of a file (or creates that file if it doesn’t exist). Actually currently it doesn’t, but we can replace cat() in the above with calls to add_text_to_file(). #' Add Some Text to a File #' #' The text will be added to the file at a particular line specified by #' `location`. The first line of `txt` will be on line `location` of the #' modified file. If `location` is NULL, then text is added to end of file. #' If file does not exist, it is created and `location` is ignored (unless #' `req_exist` is `TRUE`, in which case an error is thrown). #' #' @param txt Character vector to add to file #' @param filename Name of file #' @param location Specifies where text should be added. See description for more. #' @param req_exist If TRUE, then throws an error if file doesn't exist #' @param pad If TRUE, then when text is being added to a preexisting file, it adds a newline #' @param msg An optional message to put at top of file if this is a new file #' @keywords internal add_text_to_file <- function(txt, filename, location = NULL, req_exist = FALSE, pad = FALSE, msg = NULL) { if (!file.exists(filename)) { if (req_exist) stop(stringr::str_glue("Cannot find file {filename}.")) if (!is.null(msg)) txt <- c(msg, "", txt) writeLines(txt, con = filename) return() } if (pad) txt <- c("", txt) filetxt <- readLines(filename) if (is.null(location) || location == length(filetxt) + 1) { filetxt <- c(filetxt, txt) } else if (location > length(filetxt) + 1 | location < 1) stop("Invalid location") else if (location == 1) { filetxt <- c(txt, filetxt) } else { # location is somewhere in middle filetxt <- c(filetxt[1:(location - 1)], txt, filetxt[location:length(filetxt)]) } writeLines(filetxt, con = filename) } testthat::test_that("add_text_to_file() works", { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) # should throw error when file does not exist and req_exist is TRUE: myfile <- file.path(dir, "file.txt") sometxt <- c("hello", "there") testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE)) # should create a new file where one does not exist: myfile <- file.path(dir, "file.txt") sometxt <- c("hello", "there") add_text_to_file(sometxt, myfile) testthat::expect_true(fs::file_exists(myfile)) testthat::expect_equal(sometxt, readLines(myfile)) # should append to end of file by default moretxt <- "world" add_text_to_file(moretxt, myfile) testthat::expect_equal(c(sometxt, moretxt), readLines(myfile)) # should throw error for invalid locations: testthat::expect_error(add_text_to_file(sometxt, myfile, 0)) testthat::expect_error(add_text_to_file(sometxt, myfile, -1)) testthat::expect_error(add_text_to_file(sometxt, myfile, 5)) # should add to specified line: moretxt2 <- "hi" add_text_to_file(moretxt2, myfile, 1) testthat::expect_equal(c(moretxt2, sometxt, moretxt), readLines(myfile)) # should add to specified line: moretxt3 <- "hi2" add_text_to_file(moretxt3, myfile, 2) testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt), readLines(myfile)) # should add to specified line: moretxt4 <- "hi3" add_text_to_file(moretxt4, myfile, 6) testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt, moretxt4), readLines(myfile)) fs::dir_delete(dir) }) ## Test passed 4.2 Setting up the R package creation When the user calls litr::render() (either in the console or by pressing “Knit” in RStudio), one of the first things that function does is to call the function litr::setup(), which does several things: Creates a new empty directory at the specified location while first making sure that it won’t overwrite something it shouldn’t. In particular, we guard against the case that the package was generated by litr::render() but then someone went in manually and made some changes. Even though users should never manually edit the package that was generated by litr::render(), we don’t want to have them inadvertently lose their work by doing so. Thus, we only overwrite an R package if we can tell that it is the unedited output of a call to litr::render(). The function check_unedited() is responsible for checking this, and is a pretty interesting function which we will describe in the next section. This part of the code also makes use of a function litr::make_noticeable(), which is simply a way of making error messages produced by litr more easy to see amid a lot of knitr output. Adjusts the root directory from the generating .Rmd file’s location to the R package’s location. Note: This behavior might not actually be desirable now that additional files will be loaded in. It might be awkward for a user writing the generating .Rmd file to have to make everything relative to the package. It might be convenient to provide a litr::add_file(from, to) function, where from is the path relative to the .Rmd file and to is the path relative to the package’s location. Makes it so that the send_to_package() chunk hook is active for each code chunk. This involves registering a new chunk hook using the function knitr::knit_hooks$set() and then setting an option with the same name to TRUE within each chunk. Deactivates an internal function of the usethis package, usethis:::challenge_nested_project(). This was actually a difficult issue to address that involves the intersection of usethis, here, and our particular use case. The problem is that usethis was not designed for our setting in which an R package is being created programmatically. When using litr, the project directory will have the generating .Rmd file and when this is knit it will create an R package within this project. However, this leads usethis to prompt the user with a message of the form “New project ‘[…]’ is nested inside an existing project ‘[…]’. This is rarely a good idea. Do you wish to create anyway?” But since this is encountered through knitting rather than interactively, this results in an error. This usethis issue describes this exact problem. The solution suggested there by jennybc involving testthat::with_mock() is along the lines of what we want; however, that would lead to some ugly looking code in the generating .Rmd file. The best solution I could find was to use utils::assignInNamespace() as described here. This function allows us to change the internal function usethis:::challenge_nested_project() so that it no longer prompts the user with concerns about nested projects. Changes how chunk references are handled. In particular, consider the following code chunk: a <- 2 <<my-chunk>> a The way knitr handles this, the code chunk would no longer look like this but it would rather have replaced the <<my-chunk>> line by the code that appears in the code chunk labeled “my-chunk”. We instead would like the above code chunk to appear as written and then for the code chunk labeled “my-chunk” to have its label visible to the reader of the .html file. This gives the coder more control over when the reader learns about different parts of the code. It also more closely resembles Donald Knuth’s form of literate programming. For convenience, we’d like <<my-chunk>> to be a link that navigates to the code chunk labeled “my-chunk”. To accomplish this, we modify the document output hook in setup() (and then we also add a function called add_chunk_label_hyperlinks() within render()). Define a package_doc engine which allows users to define package-level documentation. #' Code for setup chunk #' #' * Creates directory where package will be. (Deletes what is currently there as #' long as it appears to have been created by litr and does not have any #' subsequent manual edits.) #' * Sets the root directory to this directory #' * Sets up the main chunk hook `litr::send_to_package()` that sends code to the #' R package directory. #' * In the case that `minimal_eval=TRUE`, sets up an options hook for `eval` so #' chunks are only evaluated if there is a `usethis` or `litr::document()` #' command #' * Deactivates an internal function of the `usethis` package #' * Redefines the document output hook to handle chunk references differently #' * Sets up a [custom language engine](https://bookdown.org/yihui/rmarkdown-cookbook/custom-engine.html) called #' `package_doc` that creates a package documentation file and then inserts #' whatever the user puts in the chunk. #' #' Returns the original state of the knitr objects that have been modified in #' setup. This allows us to return things to the previous state after we are #' finished. This is relevant in the case where litr-knitting occurs in the #' current session and we don't want to leave things in a permanently modified #' state. #' #' @param package_dir Directory where R package will be created <<param-minimal_eval>> #' @keywords internal setup <- function(package_dir, minimal_eval) { if (file.exists(package_dir)) { unedited <- tryCatch(check_unedited(package_dir), error = function(e) { # contents of package_dir does not resemble # a litr package return(FALSE) }) if (!unedited) { stop(make_noticeable(paste( stringr::str_glue("The directory {normalizePath(package_dir)}"), "already exists and either was not created by litr or may have manual", "edits. In either case, please rename that directory (or delete it)", "and then try again.", sep = "\\n"))) } unlink(package_dir, recursive = TRUE) } fs::dir_create(package_dir) usethis:::proj_set_(usethis:::proj_path_prep(package_dir)) # let's keep a version of the knitr objects before modifying them: original_knitr <- list(opts_knit = knitr::opts_knit$get(), knit_hooks = knitr::knit_hooks$get(), opts_chunk = knitr::opts_chunk$get(), opts_hooks = knitr::opts_hooks$get(), knit_engines = knitr::knit_engines$get() ) knitr::opts_knit$set(root.dir = package_dir) # sets wd of future chunks knitr::knit_hooks$set(send_to_package = send_to_package) knitr::opts_chunk$set(send_to_package = TRUE) if (minimal_eval) { # only evaluate chunks that appear to include usethis commands or # a call to litr::document() but if someone has specifically set eval=FALSE # in a particular chunk, do honor that usethis_exports <- getNamespaceExports("usethis") patterns <- paste(c("usethis::", usethis_exports, "litr::document\\\\("), collapse = "|") knitr::opts_hooks$set(eval = function(options) { if (options$eval) options$eval <- any(stringr::str_detect(options$code, patterns)) return(options) }) } # change usethis:::challenge_nested_project so that it will not complain # about creating a nested project (e.g. if this is called within a git # subdirectory) utils::assignInNamespace("challenge_nested_project", function(...) NULL, ns = "usethis") # define document hook to handle chunk references: knitr::knit_hooks$set(document = function(x) { # get the indices of x corresponding to code chunks chunk_start <- "^(\\n```+[a-zA-Z0-9_]+\\n)" idx_block <- stringr::str_which(x, chunk_start) original_code <- knitr::knit_code$get() # We first get indices of skipped chunks in original_code list skipped_chunks <- which(sapply(original_code, function(x){ return(isFALSE(attr(x, "chunk_opts")$echo) || isFALSE(attr(x, "chunk_opts")$include)) })) # Next we remove the indices of skipped chunks original_code_idx_fixed <- setdiff(seq(length(original_code)), skipped_chunks) labels <- names(original_code) # replace each x[i] that has code in it with the original code for (i in seq_along(idx_block)) { # break code into multiple lines: chunk <- strsplit(x[idx_block[i]], "\\n")[[1]] # get the fence used (in case it's more than three ticks): i_start <- stringr::str_which(chunk, "^```+[a-zA-Z0-9_]+") fence <- stringr::str_replace(chunk[i_start[1]], "^(```+)[a-zA-Z0-9_]+", "\\\\1") i_fences <- stringr::str_which(chunk, paste0("^", fence)) # there can be multiple code and output chunks strung together # within a single x[i] if results are not held to end i_all_code <- c() for (j in seq_along(i_start)) { # get the elements corresponding the j-th code chunk within chunk i_code_end <- i_fences[which(i_fences == i_start[j]) + 1] i_all_code <- c(i_all_code, i_start[j]:i_code_end) } i_all_code <- setdiff(i_all_code, i_start[1]) chunk_no_code <- chunk[-i_all_code] chunk <- c(chunk_no_code[1:i_start[1]], original_code[original_code_idx_fixed[i]][[1]], # insert the original version, accounting for skipped chunks fence) if (i_start[1] < length(chunk_no_code)) chunk <- c(chunk, chunk_no_code[(i_start[1] + 1):length(chunk_no_code)]) x[idx_block[i]] <- paste(chunk, collapse = "\\n") } # replace code chunks with the original code # (so we'll still have <<label>> chunk references) refs <- c() # labels that get referred to for (label in labels) { refs <- c(refs, find_labels(original_code[[label]])$chunk_ids) } refs <- unique(refs) adj_labels <- labels[!labels %in% names(skipped_chunks)] ref_id <- match(refs, adj_labels) if (any(is.na(ref_id))) { stop(make_noticeable(paste( stringr::str_glue("The chunk reference <<{refs[is.na(ref_id)][1]}>> ", "is used, but there is no chunk with that label.", sep = "\\n")))) } to_insert <- paste0('###"', adj_labels[ref_id], '"###\\n') x[idx_block[ref_id]] <- stringr::str_replace(x[idx_block[ref_id]], chunk_start, paste0("\\\\1", to_insert)) x }) # setup package_doc engine knitr::knit_engines$set(package_doc = function(options) { # create package_doc usethis::use_package_doc(open = FALSE) # insert the contents of the code chunk into the package_doc pkgdoc <- file.path("R", paste0(fs::path_file(package_dir), "-package.R")) add_text_to_file(options$code, filename = pkgdoc, location = 1) # now treat this as if it were standard R code with eval=FALSE r_engine <- knitr::knit_engines$get("R") options[["eval"]] <- FALSE return(r_engine(options)) }) return(original_knitr) } In our new document output hook defined above, we call a function find_labels(). It takes a block of code and returns both a logical vector of which lines contained chunk labels and another vector containing the labels of those referenced chunks. We define it here: #' Find a .Rmd chunk label in a code chunk #' #' @param chunk_code Character vector of code from a .Rmd code chunk. Each element is a line of the code chunk. #' @return List where chunk_idx is a logical vector for each line of the chunk corresponding to whether a chunk label of the form `<<label>>` was found and chunk_ids is a character vector of chunk label was found in that chunk. #' @keywords internal find_labels <- function(chunk_code) { rc <- knitr::all_patterns$md$ref.chunk chunk_idx <- any(idx = grepl(rc, chunk_code)) chunk_ids <- stringr::str_trim(sub(rc, "\\\\1", chunk_code[grepl(rc, chunk_code)])) return(list(chunk_idx = chunk_idx, chunk_ids = chunk_ids)) } The setup() function also uses a small function, make_noticeable(), which we define here: #' Make error messages noticeable #' #' Since litr error messages are amid a lot of output from knitting, we'd like #' the litr ones to be eye-catching. #' #' @param msg Error message #' @keywords internal make_noticeable <- function(msg) { paste("", "======", "Please read your friendly litr error message here:", paste("> ", msg), "======", sep = "\\n") } The code in this section used the fs and knitr packages, so we import those: usethis::use_package("fs") usethis::use_package("knitr") ## ✔ Adding 'fs' to Imports field in DESCRIPTION ## • Refer to functions with `fs::fun()` ## ✔ Adding 'knitr' to Imports field in DESCRIPTION ## • Refer to functions with `knitr::fun()` "],["hash.html", "5 Not overwriting a manually edited R package", " 5 Not overwriting a manually edited R package As described in the previous section, the function setup() will only overwrite a directory if it is the unedited output from using litr. The basic idea is that the function litr::render() when creating a new package finishes by adding a hash to the DESCRIPTION file. (And likewise when rmarkdown::render() is used with a litr output format, as described here.) This hash is a function of everything in the package, so if anything about the package changes (any file is modified, added, or removed) then the function check_unedited() will be able to detect that by recomputing the hash and seeing that it doesn’t match the hash in the DESCRIPTION file. Let’s start by defining the function hash_package_directory() that does the hashing. The hash is a function of everything in the outputted package except for that special line in the DESCRIPTION file with the hash. We use tools::md5sum() and digest::digest() to do the hashing. #' Hash package directory #' #' Gets an identifier that can be used to uniquely (whp) identify the current #' state of the package. This is formed by ignoring the `LitrId` field of the #' DESCRIPTION file, which is the location where the output of this function is #' stored when `litr::render` generates the package. #' #' @param package_dir Path to package #' @keywords internal hash_package_directory <- function(package_dir) { pkg_files <- fs::dir_ls(package_dir, recurse = TRUE, all = TRUE, type = "file") pkg_files <- stringr::str_subset(pkg_files, ".DS_Store$", negate = TRUE) pkg_files <- normalizePath(pkg_files) descr_file <- normalizePath(file.path(package_dir, "DESCRIPTION")) i_descr <- which(pkg_files == descr_file) if (length(i_descr) == 0) stop("Cannot find DESCRIPTION file.") txt_descr <- readLines(pkg_files[i_descr]) txt_descr_mod <- stringr::str_subset( txt_descr, stringr::str_glue("{description_litr_hash_field_name()}: .+$"), negate = TRUE) hashes <- as.character(tools::md5sum(pkg_files[-i_descr])) digest::digest(c(hashes, list(txt_descr_mod))) } We used digest, so let’s import it: usethis::use_package("digest") ## ✔ Adding 'digest' to Imports field in DESCRIPTION ## • Refer to functions with `digest::fun()` We will store this hash in a special field within the DESCRIPTION file. Let’s call this field LitrId. However, in case we ever decide to change the name of this field, it’s better that we only define it in one place. So we do this with the following function: #' Generate litr hash field name for DESCRIPTION file #' @keywords internal description_litr_hash_field_name <- function() return("LitrId") Ok, now let’s write the function that litr::render() will call that will take the generated R package and add a line that puts the hash in the DESCRIPTION file under that special litr field: #' Write the hash of the package to the DESCRIPTION file #' #' @param package_dir Path to package #' @keywords internal write_hash_to_description <- function(package_dir) { desc_file <- file.path(package_dir, "DESCRIPTION") if (!file.exists(desc_file)) file.create(desc_file) hash <- hash_package_directory(package_dir) desc::desc_set(description_litr_hash_field_name(), hash, file = desc_file) } Let’s include the desc package, which helps us manipulate DESCRIPTION files. usethis::use_package("desc") ## ✔ Adding 'desc' to Imports field in DESCRIPTION ## • Refer to functions with `desc::fun()` And of course we’ll need a function that can read the value of that field as well: #' Get the hash of the package from the DESCRIPTION file #' #' @param package_dir Path to package #' @keywords internal read_hash_from_description <- function(package_dir) { descr <- file.path(package_dir, "DESCRIPTION") if (!file.exists(descr)) stop("Cannot find DESCRIPTION file.") txt <- stringr::str_subset( readLines(descr), stringr::str_glue("{description_litr_hash_field_name()}: .+$")) if (length(txt) > 1) stop("More than one hash found in DESCRIPTION.") if (length(txt) == 0) stop("No hash found in DESCRIPTION.") stringr::str_extract(txt, "\\\\S+$") } With all this hash functionality in place, the function check_unedited() is actually quite simple to define: #' Check if package directory is the unedited output of litr::render() #' #' Uses hash stored in a special `litr` field of DESCRIPTION file to check that #' the current state of the R package directory is identical to its state at the #' time that it was created by `litr::render()`. #' #' @param package_dir Path to package #' @keywords internal check_unedited <- function(package_dir) { hash <- hash_package_directory(package_dir) hash == read_hash_from_description(package_dir) } It simply computes the hash of the current package and checks whether that hash is the same as what was originally written to the DESCRIPTION file by litr::render(). "],["document.html", "6 Wrapper to devtools::document()", " 6 Wrapper to devtools::document() This function is nearly identical to devtools::document() except that it changes the roxygen2 message that says “Please edit documentation in R/[…].R” to instead mention the generating .Rmd file. When Rcpp is used, it also makes sure that #include <RcppArmadillo.h> comes before #include <Rcpp.h>. #' Use roxygen to document a package from within a Rmd file #' #' This is a wrapper for the `devtools::document()` function, which in turn is a #' wrapper for the `roxygen2::roxygenize()` function. It is written assuming that #' it is being called from within a generating Rmd file. The purpose for `litr` #' having this wrapper is two-fold. First, it ensures that the first line #' in the outputted `Rd` files should not say "Please edit documentation in #' R/file.R" but instead should refer to the Rmd file that generates everything. #' Second, in the case that Rcpp is being used, it makes some adjustments to ensure #' that the compiling of the C++ code should be successful. #' #' @param ... Arguments to be passed to `devtools::document()` #' @export document <- function(...) { # prepare Rcpp code for compiling if (fs::file_exists("src/code.cpp")) { # make sure that #include <RcppArmadillo.h> if it exists # comes *before* (or instead of) <Rcpp.h> txt <- readLines("src/code.cpp") loc <- stringr::str_which(txt, r"(#include <RcppArmadillo.h>)") if (length(loc) > 0) { include_arma_line <- txt[loc[1]] txt <- c(include_arma_line, txt[-loc]) writeLines(txt, "src/code.cpp") } } devtools::document(...) # remove the line of the following form in each man/*.Rd file: pattern <- "% Please edit documentation in .*$" msg <- do_not_edit_message(knitr::current_input(), type = "man") for (fname in fs::dir_ls("man")) { txt <- stringr::str_replace(readLines(fname), pattern, msg) cat(paste(txt, collapse = "\\n"), file = fname) } } We used devtools, so let’s import it: usethis::use_package("devtools") ## ✔ Adding 'devtools' to Imports field in DESCRIPTION ## • Refer to functions with `devtools::fun()` "],["rendering.html", "7 Altering the rendering process 7.1 Defining litr output formats 7.2 Defining litr::render()", " 7 Altering the rendering process The focus here is to make it so that all the special litr functionality will be active when the .Rmd is rendered. In particular, we need to make sure that setup() has been called before knitting occurs (so that, for example, send_to_package() will be active). We also need to make sure that after knitting certain things occur, such as the litr-hash being written to the package and, in some cases, hyperlinks are added for easier navigation. We implement two ways for the above to occur, which we describe in the next two subsections: The first approach is by defining custom litr output formats. When rmarkdown::render() (or bookdown::render_book()) is called with one of these litr output formats, the litr-specific operations occur before and after knitting. The second way is through the function litr::render(). If a user calls litr::render() with a non-litr output format, e.g. rmarkdown::html_document(), then it adds the necessary litr-specific operations before/after rmarkdown::render() is called. Another thing of note about litr::render() is that it renders the document in a fresh environment, which ensures identical behavior to when a user presses “Knit” in RStudio. Although these are presented as two separate approaches, we have written litr::render() so that if a user passes one of the litr output formats to litr::render(), it will still work. We encourage users to use litr::render()1 rather than rmarkdown::render() since in litr::render() we’re able to wrap the call to rmarkdown::render() in the function with_cleanup(). This ensures that, if an error occurs during the knitting process, the special litr hash will still be created. This is desirable since it means that the next time we try to litr-knit, we will not get an error about overwriting a manually edited package directory. When coding an R package with litr, sometimes there are code chunks that can take a while to evaluate (e.g., tests), which slows down the coding process. We therefore provide an argument (to both litr::render() and the various litr output formats) that allows for “minimal eval” to occur. The goal is to allow the R package to be updated completely but without any of the code chunks being evaluated, except those whose involving usethis or a call to litr::document(), since these commands lead to changes in the R package itself. Here is the documentation associated with the minimal_eval parameter, which is an argument to a number of functions in this section: ###"param-minimal_eval"### #' @param minimal_eval If `TRUE`, then only chunks with `litr::document()` or #' `usethis` commands will be evaluated. This can be convenient in coding when #' you just want to quickly update the R package without having to wait for long #' evaluations to occur. 7.1 Defining litr output formats The function rmarkdown::render() allows for customizable behavior through the use of custom output formats. Given a preexisting output format (e.g. rmarkdown::html_document), we would like to modify it to have litr-behavior – i.e., to create a package as it is being rendered. This next function takes a preexisting output format and “litr-ifies” it by making three changes: It modifies the pre_knit() function. It modifies the post_processor() function. It adds a marker (litr_format <- TRUE) that will help litr::render() know when a litr output format is being passed to it. We present the function and then describe the details of the new pre_knit() and post_processor() functions below. #' Modify an existing output format to have `litr` behavior #' #' This function modifies the `pre_knit()` and `post_processor()` functions of a #' preexisting output format so that it will have the `litr` behavior (meaning that an R package will be created when `rmarkdown::render()` is called). #' #' @param base_format a preexisting, non-litr output format such as `rmarkdown::html_document` <<param-minimal_eval>> #' @export litrify_output_format <- function(base_format = rmarkdown::html_document, minimal_eval = FALSE) { force(base_format) # I think using force here is advisable? force(minimal_eval) # https://adv-r.hadley.nz/function-factories.html function(...) { old <- base_format(...) new <- old new$original_knitr_objects <- list() new$pre_knit <- function(...) { args <- list(...) input <- args$input params <- knitr::knit_params(readLines(input)) package_dir <- get_package_directory( params$package_parent_dir$value, params$package_name$value, input) new$original_knitr_objects <<- litr:::setup(package_dir, minimal_eval) if (!is.null(old$pre_knit)) old$pre_knit(...) } new$post_processor <- function(metadata, input_file, output_file, ...) { # typically the post_processor function returns the output file path # if old$post_processor is NULL, as in the case of pdf_document, # then R will throw an error when trying to call old$post_processor # if we only add a check for non null old$post_processor and otherwise # set out <- NULL then R will throw an error later in rmarkdown::render # since output_file is set to the output of the post_processor if # output_format$post_processor is not null (See line 478 in rmarkdown::render) # Therefore, our solution is to set out to the output_file path if old$post_process is null. if (!is.null(old$post_processor)){ out <- old$post_processor(metadata, input_file, output_file, ...) } else { out <- output_file } package_dir <- get_package_directory( metadata$params$package_parent_dir, metadata$params$package_name, input_file ) # remove .Rproj and .gitignore if usethis::create_package() added these remove_rstudio_extras(package_dir) # add to DESCRIPTION file the version of litr used to create package: write_version_to_description(package_dir) # add litr hash so we can tell later if package files were manually edited: write_hash_to_description(package_dir) out } new$on_exit <- function() { old$on_exit() # restore knitr to its original state restore_knitr_objects(new$original_knitr_objects) } # mark this as a litr_format new$litr_format <- TRUE # litr formats have minimal_eval as an option new$minimal_eval <- minimal_eval new } } The pre_knit() function is modified so that setup() is called before the preexisting output format’s pre_knit() function is called. As the name suggests, this is a function that gets called before knitting. The purpose of the call to setup() is to create the R package directory and make it so that when we knit the file using rmarkdown::render(), a lot of special things will happen, such as code being sent to the R package directory. The function setup() returns the state of the knitr settings before any changes were made. This previous state of the knitr settings will be restored at the end of the rendering process in on_exit(). The post_processor() function is modified so that the DESCRIPTION file gets marked with the version of litr used and with the litr hash (as already described here). Some special care is taken for the case that the original output format doesn’t have a post processor (e.g., this is the case for the pdf_document output format). The particulars of this are given in a comment in the code chunk above. The on_exit() function is modified so that it restores the state of all the knitr settings to how it was when render was first called. We use the above function to create some litr versions of common output formats, as seen in the next few subsections. Before proceeding, we define the function write_version_to_description() that is called above. #' Generate litr version field name for DESCRIPTION file #' @keywords internal description_litr_version_field_name <- function() return("LitrVersionUsed") #' Write the version of litr used to the DESCRIPTION file #' #' @param package_dir Path to package #' @keywords internal write_version_to_description <- function(package_dir) { ver <- as.character(utils::packageVersion("litr")) add_text_to_file( txt = stringr::str_glue("{description_litr_version_field_name()}: {ver}"), filename = file.path(package_dir, "DESCRIPTION"), req_exist = TRUE ) } Also, we made use of a small function for getting the package directory based on the input file’s location and the parameters that are being used in the rendering process. We define it here: #' Get package directory #' #' @param package_parent_dir The directory of where the package should go (relative to the input directory) #' @param package_name The name of the package #' @param input The file name of the input #' @keywords internal get_package_directory <- function(package_parent_dir, package_name, input) { if (package_parent_dir == ".") return(file.path(dirname(input), package_name)) file.path(dirname(input), package_parent_dir, package_name) } Let’s write some tests to make sure it’s behaving as expected: testthat::test_that("get_package_directory() works", { input <- file.path("inputdir", "input.Rmd") testthat::expect_equal( get_package_directory(".", "mypkg", input), file.path("inputdir", "mypkg") # inputdir/mypkg ) testthat::expect_equal( get_package_directory("..", "mypkg", input), file.path("inputdir", "..", "mypkg") # inputdir/../mypkg ) }) ## Test passed 7.1.1 .pdf output format We want our .pdf documents to accurately display the “logging” output from functions in packages such as devtools that use special ANSI escape codes for displaying information in the terminal. Unfortunately, these codes use escape characters which cause problems when creating .pdf versions of our documents. While it is relatively straightforward to map ANSI escape codes to HTML tags, as we will see in the .html output format section, converting these escape codes to Latex commands is more complicated. As a result, we define a post_knit function in our litr_pdf_document format to avoid this issue by stripping out all escape codes in the file before it is converted into a .tex file and then compiled into a .pdf document. Specifically, the post_knit function modifies the intermediate .knit.md file, which contains both the .Rmd file, as well as the output of each code chunk. Thus, we can inspect the output text of each code chunk and remove any ANSI escape codes before the .knit.md is converted to a .tex file and then a .pdf document. We rely upon two internal functions from the fansi R package to remove all possible escape codes and return a clean character vector. #' litr version of `rmarkdown::pdf_document()` #' #' This behaves exactly like `rmarkdown::pdf_document()` except it creates an #' R package. #' <<param-minimal_eval>> #' @param ... Parameters to be passed to `rmarkdown::pdf_document()` #' @export litr_pdf_document <- function(minimal_eval = FALSE, ...) { litr_pdf_document_ <- litrify_output_format(rmarkdown::pdf_document, minimal_eval = minimal_eval) old <- litr_pdf_document_(...) new <- old # post_knit new$post_knit = function(...){ args = list(...) input_filename <- args[[2]] knitted_filename <- fs::path_ext_set(input_filename, ".knit.md") knitted_output <- readLines(knitted_filename) cleaned_output <- sapply(1:length(knitted_output), function(i){ test_str <- knitted_output[i] fansi:::VAL_IN_ENV(x=test_str, ctl="all", warn=TRUE, warn.mask=fansi:::get_warn_mangled()) .Call(fansi:::FANSI_strip_csi, test_str, CTL.INT, WARN.INT) }) writeLines(cleaned_output, knitted_filename) } new } Since the above section uses the fansi package for handling ANSI escape sequences, we include it in our package: usethis::use_package("fansi") ## ✔ Adding 'fansi' to Imports field in DESCRIPTION ## • Refer to functions with `fansi::fun()` 7.1.2 .html output format For .html documents, we’d like to add a bit more in the postprocessing step. In particular, we include some special function and chunk hyperlinking behavior described below. The function add_function_hyperlinks() processes the outputted .html file(s), making it so that one can easily navigate to function definitions. (This function is described lower in this section.) We likewise call a function add_chunk_label_hyperlinks(), which makes chunk references into clickable links. In particular, the chunk reference <<my-chunk>> within a code chunk would link to a chunk named “my-chunk” that begins with ###\"my-chunk\"###. The ###\"my-chunk\"### line is added by a document hook defined in setup(). Finally, we replace ANSI sequences with HTML tag equivalents (the need for this is explained in the section on the .pdf output format). #' litr version of `rmarkdown::html_document()` #' #' This behaves like `rmarkdown::html_document()` with a few differences: #' - It creates an R package. #' - It adds hyperlinks to function definitions whenever a function is used #' elsewhere in the document. #' - It does "Knuth-style" chunk referencing with hyperlinks. #' <<param-minimal_eval>> #' @param ... Parameters to be passed to `rmarkdown::pdf_document()` #' @export litr_html_document <- function(minimal_eval = FALSE, ...) { litr_html_document_ <- litrify_output_format(rmarkdown::html_document, minimal_eval = minimal_eval) old <- litr_html_document_(...) new <- old # modify post_processor new$post_processor = function(metadata, input_file, output_file, ...) { out <- old$post_processor(metadata, input_file, output_file, ...) # html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$") # add hyperlinks within html output to make it easier to navigate: add_function_hyperlinks(output_file, metadata$params$package_name) add_chunk_label_hyperlinks(output_file) # replace ANSI sequences with HTML tag equivalents replace_ansi_sequences(output_file) out } new } We describe these two add_*_hyperlinks() functions next. The function add_function_hyperlinks() looks for foo followed by <- function( and then wraps foo in a span tag with id=\"foo\"; whenever foo is found elsewhere in the document, it calls the insert_hrefs() function to wrap a a href=\"file.html#foo\" tag (where file.html is the file where foo is defined), so that it will be a hyperlink to foo’s definition. #' Add hyperlinks to function definitions #' #' Finds functions that are defined in the html file(s) by looking for text of the #' form `foo <- function(` and then wraps `foo` in a `span` tag with `id="foo"` #' and then whenever `foo` is found it wraps a `a href="file.html#foo"` tag so #' that it will be a hyperlink to `foo`'s definition. #' #' @param html_files Character vector of file names of html files that were created #' from Rmd files #' @param pkg_name Name of the package created by litr. Taken from YAML front matter #' @keywords internal add_function_hyperlinks <- function(html_files, pkg_name) { find_function_defs <- function(html_file) { txt <- readLines(html_file) start_line <- which(txt == "<body>") pattern1 <- '([a-zA-Z0-9_.]+)(\\\\s*&lt;-\\\\s*function)' pattern2 <- stringr::str_replace(pattern1, '&lt;-', '<span class="ot">&lt;-</span>') pattern2 <- stringr::str_replace(pattern2, 'function', '<span class="cf">function</span>') # find functions that are defined in this file: function_names <- character(0) for (pattern in c(pattern1, pattern2)) { for (i in seq(start_line + 1, length(txt))) { fn_name <- stringr::str_match(txt[i], pattern)[, 2] if(is.na(fn_name)) next # a function was defined in this line, so put a span around it txt[i] <- stringr::str_replace( txt[i], pattern, stringr::str_glue("<span id='{fn_name}'>\\\\1</span>\\\\2") ) # and keep track of it for later: function_names <- c(function_names, fn_name) } } list(function_names = function_names, txt = txt) } fdefs <- lapply(html_files, find_function_defs) all_function_names <- unlist(lapply(fdefs, function(lst) lst$function_names)) # if a function is defined multiple times, then it's ambiguous where to link to # so let's not try linking to it (this can occur when a function is defined # within a function, such as `new$post_processor()`) repeated <- names(which(table(all_function_names) > 1)) all_function_names <- setdiff(all_function_names, repeated) if (length(all_function_names) == 0) { # no functions defined in package, so nothing more to be done here return() } num_per_file <- unlist(lapply(fdefs, function(lst) { length(setdiff(lst$function_names, repeated)) })) where_defined <- rep(fs::path_file(html_files), times = num_per_file) defined_functions_pattern <- paste0("(::)?",all_function_names, "\\\\(", collapse = "|") # There's also this case: <span class="fu">myfunction</span> defined_functions_pattern2 <- paste0( '<span class="fu">', all_function_names, '</span>\\\\(', collapse = "|") for (i in seq_along(html_files)) { # whenever one of the defined functions is named, link to its definition # using the format `file_where_foo_is_defined.html#foo` modified_txt <- insert_hrefs(fdefs[[i]]$txt, defined_functions_pattern, where_defined, all_function_names, pkg_name) modified_txt <- insert_hrefs(modified_txt, defined_functions_pattern2, where_defined, all_function_names, pkg_name, remove_span=TRUE) writeLines(modified_txt, con = html_files[i]) } } We define next the helper function insert_hrefs(), which was called in the previous function. If the function foo() is defined in the .Rmd file that defines a package named pkg, then whenever foo() or pkg::foo() appears in the .Rmd, a link will be added; however, if other_pkg::foo() appears, then no link will be added. #' Replace a function's name with a link to its definition #' #' A helper function for `add_function_hyperlinks` that wraps references to a #' function in an anchor tag with a link to the function's definition. #' #' @param txt Character vector where each element is a row of the knitted HTML file. #' @param function_pattern Regular Expression passed from `add_function_hyperlinks` that contains all referenced functions in the document. #' @param where_defined Character vector that contains the name of the file in which a function was defined. #' @param all_function_names Character vector of all referenced functions in the document. #' @param pkg_name Name of the package created by litr. Taken from YAML front matter. #' @param remove_span Boolean argument for removing span tags. Used for minimizing code duplication. #' @keywords internal insert_hrefs <- function(txt, function_pattern, where_defined, all_function_names, pkg_name, remove_span=FALSE){ # filter down matches of defined_functions_pattern has_fn_name <- which(stringr::str_detect(txt, function_pattern)) has_colon_prefix <- which(stringr::str_detect(txt, paste0("::", all_function_names, "\\\\(", collapse = "|"))) has_only_fn_name <- setdiff(has_fn_name, has_colon_prefix) has_pkg_colon_prefix <- which(stringr::str_detect(txt, paste0(stringr::str_glue("{pkg_name}::")))) # define different replacement functions for colon prefix cases and regular cases colon_pref_replace_fn <- function(x){ if(remove_span){ fn_name <- stringr::str_remove(x, "</span>\\\\(") fn_name <- stringr::str_remove(fn_name, '<span class="fu">') } else{ fn_name <- stringr::str_remove(x, "\\\\(") } fn_name <- stringr::str_remove(fn_name, stringr::str_glue('{pkg_name}::')) # implicitly assuming that a function is not redefined in another file def_file <- where_defined[all_function_names == fn_name] return(stringr::str_glue("{pkg_name}::<a href='{def_file}#{fn_name}'>{fn_name}</a>(")) } regular_replace_fn <- function(x){ if(remove_span){ fn_name <- stringr::str_remove(x, '</span>\\\\(') fn_name <- stringr::str_remove(fn_name, '<span class="fu">') } else { fn_name <- stringr::str_remove(x, "\\\\(") } # implicitly assuming that a function is not redefined in another file def_file <- where_defined[all_function_names == fn_name] stringr::str_glue("<a href='{def_file}#{fn_name}'>{fn_name}</a>(") } colon_prefix_function_pattern <- paste0(stringr::str_glue("{pkg_name}::"),all_function_names, "\\\\(", collapse = "|") colon_prefix_refs <- stringr::str_replace_all( txt[has_pkg_colon_prefix], colon_prefix_function_pattern, colon_pref_replace_fn ) regular_refs <- stringr::str_replace_all( txt[has_only_fn_name], function_pattern, regular_replace_fn ) # now put back in the changed lines txt[has_pkg_colon_prefix] <- colon_prefix_refs txt[has_only_fn_name] <- regular_refs txt } In addition to adding hyperlinks to function definitions, we also want to add hyperlinks for chunk references of the form <<chunk-name>> that link to user-defined chunk names that take the form ###chunk-name###. We want these user-defined chunk names to stand out to readers and in their original form as code comments they might be harder to notice. To solve this issue, we modify the formatting of these chunks to add a border with the name of the chunk set into the border, mimicking the look of a <fieldset> tag. The additional formatting requires inserting additional HTML tags as well as css into the knitted HTML file so we use the xml2 package to parse and manipulate the knitted HTML file. #' Add hyperlinks to embedded chunks #' #' Finds chunks that are referenced in the html file(s) by looking for comments #' of the form `###"foo"###` and then wraps `foo` in a `span` tag with `id="foo"` #' and then whenever the chunk label `<<foo>>` is found it wraps it in a #' `a href="file.html#foo"` tag so that it will be a hyperlink to `foo`'s #' definition. #' #' @param html_files Character vector of file names of html files that were created #' from Rmd files #' @param reference_start The delimiter used to indicate the start of a chunk label #' @param reference_end The delimiter used to indicate the end of a chunk label #' @keywords internal add_chunk_label_hyperlinks <- function(html_files, reference_start = "&lt;&lt;", reference_end = "&gt;&gt;"){ find_chunk_defs <- function(html_file) { txt <- readLines(html_file) start_line <- which(txt == "<body>") pattern <- '###&quot;([a-zA-Z0-9-_.]+)&quot;###' # find chunks that are defined in this file: chunk_names <- character(0) for (i in seq(start_line + 1, length(txt))) { chunk_name <- stringr::str_match(txt[i], pattern)[, 2] if(is.na(chunk_name)) next # a chunk was defined in this line, so put a span around it txt[i] <- stringr::str_replace( txt[i], pattern, stringr::str_glue("<span id='{chunk_name}'>###&quot;\\\\1&quot;###</span>") ) # and keep track of it for later. # we're using setNames here to make sure that we keep the name of file # where the chunk name is defined chunk_names <- setNames(c(chunk_names, chunk_name), c(names(chunk_names), html_file)) } list(chunk_names = chunk_names, txt = txt) } cdefs <- lapply(html_files, find_chunk_defs) all_chunk_names <- unlist(lapply(cdefs, function(lst) lst$chunk_names)) num_per_file <- unlist(lapply(cdefs, function(lst) length(lst$chunk_names))) where_defined <- rep(fs::path_file(html_files), times = num_per_file) defined_chunks_pattern <- paste0(reference_start, all_chunk_names, reference_end, collapse = "|") ref_start <- '<span class="sc">&lt;</span><span class="er">&lt;</span>' ref_start_alt <- '<span class=\\"er\\">&lt;&lt;</span>' ref_end <- '<span class="sc">&gt;</span><span class="er">&gt;</span>' hyphen_with_extras <- '<span class="sc">-</span>' all_chunk_names2 <- stringr::str_replace_all(all_chunk_names, "-", hyphen_with_extras) defined_chunks_pattern2 <- paste0( ref_start, all_chunk_names2, ref_end, collapse = "|" ) defined_chunks_pattern2_alt <- paste0( ref_start_alt, all_chunk_names2, ref_end, collapse = "|" ) defined_chunks_pattern2 <- paste( defined_chunks_pattern2, defined_chunks_pattern2_alt, sep = "|" ) for (i in seq_along(html_files)) { # whenever one of these named chunks is referenced, link to its definition # using the format `file_where_chunk_is_defined.html#chunkname` txt <- stringr::str_replace_all( cdefs[[i]]$txt, defined_chunks_pattern, function(x) { cname <- stringr::str_remove_all( x, paste(reference_start, reference_end, sep = "|") ) def_file <- where_defined[all_chunk_names == cname] stringr::str_glue( "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>" ) } ) txt <- stringr::str_replace_all( txt, defined_chunks_pattern2, function(x) { cname <- stringr::str_remove_all( x, paste(ref_start, ref_start_alt, ref_end, sep = "|") ) def_file <- where_defined[all_chunk_names2 == cname] cname <- stringr::str_replace_all(cname, hyphen_with_extras, "-") stringr::str_glue( "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>" ) } ) parsed_html <- xml2::read_html(paste(txt,collapse="\\n")) # get all possible chunk names in this file. chunk_names <- all_chunk_names[which(names(all_chunk_names) == html_files[i])] if(length(chunk_names) > 0){ for(j in seq_along(chunk_names)){ span_node <- xml2::xml_find_first(parsed_html, stringr::str_glue('(.//span[@id="{chunk_names[j]}"])')) span_node_path <- stringr::str_split(xml2::xml_path(span_node),"/") pre_path <- paste(span_node_path[[1]][1:(max(which(stringr::str_detect(span_node_path[[1]], "pre"))))],collapse="/") if(nchar(pre_path) == 0){ next() } pre_parent <- xml2::xml_find_first(parsed_html, pre_path) if(is.na(pre_parent)){ next() } xml2::xml_add_parent(pre_parent , xml2::read_xml(stringr::str_glue('<fieldset id="{chunk_names[j]}" class="chunkfield"> </fieldset>'))) xml2::xml_add_sibling(pre_parent, xml2::read_xml(stringr::str_glue('<legend class="chunklegend">{chunk_names[j]}</legend>')), where="before") # xml2::xml_add_parent(pre_parent, xml2::read_xml(stringr::str_glue('<fieldset id="{chunk_names[j]}" style="border:4px solid black"> <legend>{chunk_names[j]}</legend> </div>'))) xml2::xml_remove(span_node) # remove the extra line break that is left over from removing the span code_node <- xml2::xml_child(pre_parent) changed_txt <- stringr::str_remove(paste(as.character(xml2::xml_contents(code_node)),collapse=""), '\\n') xml2::xml_replace(code_node, xml2::read_xml(stringr::str_glue('<code>{changed_txt}</code>'))) } } # last thing is to insert an additional style node in the head with our CSS so we have a standard style whether we are using bookdown or Rmarkdown css_string <- "fieldset.chunkfield {border:4px solid black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em} legend.chunklegend {padding:0;font-size:21px;width:auto;border:0; border-bottom: none; margin-bottom:0} " head_node <- xml2::xml_find_first(parsed_html, ".//head") xml2::xml_add_child(head_node, xml2::read_xml(stringr::str_glue("<style type='text/css'>{css_string}</style>"))) txt <- xml2::write_html(parsed_html, html_files[i]) } } Since we rely on the xml2 package to add in the extra label formatting, let’s import it: usethis::use_package("xml2") ## ✔ Adding 'xml2' to Imports field in DESCRIPTION ## • Refer to functions with `xml2::fun()` Finally, we want to replace the ANSI escape sequences used by packages such as testthat and devtools with their HTML equivalents so the output matches what we see in the terminal. #' Replace ANSI escape sequences with their HTML equivalents #' #' Finds ANSI escape sequences and replaces them with HTML tags using the `fansi` package #' #' @param html_files Character vector of file names of html files that were created #' from Rmd files #' @keywords internal replace_ansi_sequences <- function(html_files) { for (i in seq_along(html_files)) { file_lines <- readLines(html_files[i]) # look for lines with escape sequences for URLs and remove the URL # escape sequences before we convert to HTML url_code_regex <- "\\\\033]8;;.*\\\\a(.*?)\\\\033]8;;\\\\a" url_seq_idx <- which(stringr::str_detect(file_lines, url_code_regex)) file_lines[url_seq_idx] <- sapply(url_seq_idx, function(idx){ line <- file_lines[idx] stringr::str_replace(line, url_code_regex, stringr::str_glue("\\\\1")) }) txt <- fansi::sgr_to_html(x = file_lines, warn = FALSE, term.cap = "256") writeLines(txt, con = html_files[i]) } } 7.1.3 bookdown output format It turns out that our modification to the bookdown::gitbook() format’s postprocessor is identical to the above. This suggests that we should probably reuse code more effectively. But for now I will leave it how it is: #' litr version of `bookdown::gitbook()` #' #' This behaves like `bookdown::gitbook()` with a few differences: #' - It creates an R package. #' - It adds hyperlinks to function definitions whenever a function is used #' elsewhere in the document. #' - It does "Knuth-style" chunk referencing with hyperlinks. #' <<param-minimal_eval>> #' @param ... Parameters to be passed to `bookdown::gitbook()` #' @export litr_gitbook <- function(minimal_eval = FALSE, ...) { litr_gitbook_ <- litrify_output_format(bookdown::gitbook, minimal_eval = minimal_eval) old <- litr_gitbook_(...) new <- old # modify post_processor new$post_processor = function(metadata, input_file, output_file, ...) { out <- old$post_processor(metadata, input_file, output_file, ...) out_dir <- fs::path_dir(out) file_stems <- readLines(file.path(out_dir, "reference-keys.txt")) html_files <- file.path(out_dir, paste0(file_stems, ".html")) html_files <- unique(intersect(c(out, html_files), fs::dir_ls(out_dir))) # add hyperlinks within html output to make it easier to navigate: add_function_hyperlinks(html_files, metadata$params$package_name) add_chunk_label_hyperlinks(html_files) # replace ANSI sequences with HTML tag equivalents replace_ansi_sequences(html_files) out } new } To use this output format, one would use bookdown::render_book() instead of rmarkdown::render(). In particular: bookdown::render_book(output_format = litr::litr_gitbook()) The preamble in index.Rmd would look something like this: --- title: "A `litr` Book" author: "Your Name" site: bookdown::bookdown_site params: package_name: "frombookdown" # <-- change this to your package name package_parent_dir: "." # <-- relative to this file's location documentclass: book --- Or one can add to the preamble the lines knit: litr::render output: litr::litr_gitbook This first line makes it so that in RStudio when you press “Knit”, it calls litr::render(), and the second line makes it so that it will use the special litr bookdown output format. Since the above function uses bookdown, we include it in our package: usethis::use_package("bookdown") ## ✔ Adding 'bookdown' to Imports field in DESCRIPTION ## • Refer to functions with `bookdown::fun()` 7.2 Defining litr::render() There are two primary use cases for this function: To render a .Rmd with a non-litr output format (e.g., rmarkdown::html_document) in such a way that it will generate an R package (and include the special litr-hyperlinking if .html files were created). To render a .Rmd with a litr output format (including the litr_gitbook() format). The second use case might seem unnecessary in that rmarkdown::render("create-pkg.Rmd", output_format = litr::litr_html_document()) or bookdown::render_book("index.Rmd", output_format = litr::litr_gitbook()) would do what we want. However, a reason to still prefer litr::render() is that this function ensures the identical behavior to when one clicks the “Knit” in RStudio. It does this by opening a fresh R session (when fresh_session=TRUE, which is the default) in which rmarkdown::render() (or bookdown::render_book()) is called. This is based on the description in the Rmarkdown Cookbook. Another reason to prefer litr::render() is that if there is an error in the rendering process, the special litr hash will still be written to the DESCRIPTION file. This means that after fixing that error when one calls litr::render(), one will not get the error telling the user to delete the partially generated package directory. We accomplish this with the function with_cleanup() defined below. In the first use case, litr::render() is responsible for ensuring all the special litr things happen (like setup() being called before knitting, the litr-hash being written afterwards, and hyperlinking occurs). The details of what it does is very similar to what is described in the output formats section, especially the one on the html output format. One thing that is different is that we need a function get_params_used(), defined at the end of this section, that gets the actual parameters that are used so that the location of the outputted package can be found. #' Render R markdown file #' #' Wrapper to `rmarkdown::render()` that produces an R package as output in #' addition to the standard output document. It does some post-processing on the #' .html file when that is the output. In particular, when an .html file is among #' the outputs, it adds hyperlinks to functions defined within the file to make #' it easier for someone reading the code to see where different functions are #' defined. #' #' @param input The input file to be rendered (see `rmarkdown::render`) <<param-minimal_eval>> #' @param fresh_session Whether to call `rmarkdown::render` from a fresh R #' session. By default TRUE, so that it matches the behavior of pressing "Knitr" #' in RStudio. However, for debugging it can be useful to set this to FALSE so #' that functions like `debug()` and `browser()` will work. #' @param ... Additional parameters to pass to `rmarkdown::render` #' @export render <- function(input, minimal_eval, fresh_session = TRUE, ...) { # call rmarkdown::render in a new environment so it behaves the same as # pressing the knit button in RStudio: # https://bookdown.org/yihui/rmarkdown-cookbook/rmarkdown-render.html args <- list(...) # let's determine if the output format being used is a litr format. # If it is, then we'll simply want to call rmarkdown::render() since the # special litr behavior will be attained through the output format. litr_format <- FALSE bookdown_format <- FALSE output_format_arg <- FALSE if ("output_format" %in% names(args)) { output_format_arg <- TRUE if ("litr_format" %in% names(args$output_format)) { litr_format <- TRUE } if ("bookdown_output_format" %in% names(args$output_format)) { bookdown_format <- TRUE } } else { frontmatter <- rmarkdown::yaml_front_matter(input) if ("output" %in% names(frontmatter)) { formats <- ifelse(is.list(frontmatter$output), names(frontmatter$output), frontmatter$output) if (any(stringr::str_detect(formats, "litr::"))) { litr_format <- TRUE } if (any(stringr::str_detect(formats, "litr::litr_gitbook"))) { bookdown_format <- TRUE } } } # get package_directory params <- get_params_used(input, args$params) package_dir <- get_package_directory( params$package_parent_dir, params$package_name, input ) # if minimal_eval was passed to render, add this to the output_options # argument that will be passed to rmarkdown::render if (is.null(args$output_options)) args$output_options <- list() if (!missing(minimal_eval)) args$output_options$minimal_eval <- minimal_eval # determine whether a new R session will be created when we run the rendering # function of rmarkdown/bookdown if (fresh_session) run_function <- xfun::Rscript_call else run_function <- do.call if (litr_format) { # this uses a litr output format, so we don't need to do anything litr-specific # here because it will happen through the output format if (output_format_arg & !missing(minimal_eval)) { # the output format was passed through the output_format argument rather # than through the metadata if (minimal_eval) { stop(make_noticeable(paste( "When passing a litr output format using the output_format argument,", "you should not pass minimal_eval = TRUE directly to render.", "Instead, pass it to the litr output format function. For example,", "litr::litr_html_document(minimal_eval = TRUE).", collapse = " " ))) } } if (bookdown_format) { if (fs::is_file(input)) input <- fs::path_dir(input) return(invisible(run_function(with_cleanup(bookdown::render_book, package_dir), c(input = input, args)))) } else return(invisible(run_function(with_cleanup(rmarkdown::render, package_dir), c(input = input, args)))) } # the output format being used is not a litr-specific one, so we need to make # sure that all the special litr things happen args$package_dir <- package_dir render_ <- function(input, package_dir, minimal_eval, ...) { knitr_objects <- litr:::setup(package_dir, minimal_eval) out <- rmarkdown::render(input, ...) restore_knitr_objects(knitr_objects) # remove .Rproj and .gitignore if usethis::create_package() added these remove_rstudio_extras(package_dir) return(out) } if (missing(minimal_eval)) minimal_eval <- FALSE out <- run_function(with_cleanup(render_, package_dir), c(input = input, minimal_eval = minimal_eval, args)) # add hyperlinks within html output to make it easier to navigate: if (any(stringr::str_detect(out, "html$"))) { html_file <- stringr::str_subset(out, "html$") add_function_hyperlinks(html_file, params$package_name) add_chunk_label_hyperlinks(html_file) } # add to DESCRIPTION file the version of litr used to create package: write_version_to_description(package_dir) # add litr hash so we can tell later if package files were manually edited: write_hash_to_description(package_dir) } We used the package xfun, so let’s import it: usethis::use_package("xfun") ## ✔ Adding 'xfun' to Imports field in DESCRIPTION ## • Refer to functions with `xfun::fun()` When litr::render() encounters an error, it can leave the output directory partially modified. We want to make sure the litr hash still gets written to the DESCRIPTION file. Otherwise, the next time one calls litr::render() it does not allow this directory to be overwritten. We do this by using withCallingHandlers(). This function, explained here, is similar to tryCatch(), but with the advantage that it lets the function to continue to run normally, meaning that we will still get the error message as it would appear if we hadn’t done the condition handling. #' Add litr hash to DESCRIPTION file if error encountered #' #' This creates a function that calls the passed function within the context of #' a try-catch. If an error is encountered, the litr hash is still added to #' the DESCRIPTION file so that future calls to `litr::render()` will recognize #' that it can safely overwrite the package directory (i.e., no manual editing #' occurred). #' #' @param fun function being called #' @param package_dir directory where package is being written to #' @param ... arguments to be passed to `fun` #' @keywords internal with_cleanup <- function(fun, package_dir) { return(function(...) { withCallingHandlers( fun(...), error = function(e) { # add litr hash so we can tell later if package files were manually edited: write_hash_to_description(package_dir) }) }) } In setup(), we modified the knitr objects (e.g., adding hooks, engines, etc.). We call the function restore_knitr_objects() after we’re done, to put things back how they were: #' Return the knitr objects to their original state #' #' @param original_knitr_objects As returned by `setup()` #' @keywords internal restore_knitr_objects <- function(original_knitr_objects) { knitr::opts_knit$restore(original_knitr_objects$opts_knit) knitr::knit_hooks$restore(original_knitr_objects$knit_hooks) knitr::opts_chunk$restore(original_knitr_objects$opts_chunk) knitr::opts_hooks$restore(original_knitr_objects$opts_hooks) knitr::knit_engines$restore(original_knitr_objects$knit_engines) } Another thing we want to do at the end of the rendering process is to remove two files that might have been created by usethis: .Rproj and .gitignore. These are created by usethis::create_package() when rstudio = TRUE. We don’t want these files created since this would suggest to a user that the R package should be worked on from within it rather than from the generating .Rmd file. #' Remove extra files added by usethis #' #' Remove .Rproj and .gitignore files if they are in the package directory. #' #' @param package_dir Path to package #' @keywords internal remove_rstudio_extras <- function(package_dir) { extras <- fs::dir_ls(package_dir, all = TRUE, regexp = "[.]Rproj$|[.]gitignore$") rbuildignore <- file.path(package_dir, ".Rbuildignore") txt <- readLines(rbuildignore) txt <- stringr::str_subset(txt, "^.*Rproj.*$", negate = TRUE) writeLines(txt, con = rbuildignore) for (extra in extras) fs::file_delete(extra) } As described earlier, the function get_params_used() combines the parameters from the YAML but allows for those values to be overridden through arguments passed to render(). #' Get parameter values used in rendering #' #' When the `params` argument of `rmarkdown::render()` is explicitly used, this #' overrides the default that appears in `input`. #' @param input The input file to be rendered (see `rmarkdown::render`) #' @param passed_params The list of parameters that were passed to `render`. #' @keywords internal get_params_used <- function(input, passed_params) { params <- rmarkdown::yaml_front_matter(input)$params for (param in names(passed_params)) { params[[param]] <- passed_params[[param]] } params } We used the package rmarkdown, so let’s import it: usethis::use_package("rmarkdown") ## ✔ Adding 'rmarkdown' to Imports field in DESCRIPTION ## • Refer to functions with `rmarkdown::fun()` For example, we include the line knit: litr::render in the yaml of the templates for this reason.↩︎ "],["functionality-to-facilitate-workflow.html", "8 Functionality to facilitate workflow", " 8 Functionality to facilitate workflow When someone is writing an R package with devtools, it is common to use devtools::load_all() to quickly try out the functions of an R package in the console. We’d like to allow for a similar workflow using litr. We define a litr function called load_all(), which will do the following: Litr-knit the .Rmd file with minimal_eval=TRUE to some temporary location (since the idea of load_all() is to leave the .html file alone) and then delete the .html file. Run devtools::load_all() on the output. #' Load complete package #' #' This is a litr wrapper to `devtools::load_all()`. It first calls #' `litr::render()` with `minimal_eval=TRUE`, then it calls #' `devtools::load_all()` on the generated package. #' #' @param input The input file to be rendered (see `rmarkdown::render`) #' @param output_dir By default (and in typical usage) this is NULL, meaning #' that no .html/bookdown/.pdf will result. However, when a directory is given, #' the result of the litr-knitting will be saved to this location. #' @param ... Additional parameters to be passed to `devtools::load_all()` #' @export load_all <- function(input, output_dir = NULL, ...) { no_output <- is.null(output_dir) if (no_output) { output_dir <- tempfile() if (fs::file_exists(output_dir)) fs::file_delete(output_dir) fs::dir_create(output_dir) } # let's copy over everything from input directory to output directory fs::dir_copy(fs::path_dir(input), output_dir, overwrite = TRUE) input_path <- fs::path_split(input)[[1]] moved_input <- file.path(output_dir, fs::path_file(input)) # get package directory params <- get_params_used(moved_input, list()) package_dir <- get_package_directory( params$package_parent_dir, params$package_name, moved_input ) # but if a package directory was copied here, let's remove it before # calling render to avoid a potential error if (fs::dir_exists(package_dir)) fs::dir_delete(package_dir) litr::render(moved_input, minimal_eval = TRUE, output_dir = output_dir, quiet = TRUE) new_package_dir <- file.path(fs::path_dir(input), params$package_name) fs::dir_copy(package_dir, new_package_dir, overwrite = TRUE) if (no_output) fs::dir_delete(output_dir) devtools::load_all(new_package_dir) } Let’s test that this works. In particular, we’ll call load_all() and then try to use one of the functions from the package. testthat::test_that('load_all() works', { # setup files for tests: dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, 'create-pkg.Rmd') fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file) html_file <- file.path(dir, "create-pkg.html") load_all(rmd_file) testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!") fs::dir_delete(dir) }) "],["adding-extras-to-an-r-package.html", "9 Adding extras to an R package 9.1 Adding a README 9.2 Adding a hex sticker 9.3 Adding vignettes 9.4 Add a pkgdown site", " 9 Adding extras to an R package When writing an R package, there are some additional items one typically wants to include, such as a README file, one or more vignettes, and a pkgdown site. In this section, we define some helper functions that will be make it easy to add these “extras” to a package. While a user could choose to generate all these items within the generating .Rmd file, for all but the simplest examples, it will probably be preferred to have these as separate source files. These source files can live in the same directory as the generating .Rmd file or in their own directory (e.g., source-files lives in the same directory as create-litr.Rmd, which is called litr-project). 9.1 Adding a README We define a helper function that takes an externally defined README.Rmd and puts it into the package, creates a README.md, and makes sure that these will be added to the .Rbuildignore. #' Add README to package #' #' This function takes a README.Rmd file, copies it into the package, and then #' renders it to a README.md file. It also adds these two files to the #' .Rbuildignore. #' #' @param rmd_file The path to a .Rmd file. #' @export add_readme <- function(rmd_file) { usethis::use_readme_rmd(open = FALSE) fs::file_copy(rmd_file, new_path = "README.Rmd", overwrite = TRUE) out <- xfun::Rscript_call( rmarkdown::render, args = list( input = "README.Rmd", output_options = list(html_preview = "false") ) ) } 9.2 Adding a hex sticker We define a helper function that takes an externally defined hex sticker (.png file) and puts it into the package under man/figures. The suggestion for storing it in this directory came from here. #' Add a hex sticker to package #' #' In addition to calling this function, you should add to your README.Rmd something like this: #' #' `# your-title <img src="man/figures/logo.png" align="right" height="139" />` #' #' See [here](https://pkgdown.r-lib.org/reference/build_home.html#package-logo) #' for more. #' #' @param hex_png_file The .png file with your package's hex sticker #' @export add_hex_sticker <- function(hex_png_file) { figures_dir <- file.path("man", "figures") fs::dir_create(figures_dir) fs::file_copy(path = hex_png_file, new_path = file.path(figures_dir, "logo.png"), overwrite = TRUE) } 9.3 Adding vignettes We next define a helper function for adding vignettes to the package. This mimics usethis::use_vignette(). We couldn’t directly use that function because we want the project file to live outside of the package directory, which confuses usethis. #' Add one or more vignettes to package #' #' @param rmd_files A character vector of .Rmd files, each corresponding to #' a vignette #' @param other_files A character vector of any other files needed in the #' vignettes directory (.bib file, images, etc.) #' @export add_vignettes <- function(rmd_files, other_files = NULL) { fs::dir_create("vignettes") for (fn in c(rmd_files, other_files)) fs::file_copy(fn, "vignettes") # update DESCRIPTION file: deps <- desc::desc_get_deps()$package if (!("knitr" %in% deps)) desc::desc_set_dep("knitr", type = "Suggests") if (!("rmarkdown" %in% deps)) desc::desc_set_dep("rmarkdown", type = "Suggests") out <- desc::desc_set("VignetteBuilder", "knitr") } 9.4 Add a pkgdown site We define a function based on usethis::use_pkgdown(), but with a few differences: Avoid the parts to do with looking for projects Allow one to use a custom _pkgdown.yml that is stored outside of package #' Add a pkgdown site #' #' This function creates a website for your package. You can see it locally by #' opening `docs/index.html` in your package. To get it online you can copy the #' `docs` directory to your website's server. #' #' Be sure that in the generating .Rmd file this is called *after* #' `litr::document()` has been called. To customize the site, you may pass a #' customized `_pkgdown.yml` file as described in [this `pkgdown` vignette](https://pkgdown.r-lib.org/articles/customise.html). #' #' @param config_path The _pkgdown.yml file that lives somewhere outside of your package. If NULL, then a basic default will be used. #' @export add_pkgdown <- function(config_path = NULL) { config_file <- "_pkgdown.yml" destdir <- "docs" usethis::use_build_ignore(c(config_file, destdir, "pkgdown")) if (is.null(config_path)) { # create a new config file (note it lives outside of package) config <- usethis:::pkgdown_config(destdir) usethis::write_over(config_file, yaml::as.yaml(config)) } else { # copy the one that already exists: fs::file_copy(config_path, config_file) } pkgdown::build_site() } After this step, you can locally see the site by opening docs/index.html in the browser. You can then copy the docs directory to your website’s server and you’re done. Since the above function uses pkgdown and yaml, we include these in our package: usethis::use_package("pkgdown") usethis::use_package("yaml") ## ✔ Adding 'pkgdown' to Imports field in DESCRIPTION ## • Refer to functions with `pkgdown::fun()` ## ✔ Adding 'yaml' to Imports field in DESCRIPTION ## • Refer to functions with `yaml::fun()` "],["combining-.r-files.html", "10 Combining .R files", " 10 Combining .R files This section should eventually be removed but for now I’m doing this to convince myself that the package generated by this .Rmd file really matches the initial version created without the package. library(magrittr) library(purrr) rfiles <- fs::dir_ls("R") code <- rfiles %>% map(readLines) %>% set_names( rfiles %>% stringr::str_remove("^.*/") %>% stringr::str_remove(".R$") ) hash_functions <- c("hash_package_directory", "description_litr_hash_field_name", "write_hash_to_description", "read_hash_from_description", "check_unedited") render_functions <- c("render", "with_cleanup", "litrify_output_format", "litr_pdf_document", "litr_html_document", "litr_gitbook", "replace_ansi_sequences", "add_function_hyperlinks", "insert_hrefs", "add_chunk_label_hyperlinks", "restore_knitr_objects", "remove_rstudio_extras", "get_params_used", "get_package_directory", "do_not_edit_message", "description_litr_version_field_name", "write_version_to_description", "document", "load_all") setup_functions <- c("setup", "make_noticeable", "send_to_package", "add_text_to_file", "find_labels") extras_functions <- c("add_readme", "add_hex_sticker", "add_vignettes", "add_pkgdown") remove_initial_lines <- function(code_list) { # drop first line of each list element except for the first list element c(code_list[1], map(code_list[-1], ~ .x[-1])) } fs::file_delete(setdiff(rfiles, "R/litr-package.R")) writeLines(unlist(remove_initial_lines(code[hash_functions])), "R/hash.R") writeLines(unlist(remove_initial_lines(code[render_functions])), "R/render.R") writeLines(unlist(remove_initial_lines(code[setup_functions])), "R/setup.R") writeLines(unlist(remove_initial_lines(code[extras_functions])), "R/extras.R") ## ## Attaching package: 'purrr' ## The following object is masked from 'package:magrittr': ## ## set_names "],["including-templates.html", "11 Including templates", " 11 Including templates We now add the .Rmd templates to the package. We have the skeleton.Rmd defined in source-files. Note that paths are relative to the outputted package’s location. The first template is the simplest imaginable package with a single function: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package", template_dir = "make-an-r-package", template_description = "Template for an Rmd file for writing an R package using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package/skeleton/skeleton.Rmd' The second template shows how to create a package with a dataset: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With a Dataset", template_dir = "make-an-r-package-with-data", template_description = "Template for an Rmd file for writing an R package with a dataset using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-data", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-data", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-data/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-data/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-data/skeleton/skeleton.Rmd' The third template shows how to create a package that uses Rcpp: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With Rcpp", template_dir = "make-an-r-package-with-rcpp", template_description = "Template for an Rmd file for writing an R package that makes use of Rcpp while using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-rcpp", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-rcpp", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/skeleton/skeleton.Rmd' The fourth template shows how to create a package with “extras” such as a README, a vignette, and a pkgdown site: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With a README, Vignette, and Pkgdown Site", template_dir = "make-an-r-package-with-extras", template_description = "Template for an Rmd file for writing an R package that has a README, vignette, and pkgdown site while using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-extras", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton" ), overwrite = TRUE ) fs::dir_copy( path = file.path( "..", "source-files", "make-an-r-package-with-extras", "source-files" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton", "source-files" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-extras/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-extras/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-extras/skeleton/skeleton.Rmd' The fifth template shows how to create a package from a bookdown site, i.e. instead of having just a single create-pkg.Rmd, we can have a series of .Rmd files that together create a bookdown: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package From a Bookdown", template_dir = "make-an-r-package-from-bookdown", template_description = "Template for a bookdown that defines an R package using literate programming.", template_create_dir = TRUE ) fs::dir_copy( path = file.path("..", "source-files", "make-an-r-package-from-bookdown"), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-from-bookdown", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/skeleton/skeleton.Rmd' The sixth template shows how to create a package that uses RcppArmadillo: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With RcppArmadillo", template_dir = "make-an-r-package-with-armadillo", template_description = "Template for an Rmd file for writing an R package that makes use of RcppArmadillo while using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-armadillo", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-armadillo", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/skeleton/skeleton.Rmd' "],["tests.html", "12 Defining some tests 12.1 Testing check_unedited() 12.2 Testing get_params_used() 12.3 Testing chunk referencing 12.4 Testing different ways of rendering 12.5 Testing other templates", " 12 Defining some tests When using litr to create packages that are not litr, one should be able to run tests along the way as we did above in testing the function add_text_to_file(). However, creating litr is a special case so we need to do something different for the tests that involve creating a .Rmd from template and then calling litr::render() on them (such as the tests in this section). In particular, we use eval=FALSE for these code blocks and then at the end of this document we will install the newly created version of litr and then call devtools::test(). Doing it this way is important for ensuring that the version of litr we are testing is the newest version, i.e. the version defined in this document. To understand the reason we are doing it this way, imagine what would happen if instead we left eval=TRUE in the test in the next section. When we use rmarkdown::draft() to create a .Rmd file from template, the file it will give us will be an old version (namely the installed version of litr’s template) rather than the latest version.2 Furthermore, consider what happens when we call render() in the test below. This will start the knitting process on my-package.Rmd. However, inside my-package.Rmd, we have litr::setup() and litr::document(). When these are called in the knitting process, it will be the versions of the functions from the currently installed litr rather than the versions defined in this document. Once we are done testing the new version of the package, we’d like to restore the state of litr to what it was previously. If we don’t do this, then this can lead to inadvertent circularity in which the next time we call litr::render(\"create-litr.Rmd\"), we are using the version currently under development, which is bad because ultimately we need this version to be rendered by the previous version of litr. The following function implements this approach to testing litr: #' Run tests for `litr` itself #' #' Special function for testing `litr`. The trick is to temporarily install #' the new version of `litr`, run the test, and then put things back how it was #' before. #' #' Typical values for `install_old` could be #' - `function() devtools::install("[location of old version]")` #' - `function() remotes::install_github("jacobbien/litr-project@*release", subdir = "litr")`. #' #' @param install_old A function that when run will install the old version #' @param location_of_new Path to the new package directory #' @keywords internal test_litr <- function(install_old, location_of_new) { devtools::unload(params$package_name) devtools::install(location_of_new) out <- devtools::test(location_of_new) install_old() return(out) } Note: The call to devtools::unload() is to address an issue discussed here. 12.1 Testing check_unedited() For our tests, we create a temporary directory (which we delete at the end). In this directory, we create a generating .Rmd file from one of the templates. We make repeated modifications to the package and each time verify that check_unedited() is FALSE with the modification and returns to TRUE when we put things back how they were. The modifications we try are the following: Adding a file Removing a file Making a change to a file (in particular, adding a comment to an R file) Changing something in the DESCRIPTION file (but not on the special litr line) Changing the litr hash line itself testthat::test_that("check_unedited works", { # Including this next line seems to be necessary for R CMD check on the cmd line: #Sys.setenv(RSTUDIO_PANDOC = "/Applications/RStudio.app/Contents/MacOS/pandoc") dir <- tempfile() fs::dir_create(dir) rmd_file <- file.path(dir, "my-package.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr", edit = FALSE) # create R package (named "rhello") from the Rmd template: render(rmd_file) package_path <- file.path(dir, "rhello") testthat::expect_true(check_unedited(package_path)) # what if a file has been added? added_file <- file.path(package_path, "R", "say_hello2.R") writeLines("# Added something here.", added_file) testthat::expect_false(check_unedited(package_path)) # what if we now remove it? fs::file_delete(added_file) testthat::expect_true(check_unedited(package_path)) # what if a file is removed from package? rfile <- file.path(package_path, "R", "say_hello.R") fs::file_move(rfile, dir) testthat::expect_false(check_unedited(package_path)) # now put it back fs::file_move(file.path(dir, "say_hello.R"), file.path(package_path, "R")) testthat::expect_true(check_unedited(package_path)) # what if something is changed in a file? txt <- readLines(rfile) txt_mod <- txt txt_mod[3] <- paste0(txt[3], " # added a comment!!") writeLines(txt_mod, rfile) testthat::expect_false(check_unedited(package_path)) # now put it back writeLines(txt, rfile) testthat::expect_true(check_unedited(package_path)) # what if something is changed in the DESCRIPTION file? descfile <- file.path(package_path, "DESCRIPTION") txt <- readLines(descfile) txt_mod <- txt txt_mod[1] <- "Package: newname" writeLines(txt_mod, descfile) testthat::expect_false(check_unedited(package_path)) # now put it back writeLines(txt, descfile) testthat::expect_true(check_unedited(package_path)) # what if the special litr hash field is changed in the DESCRIPTION file? txt <- readLines(descfile) i_litr <- stringr::str_which(txt, description_litr_hash_field_name()) txt_mod <- txt txt_mod[i_litr] <- paste0(txt_mod[i_litr], "a") writeLines(txt_mod, descfile) testthat::expect_false(check_unedited(package_path)) # now put it back writeLines(txt, descfile) testthat::expect_true(check_unedited(package_path)) fs::dir_delete(dir) }) 12.2 Testing get_params_used() Let’s now test the get_params_used() function, making sure it behaves how we expect it to: testthat::test_that("get_params_used works", { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, "my-package.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr", edit = FALSE) default_params <- get_params_used(rmd_file, passed_params = list()) testthat::expect_equal( default_params, rmarkdown::yaml_front_matter(rmd_file)$params ) params1 <- default_params params1$package_parent_dir <- "dir" testthat::expect_equal( get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir")), params1 ) params2 <- default_params params2$package_name <- "pkg" params2$package_parent_dir <- "dir" testthat::expect_equal( get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir", package_name = "pkg")), params2 ) fs::dir_delete(dir) }) 12.3 Testing chunk referencing Here we test the handling of chunk references (as implemented in the document output hook set within setup()). In particular, we use a .Rmd that uses chunk references in several different ways. Within the .Rmd itself, we have tests that ensure that the code can still be run as expected. fs::file_copy( path = file.path( "..", "source-files", "test-example-files", "create-rknuth.Rmd" ), new_path = file.path("tests", "testthat"), overwrite = TRUE ) testthat::test_that('Knuth-style references work', { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, 'create-rknuth.Rmd') fs::file_copy(path = testthat::test_path("create-rknuth.Rmd"), new_path = rmd_file) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, 'create-rknuth.html'))) fs::dir_delete(dir) }) 12.4 Testing different ways of rendering The mechanism by which rendering occurs depends on several factors: Whether litr::render() or rmarkdown::render() is being called. Whether there is a litr output format specified in the preamble of the .Rmd. Whether there is a litr output format being passed an argument to the render function. In this section, we will test that one gets the same result regardless of how rendering was invoked.3 We will use variations on a base .Rmd file whose preamble is simply the following: --- title: 'A Test' params: package_name: 'pkg' # <-- change this to your package name package_parent_dir: '.' # <-- relative to this file location --- fs::file_copy( path = file.path( "..", "source-files", "test-example-files", "create-pkg.Rmd" ), new_path = file.path("tests", "testthat"), overwrite = TRUE ) There are 7 cases to consider (\\(2^3-1\\), since we exclude the case where rmarkdown::render() is called and no argument or preamble would indicate that this should be a litr-knit). testthat::test_that('Rendering in all possible ways works', { # setup files for tests: dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) # .Rmd without output format in preamble rmd_file1 <- file.path(dir, 'create-pkg1.Rmd') fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file1) # .Rmd without output format in preamble rmd_file2 <- file.path(dir, 'create-pkg2.Rmd') fs::file_copy(rmd_file1, rmd_file2) litr:::add_text_to_file("output: litr::litr_html_document", rmd_file2, 3) # files names rmd_file <- file.path(dir, "create-pkg.Rmd") html_file <- file.path(dir, "create-pkg.html") html_file_a <- file.path(dir, "a","create-pkg.html") pkg <- file.path(dir, "pkg") pkg_a <- file.path(dir, "a", "pkg") check_outputs_are_same <- function() { # html files should be the same: testthat::expect_equal(readLines(html_file_a), readLines(html_file)) # packages should be the same (relying here on litr-hash in DESCRIPTION): testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")), readLines(file.path(pkg_a, "DESCRIPTION"))) } ## Now test that all the cases give the same outputs: # Case 1: no preamble + litr::render() fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE) render(rmd_file, output_file = html_file) if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a")) fs::dir_create(file.path(dir, "a")) fs::dir_copy(pkg, pkg_a) fs::dir_delete(pkg) fs::file_move(html_file, html_file_a) # Case 2: with preamble + litr::render() fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) render(rmd_file, output_file = html_file) check_outputs_are_same() # Case 3: no preamble + litr::render() with output format argument fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE) render(rmd_file, output_format = litr::litr_html_document(), output_file = html_file) check_outputs_are_same() # Case 4: with preamble + litr::render() with output format argument fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) render(rmd_file, output_format = litr::litr_html_document(), output_file = html_file) check_outputs_are_same() # Case 5: with preamble + rmarkdown::render() fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) xfun::Rscript_call(rmarkdown::render, list(input = rmd_file, output_file = html_file) ) check_outputs_are_same() # Case 6: no preamble + rmarkdown::render() with output format argument fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE) xfun::Rscript_call(rmarkdown::render, list(input = rmd_file, output_format = litr::litr_html_document(), output_file = html_file) ) check_outputs_are_same() # Case 7: with preamble + rmarkdown::render() with output format argument fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) xfun::Rscript_call(rmarkdown::render, list(input = rmd_file, output_format = litr::litr_html_document(), output_file = html_file) ) check_outputs_are_same() fs::dir_delete(dir) }) Let’s also make sure that we get the same R package output when using minimal_eval=TRUE as minimal_eval=TRUE. testthat::test_that('Rendering with minimal_eval=TRUE works', { # setup files for tests: dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, 'create-pkg.Rmd') fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file) # .Rmd without output format in preamble html_file <- file.path(dir, "create-pkg.html") html_file_a <- file.path(dir, "a","create-pkg.html") pkg <- file.path(dir, "pkg") pkg_a <- file.path(dir, "a", "pkg") ## Now test that all the cases give the same outputs: # Case 1: minimal_eval = FALSE render(rmd_file, output_file = html_file, minimal_eval = FALSE) if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a")) fs::dir_create(file.path(dir, "a")) fs::dir_copy(pkg, pkg_a) fs::dir_delete(pkg) # Case 2: minimal_eval = TRUE passed to render render(rmd_file, output_file = html_file, minimal_eval = TRUE) testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")), readLines(file.path(pkg_a, "DESCRIPTION"))) # Case 3: minimal_eval = TRUE passed to output format render(rmd_file, output_file = html_file, output_format = litr::litr_html_document(minimal_eval = TRUE) ) testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")), readLines(file.path(pkg_a, "DESCRIPTION"))) fs::dir_delete(dir) }) 12.5 Testing other templates Let’s now make sure that each template can be knit without error. testthat::test_that("templates can be knit", { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, "create-rhello.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-rhello.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "rhello"))) rmd_file <- file.path(dir, "create-rhasdata.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-data", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-rhasdata.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "rhasdata"))) rmd_file <- file.path(dir, "create-withrcpp.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-rcpp", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-withrcpp.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "withrcpp"))) rmd_file <- file.path(dir, "create-witharmadillo.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-armadillo", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-witharmadillo.Rmd"))) testthat::expect_true(fs::file_exists(file.path(dir, "witharmadillo"))) rmd_file <- file.path(dir, "create-withpkgdown.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-extras", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-withpkgdown.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "withpkgdown"))) rmd_file <- file.path(dir, "create-frombookdown.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-from-bookdown", package = "litr", edit = FALSE) prev_dir <- getwd() setwd(file.path(dir, "create-frombookdown")) fs::file_delete("create-frombookdown.Rmd") render("index.Rmd") setwd(prev_dir) testthat::expect_true( fs::file_exists(file.path(dir, "create-frombookdown", "_book", "index.html")) ) testthat::expect_true( fs::file_exists(file.path(dir, "create-frombookdown", "frombookdown")) ) fs::dir_delete(dir) }) Even though litr doesn’t directly use Rcpp, we’ll add it as a “Suggests” package since it would be required for running the above test. usethis::use_package("Rcpp", type = "Suggests") ## ✔ Adding 'Rcpp' to Suggests field in DESCRIPTION ## • Use `requireNamespace("Rcpp", quietly = TRUE)` to test if package is installed ## • Then directly refer to functions with `Rcpp::fun()` If this were the only problem, we could get around this by using pkgload::package_file() to get the proper file; however, the next problem discussed was something that seemed quite hard to resolve.↩︎ Note: When we call rmarkdown::render(), we call it in a fresh, non-interactive R session.↩︎ "],["documenting-the-package-and-testing.html", "13 Documenting the package and testing 13.1 Add examples folder with the output of knitting each example", " 13 Documenting the package and testing We finish by running commands that will document and test litr. The formatting of the test output does not print out very neatly. We download the latest release: litr::document() install_old <- function() { remotes::install_github("jacobbien/litr-project@*release", subdir = "litr") } xfun::Rscript_call(test_litr, list(install_old = install_old, location_of_new = ".")) ## ℹ Updating litr documentation ## ℹ Loading litr ## Writing 'NAMESPACE' ## Writing 'add_readme.Rd' ## Writing 'add_hex_sticker.Rd' ## Writing 'add_vignettes.Rd' ## Writing 'add_pkgdown.Rd' ## Writing 'hash_package_directory.Rd' ## Writing 'description_litr_hash_field_name.Rd' ## Writing 'write_hash_to_description.Rd' ## Writing 'read_hash_from_description.Rd' ## Writing 'check_unedited.Rd' ## Writing 'litr-package.Rd' ## Writing 'render.Rd' ## Writing 'with_cleanup.Rd' ## Writing 'litrify_output_format.Rd' ## Writing 'litr_pdf_document.Rd' ## Writing 'litr_html_document.Rd' ## Writing 'litr_gitbook.Rd' ## Writing 'replace_ansi_sequences.Rd' ## Writing 'add_function_hyperlinks.Rd' ## Writing 'insert_hrefs.Rd' ## Writing 'add_chunk_label_hyperlinks.Rd' ## Writing 'restore_knitr_objects.Rd' ## Writing 'remove_rstudio_extras.Rd' ## Writing 'get_params_used.Rd' ## Writing 'get_package_directory.Rd' ## Writing 'do_not_edit_message.Rd' ## Writing 'description_litr_version_field_name.Rd' ## Writing 'write_version_to_description.Rd' ## Writing 'document.Rd' ## Writing 'load_all.Rd' ## Writing 'setup.Rd' ## Writing 'make_noticeable.Rd' ## Writing 'send_to_package.Rd' ## Writing 'add_text_to_file.Rd' ## Writing 'find_labels.Rd' ## Writing 'test_litr.Rd' ## file context test nb failed skipped ## 1 tests.R tests add_text_to_file() works 10 0 FALSE ## 2 tests.R tests get_package_directory() works 2 0 FALSE ## 3 tests.R tests load_all() works 1 0 FALSE ## 4 tests.R tests check_unedited works 11 0 FALSE ## 5 tests.R tests get_params_used works 3 0 FALSE ## 6 tests.R tests Knuth-style references work 1 0 FALSE ## 7 tests.R tests Rendering in all possible ways works 12 0 FALSE ## 8 tests.R tests Rendering with minimal_eval=TRUE works 2 0 FALSE ## 9 tests.R tests templates can be knit 12 0 FALSE ## error warning user system real passed ## 1 FALSE 0 0.049 0.004 0.053 10 ## 2 FALSE 0 0.003 0.000 0.003 2 ## 3 FALSE 0 0.262 0.019 2.354 1 ## 4 FALSE 0 0.097 0.039 1.880 11 ## 5 FALSE 0 0.007 0.001 0.008 3 ## 6 FALSE 0 0.020 0.003 1.884 1 ## 7 FALSE 0 0.443 0.043 12.580 12 ## 8 FALSE 0 0.314 0.034 5.271 2 ## 9 FALSE 0 0.128 0.077 58.672 12 ## result ## 1 , 10, 3, 10, 77, 3, 77, 10, 10, testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, req_exist = TRUE), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, fs::file_exists(myfile) is not TRUE\\n\\n, 16, 3, 16, 48, 3, 48, 16, 16, 45, 47, add_text_to_file() works, `sometxt` (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 17, 3, 17, 52, 3, 52, 17, 17, 45, 47, add_text_to_file() works, c(sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 22, 3, 22, 64, 3, 64, 22, 22, 45, 47, add_text_to_file() works, , 25, 3, 25, 62, 3, 62, 25, 25, testthat::expect_error(add_text_to_file(sometxt, myfile, 0)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, 0), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, , 26, 3, 26, 63, 3, 63, 26, 26, testthat::expect_error(add_text_to_file(sometxt, myfile, -1)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, -1), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, , 27, 3, 27, 62, 3, 62, 27, 27, testthat::expect_error(add_text_to_file(sometxt, myfile, 5)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, 5), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, c(moretxt2, sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 32, 3, 32, 74, 3, 74, 32, 32, 45, 47, add_text_to_file() works, c(moretxt2, moretxt3, sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 37, 3, 38, 43, 3, 43, 37, 38, 45, 47, add_text_to_file() works, c(moretxt2, moretxt3, sometxt, moretxt, moretxt4) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 43, 3, 44, 43, 3, 43, 43, 44, 45, 47, add_text_to_file() works ## 2 get_package_directory(".", "mypkg", input) (`actual`) not equal to file.path("inputdir", "mypkg") (`expected`).\\n\\n, 50, 3, 53, 3, 3, 3, 50, 53, 45, 47, get_package_directory() works, get_package_directory("..", "mypkg", input) (`actual`) not equal to file.path("inputdir", "..", "mypkg") (`expected`).\\n\\n, 54, 3, 57, 3, 3, 3, 54, 57, 45, 47, get_package_directory() works ## 3 say_hello("Jacob") (`actual`) not equal to "Hello Jacob!" (`expected`).\\n\\n, 70, 3, 70, 60, 3, 60, 70, 70, 45, 47, load_all() works ## 4 check_unedited(package_path) is not TRUE\\n\\n, 88, 3, 88, 53, 3, 53, 88, 88, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 93, 3, 93, 54, 3, 54, 93, 93, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 97, 3, 97, 53, 3, 53, 97, 97, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 102, 3, 102, 54, 3, 54, 102, 102, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 105, 3, 105, 53, 3, 53, 105, 105, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 112, 3, 112, 54, 3, 54, 112, 112, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 115, 3, 115, 53, 3, 53, 115, 115, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 123, 3, 123, 54, 3, 54, 123, 123, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 126, 3, 126, 53, 3, 53, 126, 126, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 134, 3, 134, 54, 3, 54, 134, 134, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 137, 3, 137, 53, 3, 53, 137, 137, 45, 47, check_unedited works ## 5 `default_params` (`actual`) not equal to rmarkdown::yaml_front_matter(rmd_file)$params (`expected`).\\n\\n, 150, 3, 153, 3, 3, 3, 150, 153, 45, 47, get_params_used works, get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir")) (`actual`) not equal to `params1` (`expected`).\\n\\n, 156, 3, 159, 3, 3, 3, 156, 159, 45, 47, get_params_used works, get_params_used(...) (`actual`) not equal to `params2` (`expected`).\\n\\n, 163, 3, 168, 3, 3, 3, 163, 168, 45, 47, get_params_used works ## 6 fs::file_exists(file.path(dir, "create-rknuth.html")) is not TRUE\\n\\n, 179, 3, 179, 78, 3, 78, 179, 179, 45, 47, Knuth-style references work ## 7 readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works ## 8 readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 291, 3, 292, 68, 3, 68, 291, 292, 45, 47, Rendering with minimal_eval=TRUE works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 299, 3, 300, 68, 3, 68, 299, 300, 45, 47, Rendering with minimal_eval=TRUE works ## 9 fs::file_exists(file.path(dir, "create-rhello.html")) is not TRUE\\n\\n, 316, 3, 316, 78, 3, 78, 316, 316, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "rhello")) is not TRUE\\n\\n, 317, 3, 317, 66, 3, 66, 317, 317, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-rhasdata.html")) is not TRUE\\n\\n, 325, 3, 325, 80, 3, 80, 325, 325, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "rhasdata")) is not TRUE\\n\\n, 326, 3, 326, 68, 3, 68, 326, 326, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-withrcpp.html")) is not TRUE\\n\\n, 334, 3, 334, 80, 3, 80, 334, 334, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "withrcpp")) is not TRUE\\n\\n, 335, 3, 335, 68, 3, 68, 335, 335, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-witharmadillo.Rmd")) is not TRUE\\n\\n, 343, 3, 343, 84, 3, 84, 343, 343, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "witharmadillo")) is not TRUE\\n\\n, 344, 3, 344, 73, 3, 73, 344, 344, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-withpkgdown.html")) is not TRUE\\n\\n, 352, 3, 352, 83, 3, 83, 352, 352, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "withpkgdown")) is not TRUE\\n\\n, 353, 3, 353, 71, 3, 71, 353, 353, 45, 47, templates can be knit, fs::file_exists(...) is not TRUE\\n\\n, 365, 3, 367, 5, 3, 5, 365, 367, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-frombookdown", "frombookdown")) is not TRUE\\n\\n, 368, 3, 370, 5, 3, 5, 368, 370, 45, 47, templates can be knit 13.1 Add examples folder with the output of knitting each example In this section, we will litr-knit each template and put the outputs in an examples directory that lives outside of the litr R package. These examples are linked to in README.Rmd. build_all_templates <- function(install_old, location_of_new) { devtools::install(location_of_new) example_dir <- file.path("..", "examples") if (fs::dir_exists(example_dir)) fs::dir_delete(example_dir) fs::dir_create(example_dir) templates <- fs::path_file(fs::dir_ls("inst/rmarkdown/templates")) templates_bookdown <- stringr::str_subset(templates, "bookdown") templates_nonbookdown <- setdiff(templates, templates_bookdown) for (template in templates_nonbookdown) { tmp_file <- file.path(example_dir, "temp.Rmd") rmarkdown::draft(tmp_file, template, package = "litr", edit = FALSE) pkg_name <- rmarkdown::yaml_front_matter(tmp_file)$params$package_name rmd_file <- file.path(example_dir, paste0("create-", pkg_name, ".Rmd")) fs::file_move(tmp_file, rmd_file) render(rmd_file) # move to a template-specific directory: template_dir <- file.path(example_dir, template) fs::dir_create(template_dir) fs::file_move(fs::dir_ls(example_dir, regexp = pkg_name), template_dir) # move the source-files directory if (fs::dir_exists(file.path(example_dir, "source-files"))) { fs::dir_create(file.path(example_dir, template, "source-files")) fs::dir_copy(file.path(example_dir, "source-files"), file.path(example_dir, template)) fs::dir_delete(file.path(example_dir, "source-files")) } # move the docs directory (when pkgdown creates one) if (fs::dir_exists(file.path(example_dir, "docs"))) { fs::dir_create(file.path(example_dir, template, "docs")) fs::dir_copy(file.path(example_dir, "docs"), file.path(example_dir, template)) fs::dir_delete(file.path(example_dir, "docs")) } } for (template in templates_bookdown) { tmp_dir <- file.path(example_dir, "temp") rmarkdown::draft(file.path(example_dir, "temp.Rmd"), template, package = "litr", edit = FALSE) prev_dir <- getwd() setwd(tmp_dir) fs::file_delete("temp.Rmd") render("index.Rmd") # move to a template-specific directory: setwd(prev_dir) fs::dir_copy(tmp_dir, file.path(example_dir, template)) fs::dir_delete(tmp_dir) } install_old() } xfun::Rscript_call(build_all_templates, list(install_old = install_old, location_of_new = ".")) ## [1] "litr" "],["including-extras-for-litr.html", "14 Including extras for litr 14.1 README with hex sticker 14.2 Vignettes 14.3 A pkgdown site", " 14 Including extras for litr 14.1 README with hex sticker We include a README.Rmd and then generate the README.md based on it: add_readme(file.path("..", "source-files", "README.Rmd")) ## ✔ Writing 'README.Rmd' ## ✔ Adding '^README\\\\.Rmd$' to '.Rbuildignore' ## ✔ Creating '.git/hooks/' ## ✔ Writing '.git/hooks/pre-commit' Let’s add the litr hex sticker too (which is referred to in the README). add_hex_sticker(file.path("..", "source-files", "litr-hex.png")) Let’s also add a figure we include in the README. fs::file_copy(file.path("..", "source-files", "diagram3.png"), file.path("man", "figures")) 14.2 Vignettes add_vignettes(c(file.path("..", "source-files", "package-templates.Rmd"), file.path("..", "source-files", "packages-in-the-wild.Rmd"), file.path("..", "source-files", "faqs.Rmd"), file.path("..", "source-files", "basic-example.Rmd"))) The templates vignette uses dplyr and stringr, so we add them as “Suggests” in the DESCRIPTION file: usethis::use_package("dplyr", type = "Suggests") usethis::use_package("stringr", type = "Suggests") ## ✔ Adding 'dplyr' to Suggests field in DESCRIPTION ## • Use `requireNamespace("dplyr", quietly = TRUE)` to test if package is installed ## • Then directly refer to functions with `dplyr::fun()` ## Warning: Package 'stringr' is already listed in 'Imports' in DESCRIPTION, no ## change made. 14.3 A pkgdown site We’ll first add the github url to the DESCRIPTION file. desc::desc_set("URL", "https://github.com/jacobbien/litr-project/tree/main/litr") ## Package: litr ## Title: Literate Programming for Writing R Packages ## Version: 0.9.1 ## Authors@R (parsed): ## * Jacob Bien <jbien@usc.edu> [aut, cre] ## * Patrick Vossler [aut] ## Description: Allows one to fully create an R package in a single .Rmd ## file. Includes functionality and .Rmd templates for a literate ## programming approach to R package development. ## License: MIT + file LICENSE ## URL: https://github.com/jacobbien/litr-project/tree/main/litr ## Imports: ## bookdown, ## desc, ## devtools, ## digest, ## fansi, ## fs, ## knitr, ## pkgdown, ## rmarkdown, ## stringr, ## usethis, ## xfun, ## xml2, ## yaml ## Suggests: ## dplyr, ## Rcpp, ## testthat (>= 3.0.0) ## VignetteBuilder: ## knitr ## Config/testthat/edition: 3 ## Encoding: UTF-8 ## Roxygen: list(markdown = TRUE) ## RoxygenNote: 7.2.3 Next, we create the pkgdown site. The customizations come from the source file source-files/_pkgdown.yml. pkgdown_yml <- file.path("..", "source-files", "_pkgdown.yml") add_pkgdown(pkgdown_yml) ## ✔ Adding '^_pkgdown\\\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore' ## -- Installing package into temporary library ----------------------------------- ## == Building pkgdown site ======================================================= ## Reading from: '/Users/vossler/litr-project/litr' ## Writing to: '/Users/vossler/litr-project/docs' ## -- Initialising site ----------------------------------------------------------- ## -- Building favicons ----------------------------------------------------------- ## Building favicons with realfavicongenerator.net... ## Copying 'pkgdown/favicon/apple-touch-icon-120x120.png' to 'apple-touch-icon-120x120.png' ## Copying 'pkgdown/favicon/apple-touch-icon-152x152.png' to 'apple-touch-icon-152x152.png' ## Copying 'pkgdown/favicon/apple-touch-icon-180x180.png' to 'apple-touch-icon-180x180.png' ## Copying 'pkgdown/favicon/apple-touch-icon-60x60.png' to 'apple-touch-icon-60x60.png' ## Copying 'pkgdown/favicon/apple-touch-icon-76x76.png' to 'apple-touch-icon-76x76.png' ## Copying 'pkgdown/favicon/apple-touch-icon.png' to 'apple-touch-icon.png' ## Copying 'pkgdown/favicon/favicon-16x16.png' to 'favicon-16x16.png' ## Copying 'pkgdown/favicon/favicon-32x32.png' to 'favicon-32x32.png' ## -- Building home --------------------------------------------------------------- ## Reading 'LICENSE.md' ## Writing '404.html' ## -- Building function reference ------------------------------------------------- ## Reading 'man/add_chunk_label_hyperlinks.Rd' ## Reading 'man/add_function_hyperlinks.Rd' ## Reading 'man/add_hex_sticker.Rd' ## Reading 'man/add_pkgdown.Rd' ## Reading 'man/add_readme.Rd' ## Reading 'man/add_text_to_file.Rd' ## Reading 'man/add_vignettes.Rd' ## Reading 'man/check_unedited.Rd' ## Reading 'man/description_litr_hash_field_name.Rd' ## Reading 'man/description_litr_version_field_name.Rd' ## Reading 'man/do_not_edit_message.Rd' ## Reading 'man/document.Rd' ## Reading 'man/find_labels.Rd' ## Reading 'man/get_package_directory.Rd' ## Reading 'man/get_params_used.Rd' ## Reading 'man/hash_package_directory.Rd' ## Reading 'man/insert_hrefs.Rd' ## Reading 'man/litr-package.Rd' ## Reading 'man/litr_gitbook.Rd' ## Reading 'man/litr_html_document.Rd' ## Reading 'man/litr_pdf_document.Rd' ## Reading 'man/litrify_output_format.Rd' ## Reading 'man/load_all.Rd' ## Reading 'man/make_noticeable.Rd' ## Reading 'man/read_hash_from_description.Rd' ## Reading 'man/remove_rstudio_extras.Rd' ## Reading 'man/render.Rd' ## Reading 'man/replace_ansi_sequences.Rd' ## Reading 'man/restore_knitr_objects.Rd' ## Reading 'man/send_to_package.Rd' ## Reading 'man/setup.Rd' ## Reading 'man/test_litr.Rd' ## Reading 'man/with_cleanup.Rd' ## Reading 'man/write_hash_to_description.Rd' ## Reading 'man/write_version_to_description.Rd' ## -- Building articles ----------------------------------------------------------- ## Reading 'vignettes/basic-example.Rmd' ## Reading 'vignettes/faqs.Rmd' ## Reading 'vignettes/package-templates.Rmd' ## Reading 'vignettes/packages-in-the-wild.Rmd' ## Writing 'sitemap.xml' ## -- Building search index ------------------------------------------------------- ## == DONE ======================================================================== We follow this pkgdown vignette in our customizations. Here is the contents of the _pkgdown.yml that was used: cat(readLines("../source-files/_pkgdown.yml"), sep = '\\n') destination: ../docs/ url: ~ template: bootstrap: 5 bootswatch: cosmo repo: url: home: https://github.com/jacobbien/litr-project/tree/main/litr/ source: https://github.com/jacobbien/litr-project/tree/main/litr/ issue: https://github.com/jacobbien/litr-project/issues/ user: https://github.com/ authors: Jacob Bien: href: http://faculty.marshall.usc.edu/jacob-bien/ Patrick Vossler: href: https://www.patvoss.me/ navbar: structure: left: [reference, articles] right: [github] components: github: icon: fa-github href: https://github.com/jacobbien/litr-project/tree/main/litr/ reference: - title: Primary functions desc: > These are the functions you'll use the most. contents: - render - document - load_all - title: Functions for adding "extras" to your package desc: > These functions can help you add a README, vignettes, a pkgdown site, and a hex sticker to your package. contents: - add_readme - add_vignettes - add_pkgdown - add_hex_sticker - title: Custom output formats desc: > These are the functions for producing different output formats. contents: - litr_html_document - litr_pdf_document - litr_gitbook - litrify_output_format After this step, you can locally see the site by opening docs/index.html in the browser. You can then copy the docs directory to your website’s server and you’re done. "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]] +[["index.html", "Creating the litr R package 1 Preamble", " Creating the litr R package Jacob Bien May 27, 2022 1 Preamble This document uses literate programming to define the litr R package. This means that all source code for litr is contained and presented in this document alongside explanation. The litr R package gets created as an output when one runs a piece of code that takes this document as input. To modify the litr package, one should modify the code in this document (and the explanation, as needed) and then regenerate the package. But what piece of code do we use to create an R package from this document? Well… it turns out that this is precisely what the litr package is for. But how can we use litr to create litr? We actually use version \\(n-1\\) of litr to create version \\(n\\). When an R markdown file is rendered with litr, an R package is created in addition to the usual html/pdf/etc document. To learn more about using litr, please visit the litr website. This document is not intended for people who wish to use litr. Rather, it is intended for people who want to know how it works and (possibly) modify it. This document used litr version 0.9.0 to define the current version of litr. In particular, it was generated by running the following command in an R console from the working directory containing index.Rmd: litr::render("index.Rmd") fs::dir_copy("_book", "../docs/create", overwrite = TRUE) fs::dir_delete("_book") The second and third lines move the generated bookdown to the docs/ directory so that it will render nicely on github by visiting https://jacobbien.github.io/litr-project/create/. "],["overview.html", "2 Overview", " 2 Overview The litr package consists of (a) document templates that users can use as the basis for creating an R package, (b) code needed so that when such a R markdown file is knitted, it will generate not just an .html (or some other output format) but also an R package. The code has the following components: Functionality for generating the R package when the .Rmd file is knitted. In particular, we define a knitr chunk hook, which we call send_to_package() that identifies code chunks in the .Rmd file that should be included in the package. To make it so that this chunk hook will be active, we also have a function called setup() that is called at the start of litr::render() right before rmarkdown::render() is called. The setup() function makes it so that when the .Rmd file is knitted, the chunk hook send_to_package() will be invoked on each code chunk. It is also responsible for other preliminaries, such as defining a new knitr language engine that can interpret package-level documentation. Functionality for making sure the R package outputted will not overwrite a manually edited R package. Our approach here is to use a hash that will make it clear whether something has been modified. Wrapper to devtools::document() The reason we write a wrapper for devtools::document() is because we want it to behave slightly differently. In particular, devtools::document() reminds the reader to edit the roxygen in the R/ files, whereas in our case, we want to make sure they edit the original .Rmd file, not the R/ files. Functionality to alter the rendering process The focus here is to make it so that all the special litr functionality will be active when the .Rmd is rendered. We implement two ways this can happen. First, we have several custom output formats, such as litr_html_document(). When rmarkdown::render() is called with one of these litr output formats, setup() gets called before knitting occurs and some other litr-specific post-processing occurs as well. The second way is through the function litr::render(). If a user calls litr::render() with a non-litr output format, e.g. rmarkdown::html_document(), then setup() gets called before rmarkdown::render() and also post-processing can again happen. Another thing of note about litr::render() is that it renders the document in a fresh environment, which ensures identical behavior to when a user presses “Knit” in RStudio. "],["package-setup.html", "3 Package setup 3.1 A note on circularity", " 3 Package setup We start by specifying the information needed in the DESCRIPTION file of the R package. We mostly follow the R Packages book’s description of version numbering. Releases 0.0.1, 0.0.2, and 0.0.3 should really have been 0.1.0, 0.2.0, and 0.3.0, because these were not just patches. Rather, each added quite substantial new functionality. For this reason, we have gone straight from 0.0.3 to 0.4.0. We will keep the leftmost number (“major release”) at 0 until we feel like the package is complete with all the intended features. usethis::create_package( path = ".", fields = list( Package = params$package_name, Version = "0.9.1", Title = "Literate Programming for Writing R Packages", Description = "Allows one to fully create an R package in a single .Rmd file. Includes functionality and .Rmd templates for a literate programming approach to R package development.", `Authors@R` = c( person("Jacob", "Bien", email = "jbien@usc.edu", role = c("aut", "cre")), person("Patrick Vossler", role = "aut") ) ) ) usethis::use_mit_license(copyright_holder = "J. Bien") Let’s add some package-level documentation. This is what will show up when someone types package?litr in the console. #' Literate Programming for Writing R Packages #' #' Allows one to fully create an R package in a single .Rmd file. Includes #' functionality and .Rmd templates for a literate programming approach to R #' package development. #' #' @examples #' # Make a file create-rhello.Rmd based on a template #' \\dontrun{ #' rmarkdown::draft("create-rhello.Rmd", #' template = "make-an-r-package", #' package = "litr", #' edit = FALSE) #' # Now call litr::render (or press Knit if in RStudio) to generate not just #' # create-rhello.html, but also an R package called `rhello`. #' litr::render("create-rhello.Rmd") #' } #' @docType package #' @seealso \\code{\\link{render}} 3.1 A note on circularity Keeping track of the version of litr used is particularly important when using litr to develop litr. There is a tendency to want to use the new functionality that we are creating in create-litr.Rmd itself as soon as we have defined it. However, this is circular and thus must be avoided. To see why we need to be careful, let’s consider an actual example that arose when working on litr version 0.0.3. After release v0.0.2, one of the new features we added is a new language engine called package_doc that allows us to have a special kind of code block defining the package documentation. In trying out this feature and making sure it works on skeleton.Rmd, we would most likely install version 0.0.3. Now that version 0.0.3 is installed, there will be a tendency to want to add a package_doc block to create-litr.Rmd, and it will appear to work. However, this is circular, because we have used version 0.0.3 to create version 0.0.3! In particular, if we remove litr and re-install it from github, we will get version 0.0.2 so that when we attempt to create the package using litr::render(\"create-litr.Rmd\"), we will get an error telling us that it doesn’t have a language engine named package_doc. The code chunk in this section is for preventing this from happening. For more on circularity, see the section on testing litr. install_version_of_litr <- utils::packageVersion("litr") remote <- remotes::github_remote( repo = "jacobbien/litr-project@*release", subdir = "litr" ) version_of_latest_release <- stringr::str_remove(remote$ref, "v") if (install_version_of_litr != version_of_latest_release) stop(stringr::str_glue( "You should be using the version of litr from the latest release (version", " {version_of_latest_release}),\\n but you are using version", " {install_version_of_litr}.\\n", "You can install the release version of litr from GitHub by running the", " the following command:\\n", "remotes::install_github(repo='jacobbien/litr-project@*release', subdir = 'litr')" )) "],["generating-package.html", "4 Generating the R package 4.1 Sending code chunks to the package 4.2 Setting up the R package creation", " 4 Generating the R package 4.1 Sending code chunks to the package We start by defining a chunk hook, which is a function that runs both before and after each code chunk is run in the knitting process. In this case, the function (called send_to_package) is responsible for determining whether the code chunk looks like something that should be exported to the R package. We don’t want all code sent off to our R package. For example, sometimes we’ll want to demonstrate in the Rmd file how a certain function we’ve just created is used by running it on an example or making a plot. That bit of example code would not be included in the package. We start by making sure that code is only sent to the R package once (arbitrarily we have code outputted to the package before and not after the chunk is run). The function then checks if this code chunk is code that should be put into the package. There are four specific cases it considers: If the special option send_to is used in this code chunk, then things are very simple in that the user has explicitly told us where this code should be added. For example, if send_to=\"R/file.R\", then the code in this chunk will be appended to R/file.R in the R package (and if that file doesn’t yet exist, it will be created). Is it a piece of code to be sent to the R/ directory? In particular, it checks to see if the code chunk begins with the characteristic roxygen2 characters #'. If it does, then the name of the object being documented is identified (could be a function, a dataset, an S4 object, etc.) and then we write the code chunk to the file R/<objectname>.R. If the code chunk does not start with #', then we check if it has any line starting with test_that( or testthat::test_that(. If so, then this whole code chunk is appended to tests/testthat/tests.R (and this file is created the first time a test chunk is sent to the package). Next, it checks if the language engine is Rcpp. This occurs when the code chunk starts with {Rcpp, rather than the usual {r (or alternatively when the engine=\"Rcpp\" option is used). We then set things up appropriately for the use of Rcpp within the package (by adapting some code from within the usethis::use_rcpp() function). Finally, we write the code chunk to src/code.cpp. There is a common header used, #include <Rcpp.h> using namespace Rcpp; and we only want this to appear once in code.cpp, so we do a bit of work to remove that if it appears in the code chunk. #' A knitr chunk hook for writing R code and tests #' #' This chunk hook detects whether a chunk is defining a function or dataset #' to be included in the R package (looks for the `roxygen2` comment format `#' `). #' If so, then it is written to the `R/` directory. It also looks for chunks #' that have one or more lines that start with `test_that(` or #' `testthat::test_that(` (potentially with some leading whitespace). These #' chunks are then written to the `tests` directory of the R package. #' #' When the `send_to` option is used, this chunk hook instead simply writes the #' code chunk to the file specified. #' #' @param before Indicates whether this is being called before or after the #' chunk code is executed #' @param options Has information from the chunk #' @param envir Environment #' @keywords internal send_to_package <- function(before, options, envir) { msg <- do_not_edit_message(knitr::current_input(), type = "R") if (before == FALSE) { # Don't do anything after the code chunk has been executed. return() } package_dir <- knitr::opts_knit$get("root.dir") package_name <- fs::path_file(package_dir) if (!is.null(options$send_to)) { # the user has defined an option that indicates where in the package this # code should be written file <- file.path(package_dir, options$send_to) add_text_to_file(options$code, file, pad = TRUE, msg = msg) return() } if (stringr::str_detect(options$code[1], "^#' ")) { # starts with roxygen2, so let's assume this chunk is defining an R function # or dataset that belongs in the package non_comment <- stringr::str_subset(options$code, "^#", negate = TRUE) if (length(non_comment) > 0) { if (stringr::str_detect(non_comment[1], "<-")) { # a function is being defined objname <- stringr::str_match(non_comment[1], "^(.*)\\\\s*<-\\\\s*function")[, 2] objname <- stringr::str_trim(objname) } else if (stringr::str_detect(non_comment[1], '^".+"$')) { # a dataset is being documented objname <- stringr::str_sub(non_comment[1], start = 2, end = -2) } else { # Roxygen2 comment wasn't followed by anything recognized, so do not # send this to package return() } file <- file.path(package_dir, "R", stringr::str_glue("{objname}.R")) cat(paste(c(msg, "", options$code, ""), collapse = "\\n"), file = file) } } else if (any(stringr::str_detect(options$code, "^\\\\s*(testthat::)?test_that\\\\("))) { # This chunk is inferred to be a test test_dir <- file.path(package_dir, "tests", "testthat") test_file <- file.path(test_dir, "tests.R") if (!file.exists(test_file)) { # It's the first chunk with tests if (!dir.exists(test_dir)) usethis::use_testthat() cat(c(msg, ""), collapse = "\\n", file = test_file) } cat( paste(c(options$code, "", ""), collapse = "\\n"), file = test_file, append = TRUE ) } else if (options$engine == "Rcpp") { # To add Rcpp code, we need the package documentation file to exist if (!file.exists(file.path( package_dir, "R", paste0(package_name, "-package.R")) )) { usethis::use_package_doc(open = FALSE) } cpp_file <- file.path(package_dir, "src", "code.cpp") if (!file.exists(cpp_file)) { # set up package for Rcpp # these next few lines are taken from usethis::use_rcpp() # it approximates a call to usethis::use_rcpp(name = "code") usethis:::use_dependency("Rcpp", "LinkingTo") usethis:::use_dependency("Rcpp", "Imports") usethis:::roxygen_ns_append("@importFrom Rcpp sourceCpp") usethis:::use_src() usethis::use_template("code.cpp", save_as = "src/code.cpp") msg <- do_not_edit_message(knitr::current_input(), type = "c") cat(msg, file = cpp_file, append = TRUE) } # append code to code.cpp, but remove lines that are `#include <Rcpp.h>` # or `using namespace Rcpp;` since this already appears at top of file cat(paste(c( "", stringr::str_subset( options$code, r"(^#include <Rcpp.h>$|^using namespace Rcpp;$)", negate = TRUE), ""), collapse = "\\n"), file = cpp_file, append = TRUE) } return() } The above code makes use of a number of functions from the stringr and usethis packages, so we’ll need to add those packages to the Imports section of the DESCRIPTION file: usethis::use_package("stringr") usethis::use_package("usethis") ## ✔ Adding 'stringr' to Imports field in DESCRIPTION ## • Refer to functions with `stringr::fun()` ## ✔ Adding 'usethis' to Imports field in DESCRIPTION ## • Refer to functions with `usethis::fun()` The code also calls the function do_not_edit_message(), which adds a line at the top of the files sent to the R package reminding the user that these are not source files to be edited but rather output of the generating .Rmd file. There are two variations on this message. #' Generate do-not-edit message to put at top of file #' #' @param rmd_file Name of the Rmd file to mention #' @param type Whether this is a R/ file, man/ file, or a c file #' @keywords internal do_not_edit_message <- function(rmd_file, type = c("R", "man", "c")) { if (type[1] == "R") return(stringr::str_glue("# Generated from {rmd_file}: do not edit by hand")) else if (type[1] == "man") return(stringr::str_glue("% Please edit documentation in {rmd_file}.")) else if (type[1] == "c") return(stringr::str_glue("// Generated from {rmd_file}: do not edit by hand")) else stop("type must be either 'R', 'man', or 'c'.") } This function will also be used with type = \"man\" by litr::document(). The above also makes use of a simple helper function that inserts text into a specified location of a file (or creates that file if it doesn’t exist). Actually currently it doesn’t, but we can replace cat() in the above with calls to add_text_to_file(). #' Add Some Text to a File #' #' The text will be added to the file at a particular line specified by #' `location`. The first line of `txt` will be on line `location` of the #' modified file. If `location` is NULL, then text is added to end of file. #' If file does not exist, it is created and `location` is ignored (unless #' `req_exist` is `TRUE`, in which case an error is thrown). #' #' @param txt Character vector to add to file #' @param filename Name of file #' @param location Specifies where text should be added. See description for more. #' @param req_exist If TRUE, then throws an error if file doesn't exist #' @param pad If TRUE, then when text is being added to a preexisting file, it adds a newline #' @param msg An optional message to put at top of file if this is a new file #' @keywords internal add_text_to_file <- function(txt, filename, location = NULL, req_exist = FALSE, pad = FALSE, msg = NULL) { if (!file.exists(filename)) { if (req_exist) stop(stringr::str_glue("Cannot find file {filename}.")) if (!is.null(msg)) txt <- c(msg, "", txt) writeLines(txt, con = filename) return() } if (pad) txt <- c("", txt) filetxt <- readLines(filename) if (is.null(location) || location == length(filetxt) + 1) { filetxt <- c(filetxt, txt) } else if (location > length(filetxt) + 1 | location < 1) stop("Invalid location") else if (location == 1) { filetxt <- c(txt, filetxt) } else { # location is somewhere in middle filetxt <- c(filetxt[1:(location - 1)], txt, filetxt[location:length(filetxt)]) } writeLines(filetxt, con = filename) } testthat::test_that("add_text_to_file() works", { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) # should throw error when file does not exist and req_exist is TRUE: myfile <- file.path(dir, "file.txt") sometxt <- c("hello", "there") testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE)) # should create a new file where one does not exist: myfile <- file.path(dir, "file.txt") sometxt <- c("hello", "there") add_text_to_file(sometxt, myfile) testthat::expect_true(fs::file_exists(myfile)) testthat::expect_equal(sometxt, readLines(myfile)) # should append to end of file by default moretxt <- "world" add_text_to_file(moretxt, myfile) testthat::expect_equal(c(sometxt, moretxt), readLines(myfile)) # should throw error for invalid locations: testthat::expect_error(add_text_to_file(sometxt, myfile, 0)) testthat::expect_error(add_text_to_file(sometxt, myfile, -1)) testthat::expect_error(add_text_to_file(sometxt, myfile, 5)) # should add to specified line: moretxt2 <- "hi" add_text_to_file(moretxt2, myfile, 1) testthat::expect_equal(c(moretxt2, sometxt, moretxt), readLines(myfile)) # should add to specified line: moretxt3 <- "hi2" add_text_to_file(moretxt3, myfile, 2) testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt), readLines(myfile)) # should add to specified line: moretxt4 <- "hi3" add_text_to_file(moretxt4, myfile, 6) testthat::expect_equal(c(moretxt2, moretxt3, sometxt, moretxt, moretxt4), readLines(myfile)) fs::dir_delete(dir) }) ## Test passed 4.2 Setting up the R package creation When the user calls litr::render() (either in the console or by pressing “Knit” in RStudio), one of the first things that function does is to call the function litr::setup(), which does several things: Creates a new empty directory at the specified location while first making sure that it won’t overwrite something it shouldn’t. In particular, we guard against the case that the package was generated by litr::render() but then someone went in manually and made some changes. Even though users should never manually edit the package that was generated by litr::render(), we don’t want to have them inadvertently lose their work by doing so. Thus, we only overwrite an R package if we can tell that it is the unedited output of a call to litr::render(). The function check_unedited() is responsible for checking this, and is a pretty interesting function which we will describe in the next section. This part of the code also makes use of a function litr::make_noticeable(), which is simply a way of making error messages produced by litr more easy to see amid a lot of knitr output. Adjusts the root directory from the generating .Rmd file’s location to the R package’s location. Note: This behavior might not actually be desirable now that additional files will be loaded in. It might be awkward for a user writing the generating .Rmd file to have to make everything relative to the package. It might be convenient to provide a litr::add_file(from, to) function, where from is the path relative to the .Rmd file and to is the path relative to the package’s location. Makes it so that the send_to_package() chunk hook is active for each code chunk. This involves registering a new chunk hook using the function knitr::knit_hooks$set() and then setting an option with the same name to TRUE within each chunk. Deactivates an internal function of the usethis package, usethis:::challenge_nested_project(). This was actually a difficult issue to address that involves the intersection of usethis, here, and our particular use case. The problem is that usethis was not designed for our setting in which an R package is being created programmatically. When using litr, the project directory will have the generating .Rmd file and when this is knit it will create an R package within this project. However, this leads usethis to prompt the user with a message of the form “New project ‘[…]’ is nested inside an existing project ‘[…]’. This is rarely a good idea. Do you wish to create anyway?” But since this is encountered through knitting rather than interactively, this results in an error. This usethis issue describes this exact problem. The solution suggested there by jennybc involving testthat::with_mock() is along the lines of what we want; however, that would lead to some ugly looking code in the generating .Rmd file. The best solution I could find was to use utils::assignInNamespace() as described here. This function allows us to change the internal function usethis:::challenge_nested_project() so that it no longer prompts the user with concerns about nested projects. Changes how chunk references are handled. In particular, consider the following code chunk: a <- 2 <<my-chunk>> a The way knitr handles this, the code chunk would no longer look like this but it would rather have replaced the <<my-chunk>> line by the code that appears in the code chunk labeled “my-chunk”. We instead would like the above code chunk to appear as written and then for the code chunk labeled “my-chunk” to have its label visible to the reader of the .html file. This gives the coder more control over when the reader learns about different parts of the code. It also more closely resembles Donald Knuth’s form of literate programming. For convenience, we’d like <<my-chunk>> to be a link that navigates to the code chunk labeled “my-chunk”. To accomplish this, we modify the document output hook in setup() (and then we also add a function called add_chunk_label_hyperlinks() within render()). Define a package_doc engine which allows users to define package-level documentation. #' Code for setup chunk #' #' * Creates directory where package will be. (Deletes what is currently there as #' long as it appears to have been created by litr and does not have any #' subsequent manual edits.) #' * Sets the root directory to this directory #' * Sets up the main chunk hook `litr::send_to_package()` that sends code to the #' R package directory. #' * In the case that `minimal_eval=TRUE`, sets up an options hook for `eval` so #' chunks are only evaluated if there is a `usethis` or `litr::document()` #' command #' * Deactivates an internal function of the `usethis` package #' * Redefines the document output hook to handle chunk references differently #' * Sets up a [custom language engine](https://bookdown.org/yihui/rmarkdown-cookbook/custom-engine.html) called #' `package_doc` that creates a package documentation file and then inserts #' whatever the user puts in the chunk. #' #' Returns the original state of the knitr objects that have been modified in #' setup. This allows us to return things to the previous state after we are #' finished. This is relevant in the case where litr-knitting occurs in the #' current session and we don't want to leave things in a permanently modified #' state. #' #' @param package_dir Directory where R package will be created <<param-minimal_eval>> #' @keywords internal setup <- function(package_dir, minimal_eval) { if (file.exists(package_dir)) { unedited <- tryCatch(check_unedited(package_dir), error = function(e) { # contents of package_dir does not resemble # a litr package return(FALSE) }) if (!unedited) { stop(make_noticeable(paste( stringr::str_glue("The directory {normalizePath(package_dir)}"), "already exists and either was not created by litr or may have manual", "edits. In either case, please rename that directory (or delete it)", "and then try again.", sep = "\\n"))) } unlink(package_dir, recursive = TRUE) } fs::dir_create(package_dir) usethis:::proj_set_(usethis:::proj_path_prep(package_dir)) # let's keep a version of the knitr objects before modifying them: original_knitr <- list(opts_knit = knitr::opts_knit$get(), knit_hooks = knitr::knit_hooks$get(), opts_chunk = knitr::opts_chunk$get(), opts_hooks = knitr::opts_hooks$get(), knit_engines = knitr::knit_engines$get() ) knitr::opts_knit$set(root.dir = package_dir) # sets wd of future chunks knitr::knit_hooks$set(send_to_package = send_to_package) knitr::opts_chunk$set(send_to_package = TRUE) if (minimal_eval) { # only evaluate chunks that appear to include usethis commands or # a call to litr::document() but if someone has specifically set eval=FALSE # in a particular chunk, do honor that usethis_exports <- getNamespaceExports("usethis") patterns <- paste(c("usethis::", usethis_exports, "litr::document\\\\("), collapse = "|") knitr::opts_hooks$set(eval = function(options) { if (options$eval) options$eval <- any(stringr::str_detect(options$code, patterns)) return(options) }) } # change usethis:::challenge_nested_project so that it will not complain # about creating a nested project (e.g. if this is called within a git # subdirectory) utils::assignInNamespace("challenge_nested_project", function(...) NULL, ns = "usethis") # define document hook to handle chunk references: knitr::knit_hooks$set(document = function(x) { # get the indices of x corresponding to code chunks chunk_start <- "^(\\n```+[a-zA-Z0-9_]+\\n)" idx_block <- stringr::str_which(x, chunk_start) original_code <- knitr::knit_code$get() # We first get indices of skipped chunks in original_code list skipped_chunks <- which(sapply(original_code, function(x){ return(isFALSE(attr(x, "chunk_opts")$echo) || isFALSE(attr(x, "chunk_opts")$include)) })) # Next we remove the indices of skipped chunks original_code_idx_fixed <- setdiff(seq(length(original_code)), skipped_chunks) labels <- names(original_code) # replace each x[i] that has code in it with the original code for (i in seq_along(idx_block)) { # break code into multiple lines: chunk <- strsplit(x[idx_block[i]], "\\n")[[1]] # get the fence used (in case it's more than three ticks): i_start <- stringr::str_which(chunk, "^```+[a-zA-Z0-9_]+") fence <- stringr::str_replace(chunk[i_start[1]], "^(```+)[a-zA-Z0-9_]+", "\\\\1") i_fences <- stringr::str_which(chunk, paste0("^", fence)) # there can be multiple code and output chunks strung together # within a single x[i] if results are not held to end i_all_code <- c() for (j in seq_along(i_start)) { # get the elements corresponding the j-th code chunk within chunk i_code_end <- i_fences[which(i_fences == i_start[j]) + 1] i_all_code <- c(i_all_code, i_start[j]:i_code_end) } i_all_code <- setdiff(i_all_code, i_start[1]) chunk_no_code <- chunk[-i_all_code] chunk <- c(chunk_no_code[1:i_start[1]], original_code[original_code_idx_fixed[i]][[1]], # insert the original version, accounting for skipped chunks fence) if (i_start[1] < length(chunk_no_code)) chunk <- c(chunk, chunk_no_code[(i_start[1] + 1):length(chunk_no_code)]) x[idx_block[i]] <- paste(chunk, collapse = "\\n") } # replace code chunks with the original code # (so we'll still have <<label>> chunk references) refs <- c() # labels that get referred to for (label in labels) { refs <- c(refs, find_labels(original_code[[label]])$chunk_ids) } refs <- unique(refs) adj_labels <- labels[!labels %in% names(skipped_chunks)] ref_id <- match(refs, adj_labels) if (any(is.na(ref_id))) { stop(make_noticeable(paste( stringr::str_glue("The chunk reference <<{refs[is.na(ref_id)][1]}>> ", "is used, but there is no chunk with that label.", sep = "\\n")))) } to_insert <- paste0('###"', adj_labels[ref_id], '"###\\n') x[idx_block[ref_id]] <- stringr::str_replace(x[idx_block[ref_id]], chunk_start, paste0("\\\\1", to_insert)) x }) # setup package_doc engine knitr::knit_engines$set(package_doc = function(options) { # create package_doc usethis::use_package_doc(open = FALSE) # insert the contents of the code chunk into the package_doc pkgdoc <- file.path("R", paste0(fs::path_file(package_dir), "-package.R")) add_text_to_file(options$code, filename = pkgdoc, location = 1) # now treat this as if it were standard R code with eval=FALSE r_engine <- knitr::knit_engines$get("R") options[["eval"]] <- FALSE return(r_engine(options)) }) return(original_knitr) } In our new document output hook defined above, we call a function find_labels(). It takes a block of code and returns both a logical vector of which lines contained chunk labels and another vector containing the labels of those referenced chunks. We define it here: #' Find a .Rmd chunk label in a code chunk #' #' @param chunk_code Character vector of code from a .Rmd code chunk. Each element is a line of the code chunk. #' @return List where chunk_idx is a logical vector for each line of the chunk corresponding to whether a chunk label of the form `<<label>>` was found and chunk_ids is a character vector of chunk label was found in that chunk. #' @keywords internal find_labels <- function(chunk_code) { rc <- knitr::all_patterns$md$ref.chunk chunk_idx <- any(idx = grepl(rc, chunk_code)) chunk_ids <- stringr::str_trim(sub(rc, "\\\\1", chunk_code[grepl(rc, chunk_code)])) return(list(chunk_idx = chunk_idx, chunk_ids = chunk_ids)) } The setup() function also uses a small function, make_noticeable(), which we define here: #' Make error messages noticeable #' #' Since litr error messages are amid a lot of output from knitting, we'd like #' the litr ones to be eye-catching. #' #' @param msg Error message #' @keywords internal make_noticeable <- function(msg) { paste("", "======", "Please read your friendly litr error message here:", paste("> ", msg), "======", sep = "\\n") } The code in this section used the fs and knitr packages, so we import those: usethis::use_package("fs") usethis::use_package("knitr") ## ✔ Adding 'fs' to Imports field in DESCRIPTION ## • Refer to functions with `fs::fun()` ## ✔ Adding 'knitr' to Imports field in DESCRIPTION ## • Refer to functions with `knitr::fun()` "],["hash.html", "5 Not overwriting a manually edited R package", " 5 Not overwriting a manually edited R package As described in the previous section, the function setup() will only overwrite a directory if it is the unedited output from using litr. The basic idea is that the function litr::render() when creating a new package finishes by adding a hash to the DESCRIPTION file. (And likewise when rmarkdown::render() is used with a litr output format, as described here.) This hash is a function of everything in the package, so if anything about the package changes (any file is modified, added, or removed) then the function check_unedited() will be able to detect that by recomputing the hash and seeing that it doesn’t match the hash in the DESCRIPTION file. Let’s start by defining the function hash_package_directory() that does the hashing. The hash is a function of everything in the outputted package except for that special line in the DESCRIPTION file with the hash. We use tools::md5sum() and digest::digest() to do the hashing. #' Hash package directory #' #' Gets an identifier that can be used to uniquely (whp) identify the current #' state of the package. This is formed by ignoring the `LitrId` field of the #' DESCRIPTION file, which is the location where the output of this function is #' stored when `litr::render` generates the package. #' #' @param package_dir Path to package #' @keywords internal hash_package_directory <- function(package_dir) { pkg_files <- fs::dir_ls(package_dir, recurse = TRUE, all = TRUE, type = "file") pkg_files <- stringr::str_subset(pkg_files, ".DS_Store$", negate = TRUE) pkg_files <- normalizePath(pkg_files) descr_file <- normalizePath(file.path(package_dir, "DESCRIPTION")) i_descr <- which(pkg_files == descr_file) if (length(i_descr) == 0) stop("Cannot find DESCRIPTION file.") txt_descr <- readLines(pkg_files[i_descr]) txt_descr_mod <- stringr::str_subset( txt_descr, stringr::str_glue("{description_litr_hash_field_name()}: .+$"), negate = TRUE) hashes <- as.character(tools::md5sum(pkg_files[-i_descr])) digest::digest(c(hashes, list(txt_descr_mod))) } We used digest, so let’s import it: usethis::use_package("digest") ## ✔ Adding 'digest' to Imports field in DESCRIPTION ## • Refer to functions with `digest::fun()` We will store this hash in a special field within the DESCRIPTION file. Let’s call this field LitrId. However, in case we ever decide to change the name of this field, it’s better that we only define it in one place. So we do this with the following function: #' Generate litr hash field name for DESCRIPTION file #' @keywords internal description_litr_hash_field_name <- function() return("LitrId") Ok, now let’s write the function that litr::render() will call that will take the generated R package and add a line that puts the hash in the DESCRIPTION file under that special litr field: #' Write the hash of the package to the DESCRIPTION file #' #' @param package_dir Path to package #' @keywords internal write_hash_to_description <- function(package_dir) { desc_file <- file.path(package_dir, "DESCRIPTION") if (!file.exists(desc_file)) file.create(desc_file) hash <- hash_package_directory(package_dir) desc::desc_set(description_litr_hash_field_name(), hash, file = desc_file) } Let’s include the desc package, which helps us manipulate DESCRIPTION files. usethis::use_package("desc") ## ✔ Adding 'desc' to Imports field in DESCRIPTION ## • Refer to functions with `desc::fun()` And of course we’ll need a function that can read the value of that field as well: #' Get the hash of the package from the DESCRIPTION file #' #' @param package_dir Path to package #' @keywords internal read_hash_from_description <- function(package_dir) { descr <- file.path(package_dir, "DESCRIPTION") if (!file.exists(descr)) stop("Cannot find DESCRIPTION file.") txt <- stringr::str_subset( readLines(descr), stringr::str_glue("{description_litr_hash_field_name()}: .+$")) if (length(txt) > 1) stop("More than one hash found in DESCRIPTION.") if (length(txt) == 0) stop("No hash found in DESCRIPTION.") stringr::str_extract(txt, "\\\\S+$") } With all this hash functionality in place, the function check_unedited() is actually quite simple to define: #' Check if package directory is the unedited output of litr::render() #' #' Uses hash stored in a special `litr` field of DESCRIPTION file to check that #' the current state of the R package directory is identical to its state at the #' time that it was created by `litr::render()`. #' #' @param package_dir Path to package #' @keywords internal check_unedited <- function(package_dir) { hash <- hash_package_directory(package_dir) hash == read_hash_from_description(package_dir) } It simply computes the hash of the current package and checks whether that hash is the same as what was originally written to the DESCRIPTION file by litr::render(). "],["document.html", "6 Wrapper to devtools::document()", " 6 Wrapper to devtools::document() This function is nearly identical to devtools::document() except that it changes the roxygen2 message that says “Please edit documentation in R/[…].R” to instead mention the generating .Rmd file. When Rcpp is used, it also makes sure that #include <RcppArmadillo.h> comes before #include <Rcpp.h>. #' Use roxygen to document a package from within a Rmd file #' #' This is a wrapper for the `devtools::document()` function, which in turn is a #' wrapper for the `roxygen2::roxygenize()` function. It is written assuming that #' it is being called from within a generating Rmd file. The purpose for `litr` #' having this wrapper is two-fold. First, it ensures that the first line #' in the outputted `Rd` files should not say "Please edit documentation in #' R/file.R" but instead should refer to the Rmd file that generates everything. #' Second, in the case that Rcpp is being used, it makes some adjustments to ensure #' that the compiling of the C++ code should be successful. #' #' @param ... Arguments to be passed to `devtools::document()` #' @export document <- function(...) { # prepare Rcpp code for compiling if (fs::file_exists("src/code.cpp")) { # make sure that #include <RcppArmadillo.h> if it exists # comes *before* (or instead of) <Rcpp.h> txt <- readLines("src/code.cpp") loc <- stringr::str_which(txt, r"(#include <RcppArmadillo.h>)") if (length(loc) > 0) { include_arma_line <- txt[loc[1]] txt <- c(include_arma_line, txt[-loc]) writeLines(txt, "src/code.cpp") } } devtools::document(...) # remove the line of the following form in each man/*.Rd file: pattern <- "% Please edit documentation in .*$" msg <- do_not_edit_message(knitr::current_input(), type = "man") for (fname in fs::dir_ls("man")) { txt <- stringr::str_replace(readLines(fname), pattern, msg) cat(paste(txt, collapse = "\\n"), file = fname) } } We used devtools, so let’s import it: usethis::use_package("devtools") ## ✔ Adding 'devtools' to Imports field in DESCRIPTION ## • Refer to functions with `devtools::fun()` "],["rendering.html", "7 Altering the rendering process 7.1 Defining litr output formats 7.2 Defining litr::render()", " 7 Altering the rendering process The focus here is to make it so that all the special litr functionality will be active when the .Rmd is rendered. In particular, we need to make sure that setup() has been called before knitting occurs (so that, for example, send_to_package() will be active). We also need to make sure that after knitting certain things occur, such as the litr-hash being written to the package and, in some cases, hyperlinks are added for easier navigation. We implement two ways for the above to occur, which we describe in the next two subsections: The first approach is by defining custom litr output formats. When rmarkdown::render() (or bookdown::render_book()) is called with one of these litr output formats, the litr-specific operations occur before and after knitting. The second way is through the function litr::render(). If a user calls litr::render() with a non-litr output format, e.g. rmarkdown::html_document(), then it adds the necessary litr-specific operations before/after rmarkdown::render() is called. Another thing of note about litr::render() is that it renders the document in a fresh environment, which ensures identical behavior to when a user presses “Knit” in RStudio. Although these are presented as two separate approaches, we have written litr::render() so that if a user passes one of the litr output formats to litr::render(), it will still work. We encourage users to use litr::render()1 rather than rmarkdown::render() since in litr::render() we’re able to wrap the call to rmarkdown::render() in the function with_cleanup(). This ensures that, if an error occurs during the knitting process, the special litr hash will still be created. This is desirable since it means that the next time we try to litr-knit, we will not get an error about overwriting a manually edited package directory. When coding an R package with litr, sometimes there are code chunks that can take a while to evaluate (e.g., tests), which slows down the coding process. We therefore provide an argument (to both litr::render() and the various litr output formats) that allows for “minimal eval” to occur. The goal is to allow the R package to be updated completely but without any of the code chunks being evaluated, except those whose involving usethis or a call to litr::document(), since these commands lead to changes in the R package itself. Here is the documentation associated with the minimal_eval parameter, which is an argument to a number of functions in this section: ###"param-minimal_eval"### #' @param minimal_eval If `TRUE`, then only chunks with `litr::document()` or #' `usethis` commands will be evaluated. This can be convenient in coding when #' you just want to quickly update the R package without having to wait for long #' evaluations to occur. 7.1 Defining litr output formats The function rmarkdown::render() allows for customizable behavior through the use of custom output formats. Given a preexisting output format (e.g. rmarkdown::html_document), we would like to modify it to have litr-behavior – i.e., to create a package as it is being rendered. This next function takes a preexisting output format and “litr-ifies” it by making three changes: It modifies the pre_knit() function. It modifies the post_processor() function. It adds a marker (litr_format <- TRUE) that will help litr::render() know when a litr output format is being passed to it. We present the function and then describe the details of the new pre_knit() and post_processor() functions below. #' Modify an existing output format to have `litr` behavior #' #' This function modifies the `pre_knit()` and `post_processor()` functions of a #' preexisting output format so that it will have the `litr` behavior (meaning that an R package will be created when `rmarkdown::render()` is called). #' #' @param base_format a preexisting, non-litr output format such as `rmarkdown::html_document` <<param-minimal_eval>> #' @export litrify_output_format <- function(base_format = rmarkdown::html_document, minimal_eval = FALSE) { force(base_format) # I think using force here is advisable? force(minimal_eval) # https://adv-r.hadley.nz/function-factories.html function(...) { old <- base_format(...) new <- old new$original_knitr_objects <- list() new$pre_knit <- function(...) { args <- list(...) input <- args$input params <- knitr::knit_params(readLines(input)) package_dir <- get_package_directory( params$package_parent_dir$value, params$package_name$value, input) new$original_knitr_objects <<- litr:::setup(package_dir, minimal_eval) if (!is.null(old$pre_knit)) old$pre_knit(...) } new$post_processor <- function(metadata, input_file, output_file, ...) { # typically the post_processor function returns the output file path # if old$post_processor is NULL, as in the case of pdf_document, # then R will throw an error when trying to call old$post_processor # if we only add a check for non null old$post_processor and otherwise # set out <- NULL then R will throw an error later in rmarkdown::render # since output_file is set to the output of the post_processor if # output_format$post_processor is not null (See line 478 in rmarkdown::render) # Therefore, our solution is to set out to the output_file path if old$post_process is null. if (!is.null(old$post_processor)){ out <- old$post_processor(metadata, input_file, output_file, ...) } else { out <- output_file } package_dir <- get_package_directory( metadata$params$package_parent_dir, metadata$params$package_name, input_file ) # remove .Rproj and .gitignore if usethis::create_package() added these remove_rstudio_extras(package_dir) # add to DESCRIPTION file the version of litr used to create package: write_version_to_description(package_dir) # add litr hash so we can tell later if package files were manually edited: write_hash_to_description(package_dir) out } new$on_exit <- function() { old$on_exit() # restore knitr to its original state restore_knitr_objects(new$original_knitr_objects) } # mark this as a litr_format new$litr_format <- TRUE # litr formats have minimal_eval as an option new$minimal_eval <- minimal_eval new } } The pre_knit() function is modified so that setup() is called before the preexisting output format’s pre_knit() function is called. As the name suggests, this is a function that gets called before knitting. The purpose of the call to setup() is to create the R package directory and make it so that when we knit the file using rmarkdown::render(), a lot of special things will happen, such as code being sent to the R package directory. The function setup() returns the state of the knitr settings before any changes were made. This previous state of the knitr settings will be restored at the end of the rendering process in on_exit(). The post_processor() function is modified so that the DESCRIPTION file gets marked with the version of litr used and with the litr hash (as already described here). Some special care is taken for the case that the original output format doesn’t have a post processor (e.g., this is the case for the pdf_document output format). The particulars of this are given in a comment in the code chunk above. The on_exit() function is modified so that it restores the state of all the knitr settings to how it was when render was first called. We use the above function to create some litr versions of common output formats, as seen in the next few subsections. Before proceeding, we define the function write_version_to_description() that is called above. #' Generate litr version field name for DESCRIPTION file #' @keywords internal description_litr_version_field_name <- function() return("LitrVersionUsed") #' Write the version of litr used to the DESCRIPTION file #' #' @param package_dir Path to package #' @keywords internal write_version_to_description <- function(package_dir) { ver <- as.character(utils::packageVersion("litr")) add_text_to_file( txt = stringr::str_glue("{description_litr_version_field_name()}: {ver}"), filename = file.path(package_dir, "DESCRIPTION"), req_exist = TRUE ) } Also, we made use of a small function for getting the package directory based on the input file’s location and the parameters that are being used in the rendering process. We define it here: #' Get package directory #' #' @param package_parent_dir The directory of where the package should go (relative to the input directory) #' @param package_name The name of the package #' @param input The file name of the input #' @keywords internal get_package_directory <- function(package_parent_dir, package_name, input) { if (package_parent_dir == ".") return(file.path(dirname(input), package_name)) file.path(dirname(input), package_parent_dir, package_name) } Let’s write some tests to make sure it’s behaving as expected: testthat::test_that("get_package_directory() works", { input <- file.path("inputdir", "input.Rmd") testthat::expect_equal( get_package_directory(".", "mypkg", input), file.path("inputdir", "mypkg") # inputdir/mypkg ) testthat::expect_equal( get_package_directory("..", "mypkg", input), file.path("inputdir", "..", "mypkg") # inputdir/../mypkg ) }) ## Test passed 7.1.1 .pdf output format We want our .pdf documents to accurately display the “logging” output from functions in packages such as devtools that use special ANSI escape codes for displaying information in the terminal. Unfortunately, these codes use escape characters which cause problems when creating .pdf versions of our documents. While it is relatively straightforward to map ANSI escape codes to HTML tags, as we will see in the .html output format section, converting these escape codes to Latex commands is more complicated. As a result, we define a post_knit function in our litr_pdf_document format to avoid this issue by stripping out all escape codes in the file before it is converted into a .tex file and then compiled into a .pdf document. Specifically, the post_knit function modifies the intermediate .knit.md file, which contains both the .Rmd file, as well as the output of each code chunk. Thus, we can inspect the output text of each code chunk and remove any ANSI escape codes before the .knit.md is converted to a .tex file and then a .pdf document. We rely upon two internal functions from the fansi R package to remove all possible escape codes and return a clean character vector. #' litr version of `rmarkdown::pdf_document()` #' #' This behaves exactly like `rmarkdown::pdf_document()` except it creates an #' R package. #' <<param-minimal_eval>> #' @param ... Parameters to be passed to `rmarkdown::pdf_document()` #' @export litr_pdf_document <- function(minimal_eval = FALSE, ...) { litr_pdf_document_ <- litrify_output_format(rmarkdown::pdf_document, minimal_eval = minimal_eval) old <- litr_pdf_document_(...) new <- old # post_knit new$post_knit = function(...){ args = list(...) input_filename <- args[[2]] knitted_filename <- fs::path_ext_set(input_filename, ".knit.md") knitted_output <- readLines(knitted_filename) cleaned_output <- sapply(1:length(knitted_output), function(i){ test_str <- knitted_output[i] fansi:::VAL_IN_ENV(x=test_str, ctl="all", warn=TRUE, warn.mask=fansi:::get_warn_mangled()) .Call(fansi:::FANSI_strip_csi, test_str, CTL.INT, WARN.INT) }) writeLines(cleaned_output, knitted_filename) } new } Since the above section uses the fansi package for handling ANSI escape sequences, we include it in our package: usethis::use_package("fansi") ## ✔ Adding 'fansi' to Imports field in DESCRIPTION ## • Refer to functions with `fansi::fun()` 7.1.2 .html output format For .html documents, we’d like to add a bit more in the postprocessing step. In particular, we include some special function and chunk hyperlinking behavior described below. The function add_function_hyperlinks() processes the outputted .html file(s), making it so that one can easily navigate to function definitions. (This function is described lower in this section.) We likewise call a function add_chunk_label_hyperlinks(), which makes chunk references into clickable links. In particular, the chunk reference <<my-chunk>> within a code chunk would link to a chunk named “my-chunk” that begins with ###\"my-chunk\"###. The ###\"my-chunk\"### line is added by a document hook defined in setup(). Finally, we replace ANSI sequences with HTML tag equivalents (the need for this is explained in the section on the .pdf output format). #' litr version of `rmarkdown::html_document()` #' #' This behaves like `rmarkdown::html_document()` with a few differences: #' - It creates an R package. #' - It adds hyperlinks to function definitions whenever a function is used #' elsewhere in the document. #' - It does "Knuth-style" chunk referencing with hyperlinks. #' <<param-minimal_eval>> #' @param ... Parameters to be passed to `rmarkdown::pdf_document()` #' @export litr_html_document <- function(minimal_eval = FALSE, ...) { litr_html_document_ <- litrify_output_format(rmarkdown::html_document, minimal_eval = minimal_eval) old <- litr_html_document_(...) new <- old # modify post_processor new$post_processor = function(metadata, input_file, output_file, ...) { out <- old$post_processor(metadata, input_file, output_file, ...) # html_files <- fs::dir_ls(fs::path_dir(out), regexp = ".html$") # add hyperlinks within html output to make it easier to navigate: add_function_hyperlinks(output_file, metadata$params$package_name) add_chunk_label_hyperlinks(output_file) # replace ANSI sequences with HTML tag equivalents replace_ansi_sequences(output_file) out } new } We describe these two add_*_hyperlinks() functions next. The function add_function_hyperlinks() looks for foo followed by <- function( and then wraps foo in a span tag with id=\"foo\"; whenever foo is found elsewhere in the document, it calls the insert_hrefs() function to wrap a a href=\"file.html#foo\" tag (where file.html is the file where foo is defined), so that it will be a hyperlink to foo’s definition. #' Add hyperlinks to function definitions #' #' Finds functions that are defined in the html file(s) by looking for text of the #' form `foo <- function(` and then wraps `foo` in a `span` tag with `id="foo"` #' and then whenever `foo` is found it wraps a `a href="file.html#foo"` tag so #' that it will be a hyperlink to `foo`'s definition. #' #' @param html_files Character vector of file names of html files that were created #' from Rmd files #' @param pkg_name Name of the package created by litr. Taken from YAML front matter #' @keywords internal add_function_hyperlinks <- function(html_files, pkg_name) { find_function_defs <- function(html_file) { txt <- readLines(html_file) start_line <- which(txt == "<body>") pattern1 <- '([a-zA-Z0-9_.]+)(\\\\s*&lt;-\\\\s*function)' pattern2 <- stringr::str_replace(pattern1, '&lt;-', '<span class="ot">&lt;-</span>') pattern2 <- stringr::str_replace(pattern2, 'function', '<span class="cf">function</span>') # find functions that are defined in this file: function_names <- character(0) for (pattern in c(pattern1, pattern2)) { for (i in seq(start_line + 1, length(txt))) { fn_name <- stringr::str_match(txt[i], pattern)[, 2] if(is.na(fn_name)) next # a function was defined in this line, so put a span around it txt[i] <- stringr::str_replace( txt[i], pattern, stringr::str_glue("<span id='{fn_name}'>\\\\1</span>\\\\2") ) # and keep track of it for later: function_names <- c(function_names, fn_name) } } list(function_names = function_names, txt = txt) } fdefs <- lapply(html_files, find_function_defs) all_function_names <- unlist(lapply(fdefs, function(lst) lst$function_names)) # if a function is defined multiple times, then it's ambiguous where to link to # so let's not try linking to it (this can occur when a function is defined # within a function, such as `new$post_processor()`) repeated <- names(which(table(all_function_names) > 1)) all_function_names <- setdiff(all_function_names, repeated) if (length(all_function_names) == 0) { # no functions defined in package, so nothing more to be done here return() } num_per_file <- unlist(lapply(fdefs, function(lst) { length(setdiff(lst$function_names, repeated)) })) where_defined <- rep(fs::path_file(html_files), times = num_per_file) defined_functions_pattern <- paste0("(::)?",all_function_names, "\\\\(", collapse = "|") # There's also this case: <span class="fu">myfunction</span> defined_functions_pattern2 <- paste0( '<span class="fu">', all_function_names, '</span>\\\\(', collapse = "|") for (i in seq_along(html_files)) { # whenever one of the defined functions is named, link to its definition # using the format `file_where_foo_is_defined.html#foo` modified_txt <- insert_hrefs(fdefs[[i]]$txt, defined_functions_pattern, where_defined, all_function_names, pkg_name) modified_txt <- insert_hrefs(modified_txt, defined_functions_pattern2, where_defined, all_function_names, pkg_name, remove_span=TRUE) writeLines(modified_txt, con = html_files[i]) } } We define next the helper function insert_hrefs(), which was called in the previous function. If the function foo() is defined in the .Rmd file that defines a package named pkg, then whenever foo() or pkg::foo() appears in the .Rmd, a link will be added; however, if other_pkg::foo() appears, then no link will be added. #' Replace a function's name with a link to its definition #' #' A helper function for `add_function_hyperlinks` that wraps references to a #' function in an anchor tag with a link to the function's definition. #' #' @param txt Character vector where each element is a row of the knitted HTML file. #' @param function_pattern Regular Expression passed from `add_function_hyperlinks` that contains all referenced functions in the document. #' @param where_defined Character vector that contains the name of the file in which a function was defined. #' @param all_function_names Character vector of all referenced functions in the document. #' @param pkg_name Name of the package created by litr. Taken from YAML front matter. #' @param remove_span Boolean argument for removing span tags. Used for minimizing code duplication. #' @keywords internal insert_hrefs <- function(txt, function_pattern, where_defined, all_function_names, pkg_name, remove_span=FALSE){ # filter down matches of defined_functions_pattern has_fn_name <- which(stringr::str_detect(txt, function_pattern)) has_colon_prefix <- which(stringr::str_detect(txt, paste0("::", all_function_names, "\\\\(", collapse = "|"))) has_only_fn_name <- setdiff(has_fn_name, has_colon_prefix) has_pkg_colon_prefix <- which(stringr::str_detect(txt, paste0(stringr::str_glue("{pkg_name}::")))) # define different replacement functions for colon prefix cases and regular cases colon_pref_replace_fn <- function(x){ if(remove_span){ fn_name <- stringr::str_remove(x, "</span>\\\\(") fn_name <- stringr::str_remove(fn_name, '<span class="fu">') } else{ fn_name <- stringr::str_remove(x, "\\\\(") } fn_name <- stringr::str_remove(fn_name, stringr::str_glue('{pkg_name}::')) # implicitly assuming that a function is not redefined in another file def_file <- where_defined[all_function_names == fn_name] return(stringr::str_glue("{pkg_name}::<a href='{def_file}#{fn_name}'>{fn_name}</a>(")) } regular_replace_fn <- function(x){ if(remove_span){ fn_name <- stringr::str_remove(x, '</span>\\\\(') fn_name <- stringr::str_remove(fn_name, '<span class="fu">') } else { fn_name <- stringr::str_remove(x, "\\\\(") } # implicitly assuming that a function is not redefined in another file def_file <- where_defined[all_function_names == fn_name] stringr::str_glue("<a href='{def_file}#{fn_name}'>{fn_name}</a>(") } colon_prefix_function_pattern <- paste0(stringr::str_glue("{pkg_name}::"),all_function_names, "\\\\(", collapse = "|") colon_prefix_refs <- stringr::str_replace_all( txt[has_pkg_colon_prefix], colon_prefix_function_pattern, colon_pref_replace_fn ) regular_refs <- stringr::str_replace_all( txt[has_only_fn_name], function_pattern, regular_replace_fn ) # now put back in the changed lines txt[has_pkg_colon_prefix] <- colon_prefix_refs txt[has_only_fn_name] <- regular_refs txt } In addition to adding hyperlinks to function definitions, we also want to add hyperlinks for chunk references of the form <<chunk-name>> that link to user-defined chunk names that take the form ###chunk-name###. We want these user-defined chunk names to stand out to readers and in their original form as code comments they might be harder to notice. To solve this issue, we modify the formatting of these chunks to add a border with the name of the chunk set into the border, mimicking the look of a <fieldset> tag. The additional formatting requires inserting additional HTML tags as well as css into the knitted HTML file so we use the xml2 package to parse and manipulate the knitted HTML file. #' Add hyperlinks to embedded chunks #' #' Finds chunks that are referenced in the html file(s) by looking for comments #' of the form `###"foo"###` and then wraps `foo` in a `span` tag with `id="foo"` #' and then whenever the chunk label `<<foo>>` is found it wraps it in a #' `a href="file.html#foo"` tag so that it will be a hyperlink to `foo`'s #' definition. #' #' @param html_files Character vector of file names of html files that were created #' from Rmd files #' @param reference_start The delimiter used to indicate the start of a chunk label #' @param reference_end The delimiter used to indicate the end of a chunk label #' @keywords internal add_chunk_label_hyperlinks <- function(html_files, reference_start = "&lt;&lt;", reference_end = "&gt;&gt;"){ find_chunk_defs <- function(html_file) { txt <- readLines(html_file) start_line <- which(txt == "<body>") pattern <- '###&quot;([a-zA-Z0-9-_.]+)&quot;###' # find chunks that are defined in this file: chunk_names <- character(0) for (i in seq(start_line + 1, length(txt))) { chunk_name <- stringr::str_match(txt[i], pattern)[, 2] if(is.na(chunk_name)) next # a chunk was defined in this line, so put a span around it txt[i] <- stringr::str_replace( txt[i], pattern, stringr::str_glue("<span id='{chunk_name}'>###&quot;\\\\1&quot;###</span>") ) # and keep track of it for later. # we're using setNames here to make sure that we keep the name of file # where the chunk name is defined chunk_names <- setNames(c(chunk_names, chunk_name), c(names(chunk_names), html_file)) } list(chunk_names = chunk_names, txt = txt) } cdefs <- lapply(html_files, find_chunk_defs) all_chunk_names <- unlist(lapply(cdefs, function(lst) lst$chunk_names)) num_per_file <- unlist(lapply(cdefs, function(lst) length(lst$chunk_names))) where_defined <- rep(fs::path_file(html_files), times = num_per_file) defined_chunks_pattern <- paste0(reference_start, all_chunk_names, reference_end, collapse = "|") ref_start <- '<span class="sc">&lt;</span><span class="er">&lt;</span>' ref_start_alt <- '<span class=\\"er\\">&lt;&lt;</span>' ref_end <- '<span class="sc">&gt;</span><span class="er">&gt;</span>' hyphen_with_extras <- '<span class="sc">-</span>' all_chunk_names2 <- stringr::str_replace_all(all_chunk_names, "-", hyphen_with_extras) defined_chunks_pattern2 <- paste0( ref_start, all_chunk_names2, ref_end, collapse = "|" ) defined_chunks_pattern2_alt <- paste0( ref_start_alt, all_chunk_names2, ref_end, collapse = "|" ) defined_chunks_pattern2 <- paste( defined_chunks_pattern2, defined_chunks_pattern2_alt, sep = "|" ) for (i in seq_along(html_files)) { # whenever one of these named chunks is referenced, link to its definition # using the format `file_where_chunk_is_defined.html#chunkname` txt <- stringr::str_replace_all( cdefs[[i]]$txt, defined_chunks_pattern, function(x) { cname <- stringr::str_remove_all( x, paste(reference_start, reference_end, sep = "|") ) def_file <- where_defined[all_chunk_names == cname] stringr::str_glue( "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>" ) } ) txt <- stringr::str_replace_all( txt, defined_chunks_pattern2, function(x) { cname <- stringr::str_remove_all( x, paste(ref_start, ref_start_alt, ref_end, sep = "|") ) def_file <- where_defined[all_chunk_names2 == cname] cname <- stringr::str_replace_all(cname, hyphen_with_extras, "-") stringr::str_glue( "<a href='{def_file}#{cname}'>{reference_start}{cname}{reference_end}</a>" ) } ) parsed_html <- xml2::read_html(paste(txt,collapse="\\n")) # get all possible chunk names in this file. chunk_names <- all_chunk_names[which(names(all_chunk_names) == html_files[i])] if(length(chunk_names) > 0){ for(j in seq_along(chunk_names)){ span_node <- xml2::xml_find_first(parsed_html, stringr::str_glue('(.//span[@id="{chunk_names[j]}"])')) span_node_path <- stringr::str_split(xml2::xml_path(span_node),"/") pre_path <- paste(span_node_path[[1]][1:(max(which(stringr::str_detect(span_node_path[[1]], "pre"))))],collapse="/") if(nchar(pre_path) == 0){ next() } pre_parent <- xml2::xml_find_first(parsed_html, pre_path) if(is.na(pre_parent)){ next() } xml2::xml_add_parent(pre_parent , xml2::read_xml(stringr::str_glue('<fieldset id="{chunk_names[j]}" class="chunkfield"> </fieldset>'))) xml2::xml_add_sibling(pre_parent, xml2::read_xml(stringr::str_glue('<legend class="chunklegend">{chunk_names[j]}</legend>')), where="before") xml2::xml_remove(span_node) # remove the extra line break that is left over from removing the span code_node <- xml2::xml_child(pre_parent) changed_txt <- stringr::str_remove(paste(as.character(xml2::xml_contents(code_node)),collapse=""), '\\n') xml2::xml_replace(code_node, xml2::read_xml(stringr::str_glue('<code>{changed_txt}</code>'))) } } # last thing is to insert an additional style node in the head with our CSS so we have a standard style whether we are using bookdown or Rmarkdown css_string <- "fieldset.chunkfield {border:1px dotted black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em} legend.chunklegend {padding:0;width:auto;border:0; border-bottom: none; margin-bottom:0} " head_node <- xml2::xml_find_first(parsed_html, ".//head") xml2::xml_add_child(head_node, xml2::read_xml(stringr::str_glue("<style type='text/css'>{css_string}</style>"))) txt <- xml2::write_html(parsed_html, html_files[i]) } } Since we rely on the xml2 package to add in the extra label formatting, let’s import it: usethis::use_package("xml2") ## ✔ Adding 'xml2' to Imports field in DESCRIPTION ## • Refer to functions with `xml2::fun()` Finally, we want to replace the ANSI escape sequences used by packages such as testthat and devtools with their HTML equivalents so the output matches what we see in the terminal. #' Replace ANSI escape sequences with their HTML equivalents #' #' Finds ANSI escape sequences and replaces them with HTML tags using the `fansi` package #' #' @param html_files Character vector of file names of html files that were created #' from Rmd files #' @keywords internal replace_ansi_sequences <- function(html_files) { for (i in seq_along(html_files)) { file_lines <- readLines(html_files[i]) # look for lines with escape sequences for URLs and remove the URL # escape sequences before we convert to HTML url_code_regex <- "\\\\033]8;;.*\\\\a(.*?)\\\\033]8;;\\\\a" url_seq_idx <- which(stringr::str_detect(file_lines, url_code_regex)) file_lines[url_seq_idx] <- sapply(url_seq_idx, function(idx){ line <- file_lines[idx] stringr::str_replace(line, url_code_regex, stringr::str_glue("\\\\1")) }) txt <- fansi::sgr_to_html(x = file_lines, warn = FALSE, term.cap = "256") writeLines(txt, con = html_files[i]) } } 7.1.3 bookdown output format It turns out that our modification to the bookdown::gitbook() format’s postprocessor is identical to the above. This suggests that we should probably reuse code more effectively. But for now I will leave it how it is: #' litr version of `bookdown::gitbook()` #' #' This behaves like `bookdown::gitbook()` with a few differences: #' - It creates an R package. #' - It adds hyperlinks to function definitions whenever a function is used #' elsewhere in the document. #' - It does "Knuth-style" chunk referencing with hyperlinks. #' <<param-minimal_eval>> #' @param ... Parameters to be passed to `bookdown::gitbook()` #' @export litr_gitbook <- function(minimal_eval = FALSE, ...) { litr_gitbook_ <- litrify_output_format(bookdown::gitbook, minimal_eval = minimal_eval) old <- litr_gitbook_(...) new <- old # modify post_processor new$post_processor = function(metadata, input_file, output_file, ...) { out <- old$post_processor(metadata, input_file, output_file, ...) out_dir <- fs::path_dir(out) file_stems <- readLines(file.path(out_dir, "reference-keys.txt")) html_files <- file.path(out_dir, paste0(file_stems, ".html")) html_files <- unique(intersect(c(out, html_files), fs::dir_ls(out_dir))) # add hyperlinks within html output to make it easier to navigate: add_function_hyperlinks(html_files, metadata$params$package_name) add_chunk_label_hyperlinks(html_files) # replace ANSI sequences with HTML tag equivalents replace_ansi_sequences(html_files) out } new } To use this output format, one would use bookdown::render_book() instead of rmarkdown::render(). In particular: bookdown::render_book(output_format = litr::litr_gitbook()) The preamble in index.Rmd would look something like this: --- title: "A `litr` Book" author: "Your Name" site: bookdown::bookdown_site params: package_name: "frombookdown" # <-- change this to your package name package_parent_dir: "." # <-- relative to this file's location documentclass: book --- Or one can add to the preamble the lines knit: litr::render output: litr::litr_gitbook This first line makes it so that in RStudio when you press “Knit”, it calls litr::render(), and the second line makes it so that it will use the special litr bookdown output format. Since the above function uses bookdown, we include it in our package: usethis::use_package("bookdown") ## ✔ Adding 'bookdown' to Imports field in DESCRIPTION ## • Refer to functions with `bookdown::fun()` 7.2 Defining litr::render() There are two primary use cases for this function: To render a .Rmd with a non-litr output format (e.g., rmarkdown::html_document) in such a way that it will generate an R package (and include the special litr-hyperlinking if .html files were created). To render a .Rmd with a litr output format (including the litr_gitbook() format). The second use case might seem unnecessary in that rmarkdown::render("create-pkg.Rmd", output_format = litr::litr_html_document()) or bookdown::render_book("index.Rmd", output_format = litr::litr_gitbook()) would do what we want. However, a reason to still prefer litr::render() is that this function ensures the identical behavior to when one clicks the “Knit” in RStudio. It does this by opening a fresh R session (when fresh_session=TRUE, which is the default) in which rmarkdown::render() (or bookdown::render_book()) is called. This is based on the description in the Rmarkdown Cookbook. Another reason to prefer litr::render() is that if there is an error in the rendering process, the special litr hash will still be written to the DESCRIPTION file. This means that after fixing that error when one calls litr::render(), one will not get the error telling the user to delete the partially generated package directory. We accomplish this with the function with_cleanup() defined below. In the first use case, litr::render() is responsible for ensuring all the special litr things happen (like setup() being called before knitting, the litr-hash being written afterwards, and hyperlinking occurs). The details of what it does is very similar to what is described in the output formats section, especially the one on the html output format. One thing that is different is that we need a function get_params_used(), defined at the end of this section, that gets the actual parameters that are used so that the location of the outputted package can be found. #' Render R markdown file #' #' Wrapper to `rmarkdown::render()` that produces an R package as output in #' addition to the standard output document. It does some post-processing on the #' .html file when that is the output. In particular, when an .html file is among #' the outputs, it adds hyperlinks to functions defined within the file to make #' it easier for someone reading the code to see where different functions are #' defined. #' #' @param input The input file to be rendered (see `rmarkdown::render`) <<param-minimal_eval>> #' @param fresh_session Whether to call `rmarkdown::render` from a fresh R #' session. By default TRUE, so that it matches the behavior of pressing "Knitr" #' in RStudio. However, for debugging it can be useful to set this to FALSE so #' that functions like `debug()` and `browser()` will work. #' @param ... Additional parameters to pass to `rmarkdown::render` #' @export render <- function(input, minimal_eval, fresh_session = TRUE, ...) { # call rmarkdown::render in a new environment so it behaves the same as # pressing the knit button in RStudio: # https://bookdown.org/yihui/rmarkdown-cookbook/rmarkdown-render.html args <- list(...) # let's determine if the output format being used is a litr format. # If it is, then we'll simply want to call rmarkdown::render() since the # special litr behavior will be attained through the output format. litr_format <- FALSE bookdown_format <- FALSE output_format_arg <- FALSE if ("output_format" %in% names(args)) { output_format_arg <- TRUE if ("litr_format" %in% names(args$output_format)) { litr_format <- TRUE } if ("bookdown_output_format" %in% names(args$output_format)) { bookdown_format <- TRUE } } else { frontmatter <- rmarkdown::yaml_front_matter(input) if ("output" %in% names(frontmatter)) { formats <- ifelse(is.list(frontmatter$output), names(frontmatter$output), frontmatter$output) if (any(stringr::str_detect(formats, "litr::"))) { litr_format <- TRUE } if (any(stringr::str_detect(formats, "litr::litr_gitbook"))) { bookdown_format <- TRUE } } } # get package_directory params <- get_params_used(input, args$params) package_dir <- get_package_directory( params$package_parent_dir, params$package_name, input ) # if minimal_eval was passed to render, add this to the output_options # argument that will be passed to rmarkdown::render if (is.null(args$output_options)) args$output_options <- list() if (!missing(minimal_eval)) args$output_options$minimal_eval <- minimal_eval # determine whether a new R session will be created when we run the rendering # function of rmarkdown/bookdown if (fresh_session) run_function <- xfun::Rscript_call else run_function <- do.call if (litr_format) { # this uses a litr output format, so we don't need to do anything litr-specific # here because it will happen through the output format if (output_format_arg & !missing(minimal_eval)) { # the output format was passed through the output_format argument rather # than through the metadata if (minimal_eval) { stop(make_noticeable(paste( "When passing a litr output format using the output_format argument,", "you should not pass minimal_eval = TRUE directly to render.", "Instead, pass it to the litr output format function. For example,", "litr::litr_html_document(minimal_eval = TRUE).", collapse = " " ))) } } if (bookdown_format) { if (fs::is_file(input)) input <- fs::path_dir(input) return(invisible(run_function(with_cleanup(bookdown::render_book, package_dir), c(input = input, args)))) } else return(invisible(run_function(with_cleanup(rmarkdown::render, package_dir), c(input = input, args)))) } # the output format being used is not a litr-specific one, so we need to make # sure that all the special litr things happen args$package_dir <- package_dir render_ <- function(input, package_dir, minimal_eval, ...) { knitr_objects <- litr:::setup(package_dir, minimal_eval) out <- rmarkdown::render(input, ...) restore_knitr_objects(knitr_objects) # remove .Rproj and .gitignore if usethis::create_package() added these remove_rstudio_extras(package_dir) return(out) } if (missing(minimal_eval)) minimal_eval <- FALSE out <- run_function(with_cleanup(render_, package_dir), c(input = input, minimal_eval = minimal_eval, args)) # add hyperlinks within html output to make it easier to navigate: if (any(stringr::str_detect(out, "html$"))) { html_file <- stringr::str_subset(out, "html$") add_function_hyperlinks(html_file, params$package_name) add_chunk_label_hyperlinks(html_file) } # add to DESCRIPTION file the version of litr used to create package: write_version_to_description(package_dir) # add litr hash so we can tell later if package files were manually edited: write_hash_to_description(package_dir) } We used the package xfun, so let’s import it: usethis::use_package("xfun") ## ✔ Adding 'xfun' to Imports field in DESCRIPTION ## • Refer to functions with `xfun::fun()` When litr::render() encounters an error, it can leave the output directory partially modified. We want to make sure the litr hash still gets written to the DESCRIPTION file. Otherwise, the next time one calls litr::render() it does not allow this directory to be overwritten. We do this by using withCallingHandlers(). This function, explained here, is similar to tryCatch(), but with the advantage that it lets the function to continue to run normally, meaning that we will still get the error message as it would appear if we hadn’t done the condition handling. #' Add litr hash to DESCRIPTION file if error encountered #' #' This creates a function that calls the passed function within the context of #' a try-catch. If an error is encountered, the litr hash is still added to #' the DESCRIPTION file so that future calls to `litr::render()` will recognize #' that it can safely overwrite the package directory (i.e., no manual editing #' occurred). #' #' @param fun function being called #' @param package_dir directory where package is being written to #' @param ... arguments to be passed to `fun` #' @keywords internal with_cleanup <- function(fun, package_dir) { return(function(...) { withCallingHandlers( fun(...), error = function(e) { # add litr hash so we can tell later if package files were manually edited: write_hash_to_description(package_dir) }) }) } In setup(), we modified the knitr objects (e.g., adding hooks, engines, etc.). We call the function restore_knitr_objects() after we’re done, to put things back how they were: #' Return the knitr objects to their original state #' #' @param original_knitr_objects As returned by `setup()` #' @keywords internal restore_knitr_objects <- function(original_knitr_objects) { knitr::opts_knit$restore(original_knitr_objects$opts_knit) knitr::knit_hooks$restore(original_knitr_objects$knit_hooks) knitr::opts_chunk$restore(original_knitr_objects$opts_chunk) knitr::opts_hooks$restore(original_knitr_objects$opts_hooks) knitr::knit_engines$restore(original_knitr_objects$knit_engines) } Another thing we want to do at the end of the rendering process is to remove two files that might have been created by usethis: .Rproj and .gitignore. These are created by usethis::create_package() when rstudio = TRUE. We don’t want these files created since this would suggest to a user that the R package should be worked on from within it rather than from the generating .Rmd file. #' Remove extra files added by usethis #' #' Remove .Rproj and .gitignore files if they are in the package directory. #' #' @param package_dir Path to package #' @keywords internal remove_rstudio_extras <- function(package_dir) { extras <- fs::dir_ls(package_dir, all = TRUE, regexp = "[.]Rproj$|[.]gitignore$") rbuildignore <- file.path(package_dir, ".Rbuildignore") txt <- readLines(rbuildignore) txt <- stringr::str_subset(txt, "^.*Rproj.*$", negate = TRUE) writeLines(txt, con = rbuildignore) for (extra in extras) fs::file_delete(extra) } As described earlier, the function get_params_used() combines the parameters from the YAML but allows for those values to be overridden through arguments passed to render(). #' Get parameter values used in rendering #' #' When the `params` argument of `rmarkdown::render()` is explicitly used, this #' overrides the default that appears in `input`. #' @param input The input file to be rendered (see `rmarkdown::render`) #' @param passed_params The list of parameters that were passed to `render`. #' @keywords internal get_params_used <- function(input, passed_params) { params <- rmarkdown::yaml_front_matter(input)$params for (param in names(passed_params)) { params[[param]] <- passed_params[[param]] } params } We used the package rmarkdown, so let’s import it: usethis::use_package("rmarkdown") ## ✔ Adding 'rmarkdown' to Imports field in DESCRIPTION ## • Refer to functions with `rmarkdown::fun()` For example, we include the line knit: litr::render in the yaml of the templates for this reason.↩︎ "],["functionality-to-facilitate-workflow.html", "8 Functionality to facilitate workflow", " 8 Functionality to facilitate workflow When someone is writing an R package with devtools, it is common to use devtools::load_all() to quickly try out the functions of an R package in the console. We’d like to allow for a similar workflow using litr. We define a litr function called load_all(), which will do the following: Litr-knit the .Rmd file with minimal_eval=TRUE to some temporary location (since the idea of load_all() is to leave the .html file alone) and then delete the .html file. Run devtools::load_all() on the output. #' Load complete package #' #' This is a litr wrapper to `devtools::load_all()`. It first calls #' `litr::render()` with `minimal_eval=TRUE`, then it calls #' `devtools::load_all()` on the generated package. #' #' @param input The input file to be rendered (see `rmarkdown::render`) #' @param output_dir By default (and in typical usage) this is NULL, meaning #' that no .html/bookdown/.pdf will result. However, when a directory is given, #' the result of the litr-knitting will be saved to this location. #' @param ... Additional parameters to be passed to `devtools::load_all()` #' @export load_all <- function(input, output_dir = NULL, ...) { no_output <- is.null(output_dir) if (no_output) { output_dir <- tempfile() if (fs::file_exists(output_dir)) fs::file_delete(output_dir) fs::dir_create(output_dir) } # let's copy over everything from input directory to output directory fs::dir_copy(fs::path_dir(input), output_dir, overwrite = TRUE) input_path <- fs::path_split(input)[[1]] moved_input <- file.path(output_dir, fs::path_file(input)) # get package directory params <- get_params_used(moved_input, list()) package_dir <- get_package_directory( params$package_parent_dir, params$package_name, moved_input ) # but if a package directory was copied here, let's remove it before # calling render to avoid a potential error if (fs::dir_exists(package_dir)) fs::dir_delete(package_dir) litr::render(moved_input, minimal_eval = TRUE, output_dir = output_dir, quiet = TRUE) new_package_dir <- file.path(fs::path_dir(input), params$package_name) fs::dir_copy(package_dir, new_package_dir, overwrite = TRUE) if (no_output) fs::dir_delete(output_dir) devtools::load_all(new_package_dir) } Let’s test that this works. In particular, we’ll call load_all() and then try to use one of the functions from the package. testthat::test_that('load_all() works', { # setup files for tests: dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, 'create-pkg.Rmd') fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file) html_file <- file.path(dir, "create-pkg.html") load_all(rmd_file) testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!") fs::dir_delete(dir) }) "],["adding-extras-to-an-r-package.html", "9 Adding extras to an R package 9.1 Adding a README 9.2 Adding a hex sticker 9.3 Adding vignettes 9.4 Add a pkgdown site", " 9 Adding extras to an R package When writing an R package, there are some additional items one typically wants to include, such as a README file, one or more vignettes, and a pkgdown site. In this section, we define some helper functions that will be make it easy to add these “extras” to a package. While a user could choose to generate all these items within the generating .Rmd file, for all but the simplest examples, it will probably be preferred to have these as separate source files. These source files can live in the same directory as the generating .Rmd file or in their own directory (e.g., source-files lives in the same directory as create-litr.Rmd, which is called litr-project). 9.1 Adding a README We define a helper function that takes an externally defined README.Rmd and puts it into the package, creates a README.md, and makes sure that these will be added to the .Rbuildignore. #' Add README to package #' #' This function takes a README.Rmd file, copies it into the package, and then #' renders it to a README.md file. It also adds these two files to the #' .Rbuildignore. #' #' @param rmd_file The path to a .Rmd file. #' @export add_readme <- function(rmd_file) { usethis::use_readme_rmd(open = FALSE) fs::file_copy(rmd_file, new_path = "README.Rmd", overwrite = TRUE) out <- xfun::Rscript_call( rmarkdown::render, args = list( input = "README.Rmd", output_options = list(html_preview = "false") ) ) } 9.2 Adding a hex sticker We define a helper function that takes an externally defined hex sticker (.png file) and puts it into the package under man/figures. The suggestion for storing it in this directory came from here. #' Add a hex sticker to package #' #' In addition to calling this function, you should add to your README.Rmd something like this: #' #' `# your-title <img src="man/figures/logo.png" align="right" height="139" />` #' #' See [here](https://pkgdown.r-lib.org/reference/build_home.html#package-logo) #' for more. #' #' @param hex_png_file The .png file with your package's hex sticker #' @export add_hex_sticker <- function(hex_png_file) { figures_dir <- file.path("man", "figures") fs::dir_create(figures_dir) fs::file_copy(path = hex_png_file, new_path = file.path(figures_dir, "logo.png"), overwrite = TRUE) } 9.3 Adding vignettes We next define a helper function for adding vignettes to the package. This mimics usethis::use_vignette(). We couldn’t directly use that function because we want the project file to live outside of the package directory, which confuses usethis. #' Add one or more vignettes to package #' #' @param rmd_files A character vector of .Rmd files, each corresponding to #' a vignette #' @param other_files A character vector of any other files needed in the #' vignettes directory (.bib file, images, etc.) #' @export add_vignettes <- function(rmd_files, other_files = NULL) { fs::dir_create("vignettes") for (fn in c(rmd_files, other_files)) fs::file_copy(fn, "vignettes") # update DESCRIPTION file: deps <- desc::desc_get_deps()$package if (!("knitr" %in% deps)) desc::desc_set_dep("knitr", type = "Suggests") if (!("rmarkdown" %in% deps)) desc::desc_set_dep("rmarkdown", type = "Suggests") out <- desc::desc_set("VignetteBuilder", "knitr") } 9.4 Add a pkgdown site We define a function based on usethis::use_pkgdown(), but with a few differences: Avoid the parts to do with looking for projects Allow one to use a custom _pkgdown.yml that is stored outside of package #' Add a pkgdown site #' #' This function creates a website for your package. You can see it locally by #' opening `docs/index.html` in your package. To get it online you can copy the #' `docs` directory to your website's server. #' #' Be sure that in the generating .Rmd file this is called *after* #' `litr::document()` has been called. To customize the site, you may pass a #' customized `_pkgdown.yml` file as described in [this `pkgdown` vignette](https://pkgdown.r-lib.org/articles/customise.html). #' #' @param config_path The _pkgdown.yml file that lives somewhere outside of your package. If NULL, then a basic default will be used. #' @export add_pkgdown <- function(config_path = NULL) { config_file <- "_pkgdown.yml" destdir <- "docs" usethis::use_build_ignore(c(config_file, destdir, "pkgdown")) if (is.null(config_path)) { # create a new config file (note it lives outside of package) config <- usethis:::pkgdown_config(destdir) usethis::write_over(config_file, yaml::as.yaml(config)) } else { # copy the one that already exists: fs::file_copy(config_path, config_file) } pkgdown::build_site() } After this step, you can locally see the site by opening docs/index.html in the browser. You can then copy the docs directory to your website’s server and you’re done. Since the above function uses pkgdown and yaml, we include these in our package: usethis::use_package("pkgdown") usethis::use_package("yaml") ## ✔ Adding 'pkgdown' to Imports field in DESCRIPTION ## • Refer to functions with `pkgdown::fun()` ## ✔ Adding 'yaml' to Imports field in DESCRIPTION ## • Refer to functions with `yaml::fun()` "],["combining-.r-files.html", "10 Combining .R files", " 10 Combining .R files This section should eventually be removed but for now I’m doing this to convince myself that the package generated by this .Rmd file really matches the initial version created without the package. library(magrittr) library(purrr) rfiles <- fs::dir_ls("R") code <- rfiles %>% map(readLines) %>% set_names( rfiles %>% stringr::str_remove("^.*/") %>% stringr::str_remove(".R$") ) hash_functions <- c("hash_package_directory", "description_litr_hash_field_name", "write_hash_to_description", "read_hash_from_description", "check_unedited") render_functions <- c("render", "with_cleanup", "litrify_output_format", "litr_pdf_document", "litr_html_document", "litr_gitbook", "replace_ansi_sequences", "add_function_hyperlinks", "insert_hrefs", "add_chunk_label_hyperlinks", "restore_knitr_objects", "remove_rstudio_extras", "get_params_used", "get_package_directory", "do_not_edit_message", "description_litr_version_field_name", "write_version_to_description", "document", "load_all") setup_functions <- c("setup", "make_noticeable", "send_to_package", "add_text_to_file", "find_labels") extras_functions <- c("add_readme", "add_hex_sticker", "add_vignettes", "add_pkgdown") remove_initial_lines <- function(code_list) { # drop first line of each list element except for the first list element c(code_list[1], map(code_list[-1], ~ .x[-1])) } fs::file_delete(setdiff(rfiles, "R/litr-package.R")) writeLines(unlist(remove_initial_lines(code[hash_functions])), "R/hash.R") writeLines(unlist(remove_initial_lines(code[render_functions])), "R/render.R") writeLines(unlist(remove_initial_lines(code[setup_functions])), "R/setup.R") writeLines(unlist(remove_initial_lines(code[extras_functions])), "R/extras.R") ## ## Attaching package: 'purrr' ## The following object is masked from 'package:magrittr': ## ## set_names "],["including-templates.html", "11 Including templates", " 11 Including templates We now add the .Rmd templates to the package. We have the skeleton.Rmd defined in source-files. Note that paths are relative to the outputted package’s location. The first template is the simplest imaginable package with a single function: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package", template_dir = "make-an-r-package", template_description = "Template for an Rmd file for writing an R package using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package/skeleton/skeleton.Rmd' The second template shows how to create a package with a dataset: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With a Dataset", template_dir = "make-an-r-package-with-data", template_description = "Template for an Rmd file for writing an R package with a dataset using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-data", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-data", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-data/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-data/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-data/skeleton/skeleton.Rmd' The third template shows how to create a package that uses Rcpp: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With Rcpp", template_dir = "make-an-r-package-with-rcpp", template_description = "Template for an Rmd file for writing an R package that makes use of Rcpp while using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-rcpp", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-rcpp", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-rcpp/skeleton/skeleton.Rmd' The fourth template shows how to create a package with “extras” such as a README, a vignette, and a pkgdown site: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With a README, Vignette, and Pkgdown Site", template_dir = "make-an-r-package-with-extras", template_description = "Template for an Rmd file for writing an R package that has a README, vignette, and pkgdown site while using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-extras", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton" ), overwrite = TRUE ) fs::dir_copy( path = file.path( "..", "source-files", "make-an-r-package-with-extras", "source-files" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-extras", "skeleton", "source-files" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-extras/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-extras/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-extras/skeleton/skeleton.Rmd' The fifth template shows how to create a package from a bookdown site, i.e. instead of having just a single create-pkg.Rmd, we can have a series of .Rmd files that together create a bookdown: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package From a Bookdown", template_dir = "make-an-r-package-from-bookdown", template_description = "Template for a bookdown that defines an R package using literate programming.", template_create_dir = TRUE ) fs::dir_copy( path = file.path("..", "source-files", "make-an-r-package-from-bookdown"), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-from-bookdown", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-from-bookdown/skeleton/skeleton.Rmd' The sixth template shows how to create a package that uses RcppArmadillo: usethis::use_rmarkdown_template( template_name = "Template To Make an R Package With RcppArmadillo", template_dir = "make-an-r-package-with-armadillo", template_description = "Template for an Rmd file for writing an R package that makes use of RcppArmadillo while using literate programming.", template_create_dir = FALSE ) fs::file_copy( path = file.path( "..", "source-files", "make-an-r-package-with-armadillo", "skeleton.Rmd" ), new_path = file.path( "inst", "rmarkdown", "templates", "make-an-r-package-with-armadillo", "skeleton" ), overwrite = TRUE ) ## ✔ Creating 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/skeleton/' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/template.yaml' ## ✔ Writing 'inst/rmarkdown/templates/make-an-r-package-with-armadillo/skeleton/skeleton.Rmd' "],["tests.html", "12 Defining some tests 12.1 Testing check_unedited() 12.2 Testing get_params_used() 12.3 Testing chunk referencing 12.4 Testing different ways of rendering 12.5 Testing other templates", " 12 Defining some tests When using litr to create packages that are not litr, one should be able to run tests along the way as we did above in testing the function add_text_to_file(). However, creating litr is a special case so we need to do something different for the tests that involve creating a .Rmd from template and then calling litr::render() on them (such as the tests in this section). In particular, we use eval=FALSE for these code blocks and then at the end of this document we will install the newly created version of litr and then call devtools::test(). Doing it this way is important for ensuring that the version of litr we are testing is the newest version, i.e. the version defined in this document. To understand the reason we are doing it this way, imagine what would happen if instead we left eval=TRUE in the test in the next section. When we use rmarkdown::draft() to create a .Rmd file from template, the file it will give us will be an old version (namely the installed version of litr’s template) rather than the latest version.2 Furthermore, consider what happens when we call render() in the test below. This will start the knitting process on my-package.Rmd. However, inside my-package.Rmd, we have litr::setup() and litr::document(). When these are called in the knitting process, it will be the versions of the functions from the currently installed litr rather than the versions defined in this document. Once we are done testing the new version of the package, we’d like to restore the state of litr to what it was previously. If we don’t do this, then this can lead to inadvertent circularity in which the next time we call litr::render(\"create-litr.Rmd\"), we are using the version currently under development, which is bad because ultimately we need this version to be rendered by the previous version of litr. The following function implements this approach to testing litr: #' Run tests for `litr` itself #' #' Special function for testing `litr`. The trick is to temporarily install #' the new version of `litr`, run the test, and then put things back how it was #' before. #' #' Typical values for `install_old` could be #' - `function() devtools::install("[location of old version]")` #' - `function() remotes::install_github("jacobbien/litr-project@*release", subdir = "litr")`. #' #' @param install_old A function that when run will install the old version #' @param location_of_new Path to the new package directory #' @keywords internal test_litr <- function(install_old, location_of_new) { devtools::unload(params$package_name) devtools::install(location_of_new) out <- devtools::test(location_of_new) install_old() return(out) } Note: The call to devtools::unload() is to address an issue discussed here. 12.1 Testing check_unedited() For our tests, we create a temporary directory (which we delete at the end). In this directory, we create a generating .Rmd file from one of the templates. We make repeated modifications to the package and each time verify that check_unedited() is FALSE with the modification and returns to TRUE when we put things back how they were. The modifications we try are the following: Adding a file Removing a file Making a change to a file (in particular, adding a comment to an R file) Changing something in the DESCRIPTION file (but not on the special litr line) Changing the litr hash line itself testthat::test_that("check_unedited works", { # Including this next line seems to be necessary for R CMD check on the cmd line: #Sys.setenv(RSTUDIO_PANDOC = "/Applications/RStudio.app/Contents/MacOS/pandoc") dir <- tempfile() fs::dir_create(dir) rmd_file <- file.path(dir, "my-package.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr", edit = FALSE) # create R package (named "rhello") from the Rmd template: render(rmd_file) package_path <- file.path(dir, "rhello") testthat::expect_true(check_unedited(package_path)) # what if a file has been added? added_file <- file.path(package_path, "R", "say_hello2.R") writeLines("# Added something here.", added_file) testthat::expect_false(check_unedited(package_path)) # what if we now remove it? fs::file_delete(added_file) testthat::expect_true(check_unedited(package_path)) # what if a file is removed from package? rfile <- file.path(package_path, "R", "say_hello.R") fs::file_move(rfile, dir) testthat::expect_false(check_unedited(package_path)) # now put it back fs::file_move(file.path(dir, "say_hello.R"), file.path(package_path, "R")) testthat::expect_true(check_unedited(package_path)) # what if something is changed in a file? txt <- readLines(rfile) txt_mod <- txt txt_mod[3] <- paste0(txt[3], " # added a comment!!") writeLines(txt_mod, rfile) testthat::expect_false(check_unedited(package_path)) # now put it back writeLines(txt, rfile) testthat::expect_true(check_unedited(package_path)) # what if something is changed in the DESCRIPTION file? descfile <- file.path(package_path, "DESCRIPTION") txt <- readLines(descfile) txt_mod <- txt txt_mod[1] <- "Package: newname" writeLines(txt_mod, descfile) testthat::expect_false(check_unedited(package_path)) # now put it back writeLines(txt, descfile) testthat::expect_true(check_unedited(package_path)) # what if the special litr hash field is changed in the DESCRIPTION file? txt <- readLines(descfile) i_litr <- stringr::str_which(txt, description_litr_hash_field_name()) txt_mod <- txt txt_mod[i_litr] <- paste0(txt_mod[i_litr], "a") writeLines(txt_mod, descfile) testthat::expect_false(check_unedited(package_path)) # now put it back writeLines(txt, descfile) testthat::expect_true(check_unedited(package_path)) fs::dir_delete(dir) }) 12.2 Testing get_params_used() Let’s now test the get_params_used() function, making sure it behaves how we expect it to: testthat::test_that("get_params_used works", { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, "my-package.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr", edit = FALSE) default_params <- get_params_used(rmd_file, passed_params = list()) testthat::expect_equal( default_params, rmarkdown::yaml_front_matter(rmd_file)$params ) params1 <- default_params params1$package_parent_dir <- "dir" testthat::expect_equal( get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir")), params1 ) params2 <- default_params params2$package_name <- "pkg" params2$package_parent_dir <- "dir" testthat::expect_equal( get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir", package_name = "pkg")), params2 ) fs::dir_delete(dir) }) 12.3 Testing chunk referencing Here we test the handling of chunk references (as implemented in the document output hook set within setup()). In particular, we use a .Rmd that uses chunk references in several different ways. Within the .Rmd itself, we have tests that ensure that the code can still be run as expected. fs::file_copy( path = file.path( "..", "source-files", "test-example-files", "create-rknuth.Rmd" ), new_path = file.path("tests", "testthat"), overwrite = TRUE ) testthat::test_that('Knuth-style references work', { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, 'create-rknuth.Rmd') fs::file_copy(path = testthat::test_path("create-rknuth.Rmd"), new_path = rmd_file) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, 'create-rknuth.html'))) fs::dir_delete(dir) }) 12.4 Testing different ways of rendering The mechanism by which rendering occurs depends on several factors: Whether litr::render() or rmarkdown::render() is being called. Whether there is a litr output format specified in the preamble of the .Rmd. Whether there is a litr output format being passed an argument to the render function. In this section, we will test that one gets the same result regardless of how rendering was invoked.3 We will use variations on a base .Rmd file whose preamble is simply the following: --- title: 'A Test' params: package_name: 'pkg' # <-- change this to your package name package_parent_dir: '.' # <-- relative to this file location --- fs::file_copy( path = file.path( "..", "source-files", "test-example-files", "create-pkg.Rmd" ), new_path = file.path("tests", "testthat"), overwrite = TRUE ) There are 7 cases to consider (\\(2^3-1\\), since we exclude the case where rmarkdown::render() is called and no argument or preamble would indicate that this should be a litr-knit). testthat::test_that('Rendering in all possible ways works', { # setup files for tests: dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) # .Rmd without output format in preamble rmd_file1 <- file.path(dir, 'create-pkg1.Rmd') fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file1) # .Rmd without output format in preamble rmd_file2 <- file.path(dir, 'create-pkg2.Rmd') fs::file_copy(rmd_file1, rmd_file2) litr:::add_text_to_file("output: litr::litr_html_document", rmd_file2, 3) # files names rmd_file <- file.path(dir, "create-pkg.Rmd") html_file <- file.path(dir, "create-pkg.html") html_file_a <- file.path(dir, "a","create-pkg.html") pkg <- file.path(dir, "pkg") pkg_a <- file.path(dir, "a", "pkg") check_outputs_are_same <- function() { # html files should be the same: testthat::expect_equal(readLines(html_file_a), readLines(html_file)) # packages should be the same (relying here on litr-hash in DESCRIPTION): testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")), readLines(file.path(pkg_a, "DESCRIPTION"))) } ## Now test that all the cases give the same outputs: # Case 1: no preamble + litr::render() fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE) render(rmd_file, output_file = html_file) if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a")) fs::dir_create(file.path(dir, "a")) fs::dir_copy(pkg, pkg_a) fs::dir_delete(pkg) fs::file_move(html_file, html_file_a) # Case 2: with preamble + litr::render() fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) render(rmd_file, output_file = html_file) check_outputs_are_same() # Case 3: no preamble + litr::render() with output format argument fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE) render(rmd_file, output_format = litr::litr_html_document(), output_file = html_file) check_outputs_are_same() # Case 4: with preamble + litr::render() with output format argument fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) render(rmd_file, output_format = litr::litr_html_document(), output_file = html_file) check_outputs_are_same() # Case 5: with preamble + rmarkdown::render() fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) xfun::Rscript_call(rmarkdown::render, list(input = rmd_file, output_file = html_file) ) check_outputs_are_same() # Case 6: no preamble + rmarkdown::render() with output format argument fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE) xfun::Rscript_call(rmarkdown::render, list(input = rmd_file, output_format = litr::litr_html_document(), output_file = html_file) ) check_outputs_are_same() # Case 7: with preamble + rmarkdown::render() with output format argument fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE) xfun::Rscript_call(rmarkdown::render, list(input = rmd_file, output_format = litr::litr_html_document(), output_file = html_file) ) check_outputs_are_same() fs::dir_delete(dir) }) Let’s also make sure that we get the same R package output when using minimal_eval=TRUE as minimal_eval=TRUE. testthat::test_that('Rendering with minimal_eval=TRUE works', { # setup files for tests: dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, 'create-pkg.Rmd') fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file) # .Rmd without output format in preamble html_file <- file.path(dir, "create-pkg.html") html_file_a <- file.path(dir, "a","create-pkg.html") pkg <- file.path(dir, "pkg") pkg_a <- file.path(dir, "a", "pkg") ## Now test that all the cases give the same outputs: # Case 1: minimal_eval = FALSE render(rmd_file, output_file = html_file, minimal_eval = FALSE) if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a")) fs::dir_create(file.path(dir, "a")) fs::dir_copy(pkg, pkg_a) fs::dir_delete(pkg) # Case 2: minimal_eval = TRUE passed to render render(rmd_file, output_file = html_file, minimal_eval = TRUE) testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")), readLines(file.path(pkg_a, "DESCRIPTION"))) # Case 3: minimal_eval = TRUE passed to output format render(rmd_file, output_file = html_file, output_format = litr::litr_html_document(minimal_eval = TRUE) ) testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")), readLines(file.path(pkg_a, "DESCRIPTION"))) fs::dir_delete(dir) }) 12.5 Testing other templates Let’s now make sure that each template can be knit without error. testthat::test_that("templates can be knit", { dir <- tempfile() if (fs::file_exists(dir)) fs::file_delete(dir) fs::dir_create(dir) rmd_file <- file.path(dir, "create-rhello.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-rhello.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "rhello"))) rmd_file <- file.path(dir, "create-rhasdata.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-data", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-rhasdata.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "rhasdata"))) rmd_file <- file.path(dir, "create-withrcpp.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-rcpp", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-withrcpp.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "withrcpp"))) rmd_file <- file.path(dir, "create-witharmadillo.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-armadillo", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-witharmadillo.Rmd"))) testthat::expect_true(fs::file_exists(file.path(dir, "witharmadillo"))) rmd_file <- file.path(dir, "create-withpkgdown.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-with-extras", package = "litr", edit = FALSE) render(rmd_file) testthat::expect_true(fs::file_exists(file.path(dir, "create-withpkgdown.html"))) testthat::expect_true(fs::file_exists(file.path(dir, "withpkgdown"))) rmd_file <- file.path(dir, "create-frombookdown.Rmd") rmarkdown::draft(rmd_file, template = "make-an-r-package-from-bookdown", package = "litr", edit = FALSE) prev_dir <- getwd() setwd(file.path(dir, "create-frombookdown")) fs::file_delete("create-frombookdown.Rmd") render("index.Rmd") setwd(prev_dir) testthat::expect_true( fs::file_exists(file.path(dir, "create-frombookdown", "_book", "index.html")) ) testthat::expect_true( fs::file_exists(file.path(dir, "create-frombookdown", "frombookdown")) ) fs::dir_delete(dir) }) Even though litr doesn’t directly use Rcpp, we’ll add it as a “Suggests” package since it would be required for running the above test. usethis::use_package("Rcpp", type = "Suggests") ## ✔ Adding 'Rcpp' to Suggests field in DESCRIPTION ## • Use `requireNamespace("Rcpp", quietly = TRUE)` to test if package is installed ## • Then directly refer to functions with `Rcpp::fun()` If this were the only problem, we could get around this by using pkgload::package_file() to get the proper file; however, the next problem discussed was something that seemed quite hard to resolve.↩︎ Note: When we call rmarkdown::render(), we call it in a fresh, non-interactive R session.↩︎ "],["documenting-the-package-and-testing.html", "13 Documenting the package and testing 13.1 Add examples folder with the output of knitting each example", " 13 Documenting the package and testing We finish by running commands that will document and test litr. The formatting of the test output does not print out very neatly. We download the latest release: litr::document() install_old <- function() { remotes::install_github("jacobbien/litr-project@*release", subdir = "litr") } xfun::Rscript_call(test_litr, list(install_old = install_old, location_of_new = ".")) ## ℹ Updating litr documentation ## ℹ Loading litr ## Writing 'NAMESPACE' ## Writing 'add_readme.Rd' ## Writing 'add_hex_sticker.Rd' ## Writing 'add_vignettes.Rd' ## Writing 'add_pkgdown.Rd' ## Writing 'hash_package_directory.Rd' ## Writing 'description_litr_hash_field_name.Rd' ## Writing 'write_hash_to_description.Rd' ## Writing 'read_hash_from_description.Rd' ## Writing 'check_unedited.Rd' ## Writing 'litr-package.Rd' ## Writing 'render.Rd' ## Writing 'with_cleanup.Rd' ## Writing 'litrify_output_format.Rd' ## Writing 'litr_pdf_document.Rd' ## Writing 'litr_html_document.Rd' ## Writing 'litr_gitbook.Rd' ## Writing 'replace_ansi_sequences.Rd' ## Writing 'add_function_hyperlinks.Rd' ## Writing 'insert_hrefs.Rd' ## Writing 'add_chunk_label_hyperlinks.Rd' ## Writing 'restore_knitr_objects.Rd' ## Writing 'remove_rstudio_extras.Rd' ## Writing 'get_params_used.Rd' ## Writing 'get_package_directory.Rd' ## Writing 'do_not_edit_message.Rd' ## Writing 'description_litr_version_field_name.Rd' ## Writing 'write_version_to_description.Rd' ## Writing 'document.Rd' ## Writing 'load_all.Rd' ## Writing 'setup.Rd' ## Writing 'make_noticeable.Rd' ## Writing 'send_to_package.Rd' ## Writing 'add_text_to_file.Rd' ## Writing 'find_labels.Rd' ## Writing 'test_litr.Rd' ## file context test nb failed skipped ## 1 tests.R tests add_text_to_file() works 10 0 FALSE ## 2 tests.R tests get_package_directory() works 2 0 FALSE ## 3 tests.R tests load_all() works 1 0 FALSE ## 4 tests.R tests check_unedited works 11 0 FALSE ## 5 tests.R tests get_params_used works 3 0 FALSE ## 6 tests.R tests Knuth-style references work 1 0 FALSE ## 7 tests.R tests Rendering in all possible ways works 12 0 FALSE ## 8 tests.R tests Rendering with minimal_eval=TRUE works 2 0 FALSE ## 9 tests.R tests templates can be knit 12 0 FALSE ## error warning user system real passed ## 1 FALSE 0 0.084 0.005 0.090 10 ## 2 FALSE 0 0.004 0.001 0.004 2 ## 3 FALSE 0 0.194 0.018 1.208 1 ## 4 FALSE 0 0.050 0.007 1.104 11 ## 5 FALSE 0 0.006 0.001 0.006 3 ## 6 FALSE 0 0.019 0.002 1.181 1 ## 7 FALSE 0 0.353 0.022 7.571 12 ## 8 FALSE 0 0.209 0.015 3.131 2 ## 9 FALSE 0 0.120 0.023 37.485 12 ## result ## 1 , 10, 3, 10, 77, 3, 77, 10, 10, testthat::expect_error(add_text_to_file(sometxt, myfile, req_exist = TRUE)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, req_exist = TRUE), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, fs::file_exists(myfile) is not TRUE\\n\\n, 16, 3, 16, 48, 3, 48, 16, 16, 45, 47, add_text_to_file() works, `sometxt` (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 17, 3, 17, 52, 3, 52, 17, 17, 45, 47, add_text_to_file() works, c(sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 22, 3, 22, 64, 3, 64, 22, 22, 45, 47, add_text_to_file() works, , 25, 3, 25, 62, 3, 62, 25, 25, testthat::expect_error(add_text_to_file(sometxt, myfile, 0)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, 0), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, , 26, 3, 26, 63, 3, 63, 26, 26, testthat::expect_error(add_text_to_file(sometxt, myfile, -1)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, -1), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, , 27, 3, 27, 62, 3, 62, 27, 27, testthat::expect_error(add_text_to_file(sometxt, myfile, 5)), expect_condition_matching("error", {, {, object, }, }, regexp = regexp, class = class, ..., inherit = inherit, info = info, , label = label), quasi_capture(enquo(object), label, capture_matching_condition, , matches = matcher), .capture(act$val <- eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), , ...), withCallingHandlers(expr, condition = function(cnd) {, if (!is.null(matched) || !matches(cnd)) {, return(), }, if (can_entrace(cnd)) {, cnd <- cnd_entrace(cnd), }, matched <<- cnd, if (inherits(cnd, "message") || inherits(cnd, "warning")) {, cnd_muffle(cnd), }, else if (inherits(cnd, "error") || inherits(cnd, "skip")) {, return_from(tl, cnd), }, }), eval_bare(quo_get_expr(.quo), quo_get_env(.quo)), add_text_to_file(sometxt, myfile, 5), 0, 1, 2, 3, 4, 3, 0, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, testthat, testthat, testthat, testthat, base, rlang, litr, ::, :::, :::, local, ::, ::, :::, 45, 47, add_text_to_file() works, c(moretxt2, sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 32, 3, 32, 74, 3, 74, 32, 32, 45, 47, add_text_to_file() works, c(moretxt2, moretxt3, sometxt, moretxt) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 37, 3, 38, 43, 3, 43, 37, 38, 45, 47, add_text_to_file() works, c(moretxt2, moretxt3, sometxt, moretxt, moretxt4) (`actual`) not equal to readLines(myfile) (`expected`).\\n\\n, 43, 3, 44, 43, 3, 43, 43, 44, 45, 47, add_text_to_file() works ## 2 get_package_directory(".", "mypkg", input) (`actual`) not equal to file.path("inputdir", "mypkg") (`expected`).\\n\\n, 50, 3, 53, 3, 3, 3, 50, 53, 45, 47, get_package_directory() works, get_package_directory("..", "mypkg", input) (`actual`) not equal to file.path("inputdir", "..", "mypkg") (`expected`).\\n\\n, 54, 3, 57, 3, 3, 3, 54, 57, 45, 47, get_package_directory() works ## 3 say_hello("Jacob") (`actual`) not equal to "Hello Jacob!" (`expected`).\\n\\n, 70, 3, 70, 60, 3, 60, 70, 70, 45, 47, load_all() works ## 4 check_unedited(package_path) is not TRUE\\n\\n, 88, 3, 88, 53, 3, 53, 88, 88, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 93, 3, 93, 54, 3, 54, 93, 93, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 97, 3, 97, 53, 3, 53, 97, 97, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 102, 3, 102, 54, 3, 54, 102, 102, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 105, 3, 105, 53, 3, 53, 105, 105, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 112, 3, 112, 54, 3, 54, 112, 112, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 115, 3, 115, 53, 3, 53, 115, 115, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 123, 3, 123, 54, 3, 54, 123, 123, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 126, 3, 126, 53, 3, 53, 126, 126, 45, 47, check_unedited works, check_unedited(package_path) is not FALSE\\n\\n, 134, 3, 134, 54, 3, 54, 134, 134, 45, 47, check_unedited works, check_unedited(package_path) is not TRUE\\n\\n, 137, 3, 137, 53, 3, 53, 137, 137, 45, 47, check_unedited works ## 5 `default_params` (`actual`) not equal to rmarkdown::yaml_front_matter(rmd_file)$params (`expected`).\\n\\n, 150, 3, 153, 3, 3, 3, 150, 153, 45, 47, get_params_used works, get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir")) (`actual`) not equal to `params1` (`expected`).\\n\\n, 156, 3, 159, 3, 3, 3, 156, 159, 45, 47, get_params_used works, get_params_used(...) (`actual`) not equal to `params2` (`expected`).\\n\\n, 163, 3, 168, 3, 3, 3, 163, 168, 45, 47, get_params_used works ## 6 fs::file_exists(file.path(dir, "create-rknuth.html")) is not TRUE\\n\\n, 179, 3, 179, 78, 3, 78, 179, 179, 45, 47, Knuth-style references work ## 7 readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works, readLines(html_file_a) (`actual`) not equal to readLines(html_file) (`expected`).\\n\\n, 204, 5, 204, 72, 5, 72, 204, 204, 45, 48, Rendering in all possible ways works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 206, 5, 207, 70, 5, 70, 206, 207, 45, 48, Rendering in all possible ways works ## 8 readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 291, 3, 292, 68, 3, 68, 291, 292, 45, 47, Rendering with minimal_eval=TRUE works, readLines(file.path(pkg, "DESCRIPTION")) (`actual`) not equal to readLines(file.path(pkg_a, "DESCRIPTION")) (`expected`).\\n\\n, 299, 3, 300, 68, 3, 68, 299, 300, 45, 47, Rendering with minimal_eval=TRUE works ## 9 fs::file_exists(file.path(dir, "create-rhello.html")) is not TRUE\\n\\n, 316, 3, 316, 78, 3, 78, 316, 316, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "rhello")) is not TRUE\\n\\n, 317, 3, 317, 66, 3, 66, 317, 317, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-rhasdata.html")) is not TRUE\\n\\n, 325, 3, 325, 80, 3, 80, 325, 325, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "rhasdata")) is not TRUE\\n\\n, 326, 3, 326, 68, 3, 68, 326, 326, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-withrcpp.html")) is not TRUE\\n\\n, 334, 3, 334, 80, 3, 80, 334, 334, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "withrcpp")) is not TRUE\\n\\n, 335, 3, 335, 68, 3, 68, 335, 335, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-witharmadillo.Rmd")) is not TRUE\\n\\n, 343, 3, 343, 84, 3, 84, 343, 343, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "witharmadillo")) is not TRUE\\n\\n, 344, 3, 344, 73, 3, 73, 344, 344, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-withpkgdown.html")) is not TRUE\\n\\n, 352, 3, 352, 83, 3, 83, 352, 352, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "withpkgdown")) is not TRUE\\n\\n, 353, 3, 353, 71, 3, 71, 353, 353, 45, 47, templates can be knit, fs::file_exists(...) is not TRUE\\n\\n, 365, 3, 367, 5, 3, 5, 365, 367, 45, 47, templates can be knit, fs::file_exists(file.path(dir, "create-frombookdown", "frombookdown")) is not TRUE\\n\\n, 368, 3, 370, 5, 3, 5, 368, 370, 45, 47, templates can be knit 13.1 Add examples folder with the output of knitting each example In this section, we will litr-knit each template and put the outputs in an examples directory that lives outside of the litr R package. These examples are linked to in README.Rmd. build_all_templates <- function(install_old, location_of_new) { devtools::install(location_of_new) example_dir <- file.path("..", "examples") if (fs::dir_exists(example_dir)) fs::dir_delete(example_dir) fs::dir_create(example_dir) templates <- fs::path_file(fs::dir_ls("inst/rmarkdown/templates")) templates_bookdown <- stringr::str_subset(templates, "bookdown") templates_nonbookdown <- setdiff(templates, templates_bookdown) for (template in templates_nonbookdown) { tmp_file <- file.path(example_dir, "temp.Rmd") rmarkdown::draft(tmp_file, template, package = "litr", edit = FALSE) pkg_name <- rmarkdown::yaml_front_matter(tmp_file)$params$package_name rmd_file <- file.path(example_dir, paste0("create-", pkg_name, ".Rmd")) fs::file_move(tmp_file, rmd_file) render(rmd_file) # move to a template-specific directory: template_dir <- file.path(example_dir, template) fs::dir_create(template_dir) fs::file_move(fs::dir_ls(example_dir, regexp = pkg_name), template_dir) # move the source-files directory if (fs::dir_exists(file.path(example_dir, "source-files"))) { fs::dir_create(file.path(example_dir, template, "source-files")) fs::dir_copy(file.path(example_dir, "source-files"), file.path(example_dir, template)) fs::dir_delete(file.path(example_dir, "source-files")) } # move the docs directory (when pkgdown creates one) if (fs::dir_exists(file.path(example_dir, "docs"))) { fs::dir_create(file.path(example_dir, template, "docs")) fs::dir_copy(file.path(example_dir, "docs"), file.path(example_dir, template)) fs::dir_delete(file.path(example_dir, "docs")) } } for (template in templates_bookdown) { tmp_dir <- file.path(example_dir, "temp") rmarkdown::draft(file.path(example_dir, "temp.Rmd"), template, package = "litr", edit = FALSE) prev_dir <- getwd() setwd(tmp_dir) fs::file_delete("temp.Rmd") render("index.Rmd") # move to a template-specific directory: setwd(prev_dir) fs::dir_copy(tmp_dir, file.path(example_dir, template)) fs::dir_delete(tmp_dir) } install_old() } xfun::Rscript_call(build_all_templates, list(install_old = install_old, location_of_new = ".")) ## [1] "litr" "],["including-extras-for-litr.html", "14 Including extras for litr 14.1 README with hex sticker 14.2 Vignettes 14.3 A pkgdown site", " 14 Including extras for litr 14.1 README with hex sticker We include a README.Rmd and then generate the README.md based on it: add_readme(file.path("..", "source-files", "README.Rmd")) ## ✔ Writing 'README.Rmd' ## ✔ Adding '^README\\\\.Rmd$' to '.Rbuildignore' ## ✔ Creating '.git/hooks/' ## ✔ Writing '.git/hooks/pre-commit' Let’s add the litr hex sticker too (which is referred to in the README). add_hex_sticker(file.path("..", "source-files", "litr-hex.png")) Let’s also add a figure we include in the README. fs::file_copy(file.path("..", "source-files", "diagram3.png"), file.path("man", "figures")) 14.2 Vignettes add_vignettes(c(file.path("..", "source-files", "package-templates.Rmd"), file.path("..", "source-files", "packages-in-the-wild.Rmd"), file.path("..", "source-files", "faqs.Rmd"), file.path("..", "source-files", "basic-example.Rmd"))) The templates vignette uses dplyr and stringr, so we add them as “Suggests” in the DESCRIPTION file: usethis::use_package("dplyr", type = "Suggests") usethis::use_package("stringr", type = "Suggests") ## ✔ Adding 'dplyr' to Suggests field in DESCRIPTION ## • Use `requireNamespace("dplyr", quietly = TRUE)` to test if package is installed ## • Then directly refer to functions with `dplyr::fun()` ## Warning: Package 'stringr' is already listed in 'Imports' in DESCRIPTION, no ## change made. 14.3 A pkgdown site We’ll first add the github url to the DESCRIPTION file. desc::desc_set("URL", "https://github.com/jacobbien/litr-project/tree/main/litr") ## Package: litr ## Title: Literate Programming for Writing R Packages ## Version: 0.9.1 ## Authors@R (parsed): ## * Jacob Bien <jbien@usc.edu> [aut, cre] ## * Patrick Vossler [aut] ## Description: Allows one to fully create an R package in a single .Rmd ## file. Includes functionality and .Rmd templates for a literate ## programming approach to R package development. ## License: MIT + file LICENSE ## URL: https://github.com/jacobbien/litr-project/tree/main/litr ## Imports: ## bookdown, ## desc, ## devtools, ## digest, ## fansi, ## fs, ## knitr, ## pkgdown, ## rmarkdown, ## stringr, ## usethis, ## xfun, ## xml2, ## yaml ## Suggests: ## dplyr, ## Rcpp, ## testthat (>= 3.0.0) ## VignetteBuilder: ## knitr ## Config/testthat/edition: 3 ## Encoding: UTF-8 ## Roxygen: list(markdown = TRUE) ## RoxygenNote: 7.3.0 Next, we create the pkgdown site. The customizations come from the source file source-files/_pkgdown.yml. pkgdown_yml <- file.path("..", "source-files", "_pkgdown.yml") add_pkgdown(pkgdown_yml) ## ✔ Adding '^_pkgdown\\\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore' ## -- Installing package into temporary library ----------------------------------- ## == Building pkgdown site ======================================================= ## Reading from: '/Users/jacobbien/Documents/GitHub/litr-project/litr' ## Writing to: '/Users/jacobbien/Documents/GitHub/litr-project/docs' ## -- Initialising site ----------------------------------------------------------- ## -- Building favicons ----------------------------------------------------------- ## Building favicons with realfavicongenerator.net... ## Copying 'pkgdown/favicon/apple-touch-icon-120x120.png' to 'apple-touch-icon-120x120.png' ## Copying 'pkgdown/favicon/apple-touch-icon-152x152.png' to 'apple-touch-icon-152x152.png' ## Copying 'pkgdown/favicon/apple-touch-icon-180x180.png' to 'apple-touch-icon-180x180.png' ## Copying 'pkgdown/favicon/apple-touch-icon-60x60.png' to 'apple-touch-icon-60x60.png' ## Copying 'pkgdown/favicon/apple-touch-icon-76x76.png' to 'apple-touch-icon-76x76.png' ## Copying 'pkgdown/favicon/apple-touch-icon.png' to 'apple-touch-icon.png' ## Copying 'pkgdown/favicon/favicon-16x16.png' to 'favicon-16x16.png' ## Copying 'pkgdown/favicon/favicon-32x32.png' to 'favicon-32x32.png' ## -- Building home --------------------------------------------------------------- ## Reading 'LICENSE.md' ## Writing '404.html' ## -- Building function reference ------------------------------------------------- ## Reading 'man/add_chunk_label_hyperlinks.Rd' ## Reading 'man/add_function_hyperlinks.Rd' ## Reading 'man/add_hex_sticker.Rd' ## Reading 'man/add_pkgdown.Rd' ## Reading 'man/add_readme.Rd' ## Reading 'man/add_text_to_file.Rd' ## Reading 'man/add_vignettes.Rd' ## Reading 'man/check_unedited.Rd' ## Reading 'man/description_litr_hash_field_name.Rd' ## Reading 'man/description_litr_version_field_name.Rd' ## Reading 'man/do_not_edit_message.Rd' ## Reading 'man/document.Rd' ## Reading 'man/find_labels.Rd' ## Reading 'man/get_package_directory.Rd' ## Reading 'man/get_params_used.Rd' ## Reading 'man/hash_package_directory.Rd' ## Reading 'man/insert_hrefs.Rd' ## Reading 'man/litr-package.Rd' ## Reading 'man/litr_gitbook.Rd' ## Reading 'man/litr_html_document.Rd' ## Reading 'man/litr_pdf_document.Rd' ## Reading 'man/litrify_output_format.Rd' ## Reading 'man/load_all.Rd' ## Reading 'man/make_noticeable.Rd' ## Reading 'man/read_hash_from_description.Rd' ## Reading 'man/remove_rstudio_extras.Rd' ## Reading 'man/render.Rd' ## Reading 'man/replace_ansi_sequences.Rd' ## Reading 'man/restore_knitr_objects.Rd' ## Reading 'man/send_to_package.Rd' ## Reading 'man/setup.Rd' ## Reading 'man/test_litr.Rd' ## Reading 'man/with_cleanup.Rd' ## Reading 'man/write_hash_to_description.Rd' ## Reading 'man/write_version_to_description.Rd' ## -- Building articles ----------------------------------------------------------- ## Reading 'vignettes/basic-example.Rmd' ## Reading 'vignettes/faqs.Rmd' ## Reading 'vignettes/package-templates.Rmd' ## Reading 'vignettes/packages-in-the-wild.Rmd' ## Writing 'sitemap.xml' ## -- Building search index ------------------------------------------------------- ## == DONE ======================================================================== We follow this pkgdown vignette in our customizations. Here is the contents of the _pkgdown.yml that was used: cat(readLines("../source-files/_pkgdown.yml"), sep = '\\n') destination: ../docs/ url: ~ template: bootstrap: 5 bootswatch: cosmo repo: url: home: https://github.com/jacobbien/litr-project/tree/main/litr/ source: https://github.com/jacobbien/litr-project/tree/main/litr/ issue: https://github.com/jacobbien/litr-project/issues/ user: https://github.com/ authors: Jacob Bien: href: http://faculty.marshall.usc.edu/jacob-bien/ Patrick Vossler: href: https://www.patvoss.me/ navbar: structure: left: [reference, articles] right: [github] components: github: icon: fa-github href: https://github.com/jacobbien/litr-project/tree/main/litr/ reference: - title: Primary functions desc: > These are the functions you'll use the most. contents: - render - document - load_all - title: Functions for adding "extras" to your package desc: > These functions can help you add a README, vignettes, a pkgdown site, and a hex sticker to your package. contents: - add_readme - add_vignettes - add_pkgdown - add_hex_sticker - title: Custom output formats desc: > These are the functions for producing different output formats. contents: - litr_html_document - litr_pdf_document - litr_gitbook - litrify_output_format After this step, you can locally see the site by opening docs/index.html in the browser. You can then copy the docs directory to your website’s server and you’re done. "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]] diff --git a/create-litr/_book/tests.html b/create-litr/_book/tests.html index fe3b894..a66c163 100644 --- a/create-litr/_book/tests.html +++ b/create-litr/_book/tests.html @@ -209,26 +209,26 @@

    12 Defining some testsnext section. When we use rmarkdown::draft() to create a .Rmd file from template, the file it will give us will be an old version (namely the installed version of litr’s template) rather than the latest version.2 Furthermore, consider what happens when we call render() in the test below. This will start the knitting process on my-package.Rmd. However, inside my-package.Rmd, we have litr::setup() and litr::document(). When these are called in the knitting process, it will be the versions of the functions from the currently installed litr rather than the versions defined in this document.

    Once we are done testing the new version of the package, we’d like to restore the state of litr to what it was previously. If we don’t do this, then this can lead to inadvertent circularity in which the next time we call litr::render("create-litr.Rmd"), we are using the version currently under development, which is bad because ultimately we need this version to be rendered by the previous version of litr. The following function implements this approach to testing litr:

    -
    #' Run tests for `litr` itself
    -#' 
    -#' Special function for testing `litr`.  The trick is to temporarily install
    -#' the new version of `litr`, run the test, and then put things back how it was
    -#' before.
    -#' 
    -#' Typical values for `install_old` could be
    -#' - `function() devtools::install("[location of old version]")`
    -#' - `function() remotes::install_github("jacobbien/litr-project@*release", subdir = "litr")`.
    -#' 
    -#' @param install_old A function that when run will install the old version
    -#' @param location_of_new Path to the new package directory
    -#' @keywords internal
    -test_litr <- function(install_old, location_of_new) {
    -  devtools::unload(params$package_name)
    -  devtools::install(location_of_new)
    -  out <- devtools::test(location_of_new)
    -  install_old()
    -  return(out)
    -}
    +
    #' Run tests for `litr` itself
    +#' 
    +#' Special function for testing `litr`.  The trick is to temporarily install
    +#' the new version of `litr`, run the test, and then put things back how it was
    +#' before.
    +#' 
    +#' Typical values for `install_old` could be
    +#' - `function() devtools::install("[location of old version]")`
    +#' - `function() remotes::install_github("jacobbien/litr-project@*release", subdir = "litr")`.
    +#' 
    +#' @param install_old A function that when run will install the old version
    +#' @param location_of_new Path to the new package directory
    +#' @keywords internal
    +test_litr <- function(install_old, location_of_new) {
    +  devtools::unload(params$package_name)
    +  devtools::install(location_of_new)
    +  out <- devtools::test(location_of_new)
    +  install_old()
    +  return(out)
    +}

    Note: The call to devtools::unload() is to address an issue discussed here.

    12.1 Testing check_unedited()

    @@ -240,126 +240,126 @@

    12.1 Testing render(rmd_file) - package_path <- file.path(dir, "rhello") - testthat::expect_true(check_unedited(package_path)) - - # what if a file has been added? - added_file <- file.path(package_path, "R", "say_hello2.R") - writeLines("# Added something here.", added_file) - testthat::expect_false(check_unedited(package_path)) - - # what if we now remove it? - fs::file_delete(added_file) - testthat::expect_true(check_unedited(package_path)) - - # what if a file is removed from package? - rfile <- file.path(package_path, "R", "say_hello.R") - fs::file_move(rfile, dir) - testthat::expect_false(check_unedited(package_path)) - # now put it back - fs::file_move(file.path(dir, "say_hello.R"), file.path(package_path, "R")) - testthat::expect_true(check_unedited(package_path)) - - # what if something is changed in a file? - txt <- readLines(rfile) - txt_mod <- txt - txt_mod[3] <- paste0(txt[3], " # added a comment!!") - writeLines(txt_mod, rfile) - testthat::expect_false(check_unedited(package_path)) - # now put it back - writeLines(txt, rfile) - testthat::expect_true(check_unedited(package_path)) - - # what if something is changed in the DESCRIPTION file? - descfile <- file.path(package_path, "DESCRIPTION") - txt <- readLines(descfile) - txt_mod <- txt - txt_mod[1] <- "Package: newname" - writeLines(txt_mod, descfile) - testthat::expect_false(check_unedited(package_path)) - # now put it back - writeLines(txt, descfile) - testthat::expect_true(check_unedited(package_path)) - - # what if the special litr hash field is changed in the DESCRIPTION file? - txt <- readLines(descfile) - i_litr <- stringr::str_which(txt, description_litr_hash_field_name()) - txt_mod <- txt - txt_mod[i_litr] <- paste0(txt_mod[i_litr], "a") - writeLines(txt_mod, descfile) - testthat::expect_false(check_unedited(package_path)) - # now put it back - writeLines(txt, descfile) - testthat::expect_true(check_unedited(package_path)) - - fs::dir_delete(dir) -})

    +
    testthat::test_that("check_unedited works", {
    +  # Including this next line seems to be necessary for R CMD check on the cmd line:
    +  #Sys.setenv(RSTUDIO_PANDOC = "/Applications/RStudio.app/Contents/MacOS/pandoc")
    +  dir <- tempfile()
    +  fs::dir_create(dir)
    +  rmd_file <- file.path(dir, "my-package.Rmd")
    +  rmarkdown::draft(rmd_file,
    +                   template = "make-an-r-package",
    +                   package = "litr",
    +                   edit = FALSE)
    +  # create R package (named "rhello") from the Rmd template:
    +  render(rmd_file)
    +  package_path <- file.path(dir, "rhello")
    +  testthat::expect_true(check_unedited(package_path))
    +
    +  # what if a file has been added?
    +  added_file <- file.path(package_path, "R", "say_hello2.R")
    +  writeLines("# Added something here.", added_file)
    +  testthat::expect_false(check_unedited(package_path))
    +
    +  # what if we now remove it?
    +  fs::file_delete(added_file)
    +  testthat::expect_true(check_unedited(package_path))
    +
    +  # what if a file is removed from package?
    +  rfile <- file.path(package_path, "R", "say_hello.R")
    +  fs::file_move(rfile, dir)
    +  testthat::expect_false(check_unedited(package_path))
    +  # now put it back
    +  fs::file_move(file.path(dir, "say_hello.R"), file.path(package_path, "R"))
    +  testthat::expect_true(check_unedited(package_path))
    +
    +  # what if something is changed in a file?
    +  txt <- readLines(rfile)
    +  txt_mod <- txt
    +  txt_mod[3] <- paste0(txt[3], " # added a comment!!")
    +  writeLines(txt_mod, rfile)
    +  testthat::expect_false(check_unedited(package_path))
    +  # now put it back
    +  writeLines(txt, rfile)
    +  testthat::expect_true(check_unedited(package_path))
    +
    +  # what if something is changed in the DESCRIPTION file?
    +  descfile <- file.path(package_path, "DESCRIPTION")
    +  txt <- readLines(descfile)
    +  txt_mod <- txt
    +  txt_mod[1] <- "Package: newname"
    +  writeLines(txt_mod, descfile)
    +  testthat::expect_false(check_unedited(package_path))
    +  # now put it back
    +  writeLines(txt, descfile)
    +  testthat::expect_true(check_unedited(package_path))
    +
    +  # what if the special litr hash field is changed in the DESCRIPTION file?
    +  txt <- readLines(descfile)
    +  i_litr <- stringr::str_which(txt, description_litr_hash_field_name())
    +  txt_mod <- txt
    +  txt_mod[i_litr] <- paste0(txt_mod[i_litr], "a")
    +  writeLines(txt_mod, descfile)
    +  testthat::expect_false(check_unedited(package_path))
    +  # now put it back
    +  writeLines(txt, descfile)
    +  testthat::expect_true(check_unedited(package_path))
    +
    +  fs::dir_delete(dir)
    +})

    12.2 Testing get_params_used()

    Let’s now test the get_params_used() function, making sure it behaves how we expect it to:

    -
    testthat::test_that("get_params_used works", {
    -  dir <- tempfile()
    -  if (fs::file_exists(dir)) fs::file_delete(dir)
    -  fs::dir_create(dir)
    -  rmd_file <- file.path(dir, "my-package.Rmd")
    -  rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr",
    -                   edit = FALSE)
    -  default_params <- get_params_used(rmd_file, passed_params = list())
    -  testthat::expect_equal(
    -    default_params,
    -    rmarkdown::yaml_front_matter(rmd_file)$params
    -  )
    -  params1 <- default_params
    -  params1$package_parent_dir <- "dir"
    -  testthat::expect_equal(
    -    get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir")),
    -    params1
    -  )
    -  params2 <- default_params
    -  params2$package_name <- "pkg"
    -  params2$package_parent_dir <- "dir"
    -  testthat::expect_equal(
    -    get_params_used(rmd_file,
    -                    passed_params = list(package_parent_dir = "dir",
    -                                         package_name = "pkg")),
    -    params2
    -  )
    -  fs::dir_delete(dir)
    -})
    +
    testthat::test_that("get_params_used works", {
    +  dir <- tempfile()
    +  if (fs::file_exists(dir)) fs::file_delete(dir)
    +  fs::dir_create(dir)
    +  rmd_file <- file.path(dir, "my-package.Rmd")
    +  rmarkdown::draft(rmd_file, template = "make-an-r-package", package = "litr",
    +                   edit = FALSE)
    +  default_params <- get_params_used(rmd_file, passed_params = list())
    +  testthat::expect_equal(
    +    default_params,
    +    rmarkdown::yaml_front_matter(rmd_file)$params
    +  )
    +  params1 <- default_params
    +  params1$package_parent_dir <- "dir"
    +  testthat::expect_equal(
    +    get_params_used(rmd_file, passed_params = list(package_parent_dir = "dir")),
    +    params1
    +  )
    +  params2 <- default_params
    +  params2$package_name <- "pkg"
    +  params2$package_parent_dir <- "dir"
    +  testthat::expect_equal(
    +    get_params_used(rmd_file,
    +                    passed_params = list(package_parent_dir = "dir",
    +                                         package_name = "pkg")),
    +    params2
    +  )
    +  fs::dir_delete(dir)
    +})

    12.3 Testing chunk referencing

    Here we test the handling of chunk references (as implemented in the document output hook set within setup()). In particular, we use a .Rmd that uses chunk references in several different ways. Within the .Rmd itself, we have tests that ensure that the code can still be run as expected.

    -
    fs::file_copy(
    -  path = file.path(
    -    "..", "source-files", "test-example-files", "create-rknuth.Rmd"
    -    ), 
    -  new_path = file.path("tests", "testthat"), 
    -  overwrite = TRUE
    -)
    -
    testthat::test_that('Knuth-style references work', {
    -  dir <- tempfile()
    -  if (fs::file_exists(dir)) fs::file_delete(dir)
    -  fs::dir_create(dir)
    -  rmd_file <- file.path(dir, 'create-rknuth.Rmd')
    -  fs::file_copy(path = testthat::test_path("create-rknuth.Rmd"), new_path = rmd_file)
    -  render(rmd_file)
    -  testthat::expect_true(fs::file_exists(file.path(dir, 'create-rknuth.html')))
    -  fs::dir_delete(dir)
    -})
    +
    fs::file_copy(
    +  path = file.path(
    +    "..", "source-files", "test-example-files", "create-rknuth.Rmd"
    +    ), 
    +  new_path = file.path("tests", "testthat"), 
    +  overwrite = TRUE
    +)
    +
    testthat::test_that('Knuth-style references work', {
    +  dir <- tempfile()
    +  if (fs::file_exists(dir)) fs::file_delete(dir)
    +  fs::dir_create(dir)
    +  rmd_file <- file.path(dir, 'create-rknuth.Rmd')
    +  fs::file_copy(path = testthat::test_path("create-rknuth.Rmd"), new_path = rmd_file)
    +  render(rmd_file)
    +  testthat::expect_true(fs::file_exists(file.path(dir, 'create-rknuth.html')))
    +  fs::dir_delete(dir)
    +})

    12.4 Testing different ways of rendering

    @@ -370,216 +370,216 @@

    12.4 Testing different ways of re
  • Whether there is a litr output format being passed an argument to the render function.

  • In this section, we will test that one gets the same result regardless of how rendering was invoked.3 We will use variations on a base .Rmd file whose preamble is simply the following:

    -
    ---
    -title: 'A Test'
    -params:
    -  package_name: 'pkg' # <-- change this to your package name
    -  package_parent_dir: '.' # <-- relative to this file location
    ----
    -
    fs::file_copy(
    -  path = file.path(
    -    "..", "source-files", "test-example-files", "create-pkg.Rmd"
    -    ), 
    -  new_path = file.path("tests", "testthat"), 
    -  overwrite = TRUE
    -)
    +
    ---
    +title: 'A Test'
    +params:
    +  package_name: 'pkg' # <-- change this to your package name
    +  package_parent_dir: '.' # <-- relative to this file location
    +---
    +
    fs::file_copy(
    +  path = file.path(
    +    "..", "source-files", "test-example-files", "create-pkg.Rmd"
    +    ), 
    +  new_path = file.path("tests", "testthat"), 
    +  overwrite = TRUE
    +)

    There are 7 cases to consider (\(2^3-1\), since we exclude the case where rmarkdown::render() is called and no argument or preamble would indicate that this should be a litr-knit).

    -
    testthat::test_that('Rendering in all possible ways works', {
    -  
    -  # setup files for tests:
    -  dir <- tempfile()
    -  if (fs::file_exists(dir)) fs::file_delete(dir)
    -  fs::dir_create(dir)
    -  # .Rmd without output format in preamble
    -  rmd_file1 <- file.path(dir, 'create-pkg1.Rmd')
    -  fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file1)
    -  # .Rmd without output format in preamble
    -  rmd_file2 <- file.path(dir, 'create-pkg2.Rmd')
    -  fs::file_copy(rmd_file1, rmd_file2)
    -  litr:::add_text_to_file("output: litr::litr_html_document", rmd_file2, 3)
    -  # files names
    -  rmd_file <- file.path(dir, "create-pkg.Rmd")  
    -  html_file <- file.path(dir, "create-pkg.html")
    -  html_file_a <- file.path(dir, "a","create-pkg.html")
    -  pkg <- file.path(dir, "pkg")
    -  pkg_a <- file.path(dir, "a", "pkg")
    -  check_outputs_are_same <- function() {
    -    # html files should be the same:
    -    testthat::expect_equal(readLines(html_file_a), readLines(html_file))
    -    # packages should be the same (relying here on litr-hash in DESCRIPTION):
    -    testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")),
    -                           readLines(file.path(pkg_a, "DESCRIPTION")))
    -  }
    -
    -  ## Now test that all the cases give the same outputs:
    -  
    -  # Case 1: no preamble + litr::render()
    -  fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE)
    -  render(rmd_file, output_file = html_file)
    -  if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a"))
    -  fs::dir_create(file.path(dir, "a"))
    -  fs::dir_copy(pkg, pkg_a)
    -  fs::dir_delete(pkg)
    -  fs::file_move(html_file, html_file_a)
    -
    -  # Case 2: with preamble + litr::render()
    -  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
    -  render(rmd_file, output_file = html_file)
    -  check_outputs_are_same()
    -  
    -  # Case 3: no preamble + litr::render() with output format argument
    -  fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE)
    -  render(rmd_file, output_format = litr::litr_html_document(),
    -         output_file = html_file)
    -  check_outputs_are_same()
    -  
    -  # Case 4: with preamble + litr::render() with output format argument
    -  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
    -  render(rmd_file, output_format = litr::litr_html_document(),
    -         output_file = html_file)
    -  check_outputs_are_same()
    -
    -  # Case 5: with preamble + rmarkdown::render()
    -  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
    -  xfun::Rscript_call(rmarkdown::render,
    -                     list(input = rmd_file, output_file = html_file)
    -                     )
    -  check_outputs_are_same()
    -
    -  # Case 6: no preamble + rmarkdown::render() with output format argument
    -  fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE)
    -  xfun::Rscript_call(rmarkdown::render,
    -                     list(input = rmd_file,
    -                          output_format = litr::litr_html_document(),
    -                          output_file = html_file)
    -                     )
    -  check_outputs_are_same()
    -
    -  # Case 7: with preamble + rmarkdown::render() with output format argument
    -  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
    -  xfun::Rscript_call(rmarkdown::render,
    -                     list(input = rmd_file,
    -                          output_format = litr::litr_html_document(),
    -                          output_file = html_file)
    -                     )
    -  check_outputs_are_same()
    -  
    -  fs::dir_delete(dir)
    -})
    +
    testthat::test_that('Rendering in all possible ways works', {
    +  
    +  # setup files for tests:
    +  dir <- tempfile()
    +  if (fs::file_exists(dir)) fs::file_delete(dir)
    +  fs::dir_create(dir)
    +  # .Rmd without output format in preamble
    +  rmd_file1 <- file.path(dir, 'create-pkg1.Rmd')
    +  fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file1)
    +  # .Rmd without output format in preamble
    +  rmd_file2 <- file.path(dir, 'create-pkg2.Rmd')
    +  fs::file_copy(rmd_file1, rmd_file2)
    +  litr:::add_text_to_file("output: litr::litr_html_document", rmd_file2, 3)
    +  # files names
    +  rmd_file <- file.path(dir, "create-pkg.Rmd")  
    +  html_file <- file.path(dir, "create-pkg.html")
    +  html_file_a <- file.path(dir, "a","create-pkg.html")
    +  pkg <- file.path(dir, "pkg")
    +  pkg_a <- file.path(dir, "a", "pkg")
    +  check_outputs_are_same <- function() {
    +    # html files should be the same:
    +    testthat::expect_equal(readLines(html_file_a), readLines(html_file))
    +    # packages should be the same (relying here on litr-hash in DESCRIPTION):
    +    testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")),
    +                           readLines(file.path(pkg_a, "DESCRIPTION")))
    +  }
    +
    +  ## Now test that all the cases give the same outputs:
    +  
    +  # Case 1: no preamble + litr::render()
    +  fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE)
    +  render(rmd_file, output_file = html_file)
    +  if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a"))
    +  fs::dir_create(file.path(dir, "a"))
    +  fs::dir_copy(pkg, pkg_a)
    +  fs::dir_delete(pkg)
    +  fs::file_move(html_file, html_file_a)
    +
    +  # Case 2: with preamble + litr::render()
    +  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
    +  render(rmd_file, output_file = html_file)
    +  check_outputs_are_same()
    +  
    +  # Case 3: no preamble + litr::render() with output format argument
    +  fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE)
    +  render(rmd_file, output_format = litr::litr_html_document(),
    +         output_file = html_file)
    +  check_outputs_are_same()
    +  
    +  # Case 4: with preamble + litr::render() with output format argument
    +  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
    +  render(rmd_file, output_format = litr::litr_html_document(),
    +         output_file = html_file)
    +  check_outputs_are_same()
    +
    +  # Case 5: with preamble + rmarkdown::render()
    +  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
    +  xfun::Rscript_call(rmarkdown::render,
    +                     list(input = rmd_file, output_file = html_file)
    +                     )
    +  check_outputs_are_same()
    +
    +  # Case 6: no preamble + rmarkdown::render() with output format argument
    +  fs::file_copy(rmd_file1, rmd_file, overwrite = TRUE)
    +  xfun::Rscript_call(rmarkdown::render,
    +                     list(input = rmd_file,
    +                          output_format = litr::litr_html_document(),
    +                          output_file = html_file)
    +                     )
    +  check_outputs_are_same()
    +
    +  # Case 7: with preamble + rmarkdown::render() with output format argument
    +  fs::file_copy(rmd_file2, rmd_file, overwrite = TRUE)
    +  xfun::Rscript_call(rmarkdown::render,
    +                     list(input = rmd_file,
    +                          output_format = litr::litr_html_document(),
    +                          output_file = html_file)
    +                     )
    +  check_outputs_are_same()
    +  
    +  fs::dir_delete(dir)
    +})

    Let’s also make sure that we get the same R package output when using minimal_eval=TRUE as minimal_eval=TRUE.

    -
    testthat::test_that('Rendering with minimal_eval=TRUE works', {
    -  
    -  # setup files for tests:
    -  dir <- tempfile()
    -  if (fs::file_exists(dir)) fs::file_delete(dir)
    -  fs::dir_create(dir)
    -  rmd_file <- file.path(dir, 'create-pkg.Rmd')
    -  fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file)
    -  # .Rmd without output format in preamble
    -  html_file <- file.path(dir, "create-pkg.html")
    -  html_file_a <- file.path(dir, "a","create-pkg.html")
    -  pkg <- file.path(dir, "pkg")
    -  pkg_a <- file.path(dir, "a", "pkg")
    -
    -  ## Now test that all the cases give the same outputs:
    -  
    -  # Case 1: minimal_eval = FALSE
    -  render(rmd_file, output_file = html_file, minimal_eval = FALSE)
    -  if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a"))
    -  fs::dir_create(file.path(dir, "a"))
    -  fs::dir_copy(pkg, pkg_a)
    -  fs::dir_delete(pkg)
    -
    -  # Case 2: minimal_eval = TRUE passed to render
    -  render(rmd_file, output_file = html_file, minimal_eval = TRUE)
    -  testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")),
    -                         readLines(file.path(pkg_a, "DESCRIPTION")))
    -
    -  # Case 3: minimal_eval = TRUE passed to output format
    -  render(rmd_file,
    -         output_file = html_file,
    -         output_format = litr::litr_html_document(minimal_eval = TRUE)
    -         )
    -  testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")),
    -                         readLines(file.path(pkg_a, "DESCRIPTION")))
    -
    -  fs::dir_delete(dir)
    -})
    +
    testthat::test_that('Rendering with minimal_eval=TRUE works', {
    +  
    +  # setup files for tests:
    +  dir <- tempfile()
    +  if (fs::file_exists(dir)) fs::file_delete(dir)
    +  fs::dir_create(dir)
    +  rmd_file <- file.path(dir, 'create-pkg.Rmd')
    +  fs::file_copy(testthat::test_path("create-pkg.Rmd"), rmd_file)
    +  # .Rmd without output format in preamble
    +  html_file <- file.path(dir, "create-pkg.html")
    +  html_file_a <- file.path(dir, "a","create-pkg.html")
    +  pkg <- file.path(dir, "pkg")
    +  pkg_a <- file.path(dir, "a", "pkg")
    +
    +  ## Now test that all the cases give the same outputs:
    +  
    +  # Case 1: minimal_eval = FALSE
    +  render(rmd_file, output_file = html_file, minimal_eval = FALSE)
    +  if (fs::file_exists(file.path(dir, "a"))) fs::file_delete(file.path(dir, "a"))
    +  fs::dir_create(file.path(dir, "a"))
    +  fs::dir_copy(pkg, pkg_a)
    +  fs::dir_delete(pkg)
    +
    +  # Case 2: minimal_eval = TRUE passed to render
    +  render(rmd_file, output_file = html_file, minimal_eval = TRUE)
    +  testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")),
    +                         readLines(file.path(pkg_a, "DESCRIPTION")))
    +
    +  # Case 3: minimal_eval = TRUE passed to output format
    +  render(rmd_file,
    +         output_file = html_file,
    +         output_format = litr::litr_html_document(minimal_eval = TRUE)
    +         )
    +  testthat::expect_equal(readLines(file.path(pkg, "DESCRIPTION")),
    +                         readLines(file.path(pkg_a, "DESCRIPTION")))
    +
    +  fs::dir_delete(dir)
    +})

    12.5 Testing other templates

    Let’s now make sure that each template can be knit without error.

    -
    testthat::test_that("templates can be knit", {
    -  dir <- tempfile()
    -  if (fs::file_exists(dir)) fs::file_delete(dir)
    -  fs::dir_create(dir)
    -  
    -  rmd_file <- file.path(dir, "create-rhello.Rmd")
    -  rmarkdown::draft(rmd_file,
    -                   template = "make-an-r-package",
    -                   package = "litr",
    -                   edit = FALSE)
    -  render(rmd_file)
    -  testthat::expect_true(fs::file_exists(file.path(dir, "create-rhello.html")))
    -  testthat::expect_true(fs::file_exists(file.path(dir, "rhello")))
    -
    -  rmd_file <- file.path(dir, "create-rhasdata.Rmd")
    -  rmarkdown::draft(rmd_file,
    -                   template = "make-an-r-package-with-data",
    -                   package = "litr",
    -                   edit = FALSE)
    -  render(rmd_file)
    -  testthat::expect_true(fs::file_exists(file.path(dir, "create-rhasdata.html")))
    -  testthat::expect_true(fs::file_exists(file.path(dir, "rhasdata")))
    -
    -  rmd_file <- file.path(dir, "create-withrcpp.Rmd")
    -  rmarkdown::draft(rmd_file,
    -                   template = "make-an-r-package-with-rcpp",
    -                   package = "litr",
    -                   edit = FALSE)
    -  render(rmd_file)
    -  testthat::expect_true(fs::file_exists(file.path(dir, "create-withrcpp.html")))
    -  testthat::expect_true(fs::file_exists(file.path(dir, "withrcpp")))
    -
    -  rmd_file <- file.path(dir, "create-witharmadillo.Rmd")
    -  rmarkdown::draft(rmd_file,
    -                   template = "make-an-r-package-with-armadillo",
    -                   package = "litr",
    -                   edit = FALSE)
    -  render(rmd_file)
    -  testthat::expect_true(fs::file_exists(file.path(dir, "create-witharmadillo.Rmd")))
    -  testthat::expect_true(fs::file_exists(file.path(dir, "witharmadillo")))
    -    
    -  rmd_file <- file.path(dir, "create-withpkgdown.Rmd")
    -  rmarkdown::draft(rmd_file,
    -                   template = "make-an-r-package-with-extras",
    -                   package = "litr",
    -                   edit = FALSE)
    -  render(rmd_file)
    -  testthat::expect_true(fs::file_exists(file.path(dir, "create-withpkgdown.html")))
    -  testthat::expect_true(fs::file_exists(file.path(dir, "withpkgdown")))
    -
    -  rmd_file <- file.path(dir, "create-frombookdown.Rmd")
    -  rmarkdown::draft(rmd_file,
    -                   template = "make-an-r-package-from-bookdown",
    -                   package = "litr",
    -                   edit = FALSE)
    -  prev_dir <- getwd()
    -  setwd(file.path(dir, "create-frombookdown"))
    -  fs::file_delete("create-frombookdown.Rmd")
    -  render("index.Rmd")
    -  setwd(prev_dir)
    -  testthat::expect_true(
    -    fs::file_exists(file.path(dir, "create-frombookdown", "_book", "index.html"))
    -    )
    -  testthat::expect_true(
    -    fs::file_exists(file.path(dir, "create-frombookdown", "frombookdown"))
    -    )
    -
    -  fs::dir_delete(dir)
    - })
    +
    testthat::test_that("templates can be knit", {
    +  dir <- tempfile()
    +  if (fs::file_exists(dir)) fs::file_delete(dir)
    +  fs::dir_create(dir)
    +  
    +  rmd_file <- file.path(dir, "create-rhello.Rmd")
    +  rmarkdown::draft(rmd_file,
    +                   template = "make-an-r-package",
    +                   package = "litr",
    +                   edit = FALSE)
    +  render(rmd_file)
    +  testthat::expect_true(fs::file_exists(file.path(dir, "create-rhello.html")))
    +  testthat::expect_true(fs::file_exists(file.path(dir, "rhello")))
    +
    +  rmd_file <- file.path(dir, "create-rhasdata.Rmd")
    +  rmarkdown::draft(rmd_file,
    +                   template = "make-an-r-package-with-data",
    +                   package = "litr",
    +                   edit = FALSE)
    +  render(rmd_file)
    +  testthat::expect_true(fs::file_exists(file.path(dir, "create-rhasdata.html")))
    +  testthat::expect_true(fs::file_exists(file.path(dir, "rhasdata")))
    +
    +  rmd_file <- file.path(dir, "create-withrcpp.Rmd")
    +  rmarkdown::draft(rmd_file,
    +                   template = "make-an-r-package-with-rcpp",
    +                   package = "litr",
    +                   edit = FALSE)
    +  render(rmd_file)
    +  testthat::expect_true(fs::file_exists(file.path(dir, "create-withrcpp.html")))
    +  testthat::expect_true(fs::file_exists(file.path(dir, "withrcpp")))
    +
    +  rmd_file <- file.path(dir, "create-witharmadillo.Rmd")
    +  rmarkdown::draft(rmd_file,
    +                   template = "make-an-r-package-with-armadillo",
    +                   package = "litr",
    +                   edit = FALSE)
    +  render(rmd_file)
    +  testthat::expect_true(fs::file_exists(file.path(dir, "create-witharmadillo.Rmd")))
    +  testthat::expect_true(fs::file_exists(file.path(dir, "witharmadillo")))
    +    
    +  rmd_file <- file.path(dir, "create-withpkgdown.Rmd")
    +  rmarkdown::draft(rmd_file,
    +                   template = "make-an-r-package-with-extras",
    +                   package = "litr",
    +                   edit = FALSE)
    +  render(rmd_file)
    +  testthat::expect_true(fs::file_exists(file.path(dir, "create-withpkgdown.html")))
    +  testthat::expect_true(fs::file_exists(file.path(dir, "withpkgdown")))
    +
    +  rmd_file <- file.path(dir, "create-frombookdown.Rmd")
    +  rmarkdown::draft(rmd_file,
    +                   template = "make-an-r-package-from-bookdown",
    +                   package = "litr",
    +                   edit = FALSE)
    +  prev_dir <- getwd()
    +  setwd(file.path(dir, "create-frombookdown"))
    +  fs::file_delete("create-frombookdown.Rmd")
    +  render("index.Rmd")
    +  setwd(prev_dir)
    +  testthat::expect_true(
    +    fs::file_exists(file.path(dir, "create-frombookdown", "_book", "index.html"))
    +    )
    +  testthat::expect_true(
    +    fs::file_exists(file.path(dir, "create-frombookdown", "frombookdown"))
    +    )
    +
    +  fs::dir_delete(dir)
    + })

    Even though litr doesn’t directly use Rcpp, we’ll add it as a “Suggests” package since it would be required for running the above test.

    -
    usethis::use_package("Rcpp", type = "Suggests")
    +
    usethis::use_package("Rcpp", type = "Suggests")
    ## ✔ Adding 'Rcpp' to Suggests field in DESCRIPTION
     ## • Use `requireNamespace("Rcpp", quietly = TRUE)` to test if package is installed
     ## • Then directly refer to functions with `Rcpp::fun()`
    diff --git a/create-litr/index.Rmd b/create-litr/index.Rmd index bdbc025..48072a3 100644 --- a/create-litr/index.Rmd +++ b/create-litr/index.Rmd @@ -1290,7 +1290,6 @@ add_chunk_label_hyperlinks <- function(html_files, xml2::xml_add_parent(pre_parent , xml2::read_xml(stringr::str_glue('
    '))) xml2::xml_add_sibling(pre_parent, xml2::read_xml(stringr::str_glue('{chunk_names[j]}')), where="before") - # xml2::xml_add_parent(pre_parent, xml2::read_xml(stringr::str_glue('
    {chunk_names[j]}
    '))) xml2::xml_remove(span_node) # remove the extra line break that is left over from removing the span code_node <- xml2::xml_child(pre_parent) @@ -1299,8 +1298,8 @@ add_chunk_label_hyperlinks <- function(html_files, } } # last thing is to insert an additional style node in the head with our CSS so we have a standard style whether we are using bookdown or Rmarkdown - css_string <- "fieldset.chunkfield {border:4px solid black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em} - legend.chunklegend {padding:0;font-size:21px;width:auto;border:0; border-bottom: none; margin-bottom:0} + css_string <- "fieldset.chunkfield {border:1px dotted black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em} + legend.chunklegend {padding:0;width:auto;border:0; border-bottom: none; margin-bottom:0} " head_node <- xml2::xml_find_first(parsed_html, ".//head") xml2::xml_add_child(head_node, xml2::read_xml(stringr::str_glue(""))) diff --git a/docs/apple-touch-icon-120x120.png b/docs/apple-touch-icon-120x120.png index 4689f32ac061c5db08094b55ccaee9f7683759f8..68263385f630b2509227a72f66da391b50ac1fc6 100644 GIT binary patch delta 71 zcmZ4KveIQj6$h`dk%iK2<0l)NJ}HPA8iyEKTA3PHnVM-E7+4t?WcrlNoZP7>gDE2+ LcusZlF2#HRD0dYm delta 71 zcmZ4KveIQj6$gums!XyB*P4w@pA3TMah@lMo{VD`RsjBTH=q11kdqIp#xQ3=9maC9V-A jDTyViR=N2pnQ4^_Mg~SEx(0^27#i~9s$NZI)yxL~)G!&b diff --git a/docs/apple-touch-icon-180x180.png b/docs/apple-touch-icon-180x180.png index 55799f6b5f257d343ba401801a9d1328b4777b5d..19af9fb132c40eb30cfffa191367241eb5395295 100644 GIT binary patch delta 94 zcmaEs{49Aw6$h`dk!9!|=Aw;F>kZrtjYAAAtxOH9OwF|o46FIN8fB+n; delta 94 zcmaEs{49Aw6$gums$8#7cEiS|^#*PRCLu-!R>tO5#s=C3237_J?uPBs3=9maC9V-A jDTyViR=N2pnQ4^_Mg~SEx(0^27#hrS?3Yh2H_QhBIqn*} diff --git a/docs/apple-touch-icon-60x60.png b/docs/apple-touch-icon-60x60.png index d524f5bf3e3325da3a1592a53bd21b90345feecc..b83075840460547c24c793ab050a963df79b20f4 100644 GIT binary patch delta 71 zcmew$|3Q926$h`dk=fB?xyKuucJhfD8iyEKTA3PHnVM)D7+4t?{JWgKVR8V!45mzf L-+i9Rjr{olR4Nvz delta 71 zcmew$|3Q926$gums-$q)Kjn>0JNZNnOhSwdtc=aAj7+r+46FScF9lOhSwdtc=aAjLftR46F2K_@mgtv7HpG!8Mev@$iYGPTe)Ft9Q(uq@_1!oa|wTH+c} jl9E`GYL%Oxl9^V?U}RuqqHAENi=n|oZ)?Tma>IN8#b_M0 delta 94 zcmaEs{49Aw6$gums)A2(nfAt}^#*PRCLu-!R>tO5#zxu(237_J7uHQPVqjoUEpd$~ jNl7e8waU#;$xN$cFfuSQ(KRsC#n7IN8b}$@^ diff --git a/docs/favicon-16x16.png b/docs/favicon-16x16.png index 883c7d354262f0ecea366afce60c08dadacfbdec..a5f52502eb38617031c045906c7394138fae6d93 100644 GIT binary patch delta 72 zcmaFQ@t$Kt4-*HkurXuG(XO?dr!c88iW(Y+7+PAH8d@0`Y8x0>85nS%)b*Wwo>>M{ MrfSO6kCRyP0R-(7?*IS* delta 72 zcmaFQ@t$Kt4-*HAh^nfMWYdz(Q<&5kMGZ_sj0~)d&4EbUz`)ADplRC9sgut$%V5fU LKFz2(i6tKZ^zamp diff --git a/docs/favicon-32x32.png b/docs/favicon-32x32.png index f7a3efaae7832c1f8dbc8fddada04655a07ace28..9a482de772e7ac12349fcd245ed3a455b01ddb2f 100644 GIT binary patch delta 71 zcmdleuu)(_6$h`dG1K2Ox5YO${bCa}G!8Mev@$icGBDCMFt7q5Me_@jC$h_6%6QlG K=S)7to(}*VWfeXE delta 71 zcmdleuu)(_6$gums@hV4%W@l=ezA!fn1mP^SQ(pJ8Cz%@7+4t?l-xU6GkGGr45kcg LbK!x>huHH02S*ic diff --git a/docs/index.html b/docs/index.html index 501af2a..d08b0ba 100644 --- a/docs/index.html +++ b/docs/index.html @@ -88,9 +88,8 @@

    Overview

    The litr R package lets you write a complete R package in a single R markdown document. This enables a workflow for writing R packages that is probably very different from what you are used to.

    -
    -

    With litr, knitting creates an R package in addition to the .html file.

    -
    +
    With litr, knitting creates an R package in addition to the .html file.

    Why write R packages in this way? diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 0bb315f..af187ae 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -1,4 +1,4 @@ -pandoc: 2.19.2 +pandoc: 3.1.1 pkgdown: 2.0.7 pkgdown_sha: ~ articles: @@ -6,5 +6,5 @@ articles: faqs: faqs.html package-templates: package-templates.html packages-in-the-wild: packages-in-the-wild.html -last_built: 2024-01-04T20:37Z +last_built: 2024-01-13T19:51Z diff --git a/examples/make-an-r-package-from-bookdown/_book/404.html b/examples/make-an-r-package-from-bookdown/_book/404.html index 01cab21..6e39398 100644 --- a/examples/make-an-r-package-from-bookdown/_book/404.html +++ b/examples/make-an-r-package-from-bookdown/_book/404.html @@ -23,7 +23,7 @@ - + diff --git a/examples/make-an-r-package-from-bookdown/_book/conclude.html b/examples/make-an-r-package-from-bookdown/_book/conclude.html index 2758067..2b973b7 100644 --- a/examples/make-an-r-package-from-bookdown/_book/conclude.html +++ b/examples/make-an-r-package-from-bookdown/_book/conclude.html @@ -12,7 +12,7 @@ - + @@ -94,8 +94,8 @@ div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} - @@ -130,7 +130,7 @@

    5 Conclusion

    When you are done defining the package, it remains to convert the Roxygen to documentation.

    -
    litr::document() # <-- use instead of devtools::document()
    +
    litr::document() # <-- use instead of devtools::document()
    ## ℹ Updating frombookdown documentation
     ## ℹ Loading frombookdown
     ## Writing 'NAMESPACE'
    diff --git a/examples/make-an-r-package-from-bookdown/_book/generalization-to-other-greetings.html b/examples/make-an-r-package-from-bookdown/_book/generalization-to-other-greetings.html
    index 17c208f..4111b4d 100644
    --- a/examples/make-an-r-package-from-bookdown/_book/generalization-to-other-greetings.html
    +++ b/examples/make-an-r-package-from-bookdown/_book/generalization-to-other-greetings.html
    @@ -12,7 +12,7 @@
     
     
     
    -
    +
     
     
     
    @@ -95,8 +95,8 @@
       
       div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
     
    -
     
     
    diff --git a/examples/make-an-r-package-from-bookdown/_book/index.html b/examples/make-an-r-package-from-bookdown/_book/index.html
    index 95a6a65..b1e6bc8 100644
    --- a/examples/make-an-r-package-from-bookdown/_book/index.html
    +++ b/examples/make-an-r-package-from-bookdown/_book/index.html
    @@ -12,7 +12,7 @@
     
     
     
    -
    +
     
     
     
    @@ -94,8 +94,8 @@
       
       div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
     
    -
     
     
    @@ -128,7 +128,7 @@ 

    @@ -136,7 +136,7 @@

    You can introduce what your R package does here.

    Note: If you are working in RStudio, you can simply press “Knit” to render this bookdown (and open _book/index.html to see the result). More generally, in a console you can run the following:

    -
    litr::render("index.Rmd", output_format = litr::litr_gitbook())
    +
    litr::render("index.Rmd", output_format = litr::litr_gitbook())
    diff --git a/examples/make-an-r-package-from-bookdown/_book/package-setup.html b/examples/make-an-r-package-from-bookdown/_book/package-setup.html index 59e30cd..53b7b0c 100644 --- a/examples/make-an-r-package-from-bookdown/_book/package-setup.html +++ b/examples/make-an-r-package-from-bookdown/_book/package-setup.html @@ -12,7 +12,7 @@ - + @@ -95,8 +95,8 @@ div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} - @@ -131,22 +131,22 @@

    2 Package setup

    Every R package needs a DESCRIPTION file, so we start by specifying this information:

    -
    usethis::create_package(
    -  path = ".",
    -  fields = list(
    -    Package = params$package_name,
    -    Version = "0.0.0.9000",
    -    Title = "A Package Defined in a Bookdown",
    -    Description = "This package uses litr to define an R package through writing a bookdown.",
    -    `Authors@R` = person(
    -      given = "First",
    -      family = "Last",
    -      email = "you@gmail.com",
    -      role = c("aut", "cre")
    -      )
    -  )
    -)
    -usethis::use_mit_license(copyright_holder = "F. Last")
    +
    usethis::create_package(
    +  path = ".",
    +  fields = list(
    +    Package = params$package_name,
    +    Version = "0.0.0.9000",
    +    Title = "A Package Defined in a Bookdown",
    +    Description = "This package uses litr to define an R package through writing a bookdown.",
    +    `Authors@R` = person(
    +      given = "First",
    +      family = "Last",
    +      email = "you@gmail.com",
    +      role = c("aut", "cre")
    +      )
    +  )
    +)
    +usethis::use_mit_license(copyright_holder = "F. Last")

    Although it’s not required, it can be nice to add some package-level documentation. This is what will show up when someone types package?<your-package-name> in the console.

    #' A Package Defined in a Bookdown
     #'
    diff --git a/examples/make-an-r-package-from-bookdown/_book/search_index.json b/examples/make-an-r-package-from-bookdown/_book/search_index.json
    index abb946d..00d4a4e 100644
    --- a/examples/make-an-r-package-from-bookdown/_book/search_index.json
    +++ b/examples/make-an-r-package-from-bookdown/_book/search_index.json
    @@ -1 +1 @@
    -[["index.html", "Creating the frombookdown R package 1 Introduction", " Creating the frombookdown R package Jacob Bien 2024-01-04 1 Introduction You can introduce what your R package does here. Note: If you are working in RStudio, you can simply press “Knit” to render this bookdown (and open _book/index.html to see the result). More generally, in a console you can run the following: litr::render("index.Rmd", output_format = litr::litr_gitbook()) "],["package-setup.html", "2 Package setup", " 2 Package setup Every R package needs a DESCRIPTION file, so we start by specifying this information: usethis::create_package( path = ".", fields = list( Package = params$package_name, Version = "0.0.0.9000", Title = "A Package Defined in a Bookdown", Description = "This package uses litr to define an R package through writing a bookdown.", `Authors@R` = person( given = "First", family = "Last", email = "you@gmail.com", role = c("aut", "cre") ) ) ) usethis::use_mit_license(copyright_holder = "F. Last") Although it’s not required, it can be nice to add some package-level documentation. This is what will show up when someone types package?<your-package-name> in the console. #' A Package Defined in a Bookdown #' #' This package uses `litr` to define an R package through writing a `bookdown`. #' #' @docType package "],["the-basics-of-saying-hello.html", "3 The basics of saying hello", " 3 The basics of saying hello Let’s define a function for our R package: #' Say hello to someone #' #' @param name Name of a person #' @param exclamation Whether to include an exclamation mark #' @export say_hello <- function(name, exclamation = TRUE) { paste0("Hello ", name, ifelse(exclamation, "!", ".")) } Code chunks whose first line starts with #' are added to the package. We can try running it. say_hello("Jacob") ## [1] "Hello Jacob!" That code chunk does not start with #', so it is not added to the package. Let’s write some tests to make sure the function behaves as desired: testthat::test_that("say_hello works", { testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!") testthat::expect_equal(say_hello("Jacob", exclamation = FALSE), "Hello Jacob.") }) ## Test passed Code chunks that have one or more lines starting with test_that( (or testthat::test_that() are added to the package as tests. "],["generalization-to-other-greetings.html", "4 Generalization to other greetings", " 4 Generalization to other greetings We can now get fancier and define more functions across as many .Rmd files as we like. Hyperlinks work across different .Rmd files. For example, you can click on say_hello() and it will bring you to the definition in the previous chapter. "],["conclude.html", "5 Conclusion", " 5 Conclusion When you are done defining the package, it remains to convert the Roxygen to documentation. litr::document() # <-- use instead of devtools::document() ## ℹ Updating frombookdown documentation ## ℹ Loading frombookdown ## Writing 'NAMESPACE' ## Writing 'frombookdown-package.Rd' ## Writing 'say_hello.Rd' You can also add some extra things to your package here if you like, such as a README, some vignettes, a pkgdown site, etc. See here for an example of how to do this with litr. "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]]
    +[["index.html", "Creating the frombookdown R package 1 Introduction", " Creating the frombookdown R package Jacob Bien 2024-01-13 1 Introduction You can introduce what your R package does here. Note: If you are working in RStudio, you can simply press “Knit” to render this bookdown (and open _book/index.html to see the result). More generally, in a console you can run the following: litr::render("index.Rmd", output_format = litr::litr_gitbook()) "],["package-setup.html", "2 Package setup", " 2 Package setup Every R package needs a DESCRIPTION file, so we start by specifying this information: usethis::create_package( path = ".", fields = list( Package = params$package_name, Version = "0.0.0.9000", Title = "A Package Defined in a Bookdown", Description = "This package uses litr to define an R package through writing a bookdown.", `Authors@R` = person( given = "First", family = "Last", email = "you@gmail.com", role = c("aut", "cre") ) ) ) usethis::use_mit_license(copyright_holder = "F. Last") Although it’s not required, it can be nice to add some package-level documentation. This is what will show up when someone types package?<your-package-name> in the console. #' A Package Defined in a Bookdown #' #' This package uses `litr` to define an R package through writing a `bookdown`. #' #' @docType package "],["the-basics-of-saying-hello.html", "3 The basics of saying hello", " 3 The basics of saying hello Let’s define a function for our R package: #' Say hello to someone #' #' @param name Name of a person #' @param exclamation Whether to include an exclamation mark #' @export say_hello <- function(name, exclamation = TRUE) { paste0("Hello ", name, ifelse(exclamation, "!", ".")) } Code chunks whose first line starts with #' are added to the package. We can try running it. say_hello("Jacob") ## [1] "Hello Jacob!" That code chunk does not start with #', so it is not added to the package. Let’s write some tests to make sure the function behaves as desired: testthat::test_that("say_hello works", { testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!") testthat::expect_equal(say_hello("Jacob", exclamation = FALSE), "Hello Jacob.") }) ## Test passed Code chunks that have one or more lines starting with test_that( (or testthat::test_that() are added to the package as tests. "],["generalization-to-other-greetings.html", "4 Generalization to other greetings", " 4 Generalization to other greetings We can now get fancier and define more functions across as many .Rmd files as we like. Hyperlinks work across different .Rmd files. For example, you can click on say_hello() and it will bring you to the definition in the previous chapter. "],["conclude.html", "5 Conclusion", " 5 Conclusion When you are done defining the package, it remains to convert the Roxygen to documentation. litr::document() # <-- use instead of devtools::document() ## ℹ Updating frombookdown documentation ## ℹ Loading frombookdown ## Writing 'NAMESPACE' ## Writing 'frombookdown-package.Rd' ## Writing 'say_hello.Rd' You can also add some extra things to your package here if you like, such as a README, some vignettes, a pkgdown site, etc. See here for an example of how to do this with litr. "],["404.html", "Page not found", " Page not found The page you requested cannot be found (perhaps it was moved or renamed). You may want to try searching to find the page's new location, or use the table of contents to find the page you are looking for. "]]
    diff --git a/examples/make-an-r-package-from-bookdown/_book/the-basics-of-saying-hello.html b/examples/make-an-r-package-from-bookdown/_book/the-basics-of-saying-hello.html
    index 951f0c9..e184788 100644
    --- a/examples/make-an-r-package-from-bookdown/_book/the-basics-of-saying-hello.html
    +++ b/examples/make-an-r-package-from-bookdown/_book/the-basics-of-saying-hello.html
    @@ -12,7 +12,7 @@
     
     
     
    -
    +
     
     
     
    @@ -95,8 +95,8 @@
       
       div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
     
    -
     
     
    @@ -131,24 +131,24 @@ 

    3 The basics of saying hello

    Let’s define a function for our R package:

    -
    #' Say hello to someone
    -#' 
    -#' @param name Name of a person
    -#' @param exclamation Whether to include an exclamation mark
    -#' @export 
    -say_hello <- function(name, exclamation = TRUE) {
    -  paste0("Hello ", name, ifelse(exclamation, "!", "."))
    -}
    +
    #' Say hello to someone
    +#' 
    +#' @param name Name of a person
    +#' @param exclamation Whether to include an exclamation mark
    +#' @export 
    +say_hello <- function(name, exclamation = TRUE) {
    +  paste0("Hello ", name, ifelse(exclamation, "!", "."))
    +}

    Code chunks whose first line starts with #' are added to the package.

    We can try running it.

    -
    say_hello("Jacob")
    +
    say_hello("Jacob")
    ## [1] "Hello Jacob!"

    That code chunk does not start with #', so it is not added to the package.

    Let’s write some tests to make sure the function behaves as desired:

    -
    testthat::test_that("say_hello works", {
    -  testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!")
    -  testthat::expect_equal(say_hello("Jacob", exclamation = FALSE), "Hello Jacob.")
    -})
    +
    testthat::test_that("say_hello works", {
    +  testthat::expect_equal(say_hello("Jacob"), "Hello Jacob!")
    +  testthat::expect_equal(say_hello("Jacob", exclamation = FALSE), "Hello Jacob.")
    +})
    ## Test passed

    Code chunks that have one or more lines starting with test_that( (or testthat::test_that() are added to the package as tests.

    diff --git a/examples/make-an-r-package-from-bookdown/frombookdown/DESCRIPTION b/examples/make-an-r-package-from-bookdown/frombookdown/DESCRIPTION index f4a5999..1e28351 100644 --- a/examples/make-an-r-package-from-bookdown/frombookdown/DESCRIPTION +++ b/examples/make-an-r-package-from-bookdown/frombookdown/DESCRIPTION @@ -8,9 +8,9 @@ Description: This package uses litr to define an R package through writing License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.0 Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 LitrVersionUsed: 0.9.1 -LitrId: 610d416503c8b51447c9212e7b832c38 +LitrId: f0407b5f1dad14ffece192fce52a22c8 diff --git a/examples/make-an-r-package-with-armadillo/create-witharmadillo.html b/examples/make-an-r-package-with-armadillo/create-witharmadillo.html index 58722a7..f0bce49 100644 --- a/examples/make-an-r-package-with-armadillo/create-witharmadillo.html +++ b/examples/make-an-r-package-with-armadillo/create-witharmadillo.html @@ -298,8 +298,8 @@ display: none; } - @@ -404,11 +404,11 @@

    Documenting the package and building

    this file.

    litr::document() # <-- use instead of devtools::document()
    ## ℹ Updating witharmadillo documentation
    +## Writing 'NAMESPACE'
     ## ℹ Loading witharmadillo
     ## ℹ Re-compiling witharmadillo (debug build)
     ## 
     ## Writing 'NAMESPACE'
    -## Writing 'NAMESPACE'
     ## Writing 'my_chol.Rd'
     ## Writing 'witharmadillo-package.Rd'
    diff --git a/examples/make-an-r-package-with-armadillo/witharmadillo/DESCRIPTION b/examples/make-an-r-package-with-armadillo/witharmadillo/DESCRIPTION index eee276f..359c5dc 100644 --- a/examples/make-an-r-package-with-armadillo/witharmadillo/DESCRIPTION +++ b/examples/make-an-r-package-with-armadillo/witharmadillo/DESCRIPTION @@ -7,7 +7,7 @@ Description: This package uses RcppArmadillo. License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.0 LinkingTo: Rcpp, RcppArmadillo @@ -17,4 +17,4 @@ Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 LitrVersionUsed: 0.9.1 -LitrId: 696856b062e814802490e96a2dcbe966 +LitrId: 42e05105cbf02fe7810f26086195d6f1 diff --git a/examples/make-an-r-package-with-armadillo/witharmadillo/R/RcppExports.R b/examples/make-an-r-package-with-armadillo/witharmadillo/R/RcppExports.R index 466c8d4..7713e1e 100644 --- a/examples/make-an-r-package-with-armadillo/witharmadillo/R/RcppExports.R +++ b/examples/make-an-r-package-with-armadillo/witharmadillo/R/RcppExports.R @@ -6,6 +6,6 @@ #' @param X A positive definite matrix #' @export my_chol <- function(X) { - .Call('_witharmadillo_my_chol', PACKAGE = 'witharmadillo', X) + .Call(`_witharmadillo_my_chol`, X) } diff --git a/examples/make-an-r-package-with-data/create-rhasdata.html b/examples/make-an-r-package-with-data/create-rhasdata.html index 97efd4c..01c84da 100644 --- a/examples/make-an-r-package-with-data/create-rhasdata.html +++ b/examples/make-an-r-package-with-data/create-rhasdata.html @@ -298,8 +298,8 @@ display: none; } - diff --git a/examples/make-an-r-package-with-data/rhasdata/DESCRIPTION b/examples/make-an-r-package-with-data/rhasdata/DESCRIPTION index 1dbb7b6..e9aaea6 100644 --- a/examples/make-an-r-package-with-data/rhasdata/DESCRIPTION +++ b/examples/make-an-r-package-with-data/rhasdata/DESCRIPTION @@ -8,9 +8,9 @@ Description: This package has a dataset. It could also have some License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.0 Depends: R (>= 2.10) LazyData: true LitrVersionUsed: 0.9.1 -LitrId: fb3a1a452a2b78378d71b674486d6673 +LitrId: 0e69da7455711b247439efbde3647659 diff --git a/examples/make-an-r-package-with-data/rhasdata/data/mydata.rda b/examples/make-an-r-package-with-data/rhasdata/data/mydata.rda index 32ac6c2f1c0e01de16ce5bb2f6c8a4cf0be708b5..f689c7048230dbff211325cff03a27e58b44a5c3 100644 GIT binary patch delta 2009 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 - @@ -445,18 +445,18 @@

    Add a pkgdown site

    ## knitr ## Encoding: UTF-8 ## Roxygen: list(markdown = TRUE) -## RoxygenNote: 7.2.3

    +## RoxygenNote: 7.3.0

    Be sure that this next command appears after litr::document() has been called in this file.

    litr::add_pkgdown("../source-files/_pkgdown.yml")
    ## ✔ Adding '^_pkgdown\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore'
    ## -- Installing package into temporary library -----------------------------------
     ## == Building pkgdown site =======================================================
    -## Reading from: '/Users/vossler/litr-project/examples/withpkgdown'
    -## Writing to:   '/Users/vossler/litr-project/examples/docs'
    +## Reading from: '/Users/jacobbien/Documents/GitHub/litr-project/examples/withpkgdown'
    +## Writing to:   '/Users/jacobbien/Documents/GitHub/litr-project/examples/docs'
     ## -- Initialising site -----------------------------------------------------------
    -## Copying '../../../../../Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library/pkgdown/BS5/assets/link.svg' to 'link.svg'
    -## Copying '../../../../../Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library/pkgdown/BS5/assets/pkgdown.js' to 'pkgdown.js'
    +## Copying '../../../../../Library/R/arm64/4.3/library/pkgdown/BS5/assets/link.svg' to 'link.svg'
    +## Copying '../../../../../Library/R/arm64/4.3/library/pkgdown/BS5/assets/pkgdown.js' to 'pkgdown.js'
     ## -- Building favicons -----------------------------------------------------------
     ## Building favicons with realfavicongenerator.net...
     ## Copying 'pkgdown/favicon/apple-touch-icon-120x120.png' to 'apple-touch-icon-120x120.png'
    diff --git a/examples/make-an-r-package-with-extras/docs/apple-touch-icon-120x120.png b/examples/make-an-r-package-with-extras/docs/apple-touch-icon-120x120.png
    index 75434770d5a97357f0b166b9ad1caf5c52d9af42..d42c784691ea7678d49c378f69da2cb405e1f6ca 100644
    GIT binary patch
    delta 71
    zcmZ4KveIQj6$h`dk$5K;-`=t7bj`i0&KL
    
    diff --git a/examples/make-an-r-package-with-extras/docs/apple-touch-icon-180x180.png b/examples/make-an-r-package-with-extras/docs/apple-touch-icon-180x180.png
    index abbdc98eee29abab596b890f0f0668d3643a79d1..0a6b54a3c08d01e42a483e55f89dae4e00bea487 100644
    GIT binary patch
    delta 94
    zcmaEs{49Aw6$h`dk%Z({VV#Xl>kZrtjYAAAtxOH9j7+o*46Fm3y3
    
    delta 71
    zcmew$|3Q926$gumirB16mp*T7+Q}zsU=m_vU}bD(Wn`pnU|?ln@cG!^Ns|NkWiVx$
    MX1nT5ZsgAg0Ew^{Pyhe`
    
    diff --git a/examples/make-an-r-package-with-extras/docs/apple-touch-icon-76x76.png b/examples/make-an-r-package-with-extras/docs/apple-touch-icon-76x76.png
    index 784c01e1e3854aaca9b24a332574a355ede305c3..e89146de82a1d58c624e4bb9806920e38aca4924 100644
    GIT binary patch
    delta 71
    zcmdn3v0Gz86$h`dk=Q?8JME24EW)CO#vz85R;C74Mn>8O237_J(~m_@o;+Jv22kZrtOhSwdtc=a9j4ZSb46FIN8lh+(q
    
    diff --git a/examples/make-an-r-package-with-extras/docs/articles/using-package.html b/examples/make-an-r-package-with-extras/docs/articles/using-package.html
    index dc98142..72f1b54 100644
    --- a/examples/make-an-r-package-with-extras/docs/articles/using-package.html
    +++ b/examples/make-an-r-package-with-extras/docs/articles/using-package.html
    @@ -78,7 +78,7 @@
           

    Using withpkgdown Package

    Your Name

    -

    2024-01-04

    +

    2024-01-13

    Source: vignettes/using-package.Rmd
    using-package.Rmd
    diff --git a/examples/make-an-r-package-with-extras/docs/favicon-16x16.png b/examples/make-an-r-package-with-extras/docs/favicon-16x16.png index 9e9f82753dc8bd12ff4911605a6c60965822cd22..1343523a6547271e7a01afe3080797ea7f49d1b4 100644 GIT binary patch delta 72 zcmaFQ@t$Kt4-*Hku#t@1jq9eHr!c88iW(Y+7+PAH8dw=wXd4(<85kIEW4Sx|JhKd@ MjMM8qiIZ6J0R=S_?EnA( delta 72 zcmaFQ@t$Kt4-*HAh>FtgG&Q%)Q<&5kMGZ_sj0~)d&8&<~v<(cb3=CrSoG_bwo>>M{ MM)zSs*CdvF0PZXlWB>pF diff --git a/examples/make-an-r-package-with-extras/docs/favicon-32x32.png b/examples/make-an-r-package-with-extras/docs/favicon-32x32.png index d6adaf552543c8c18f512b9f518e0ed1f63de70c..56f875fce4148d6751cedfae2b63ca0fd2961cee 100644 GIT binary patch delta 71 zcmdleuu)(_6$h`dk!=t7bj`i0&KL diff --git a/examples/make-an-r-package-with-extras/withpkgdown/pkgdown/favicon/apple-touch-icon-180x180.png b/examples/make-an-r-package-with-extras/withpkgdown/pkgdown/favicon/apple-touch-icon-180x180.png index abbdc98eee29abab596b890f0f0668d3643a79d1..0a6b54a3c08d01e42a483e55f89dae4e00bea487 100644 GIT binary patch delta 94 zcmaEs{49Aw6$h`dk%Z({VV#Xl>kZrtjYAAAtxOH9j7+o*46Fm3y3 delta 71 zcmew$|3Q926$gumirB16mp*T7+Q}zsU=m_vU}bD(Wn`pnU|?ln@cG!^Ns|NkWiVx$ MX1nT5ZsgAg0Ew^{Pyhe` diff --git a/examples/make-an-r-package-with-extras/withpkgdown/pkgdown/favicon/apple-touch-icon-76x76.png b/examples/make-an-r-package-with-extras/withpkgdown/pkgdown/favicon/apple-touch-icon-76x76.png index 784c01e1e3854aaca9b24a332574a355ede305c3..e89146de82a1d58c624e4bb9806920e38aca4924 100644 GIT binary patch delta 71 zcmdn3v0Gz86$h`dk=Q?8JME24EW)CO#vz85R;C74Mn>8O237_J(~m_@o;+Jv22kZrtOhSwdtc=a9j4ZSb46FIN8lh+(q diff --git a/examples/make-an-r-package-with-extras/withpkgdown/pkgdown/favicon/favicon-16x16.png b/examples/make-an-r-package-with-extras/withpkgdown/pkgdown/favicon/favicon-16x16.png index 9e9f82753dc8bd12ff4911605a6c60965822cd22..1343523a6547271e7a01afe3080797ea7f49d1b4 100644 GIT binary patch delta 72 zcmaFQ@t$Kt4-*Hku#t@1jq9eHr!c88iW(Y+7+PAH8dw=wXd4(<85kIEW4Sx|JhKd@ MjMM8qiIZ6J0R=S_?EnA( delta 72 zcmaFQ@t$Kt4-*HAh>FtgG&Q%)Q<&5kMGZ_sj0~)d&8&<~v<(cb3=CrSoG_bwo>>M{ MM)zSs*CdvF0PZXlWB>pF diff --git a/examples/make-an-r-package-with-extras/withpkgdown/pkgdown/favicon/favicon-32x32.png b/examples/make-an-r-package-with-extras/withpkgdown/pkgdown/favicon/favicon-32x32.png index d6adaf552543c8c18f512b9f518e0ed1f63de70c..56f875fce4148d6751cedfae2b63ca0fd2961cee 100644 GIT binary patch delta 71 zcmdleuu)(_6$h`dk! - @@ -416,25 +416,28 @@

    Documenting the package and building

    # devtools::install() # devtools::check(document = FALSE)
    ## ℹ Updating withrcpp documentation
    +## Writing 'NAMESPACE'
     ## ℹ Loading withrcpp
    -
    ## Exports from /Users/vossler/litr-project/examples/withrcpp/src/code.cpp:
    +
    ## Exports from /Users/jacobbien/Documents/GitHub/litr-project/examples/withrcpp/src/code.cpp:
     ##    NumericVector alternate_signs(NumericVector v)
     ## 
    -## /Users/vossler/litr-project/examples/withrcpp/src/RcppExports.cpp updated.
    -## /Users/vossler/litr-project/examples/withrcpp/R/RcppExports.R updated.
    +## /Users/jacobbien/Documents/GitHub/litr-project/examples/withrcpp/src/RcppExports.cpp updated. +## /Users/jacobbien/Documents/GitHub/litr-project/examples/withrcpp/R/RcppExports.R updated.
    ## ℹ Re-compiling withrcpp (debug build)
    ## ── R CMD INSTALL ───────────────────────────────────────────────────────────────
     ## * installing *source* package ‘withrcpp’ ...
     ## ** using staged installation
     ## ** libs
    -## clang++ -arch arm64 -std=gnu++14 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I'/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library/Rcpp/include' -I/opt/R/arm64/include   -fPIC  -falign-functions=64 -Wall -g -O2  -DSTAN_THREADS -pthread -UNDEBUG -Wall -pedantic -g -O0 -c RcppExports.cpp -o RcppExports.o
    -## clang++ -arch arm64 -std=gnu++14 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I'/Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library/Rcpp/include' -I/opt/R/arm64/include   -fPIC  -falign-functions=64 -Wall -g -O2  -DSTAN_THREADS -pthread -UNDEBUG -Wall -pedantic -g -O0 -c code.cpp -o code.o
    -## clang++ -arch arm64 -std=gnu++14 -dynamiclib -Wl,-headerpad_max_install_names -undefined dynamic_lookup -single_module -multiply_defined suppress -L/Library/Frameworks/R.framework/Resources/lib -L/opt/R/arm64/lib -o withrcpp.so RcppExports.o code.o -F/Library/Frameworks/R.framework/.. -framework R -Wl,-framework -Wl,CoreFoundation
    -## installing to /private/var/folders/x8/5qdk28wd0lj649bpd3k64c9w0000gp/T/Rtmp4mM0RJ/devtools_install_16c676c1fc8f/00LOCK-withrcpp/00new/withrcpp/libs
    +## using C++ compiler: ‘Apple clang version 14.0.0 (clang-1400.0.29.202)’
    +## using SDK: ‘’
    +## clang++ -arch arm64 -std=gnu++17 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I'/Users/jacobbien/Library/R/arm64/4.3/library/Rcpp/include' -I/opt/R/arm64/include    -fPIC  -falign-functions=64 -Wall -g -O2  -UNDEBUG -Wall -pedantic -g -O0 -c RcppExports.cpp -o RcppExports.o
    +## clang++ -arch arm64 -std=gnu++17 -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG  -I'/Users/jacobbien/Library/R/arm64/4.3/library/Rcpp/include' -I/opt/R/arm64/include    -fPIC  -falign-functions=64 -Wall -g -O2  -UNDEBUG -Wall -pedantic -g -O0 -c code.cpp -o code.o
    +## clang++ -arch arm64 -std=gnu++17 -dynamiclib -Wl,-headerpad_max_install_names -undefined dynamic_lookup -single_module -multiply_defined suppress -L/Library/Frameworks/R.framework/Resources/lib -L/opt/R/arm64/lib -o withrcpp.so RcppExports.o code.o -F/Library/Frameworks/R.framework/.. -framework R -Wl,-framework -Wl,CoreFoundation
    +## ld: warning: -undefined dynamic_lookup may not work with chained fixups
    +## installing to /private/var/folders/wk/ysb32prj0gqdt1q08l3cwxlm0000gp/T/Rtmp6I9abF/devtools_install_e57a50c3906a/00LOCK-withrcpp/00new/withrcpp/libs
     ## ** checking absolute paths in shared objects and dynamic libraries
     ## * DONE (withrcpp)
    ## Writing 'NAMESPACE'
    -## Writing 'NAMESPACE'
     ## Writing 'alternate_signs.Rd'
     ## Writing 'withrcpp-package.Rd'
    diff --git a/examples/make-an-r-package-with-rcpp/withrcpp/DESCRIPTION b/examples/make-an-r-package-with-rcpp/withrcpp/DESCRIPTION index 8ad5f3e..e80e74a 100644 --- a/examples/make-an-r-package-with-rcpp/withrcpp/DESCRIPTION +++ b/examples/make-an-r-package-with-rcpp/withrcpp/DESCRIPTION @@ -7,7 +7,7 @@ Description: This package uses Rcpp. License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.0 LinkingTo: Rcpp Imports: @@ -16,4 +16,4 @@ Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 LitrVersionUsed: 0.9.1 -LitrId: 7d3722abb053943f0ff4eaddeba00c1e +LitrId: 40da4cf7b83c7201ff66496ab0c1b838 diff --git a/examples/make-an-r-package-with-rcpp/withrcpp/R/RcppExports.R b/examples/make-an-r-package-with-rcpp/withrcpp/R/RcppExports.R index c53f5cb..21e0687 100644 --- a/examples/make-an-r-package-with-rcpp/withrcpp/R/RcppExports.R +++ b/examples/make-an-r-package-with-rcpp/withrcpp/R/RcppExports.R @@ -6,6 +6,6 @@ #' @param v A numerical vector #' @export alternate_signs <- function(v) { - .Call('_withrcpp_alternate_signs', PACKAGE = 'withrcpp', v) + .Call(`_withrcpp_alternate_signs`, v) } diff --git a/examples/make-an-r-package/create-rhello.html b/examples/make-an-r-package/create-rhello.html index 6731a8e..b47756e 100644 --- a/examples/make-an-r-package/create-rhello.html +++ b/examples/make-an-r-package/create-rhello.html @@ -298,8 +298,8 @@ display: none; } - diff --git a/examples/make-an-r-package/rhello/DESCRIPTION b/examples/make-an-r-package/rhello/DESCRIPTION index c2a3e2f..3f48875 100644 --- a/examples/make-an-r-package/rhello/DESCRIPTION +++ b/examples/make-an-r-package/rhello/DESCRIPTION @@ -8,9 +8,9 @@ Description: This package says hello. But its actual purpose is to show License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.0 Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 LitrVersionUsed: 0.9.1 -LitrId: e58fee9a91f4c05182af3a6cbe4b9bc5 +LitrId: bec083fecd045bcfe2a0c08ebc0ec8e5 diff --git a/litr/DESCRIPTION b/litr/DESCRIPTION index 942f629..aaaf69e 100644 --- a/litr/DESCRIPTION +++ b/litr/DESCRIPTION @@ -11,7 +11,7 @@ Description: Allows one to fully create an R package in a single .Rmd License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.0 Imports: bookdown, desc, @@ -35,4 +35,4 @@ Config/testthat/edition: 3 VignetteBuilder: knitr URL: https://github.com/jacobbien/litr-project/tree/main/litr LitrVersionUsed: 0.9.1 -LitrId: 35636597ca8bb590fd7b0088c6865899 +LitrId: ac32204662ad3f2cc8837ad60437c283 diff --git a/litr/R/render.R b/litr/R/render.R index 2a33267..20f6178 100644 --- a/litr/R/render.R +++ b/litr/R/render.R @@ -619,7 +619,6 @@ add_chunk_label_hyperlinks <- function(html_files, xml2::xml_add_parent(pre_parent , xml2::read_xml(stringr::str_glue('
    '))) xml2::xml_add_sibling(pre_parent, xml2::read_xml(stringr::str_glue('{chunk_names[j]}')), where="before") - # xml2::xml_add_parent(pre_parent, xml2::read_xml(stringr::str_glue('
    {chunk_names[j]} '))) xml2::xml_remove(span_node) # remove the extra line break that is left over from removing the span code_node <- xml2::xml_child(pre_parent) @@ -628,8 +627,8 @@ add_chunk_label_hyperlinks <- function(html_files, } } # last thing is to insert an additional style node in the head with our CSS so we have a standard style whether we are using bookdown or Rmarkdown - css_string <- "fieldset.chunkfield {border:4px solid black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em} - legend.chunklegend {padding:0;font-size:21px;width:auto;border:0; border-bottom: none; margin-bottom:0} + css_string <- "fieldset.chunkfield {border:1px dotted black;padding-bottom: 0px;padding-top: 0px;margin:0 2px;padding:.35em .625em .75em} + legend.chunklegend {padding:0;width:auto;border:0; border-bottom: none; margin-bottom:0} " head_node <- xml2::xml_find_first(parsed_html, ".//head") xml2::xml_add_child(head_node, xml2::read_xml(stringr::str_glue(""))) diff --git a/litr/README.md b/litr/README.md index 8bf62e3..1d60eff 100644 --- a/litr/README.md +++ b/litr/README.md @@ -12,8 +12,12 @@ The `litr` R package lets you write a complete R package in a single R markdown document. This enables a workflow for writing R packages that is probably very different from what you are used to. -![*With litr, knitting creates an R package in addition to the .html -file.*](man/figures/diagram3.png) +
    + + +
    ## Why write R packages in this way? diff --git a/litr/pkgdown/favicon/apple-touch-icon-120x120.png b/litr/pkgdown/favicon/apple-touch-icon-120x120.png index 4689f32ac061c5db08094b55ccaee9f7683759f8..68263385f630b2509227a72f66da391b50ac1fc6 100644 GIT binary patch delta 71 zcmZ4KveIQj6$h`dk%iK2<0l)NJ}HPA8iyEKTA3PHnVM-E7+4t?WcrlNoZP7>gDE2+ LcusZlF2#HRD0dYm delta 71 zcmZ4KveIQj6$gums!XyB*P4w@pA3TMah@lMo{VD`RsjBTH=q11kdqIp#xQ3=9maC9V-A jDTyViR=N2pnQ4^_Mg~SEx(0^27#i~9s$NZI)yxL~)G!&b diff --git a/litr/pkgdown/favicon/apple-touch-icon-180x180.png b/litr/pkgdown/favicon/apple-touch-icon-180x180.png index 55799f6b5f257d343ba401801a9d1328b4777b5d..19af9fb132c40eb30cfffa191367241eb5395295 100644 GIT binary patch delta 94 zcmaEs{49Aw6$h`dk!9!|=Aw;F>kZrtjYAAAtxOH9OwF|o46FIN8fB+n; delta 94 zcmaEs{49Aw6$gums$8#7cEiS|^#*PRCLu-!R>tO5#s=C3237_J?uPBs3=9maC9V-A jDTyViR=N2pnQ4^_Mg~SEx(0^27#hrS?3Yh2H_QhBIqn*} diff --git a/litr/pkgdown/favicon/apple-touch-icon-60x60.png b/litr/pkgdown/favicon/apple-touch-icon-60x60.png index d524f5bf3e3325da3a1592a53bd21b90345feecc..b83075840460547c24c793ab050a963df79b20f4 100644 GIT binary patch delta 71 zcmew$|3Q926$h`dk=fB?xyKuucJhfD8iyEKTA3PHnVM)D7+4t?{JWgKVR8V!45mzf L-+i9Rjr{olR4Nvz delta 71 zcmew$|3Q926$gums-$q)Kjn>0JNZNnOhSwdtc=aAj7+r+46FScF9lOhSwdtc=aAjLftR46F2K_@mgtv7HpG!8Mev@$iYGPTe)Ft9Q(uq@_1!oa|wTH+c} jl9E`GYL%Oxl9^V?U}RuqqHAENi=n|oZ)?Tma>IN8#b_M0 delta 94 zcmaEs{49Aw6$gums)A2(nfAt}^#*PRCLu-!R>tO5#zxu(237_J7uHQPVqjoUEpd$~ jNl7e8waU#;$xN$cFfuSQ(KRsC#n7IN8b}$@^ diff --git a/litr/pkgdown/favicon/favicon-16x16.png b/litr/pkgdown/favicon/favicon-16x16.png index 883c7d354262f0ecea366afce60c08dadacfbdec..a5f52502eb38617031c045906c7394138fae6d93 100644 GIT binary patch delta 72 zcmaFQ@t$Kt4-*HkurXuG(XO?dr!c88iW(Y+7+PAH8d@0`Y8x0>85nS%)b*Wwo>>M{ MrfSO6kCRyP0R-(7?*IS* delta 72 zcmaFQ@t$Kt4-*HAh^nfMWYdz(Q<&5kMGZ_sj0~)d&4EbUz`)ADplRC9sgut$%V5fU LKFz2(i6tKZ^zamp diff --git a/litr/pkgdown/favicon/favicon-32x32.png b/litr/pkgdown/favicon/favicon-32x32.png index f7a3efaae7832c1f8dbc8fddada04655a07ace28..9a482de772e7ac12349fcd245ed3a455b01ddb2f 100644 GIT binary patch delta 71 zcmdleuu)(_6$h`dG1K2Ox5YO${bCa}G!8Mev@$icGBDCMFt7q5Me_@jC$h_6%6QlG K=S)7to(}*VWfeXE delta 71 zcmdleuu)(_6$gums@hV4%W@l=ezA!fn1mP^SQ(pJ8Cz%@7+4t?l-xU6GkGGr45kcg LbK!x>huHH02S*ic