[
  {
    "path": ".Rbuildignore",
    "content": ".gitignore\n^.*\\.Rproj$\n^\\.Rproj\\.user$\nMakefile\n^\\.github$\n^LICENSE\\.md$\n^docs$\n^examples$\n^playground$\n^site$\n.*\\.tar\\.gz$\n.*\\.Rcheck$\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# Repository Instructions for Copilot\n\n## Build and Test Instructions\n\n``` bash\n# Build the R package\nR CMD build .\n\n# Install the package\nR CMD INSTALL *_*.tar.gz\n\n# Test the package\ncd tests\nRscript *.R\n```\n\nTests are typically in `tests/testit/test-*.R` (for each `R/foo.R`, there is a\ncorresponding `tests/testit/test-foo.R`). In certain cases they may be in other\ndirectories, e.g., `tests/test-cran/` (for tests to run on anywhere, including\nCRAN) and `tests/test-ci/` (tests to run on CI only because they might fail on\nCRAN due to Internet connection or resource limits). The conditioning is done in\ntop-level `*.R` under `tests/`, e.g.,\n\n``` r\n# tests/test-cran.R\ntestit::test_pkg(dir = 'test-cran')\n\n# tests/test-ci.R\nif (tolower(Sys.getenv('CI')) == 'true') testit::test_pkg(dir = 'test-ci')\n```\n\nTests consist of assertions of this form:\n\n``` r\nlibrary(testit)\n\nassert('expectation message', {\n  actual = FUN(args, ...)\n  (actual %==% expected)\n  # more tests of the above form, e.g.,\n  (length(res) %==% 3L)\n})\n```\n\n-   Use `has_error()` instead of `tryCatch()` for error testing\n-   Never use `:::` to access internal functions in tests; testit exposes\n    internal functions automatically, so call them directly\n\n## Important Conventions\n\n### R Code Style\n\n1.  **Assignment**: Use `=` instead of `<-` for assignment\n2.  **Strings**: Use single quotes for strings (e.g., `'text'`)\n3.  **Indentation**: Use 2 spaces (not 4 spaces or tabs)\n4.  **Compact code**: Avoid `{}` for single-expression if statements; prefer\n    compact forms when possible\n5.  **Roxygen documentation**: Don't use `@description` or `@details` explicitly\n    — just write the description text directly after the title. Don't use\n    `@title` either.\n6.  **Examples**: Avoid `\\dontrun{}` unless absolutely necessary. Prefer\n    runnable examples that can be tested automatically.\n7.  **Function definitions**: For functions with many arguments, break the line\n    right after the opening `(`, indent arguments by 2 spaces, and try to wrap\n    them at 80-char width.\n8.  **Re-wrap code**: Always re-wrap the code after making changes to maintain\n    consistent formatting and line length.\n9.  **Implicit NULL**: Don't write `if (cond) foo else NULL`; the `else NULL` is\n    unnecessary since R's `if` without `else` already returns `NULL`. Never\n    write `return(NULL)`; use `return()` instead since R functions return `NULL`\n    by default when no value is given.\n10. **US spelling**: Use US spelling throughout all documentation, code\n    comments, and example text (e.g., \"color\" not \"colour\", \"center\" not\n    \"centre\", \"summarize\" not \"summarise\").\n11. **DRY (Don't Repeat Yourself)**: Never duplicate code. When the same logic\n    appears more than once, factor it into a shared helper function. This\n    applies to expressions, patterns, and multi-line blocks alike.\n\n### Check list\n\nAlways send a pull request, unless you are told otherwise. For each PR:\n\n1.  **Every change must have tests**: Every code change must come with\n    corresponding tests. If you add or fix a function, add assertions in the\n    test file that cover the new or fixed behavior. Tests are the first place to\n    catch regressions and errors.\n2.  **Always re-roxygenize**: Run `roxygen2::roxygenize()` after changing any\n    roxygen documentation to update man files\n3.  **MANDATORY: `R CMD check` before `git push`**: You MUST run a comprehensive\n    `R CMD check` successfully before submitting ANY code changes.\n4.  **MANDATORY: Wait for CI to be green**: After pushing code, you MUST wait\n    for GitHub Actions CI to complete successfully before claiming the task is\n    done. Do not wait more than 5 minutes for any single CI job; if it hasn't\n    finished, skip it and continue your work. Fix problems in CI as soon as any\n    job has failed instead of waiting for all jobs to finish.\n5.  **MANDATORY: Merge latest main before pushing**: Before pushing to a branch\n    or PR, always pull and merge the latest main branch. If there are merge\n    conflicts, resolve them before pushing.\n6.  **Bump version in PRs**: Bump the patch version number in DESCRIPTION once\n    per PR (on the first commit or when you first make changes), not on every\n    commit to the PR\n7.  **Never commit irrelevant files**: Don't run `git add .` blindly, as that\n    might add irrelevant files such as generated output or other artifacts. Only\n    add the ones you modified or created explicitly. Normally changes generated\n    automatically by roxygen2 are the only exception (they should be committed).\n8.  **Update NEWS.md**: When making changes, make sure to update `NEWS.md`\n    accordingly to document what changed. The first heading in NEWS.md always\n    represents the dev version and must be of the form `# PKG x.y` where PKG is\n    the package name and x.y is the next version to be released to CRAN (note:\n    x.y, not x.y.0). Usually y is bumped from the current minor version, e.g.,\n    if the current dev version is 1.8.3, the next CRAN release is expected to be\n    1.9.\n"
  },
  {
    "path": ".github/workflows/R-CMD-check.yaml",
    "content": "on:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\nname: R-CMD-check\n\njobs:\n  R-CMD-check:\n    runs-on: ${{ matrix.config.os }}\n\n    name: ${{ matrix.config.os }} (${{ matrix.config.r }})\n\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n          - {os: ubuntu-latest, r: 'release'}\n          - {os: ubuntu-latest, r: '4.4'}\n          - {os: ubuntu-latest, r: '4.3'}\n          - {os: ubuntu-latest, r: '4.2'}\n          - {os: ubuntu-latest, r: '4.1'}\n          - {os: ubuntu-latest, r: '4.0'}\n          - {os: ubuntu-latest, r: '3.6'}\n          - {os: ubuntu-latest, r: '3.5'}\n          - {os: ubuntu-latest, r: '3.4'}\n          - {os: ubuntu-latest, r: '3.3'}\n          - {os: ubuntu-latest, r: '3.2'}\n          - {os: ubuntu-latest, r: '3.2.0'}\n          - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}\n          - {os: macOS-latest, r: 'release'}\n          - {os: windows-latest, r: 'release'}\n\n    env:\n      GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}\n      R_KEEP_PKG_SOURCE: yes\n      _R_CHECK_FORCE_SUGGESTS_: false\n      _R_CHECK_RD_XREFS_: false\n\n    steps:\n      - uses: actions/checkout@HEAD\n\n      - uses: r-lib/actions/setup-r@HEAD\n        with:\n          r-version: ${{ matrix.config.r }}\n          http-user-agent: ${{ matrix.config.http-user-agent }}\n          use-public-rspm: true\n\n      - name: Whether to use codecov\n        run: |\n          echo \"USE_R_CODECOV=${{ (runner.os == 'Linux' && matrix.config.r == 'release') && true || false }}\" >> $GITHUB_ENV\n\n      - uses: yihui/actions/setup-r-dependencies@HEAD\n        with:\n          # install the package itself as we register vignette engine\n          extra-packages: . ${{ env.USE_R_CODECOV == 'true' && ' covr xml2' || '' }}\n          # install dependencies for cairo_pdf device\n          brew-packages: xquartz\n\n      - uses: yihui/actions/check-r-package@HEAD\n\n      - name: Run examples\n        working-directory: examples\n        run: |\n          Rscript _run.R\n          git diff --quiet || (git diff && exit 1)\n\n      - name: Test coverage\n        if: success() && env.USE_R_CODECOV == 'true'\n        run: |\n          cov = covr::package_coverage(\n            quiet = FALSE,\n            clean = FALSE,\n            install_path = file.path(normalizePath(Sys.getenv(\"RUNNER_TEMP\"), winslash = \"/\"), \"package\")\n          )\n          covr::to_cobertura(cov)\n          xfun::file_string('./cobertura.xml')\n        shell: Rscript {0}\n\n      - uses: codecov/codecov-action@HEAD\n        if: env.USE_R_CODECOV == 'true'\n        with:\n          fail_ci_if_error: ${{ github.event_name != 'pull_request' }}\n          files: ./cobertura.xml\n          plugins: noop\n          disable_search: true\n\n      - uses: actions/checkout@HEAD\n        if: runner.os == 'macOS'\n        with:\n          path: gh-pages\n          ref: gh-pages\n\n      - name: Publish book\n        if: runner.os == 'macOS'\n        run: |\n          Rscript -e 'install.packages(\"xfun\", repos=\"https://yihui.r-universe.dev\")'\n          Rscript -e 'litedown::fuse_book(\"docs\")'\n          cd docs; cp -r *.html ../gh-pages/; cd ../gh-pages\n          git config user.name github-actions\n          git config user.email github-actions@github.com\n          git add .\n          git commit -m \"update docs\" && git push || true\n"
  },
  {
    "path": ".github/workflows/copilot-setup-steps.yml",
    "content": "name: Copilot Setup Steps\n\non:\n  workflow_dispatch:\n  push:\n    paths:\n      - .github/workflows/copilot-setup-steps.yml\n  pull_request:\n    paths:\n      - .github/workflows/copilot-setup-steps.yml\n\njobs:\n  copilot-setup-steps:\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@HEAD\n\n      - name: Install R\n        uses: r-lib/actions/setup-r@HEAD\n        with:\n          use-public-rspm: true\n\n      - uses: yihui/actions/setup-r-dependencies@HEAD\n        with:\n          extra-packages: roxygen2 .\n"
  },
  {
    "path": ".github/workflows/github-pages.yml",
    "content": "name: Build and deploy package site\n\non:\n  push:\n    branches: [\"main\"]\n\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\njobs:\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@HEAD\n      - uses: actions/configure-pages@HEAD\n        with:\n          enablement: true\n      - uses: yihui/litedown/site@HEAD\n        with:\n          site-dir: 'site'\n      - uses: actions/upload-pages-artifact@HEAD\n        with:\n          path: 'site'\n      - id: deployment\n        uses: actions/deploy-pages@HEAD\n"
  },
  {
    "path": ".gitignore",
    "content": ".Rproj.user\n.Rhistory\n.RData\n.Ruserdata\n/examples/*__files/\n/examples/figures/\n/site/doc/\n/site/_footer.md\n/docs/packages.bib\n*.Rcheck/\n*.tar.gz\n"
  },
  {
    "path": "DESCRIPTION",
    "content": "Package: litedown\nType: Package\nTitle: A Lightweight Version of R Markdown\nVersion: 0.9.9\nAuthors@R: c(\n    person(\"Yihui\", \"Xie\", role = c(\"aut\", \"cre\"), email = \"xie@yihui.name\", comment = c(ORCID = \"0000-0003-0645-5666\", URL = \"https://yihui.org\")),\n    person(\"Tim\", \"Taylor\", role = \"ctb\", comment = c(ORCID = \"0000-0002-8587-7113\")),\n    person()\n    )\nDescription: Render R Markdown to Markdown (without using 'knitr'), and Markdown\n    to lightweight HTML or 'LaTeX' documents with the 'commonmark' package (instead\n    of 'Pandoc'). Some missing Markdown features in 'commonmark' are also\n    supported, such as raw HTML or 'LaTeX' blocks, 'LaTeX' math, superscripts,\n    subscripts, footnotes, element attributes, and appendices,\n    but not all 'Pandoc' Markdown features are (or will be) supported. With\n    additional JavaScript and CSS, you can also create HTML slides and articles.\n    This package can be viewed as a trimmed-down version of R Markdown and\n    'knitr'. It does not aim at rich Markdown features or a large variety of\n    output formats (the primary formats are HTML and 'LaTeX'). Book and website\n    projects of multiple input documents are also supported.\nDepends: R (>= 3.2.0)\nImports:\n    utils,\n    commonmark (>= 2.0.0),\n    xfun (>= 0.55)\nSuggests:\n    rbibutils,\n    rstudioapi,\n    testit,\n    tinytex\nLicense: MIT + file LICENSE\nURL: https://github.com/yihui/litedown\nBugReports: https://github.com/yihui/litedown/issues\nVignetteBuilder: litedown\nRoxygenNote: 7.3.3\nEncoding: UTF-8\nRoxygen: list(markdown = TRUE)\n"
  },
  {
    "path": "LICENSE",
    "content": "YEAR: 2024-2026\nCOPYRIGHT HOLDER: Yihui Xie\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# MIT License\n\nCopyright (c) 2023 Posit Software, PBC\nCopyright (c) 2024-2026 Yihui Xie\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "all:\n\tln -f ../lite.js/js/snap.js ../lite.js/css/snap.css ../lite.js/css/default.css inst/resources/\n\tRscript -e \"Rd2roxygen::rab('.', install=TRUE)\"\n\trm litedown_*.tar.gz\n\tcd examples && Rscript _run.R\n"
  },
  {
    "path": "NAMESPACE",
    "content": "# Generated by roxygen2: do not edit by hand\n\nS3method(print,litedown_env)\nS3method(record_print,data.frame)\nS3method(record_print,knitr_kable)\nS3method(record_print,matrix)\nS3method(record_print,tbl_df)\nexport(crack)\nexport(engines)\nexport(fiss)\nexport(fuse)\nexport(fuse_book)\nexport(fuse_env)\nexport(fuse_site)\nexport(get_context)\nexport(html_format)\nexport(latex_format)\nexport(mark)\nexport(markdown_options)\nexport(pkg_citation)\nexport(pkg_code)\nexport(pkg_desc)\nexport(pkg_manual)\nexport(pkg_news)\nexport(raw_text)\nexport(reactor)\nexport(roam)\nexport(sieve)\nexport(timing_data)\nexport(vest)\nimport(utils)\nimportFrom(xfun,Rscript_call)\nimportFrom(xfun,alnum_id)\nimportFrom(xfun,base64_uri)\nimportFrom(xfun,csv_options)\nimportFrom(xfun,del_empty_dir)\nimportFrom(xfun,dir_create)\nimportFrom(xfun,divide_chunk)\nimportFrom(xfun,download_cache)\nimportFrom(xfun,exit_call)\nimportFrom(xfun,fenced_block)\nimportFrom(xfun,fenced_div)\nimportFrom(xfun,file_exists)\nimportFrom(xfun,file_ext)\nimportFrom(xfun,grep_sub)\nimportFrom(xfun,html_escape)\nimportFrom(xfun,html_tag)\nimportFrom(xfun,html_value)\nimportFrom(xfun,in_dir)\nimportFrom(xfun,is_abs_path)\nimportFrom(xfun,is_blank)\nimportFrom(xfun,is_rel_path)\nimportFrom(xfun,loadable)\nimportFrom(xfun,mime_type)\nimportFrom(xfun,new_app)\nimportFrom(xfun,new_record)\nimportFrom(xfun,normalize_path)\nimportFrom(xfun,parse_only)\nimportFrom(xfun,prose_index)\nimportFrom(xfun,raw_string)\nimportFrom(xfun,read_all)\nimportFrom(xfun,read_utf8)\nimportFrom(xfun,record_print)\nimportFrom(xfun,relative_path)\nimportFrom(xfun,same_path)\nimportFrom(xfun,sans_ext)\nimportFrom(xfun,set_envvar)\nimportFrom(xfun,split_lines)\nimportFrom(xfun,try_error)\nimportFrom(xfun,try_silent)\nimportFrom(xfun,with_ext)\nimportFrom(xfun,write_utf8)\n"
  },
  {
    "path": "NEWS.md",
    "content": "# CHANGES IN litedown VERSION 0.10\n\n- When the `output` argument of `mark()` is a `.pdf` file, Markdown will be converted to a full `.tex` file instead of a LaTeX fragment before it is compiled to PDF.\n\n- Fixed a bug that sections in the appendix could not be cross-referenced.\n\n- Added a meta variable `lang` for HTML output, which is the language of the document (e.g., `en-US` for US English). This variable is used in the `<html lang=\"...\">` tag of the HTML output file. By default, the language is detected from the system locale, but you can also set it via the `lang` field under `meta` in YAML metadata, e.g., `lang: en-GB` for British English (thanks, @TimTaylor, #121).\n\n- Fixed a bug when embedding JS resources: previously `</` was escaped to `<\\/` to avoid `</script>` from being present in the JS source, but the escaping was too general (e.g., `</` may appear in a regex `/</g`). Now we only escape `</script>` to `<\\/script>`.\n\n- For `fuse_site()`, the generated site menu now automatically includes landing pages for one-level subdirectories containing `index.html` (e.g., `playground/index.html`), so subdirectory index pages appear in the navigation.\n\n- Fixed a re-entrancy bug that chunk options `fig.path` and `cache.path` in nested `fuse()` calls caused overridden figure/cache files (thanks, @nanxstats, #127).\n\n# CHANGES IN litedown VERSION 0.9\n\n- Provided [a new chunk option `filter`](https://yihui.org/litedown/#sec:option-filter) to filter the output elements via a custom function. This makes it possible to re-order output elements. As a result, text output and plots from a `for`-loop can be interleaved (thanks, @reedacartwright, #106).\n\n- The chunk option `attr.source` will default to `.lang` (where `lang` is the engine name) only when it is not provided, i.e., `NULL`. Previously the value `.lang` would still be used when `attr.source` has been provided. Now it's possible to completely override it, e.g., `attr.source = 'language-r'` as in #107 (thanks, @ThomasSoeiro).\n\n- The chunk option `collapse = TRUE` also applies to message blocks, including warnings, messages, and errors (thanks, @ThomasSoeiro, #108).\n\n- `pkg_desc()` gained a new argument `type` and the package description can be generated to either a table or a definition list now.\n\n- The `embed_resources` option was buggy for deferred JS resources. Previously they were moved to `<head>`, but deferred scripts should be executed after the full DOM is ready, so they are moved before `</body>` instead if they are to be embedded.\n\n- Fixed a bug in `pkg_manual()` that may lead to omission of certain items when building TOC.\n\n# CHANGES IN litedown VERSION 0.8\n\n- Added a new chunk option `fig.keep` to select plots to be kept in a code chunk (thanks, @Gabrielforest, #99). See https://yihui.org/litedown/#sec:option-fig for documentation.\n\n- Improved support for LaTeX footnotes. The footnote identifier no longer has to be a number, and the footnote can include arbitrary elements (not necessarily a single paragraph).\n\n- The chunk option `results = 'hide'` will imply `collapse = TRUE`, i.e., when text output is hidden, the source blocks will be merged (thanks, @jangorecki, #87). \n\n- `fuse(text = '# text')` will be treated as Markdown input instead of R code input (thanks, @chuxinyuan, #102).\n\n- Fixed the bug that the chunk option `fig.path` does not work when it does not contain `/` (thanks, @J-Moravec, #88).\n\n- `get_context('full_input')` gives the full path to the input file of `fuse()` (thanks, @rikivillalba, #104).\n\n# CHANGES IN litedown VERSION 0.7\n\n- `pkg_manual()` will exclude help pages with the keyword `internal` (thanks, @TimTaylor, #78).\n\n- `pkg_news()` will add links to Github users and issue numbers if the package is hosted on Github.\n\n- Allow `,` and `.` in superscripts and subscripts (thanks, @janlisec, #81).\n\n- Special characters in bibliography entries such as `{\\\"u}` can be correctly read now (thanks, @bastistician).\n\n- Fixed a bug that inline code expressions (`` `{lang} expr` ``) cannot be correctly located when the line has leading spaces that are not meaningful.\n\n- Deleted vignettes `markdown-examples` and `markdown-output`. They are rendered on the package site now: https://git.yihui.org/litedown/examples/test-options.html\n\n# CHANGES IN litedown VERSION 0.6\n\n- Added a Markdown rendering option `offline` to download web resources when this option is set to true, so that the HTML output can be viewed offline (thanks, @TimTaylor, #73). See https://yihui.org/litedown/#sec:offline for more info.\n\n- Added a function `get_context()` to query the `fuse()` context such as the input file path or the output format (thanks, @MichaelChirico #67, @vincentarelbundock #70).\n\n- Added a function `raw_text()` to output raw text content in a code chunk (thanks, @vincentarelbundock, #69).\n\n- Dropped the chunk option `ref.label` and added a new chunk option `fill`, which is more general (`ref.label = \"LABEL\"` can be achieved by `` `<LABEL>` `` inside a chunk). See https://yihui.org/litedown/#sec:option-fill for more information.\n\n- Fixed a bug that `fuse()` fails to print the error location when the whole input document consists of a single chunk that throws an error (thanks, @kevinushey, yihui/knitr#2387).\n\n- `fuse_book()` will ignore YAML headers in book chapters except for the index chapter.\n\n# CHANGES IN litedown VERSION 0.5\n\n- Added a wizard in `roam()` to create new `.Rmd`/`.md`/`.R` files with selected HTML features.\n\n- Added a new engine `embed` to embed text files via a code chunk.\n\n- Changed the meaning of the chunk option `order`: previously, higher values indicate earlier execution; now higher values indicate later execution. This is a breaking change, but the new meaning should feel more natural. For example, `order = i` means to execute the chunk in the i-th step, and `order = i - 1.5` means to move the chunk back 1.5 step in the queue so it will be executed earlier than its previous chunk. See https://yihui.org/litedown/#sec:option-order for details.\n\n- Shortened the output format names `litedown::html_format` to `html`, and `litedown::latex_format` to `latex`. The names `litedown::*` can still be used if you like.\n\n- Added options `dollar`, `signif`, and `power` to format numbers from inline code. See https://yihui.org/litedown/#sec:inline-code for details.\n\n- When embedding SVG images in HTML output, embed their raw XML content instead of base64 encoding them.\n\n- Empty table headers are removed in HTML output (they may be generated from data frames or matrices without column names).\n\n- Added support for the chunk option `collapse = TRUE` (thanks, @J-Moravec, #40).\n\n- Added support for the chunk option `fig.dim`, which is a shortcut for `fig.width` and `fig.height`.\n\n- Added a new function `vest()` as another way to add CSS/JS assets to HTML output.\n\n- Provided templates and a Github action `yihui/litedown/site` to build package websites. See https://yihui.org/litedown/#sec:pkg-site for details.\n\n- Added an argument `examples` to `pkg_manual()` to run examples and show their output (thanks, @TimTaylor, #54).\n\n- Fixed a bug that the default CSS wouldn't be added when a math expression exists on the page (thanks, @calvinw, #61).\n\n- Fixed a bug that cross-references to other chapters of a book could not be resolved when previewing a single chapter.\n\n- Fixed a bug that the file navigation by line numbers on code blocks stopped working in `litedown::roam()` due to yihui/lite.js@5e06d19.\n\n- Fixed a bug that `R` code blocks could not be embedded when using prism.js for syntax highlighting (thanks, @TimTaylor, #53).\n\n- `pkg_manual()` will point out the name of the problematic Rd file when the Rd file fails to convert to HTML (thanks, @BSchamberger).\n\n- Dropped **knitr** and **rmarkdown** from the `Suggests` field in `DESCRIPTION`. Previously, **litedown** allowed `rmarkdown::render()` to use the output formats `litedown::html_format` and `litedown::latex_format`. Now `rmarkdown::render()` is no longer supported, and `litedown::fuse()` must be used instead.\n\n# CHANGES IN litedown VERSION 0.4\n\n- Provided an option `options(litedown.roam.cleanup = TRUE)` to clean up the `*__files/` directory after previewing `.Rmd` or `.R` files via `litedown::roam()` (thanks, @TimTaylor, #36).\n\n- Added the keyboard shortcut `Ctrl + K` (or `Command + K` on macOS) for rendering a file to disk in the `litedown::roam()` preview.\n\n- Cross-references also work for LaTeX output now.\n\n- Fixed an error in the internal function `detect_pkg()` during `R CMD check` on CRAN.\n\n- Set `options(bitmapType = 'cairo')` on macOS only when `xquartz` is available. Previously only `capabilities('cairo')` was checked, which was not enough. This option can also be manually set via `options(bitmapType)` in a code chunk if the automatic switch to `cairo` is not desired.\n\n- Fixed the bug that indented or quoted code blocks are not correctly indented or quoted when a code expression contains multiple lines.\n\n- Fixed the bug that the span syntax `[text](){...}` doesn't work when `text` contains markup (e.g., bold or italic).\n\n# CHANGES IN litedown VERSION 0.3\n\n- Added a new engine `md` to output Markdown text both verbatim and as-is, which can be useful for showing Markdown examples, e.g.,\n\n  ````md\n  ```{md}\n  You can see both the _source_ and _output_ of\n  this `md` chunk.\n  ```\n  \n  You can also use `{md} the engine **inline**`.\n  ````\n\n- Added a new engine `mermaid` to generate Mermaid diagrams, e.g.,\n\n  ````md\n  ```{mermaid, fig.cap='A nice flowchart.'}\n  graph TD;\n      A-->B;\n      A-->C;\n      B-->D;\n      C-->D;\n  ```\n  ````\n\n- Added helper functions `pkg_desc()`, `pkg_news()`, `pkg_citation()`, `pkg_code()`, and `pkg_manual()` to get various package information for building the full package documentation as a single-file book (thanks, @jangorecki @llrs #24, @TimTaylor #22).\n\n- LaTeX math environments such as equations can be numbered and cross-referenced now (thanks, @hturner, #32).\n\n- Section headings containing the class name \"unlisted\" will be excluded in the table of contents.\n\n- Provided a way to write `<span>` with attributes based on empty links, i.e., `[text](){.class #id ...}`. The empty URL here tells `mark()` to treat the link as a `<span>` instead of `<a>`.\n\n- Added back/forward/refresh/print buttons to the toolbar in the `litedown::roam()` preview interface.\n\n- Changed the behavior of `.Rmd` and `.R` file links in the `litedown::roam()` interface: previously, clicking on an `.Rmd` or `.R` filename will execute them; now it will only show their content, because fully executing the code may be expensive or even dangerous (especially when the files were not authored by you). A new \"Run\" button has been provided in the interface, on which you can click on to run a file in memory and preview it (i.e., the old behavior of clicking on filenames). You should use this button only if you trust the file.\n\n- Added the JS asset [`@mathjax-config`](https://github.com/yihui/lite.js/blob/main/js/mathjax-config.js) to enable equation numbering by default when the JS math library is set to MathJax (thanks, @hturner, #32).\n\n- Set `options(bitmapType = 'cairo')` in `fuse()` if `capabilities('cairo')` is TRUE, which will generate smaller bitmap plot files (e.g., `.png`) than using `quartz` or `Xlib`, and is also a safer option for `fuse()` to be executed in parallel (rstudio/rmarkdown#2561).\n\n- Added a new vignette engine `litedown::book` to make it possible to build multiple vignettes into a book. To use this engine, declare `\\VignetteEngine{litedown::book}` only in the book index file (e.g., `index.Rmd`) but not in other book chapter files.\n\n- Added support for an array of multiple authors in the YAML metadata (thanks, @AlbertLei, #28). If the `author` field in YAML is an array of length > 1, each author will be written to a separate `<h2>` in HTML output, or concatenated by `\\and` in LaTeX output. Note that you can also write multiple authors in a single string (e.g., `author: \"Jane X and John Y\"`) instead of using an array (`author: [\"Jane X\", \"John Y\"]`), in which case the string will be treated as a single author (they will be put inside a single `<h2>` in HTML output).\n\n- Fixed the bug that the leading `-`, `+`, or `*` in a LaTeX math expression was recognized as the bullet list marker, which would invalidate the math expression (thanks, @hturner, #33).\n\n- Changed the first `-` to `:` in automatically generated element IDs, including section, figure, and table IDs, e.g., the ID `sec-intro-methods` is changed to `sec:intro-methods`, and `fig-nice-plot` is changed to `fig:nice-plot`. You can still use `-` when manually assigning IDs to elements, e.g., `# Intro Methods {#sec-intro-methods}`. For backward compatibility, cross-references using `-` will be resolved if the `:` version of the ID can be found, e.g., `@sec-intro-methods` will be resolved to `@sec:intro-methods` if the former cannot be found but the latter can.\n\n- Fixed a bug that when LaTeX math environments are written in raw LaTeX blocks (i.e., ```` ```{=latex}````), `mark()` will not load the math JS library such as MathJax or KaTeX unless `$ $` or `$$ $$` expressions are present in the document.\n\n- As-is output accepts attributes via the chunk option `attr.asis` now. If provided, as-is output will be wrapped in a fenced Div with these attributes.\n\n- Numeric output from inline code will no longer be formatted if the value is wrapped in `I()`.\n\n- The prefix for the automatic IDs of `h1` headings has been changed from `sec:` to `chp:`. For other levels of headings, the prefix is still `sec:`.\n\n- Provided a new option `embed_cleanup` to clean up plot files that have been embedded in HTML output (thanks, @TimTaylor, #16).\n\n- `fuse()` supports the output format `litedown::markdown_format` now, which generates the intermediate Markdown from R Markdown without further rendering Markdown to other formats. Using this output format is equivalent to `fuse(..., output = '.md')` or `fuse(..., output = 'markdown')` (thanks, @mikmart, #35).\n\n# CHANGES IN litedown VERSION 0.2\n\n- A data frame (or matrix/tibble) wrapped in `I()` is fully printed to a table now by default. Without `I()`, data objects are truncated to 10 rows by default when printing to tables.\n\n- When `options(litedown.fig.alt = TRUE)` and the chunk option `fig.alt` is unset, `fuse()` will emit reminders about the missing alt text for code chunks containing plots (thanks, @TimTaylor, #23). Providing alt text can improve the accessibility of images in HTML output. To avoid omitting the alt text inadvertently, you can set the option `litedown.fig.alt` in your `.Rprofile`.\n\n- Added the meta variable `plain-title` for HTML output, which is the plain version of the document title (i.e., without HTML tags), and used in the `<title>` tag.\n\n- Check boxes from `- [ ] ...` are no longer disabled in HTML output.\n\n- The implicit latest version of jsdelivr resources will be resolved to an explicit version, e.g., `https://cdn.jsdelivr.net/npm/@xiee/utils/css/default.css` will be resolved to `https://cdn.jsdelivr.net/npm/@xiee/utils@X.Y.Z/css/default.css`, where `X.Y.Z` is the current latest version. This will make sure the HTML output containing jsdelivr resources is stable.\n\n# CHANGES IN litedown VERSION 0.1\n\n- Initial CRAN release.\n"
  },
  {
    "path": "R/format.R",
    "content": "is_rmd_preview = function() Sys.getenv('RMARKDOWN_PREVIEW_DIR') != ''\n\noutput_format = function(to, options, meta, ...) {\n  if (is_rmd_preview()) stop(\n    \"It appears that you clicked the 'Knit' button in RStudio to render the document. \",\n    \"You are recommended to use litedown::roam() to preview or render documents instead. \",\n    \"Alternatively, you can add a top-level field 'knit: litedown:::knit' to the YAML metadata, \",\n    \"so the document can be rendered by litedown::fuse() instead of rmarkdown::render().\",\n    call. = FALSE\n  )\n  msg = 'Please render the document via litedown::fuse() instead of rmarkdown::render().'\n  if ('pkgdown' %in% loadedNamespaces()) {\n    warning(\n      msg, '\\n\\nIf you intend to build a package website, you can also use litedown:',\n      ' https://yihui.org/litedown/#sec:pkg-site', call. = FALSE\n    )\n    ns = asNamespace('rmarkdown')\n    ag = merge_list(list(to = to), list(...))\n    ag = ag[intersect(names(formals(ns$pandoc_options)), names(ag))]\n    opts = do.call(ns$pandoc_options, ag)\n    return(ns$output_format(NULL, opts))\n  }\n  stop(msg, call. = FALSE)\n}\n\n#' Output formats in YAML metadata\n#'\n#' These functions exist only for historical reasons, and should never be called\n#' directly. They can be used to configure output formats in YAML, but you are\n#' recommended to use the file format names instead of these function names.\n#'\n#' To configure output formats in the YAML metadata of the Markdown document,\n#' simply use the output format names such as `html` or `latex` in the `output`\n#' field in YAML, e.g.,\n#'\n#' ```yaml\n#' ---\n#' output:\n#'   html:\n#'     options:\n#'       toc: true\n#'     keep_md: true\n#'   latex:\n#'     latex_engine: pdflatex\n#' ---\n#' ```\n#'\n#' You can also use `litedown::html_format` instead of `html` (or\n#' `litedown::latex_format` instead of `latex`) if you like.\n#' @param meta,options Arguments to be passed to [mark()].\n#' @param template A template file path.\n#' @param keep_md,keep_tex Whether to keep the intermediate \\file{.md} and\n#'   \\file{.tex} files generated from \\file{.Rmd}.\n#' @param latex_engine The LaTeX engine to compile \\file{.tex} to \\file{.pdf}.\n#' @param citation_package The LaTeX package for processing citations. Possible\n#'   values are `none`, `natbib`, and `biblatex`.\n#' @note If you want to use the `Knit` button in RStudio, you must add a\n#'   top-level field `knit: litedown:::knit` to the YAML metadata. See\n#'   \\url{https://yihui.org/litedown/#sec:knit-button} for more information.\n#' @export\nhtml_format = function(options = NULL, meta = NULL, template = NULL, keep_md = FALSE) {\n  output_format('html', options, meta, template, keep_md)\n}\n\n#' @rdname html_format\n#' @export\nlatex_format = function(\n  options = NULL, meta = NULL, template = NULL, keep_md = FALSE,\n  keep_tex = FALSE, latex_engine = 'xelatex', citation_package = 'natbib'\n) {\n  output_format()\n}\n\n# map rmarkdown arguments to markdown\nmap_args = function(\n  toc = FALSE, toc_depth = 3, number_sections = FALSE, anchor_sections = FALSE,\n  code_folding = 'none', self_contained = TRUE, math_method = 'default',\n  css = NULL, includes = NULL, ...\n) {\n  opts = list(\n    toc = toc, number_sections = number_sections, embed_resources = self_contained\n  )\n  meta = list(css = c('default', css))\n  if (toc) opts$toc = list(depth = toc_depth)\n  if (identical(\n    if (is.list(math_method)) math_method$engine else math_method, 'mathjax'\n  )) opts$js_math = 'mathjax'\n  if (!isFALSE(anchor_sections)) {\n    meta$js = c(meta$js, '@heading-anchor')\n    meta$css = c(meta$css, '@heading-anchor')\n  }\n  # 'hide' is not supported here; if it is desired, use <script data-open=false>\n  if (code_folding != 'none') meta$js = c(\n    meta$js, '@fold-details'\n  )\n  if (is.list(includes)) meta[\n    c('header_includes', 'include_before', 'include_after')\n  ] = includes[c('in_header', 'before_body', 'after_body')]\n  list(meta = meta, options = opts, ...)\n}\n\n# split YAML and body from text input\nyaml_body = function(text, ...) {\n  res = xfun::yaml_body(text, use_yaml = FALSE, ...)\n  if (!is.null(yaml <- normalize_yaml(res$yaml))) res$yaml = yaml\n  res\n}\n\n# normalize (rmarkdown) output formats in YAML to litedown's formats\nnormalize_yaml = function(x) {\n  if (!length(out <- x[['output']])) {\n    # if the key 'format' is provided, normalize it to 'output'\n    if (length(out <- x[['format']])) x$format = NULL else return()\n  }\n  if (is.character(out)) out = set_names(vector('list', length(out)), out)\n  if (!is.list(out))\n    stop('The output format field in YAML must be either list or character')\n  fmt = c(html_document = 'html', html_vignette = 'html', pdf_document = 'latex')\n  for (i in intersect(names(fmt), names(out))) {\n    out[[i]] = if (is.list(out[[i]])) do.call(map_args, out[[i]]) else list()\n    names(out)[names(out) == i] = fmt[i]\n  }\n  # normalize format names `(lite|mark)down::*_format` to `*`\n  names(out) = gsub('^(lite|mark)down::+([^_]+)_.*', '\\\\2', names(out))\n  x$output = out\n  x\n}\n\n# get metadata from a certain field under an output format\nyaml_field = function(yaml, format, name = 'meta') {\n  if (is.list(out <- yaml[['output']]) && is.list(out <- out[[format]])) {\n    if (length(name) == 1) out[[name]] else out[name]\n  }\n}\n\n# get output format from YAML's `output` field\nyaml_format = function(yaml) {\n  if (is.list(out <- yaml[['output']])) out = names(out)\n  c(out, 'html')[1]\n}\n\n# determine output format based on output file name and input's YAML\ndetect_format = function(output, yaml) {\n  res = if (is.character(output)) {\n    # check if output is a known format, e.g., output = 'html'\n    if (output %in% names(md_formats)) output else {\n      # check file extension, e.g., output = '.pdf'\n      ext = file_ext(output)\n      if (ext == 'pdf') 'latex' else {\n        names(which(md_formats == paste0('.', ext))) %|%\n          # output = 'markdown:format', e.g., markdown:latex means the final\n          # format is latex but the intermediate output should be markdown\n          if (startsWith(output, 'markdown:')) intersect(\n            sub('^markdown:', '', output), names(md_formats)\n          )\n      }\n    }\n  }\n  # if unable to detect format from `output`, try YAML\n  if (length(res) == 1) res else yaml_format(yaml)\n}\n\nmd_formats = c(\n  html = '.html', xml = '.xml', man = '.man', commonmark = '.markdown',\n  markdown = '.md', text = '.txt', latex = '.tex'\n)\n"
  },
  {
    "path": "R/fuse.R",
    "content": "new_env = function(...) new.env(..., parent = emptyenv())\n\n#' Parse R Markdown or R scripts\n#'\n#' Parse input into code chunks, inline code expressions, and text fragments:\n#' [crack()] is for parsing R Markdown, and [sieve()] is for R scripts.\n#'\n#' For R Markdown, a code chunk must start with a fence of the form ````\n#' ```{lang} ````, where `lang` is the language name, e.g., `r` or `python`. The\n#' body of a code chunk can start with chunk options written in \"pipe comments\",\n#' e.g., `#| eval = TRUE, echo = FALSE` (the CSV syntax) or `#| eval: true` (the\n#' YAML syntax). An inline code fragment is of the form `` `{lang} source` ``\n#' embedded in Markdown text.\n#' @inheritParams mark\n#' @export\n#' @return A list of code chunks and text blocks:\n#'\n#'   - Code chunks are of the form `list(source, type = \"code_chunk\", options,\n#'   comments, ...)`: `source` is a character vector of the source code of a\n#'   code chunk, `options` is a list of chunk options, and `comments` is a\n#'   vector of pipe comments.\n#'\n#'   - Text blocks are of the form `list(source, type = \"text_block\", ...)`. If\n#'   the text block does not contain any inline code, `source` will be a\n#'   character string (lines of text concatenated by line breaks), otherwise it\n#'   will be a list with members that are either character strings (normal text\n#'   fragments) or lists of the form `list(source, options, ...)` (`source` is\n#'   the inline code, and `options` contains its options specified inside ``\n#'   `{lang, ...}` ``).\n#'\n#'   Both code chunks and text blocks have a list member named `lines` that\n#'   stores their starting and ending line numbers in the input.\n#' @examples\n#' library(litedown)\n#' # parse R Markdown\n#' res = crack(c('```{r}\\n1+1\\n```', 'Hello, `pi` = `{r} pi` and `e` = `{r} exp(1)`!'))\n#' str(res)\n#' # evaluate inline code and combine results with text fragments\n#' txt = lapply(res[[2]]$source, function(x) {\n#'   if (is.character(x)) x else eval(parse(text = x$source))\n#' })\n#' paste(unlist(txt), collapse = '')\ncrack = function(input, text = NULL) {\n  text = read_input(input, text); input = attr(text, 'input')\n  xml = commonmark::markdown_xml(text, sourcepos = TRUE)\n  rx_engine = '([a-zA-Z0-9_]+)'  # only allow these characters for engine names\n  r = paste0(\n    '<(code|code_block) sourcepos=\"(\\\\d+):(\\\\d+)-(\\\\d+):(\\\\d+)\"( info=\"[{]+',\n    rx_engine, '[^\"]*?[}]\")? xml:space=\"[^>]*>([^<]*)<'\n  )\n  m = match_all(xml, r, perl = TRUE)[[1]] %|% matrix(character(), 9)\n  # code blocks must have non-empty info strings\n  m = m[, m[2, ] != 'code_block' | m[8, ] != '', drop = FALSE]\n\n  res = list()\n  # add a block of text and the line range info\n  add_block = function(l1, l2, ...) {\n    res[[length(res) + 1]] <<- list(source = text[l1:l2], ..., lines = c(l1, l2))\n    res\n  }\n\n  n = length(text)\n  i = 1L  # the possible start line number of text blocks\n  for (j in which(m[2, ] == 'code_block')) {\n    # start (3) and end (5) line numbers for code chunks\n    pos = as.integer(m[c(3, 5), j]); i1 = pos[1]; i2 = pos[2]\n    # add the possible text block before the current code chunk\n    if (i1 > i) add_block(i, i1 - 1L, type = 'text_block')\n    # add the code chunk\n    add_block(i1, i2, info = m[8, j], type = 'code_chunk')\n    i = i2 + 1L  # the earliest line for the next text block is next line\n  }\n  # if there are lines remaining, they must be a text block\n  if (i <= n) add_block(i, n, type = 'text_block')\n\n  if (!length(m)) return(res)\n\n  set_error_handler(input)\n\n  m = m[, m[2, ] == 'code', drop = FALSE]\n  # find out inline code `{lang} expr`\n  rx_inline = '^\\\\s*[{](.+?)[}]\\\\s+(.+?)\\\\s*$'\n  # look for `r expr` if `{lang}` not found (for compatibility with knitr)\n  if (!any(j <- grepl(rx_inline, m[9, ])) && getOption('litedown.enable.knitr_inline', FALSE)) {\n    rx_inline = '^(r) +(.+?)\\\\s*$'\n    j = grepl(rx_inline, m[9, ])\n  }\n  m = m[, j, drop = FALSE]\n  n_start = uapply(res, function(x) x$lines[1])  # starting line numbers\n  j = findInterval(m[3, ], n_start)  # find which block each inline code belongs to\n  for (i in seq_len(ncol(m))) {\n    b = res[[j[i]]]; l = b$lines\n    # column position is based on bytes instead of chars; needs to be adjusted to the latter\n    pos = char_pos(text, as.integer(m[3:6, i]))\n    i1 = pos[1]; i2 = pos[3]\n    # commonmark::markdown_xml(sourcepos = TRUE) gives wrong column info when\n    # the line has leading spaces (which are ignored), so doublecheck here (in\n    # theory we also need to consider the case i1 != i2 but that's a little too\n    # complicated and may be rare, too)\n    if (i1 == i2 && grepl('^\\\\s+', ti <- text[i1])) {\n      mi = restore_html(m[9, i])\n      if (substring(ti, pos[2], pos[4]) != mi) {\n        p = base::gregexpr(mi, ti, fixed = TRUE)[[1]]\n        if (length(p) > 1 || p < 1) {\n          save_pos(c(i1, i2)); stop(\n            'Unable to locate the inline code expression ', mi,\n            '. Please file an issue to https://github.com/yihui/litedown/issues ',\n            'with a minimal reproducible example.'\n          )\n        }\n        pos[2] = p; pos[4] = p + attr(p, 'match.length') - 1\n      }\n    }\n    s = nchar(b$source)\n    # calculate new position of code after we concatenate all lines of this block by \\n\n    b$col = c(b$col, c(\n      sum(s[seq_len(i1 - l[1])] + 1) + pos[2],\n      sum(s[seq_len(i2 - l[1])] + 1) + pos[4]\n    ))\n    b$pos = c(b$pos, pos)\n    res[[j[i]]] = b\n  }\n\n  i1 = 0  # code chunk index\n  # remove code fences, and extract code in text blocks\n  for (j in seq_along(res)) {\n    b = res[[j]]\n    if (b$type == 'code_chunk') {\n      code = b$source\n      N = length(code)\n      # a code block may be indented or inside a blockquote\n      p = grep_sub('^([\\t >]*)(`{3,}|~{3,}).*', '\\\\1\\\\2', code[1])\n      if (length(p) == 0) stop('Possibly malformed code block fence: ', code[1])\n      if (!grepl(sub('^[\\t >]*', '', p), code[N])) stop(\n        'The fences of the code block do not match:\\n\\n', code[1], '\\n', code[N]\n      )\n      p = gsub('[`~]+$', '', p)\n      if (p != '') {\n        i = startsWith(code, p)\n        # remove indentation or >\n        code[i] = substr(code[i], nchar(p) + 1, nchar(code[i]))\n        # trailing spaces in the prefix may have been trimmed: yihui/knitr#1446\n        code[!i] = gsub(gsub('(.+?)\\\\s+$', '^\\\\1', p), '', code[!i])\n        b$prefix = p\n      }\n      # possible comma-separated chunk options in header\n      rx_opts = paste0('^(`{3,}|~{3,})\\\\s*([{]+)', rx_engine, '(.*?)\\\\s*[}]+\\\\s*$')\n      o = match_one(code[1], rx_opts)[[1]]\n      if (length(o)) {\n        # if two or more `{` is used, we will write chunk fences to output\n        if (nchar(o[3]) > 1) b$fences = c(\n          sub('{{', '{', sub('}}\\\\s*$', '}', code[1]), fixed = TRUE), code[N]\n        )\n        o = if (o[5] != '') csv_options(o[5])\n      }\n      code = code[-c(1, N)]  # remove fences\n      save_pos(b$lines)\n      code = split_chunk(b$info, code)\n      b[c('source', 'options', 'comments')] = code[c('code', 'options', 'src')]\n      # starting line number of code\n      b$code_start = b$lines[1] + 1L + length(b$comments)\n      # default label is chunk-i (or parent-label-i for child documents)\n      i1 = i1 + 1\n      # merge chunk options from header with pipe comment options\n      b$options = merge_list(list(\n        label = sprintf('%s-%d', (if (isTRUE(.env$child)) reactor('label')) %||% 'chunk', i1)\n      ), o, b$options)\n      b$options$engine = b$info\n      b$info = NULL  # the info is stored in chunk options as `engine`\n    } else if (length(p <- b$col) > 0) {\n      p = matrix(p, nrow = 2)\n      x = one_string(b$source)\n      x1 = substring(x, p[1, ], p[2, ])  # code\n      # then extract normal text\n      p = rbind(p[1, ] - 1, p[2, ] + 1)\n      p = matrix(c(1, p, nchar(x)), nrow = 2)\n      x2 = substring(x, p[1, ], p[2, ])  # text\n      # get rid of left-over backticks\n      N = length(x2)\n      x2[1] = gsub('`+$', '', x2[1])  # trailing ` of first\n      x2[N] = gsub('^`+', '', x2[N])  # leading ` of last\n      # ` at both ends for text in the middle\n      if (N > 2) x2[2:(N - 1)] = gsub('^`+|`+$', '', x2[2:(N - 1)])\n      # see if the code is wrapped in $ $\n      d1 = substring(x2, nchar(x2), nchar(x2)) == '$'\n      d2 = substring(x2, 1, 1) == '$'\n      dollar = d1[-N] & d2[-1]\n      # position of code c(row1, col1, row2, col2)\n      pos = matrix(b$pos, nrow = 4)\n      x = as.list(head(c(rbind(x2, c(x1, ''))), -1))\n      for (i in seq_len(N - 1)) {\n        z = match_one(x1[i], rx_inline)[[1]][-1]\n        p2 = pos[, i]; save_pos(p2)\n        xi = list(\n          source = z[2], pos = p2,\n          options = csv_options(gsub('^([^,]+)', 'engine=\"\\\\1\"', z[1]))\n        )\n        if (dollar[i]) xi$math = TRUE\n        x[[2 * i]] = xi\n      }\n      b$source = x\n    } else {\n      b$source = paste(b$source, collapse = '\\n')\n    }\n    b$pos = b$col = NULL  # positions not useful anymore\n    res[[j]] = b\n  }\n  res\n}\n\nset_error_handler = function(input) {\n  opts = options(xfun.handle_error.loc_fun = get_loc)\n  oenv = as.list(.env)\n  exit_call(function() { options(opts); reset_env(oenv, .env) })\n  .env$input = input  # store the input name for get_loc()\n}\n\n# convert byte position to character position\nchar_pos = function(x, p) {\n  x2 = x[p[c(1, 3)]]\n  # no need to convert if no multibyte chars\n  if (all(nchar(x2) == nchar(x2, 'bytes'))) return(p)\n  p2 = p[c(2, 4)]\n  Encoding(x2) = 'bytes'\n  x2 = substr(x2, 1, p2 - 1)  # go back one char in case current column is multibyte\n  Encoding(x2) = 'UTF-8'\n  p[c(2, 4)] = nchar(x2) + 1L  # go forward by one char\n  if (p2[2] == 0) p[4] = 0L  # boundary case: \\n before the closing backtick\n  p\n}\n\n#' @details For R scripts, text blocks are extracted by removing the leading\n#'   `#'` tokens. All other lines are treated as R code, which can optionally be\n#'   separated into chunks by consecutive lines of `#|` comments (chunk options\n#'   are written in these comments). If no `#'` or `#|` tokens are found in the\n#'   script, the script will be divided into chunks that contain smallest\n#'   possible complete R expressions.\n#' @note For simplicity, [sieve()] does not support inline code expressions.\n#'   Text after `#'` is treated as pure Markdown.\n#'\n#'   It is a pure coincidence that the function names `crack()` and `sieve()`\n#'   weakly resemble Carson Sievert's name, but I will consider adding a class\n#'   name `sievert` to the returned value of `sieve()` if Carson becomes the\n#'   president of the United States someday, which may make the value\n#'   radioactive and introduce a new programming paradigm named _Radioactive\n#'   Programming_ (in case _Reactive Programming_ is no longer fun or cool).\n#' @rdname crack\n#' @export\n#' @examples\n#'\n#' # parse R code\n#' res = sieve(c(\"#' This is _doc_.\", '', '#| eval=TRUE', '# this is code', '1 + 1'))\n#' str(res)\nsieve = function(input, text = NULL) {\n  text = read_input(input, text); input = attr(text, 'input')\n  n = length(text)\n  r = run_range(grepl(\"^#'( .+| *)$\", text), is_blank(text))\n  nc = ncol(r)\n\n  # no #' or #|: split code into smallest expressions\n  if (nc == 0 && !any(startsWith(text, '#| '))) {\n    res = xfun::split_source(text, TRUE, TRUE)\n    res = .mapply(function(code, label) {\n      l = attr(code, 'lines')\n      list(\n        source = c(code), type = 'code_chunk', lines = l, code_start = l[1],\n        options = list(engine = 'r', label = label)\n      )\n    }, res, sprintf('chunk-%d', seq_along(res)))\n    return(res)\n  }\n\n  # split doc and code by #', and divide code by #|\n  res = list()\n  add_block = function(l1, l2, type = 'code_chunk', pipe = FALSE) {\n    x = text[l1:l2]\n    if (type == 'text_block') {\n      el = list(source = one_string(sub(\"^#' ?\", '', x)))\n    } else {\n      if (all(i <- is_blank(x))) return()\n      # trim blank lines at both ends\n      i2 = range(which(!i))  # first and last non-empty lines\n      l1 = l1 + (i2[1] - 1)\n      l2 = l2 - (length(i) - i2[2])\n      save_pos(c(l1, l2))\n      x = text[l1:l2]\n      el = if (pipe) partition(x) else list(source = x)\n      el$code_start = as.integer(l1) + length(el$comments)\n      el$options$engine = 'r'\n    }\n    el$type = type\n    el$lines = as.integer(c(l1, l2))\n    res[[length(res) + 1]] <<- el\n  }\n  partition = function(code) {\n    code = split_chunk('r', code)\n    set_names(code[c('code', 'options', 'src')], c('source', 'options', 'comments'))\n  }\n  # detect #| and split a block of code into chunks\n  add_chunk = function(l1, l2) {\n    x = text[l1:l2]; N = length(x)\n    k = run_range(startsWith(x, '#| '))[1, ]\n    if ((n <- length(k)) == 0) return(add_block(l1, l2))\n    if (k[1] > 1) { k = c(1, k); n = n + 1 }  # make sure to scan from start\n    for (i in seq_len(n)) {\n      add_block(l1 - 1 + k[i], if (i == n) l2 else l1 - 1 + k[i + 1] - 1, pipe = TRUE)\n    }\n  }\n\n  set_error_handler(input)\n\n  i = 1\n  for (j in seq_len(nc)) {\n    i1 = r[1, j]; i2 = r[2, j]\n    if (i1 > i) add_chunk(i, i1 - 1)\n    add_block(i1, i2, type = 'text_block')\n    i = i2 + 1\n  }\n  if (i <= n) add_chunk(i, n)\n  # add possibly missing chunk labels\n  i = vapply(res, function(x) x$type == 'code_chunk', FALSE)\n  res[i] = .mapply(function(x, label) {\n    if (is.null(x$options$label)) x$options$label = label\n    x\n  }, res[i], sprintf('chunk-%d', seq_len(sum(i))))\n  res\n}\n\n# ranges of TRUE runs in a logical vector\nrun_range = function(x, extend = NULL) {\n  r = rle(x); l = r$lengths; i = r$values\n  i2 = cumsum(l); i1 = i2 - l + 1\n  i1 = i1[i]; i2 = i2[i]\n  # include adjacent blank lines, e.g., a blank line before or after #' should be doc\n  if (!is.null(extend)) {\n    k = extend[pmax(i1 - 1, 1)]  # check if previous line is empty\n    i1[k] = i1[k] - 1\n    k = extend[pmin(i2 + 1, length(extend))]  # check if next line is empty\n    i2[k] = i2[k] + 1\n    # merge adjacent runs, e.g., 1:2 and 2:4 -> 1:4\n    k = NULL\n    if ((n <- length(i1)) > 1) {\n      k = i2[1:(n - 1)] < i1[2:n]\n      i1 = i1[c(TRUE, k)]; i2 = i2[c(k, TRUE)]\n    }\n  }\n  rbind(i1, i2)\n}\n\n# convert knitr's inline `r code` to litedown's `{r} code`\nconvert_knitr = function(input) {\n  x = read_utf8(input)\n  r = '(?<!(^``))(?<!(\\n``))`r[ #]([^`]+)\\\\s*`'\n  i = prose_index(x)\n  x[i] = gsub(r, '`{r} \\\\3`', x[i], perl = TRUE)\n  write_utf8(x, input)\n}\n\n#' Get the `fuse()` context\n#'\n#' A helper function to query the [fuse()] context (such as the input file path\n#' or the output format name) when called inside a code chunk.\n#' @param item The name of the context item.\n#' @return If the `item` is provided, return its value in the context. If\n#'   `NULL`, the whole context (an environment) is returned.\n#' @export\n#' @examples\n#' litedown::get_context('input')\n#' litedown::get_context('format')\n#' names(litedown::get_context())  # all available items\nget_context = function(item = NULL) if (is.null(item)) .env else .env[[item]]\n\n# return a string to indicate the error location\nget_loc = function(label) {\n  l = .env$source_pos; n = length(l)\n  if (n == 4) l = sprintf('#%d:%d-%d:%d', l[1], l[2], l[3], l[4])  # row1:col1-row2:col2\n  if (n == 2) l = sprintf('#%d-%d', l[1], l[2])  # row1-row2\n  paste0(.env$input2 %|% .env$input, l, if (label != '') paste0(' [', label, ']'))\n}\n\n# save line numbers in .env to be used in error messages\nsave_pos = function(x) .env$source_pos = x\n\n# line/col info for ANSI links\nlink_pos = function() {\n  l = .env$source_pos\n  sprintf('line = %d:col = %d', l[1], if (length(l) == 4) l[2] else 1)\n}\n\n# get the execution order of code/text blocks via the `order` option (lower\n# values indicate earlier execution)\nblock_order = function(res, N = length(res)) {\n  check = function(b, env) {\n    if (is.null(o <- b$options[['order']])) return(NA)\n    if (is_lang(o)) o = eval(o, env)\n    if (length(o) == 1 && is.numeric(o)) return(o)\n    save_pos(b$pos %||% b$lines)\n    stop(\"The chunk option 'order' must be either NULL or a number.\", call. = FALSE)\n  }\n  x = seq_len(N); e = new.env(parent = fuse_env()); e$N = N\n  for (i in x) {\n    b = res[[i]]; e$i = i\n    if (b$type == 'code_chunk') {\n      if (!is.na(o <- check(b, e))) x[i] = o\n    } else if (is.list(b$source)) {\n      # use the first found `order` option in all inline expressions\n      for (s in b$source) if (is.list(s) && !is.na(o <- check(s, e))) {\n        x[i] = o; break\n      }\n    }\n  }\n  order(x)\n}\n\n#' @description The function `fuse()` runs code from code chunks and inline code\n#'   expressions in R Markdown, interweaves the results with the rest of text in\n#'   the input to intermediate Markdown output (which is similar to what\n#'   `knitr::knit()` does), and renders the Markdown output through `mark()` to\n#'   the final output format, such as HTML or LaTeX (similar to\n#'   `rmarkdown::render()`). It also works on R scripts in a way similar to\n#'   `knitr::spin()`. The function `fiss()` extracts code from the input, and is\n#'   similar to `knitr::purl()`.\n#' @rdname mark\n#' @param envir An environment in which the code is to be evaluated. It can be\n#'   accessed via [fuse_env()] inside [fuse()].\n#' @param quiet If `TRUE`, do not show the progress bar. If `FALSE`, the\n#'   progress bar will be shown after a number of seconds, which can be set via\n#'   a global [option][options] `litedown.progress.delay` (the default is `2`).\n#'   THe progress bar output can be set via a global option\n#'   `litedown.progress.output` (the default is [stderr()]).\n#' @note For `fuse()`, you can generate the intermediate Markdown output via\n#'   `output = '.md'` or `output = 'markdown'` without further calling `mark()`.\n#' @seealso [sieve()], for the syntax of R scripts to be passed to [fuse()].\n#' @export\n#' @examples\n#' library(litedown)\n#' doc = c('```{r}', '1 + 1', '```', '', '$\\\\pi$ = `{r} pi`.')\n#' fuse(doc)\n#' fuse(doc, '.tex')\n#' fiss(doc)\nfuse = function(input, output = NULL, text = NULL, envir = parent.frame(), quiet = FALSE) {\n  text = read_input(input, text); input = attr(text, 'input')\n  # determine if the input is R or R Markdown\n  if (r_input <- is_R(input, text)) {\n    blocks = sieve(input, text)\n    yaml = yaml_body(blocks[[1]]$source)$yaml\n  } else {\n    blocks = crack(input, text)\n    yaml = yaml_body(text)$yaml\n  }\n  full = is_output_full(output)\n  format = detect_format(output, yaml)\n  output = auto_output(input, output, format)\n  if (!is.null(output_base <- output_path(input, output)))\n    output_base = sans_ext(output_base)\n\n  opts = reactor()\n  # clean up the figure folder on exit if it's empty\n  on.exit(del_empty_dir({\n    if (dir.exists(fig.dir <- opts$fig.path)) fig.dir else dirname(fig.dir)\n  }), add = TRUE)\n\n  # restore and clean up some objects on exit\n  opts2 = as.list(opts); on.exit(reactor(opts2), add = TRUE)\n  oenv = as.list(.env); on.exit(reset_env(oenv, .env), add = TRUE)\n  # use default fig.path/cache.path instead of inheriting (#127)\n  nested = isTRUE(.env$in_fuse); .env$in_fuse = TRUE\n  if (nested) reactor(fig.path = NULL, cache.path = NULL)\n\n  # set working directory if unset\n  if (is_file(input) && is.null(opts$wd)) opts$wd = dirname(normalizePath(input))\n\n  # store output dir so we can calculate relative paths for plot files later\n  .env$wd_out = normalize_path(\n    if (is.null(output_base)) {\n      if (is.character(.env$wd_out)) .env$wd_out else '.'\n    } else dirname(output_base)\n  )\n  # store the environment and output format\n  .env$global = envir; .env$format = format\n\n  # set default device to 'cairo_pdf' for LaTeX output, and 'png' for other formats\n  if (is.null(opts$dev)) {\n    opts$dev = if (format == 'latex') 'cairo_pdf' else 'png'\n  }\n  # set default figure and cache paths\n  set_path = function(name) {\n    # fig.path = output__files/ if `output` is a path, otherwise use\n    # litedown__files/ (we don't use _files because of rstudio/rmarkdown#2550)\n    if (is.null(p <- opts[[name]])) p = aux_path(output_base %||% 'litedown', name)\n    slash = endsWith(p, '/')\n    # make sure path is absolute so it will be immune to setwd() (in code chunks)\n    if (is_rel_path(p)) {\n      p = file.path(getwd(), p)\n      # preserve trailing slash because file.path() removes it on Windows\n      if (slash) p = sub('/*$', '/', p)\n    }\n    opts[[name]] = p\n  }\n  set_path('fig.path'); set_path('cache.path')\n\n  .env$input = input\n  if (is_file(input)) .env$full_input = normalizePath(input)\n  res = .fuse(blocks, input, quiet)\n\n  # save timing data if necessary\n  timing_data()\n\n  # keep the markdown output if keep_md = TRUE is set in YAML output format\n  if (is_output_file(output) && isTRUE(yaml_field(yaml, format, 'keep_md'))) {\n    write_utf8(res, with_ext(output, '.md'))\n  }\n  fuse_output(input, output, res, full)\n}\n\n# default fig/cache path\naux_path = function(base = sans_ext(file), type, file) {\n  paste0(base, c(fig.path = '__files/', cache.path = '__cache/')[type])\n}\nfig_path = function(path) aux_path(, 'fig.path', path)\n\n# if output = '.md' or 'markdown', no need for further mark() conversion\nfuse_output = function(input, output, res, full = NULL) {\n  if (is.character(output) && grepl('[.]md$|^markdown$', output)) {\n    if (is_output_file(output)) {\n      write_utf8(res, output)\n    } else raw_string(res)\n  } else {\n    if (isTRUE(full)) attr(output, 'full') = TRUE\n    mark(input, output, res)\n  }\n}\n\n#' @rdname mark\n#' @export\nfiss = function(input, output = '.R', text = NULL) {\n  text = read_input(input, text); input = attr(text, 'input')\n  output = auto_output(input, output, NULL)\n  blocks = crack(input, text)\n  # TODO: what should we do for non-R code? also consider eval=FALSE and error=TRUE\n  res = uapply(blocks, function(b) {\n    if (b$type == 'code_chunk' && !isFALSE(b$options$purl) && b$options$engine == 'r')\n      c(b$source, '')\n  })\n  if (is_output_file(output)) write_utf8(res, output) else raw_string(res)\n}\n\n.fuse = function(blocks, input, quiet) {\n  n = length(blocks)\n  nms = vapply(blocks, function(x) x$options[['label']] %||% '', character(1))\n  names(blocks) = nms\n\n  # a simple progress indicator: we need to know how many spaces we need to wipe\n  # out previous progress text of the form:\n  # xxx% | input_file#line1-line2 [label]\n  # ...4..3          1     1     .2     1\n  p_len = 4 + 3 + sum(nchar(input)) + 1 +\n    (sum(nchar(sprintf('%d', blocks[[n]]$lines))) + 1) + 2 + max(nchar(nms)) + 1\n  p_clr = paste0('\\r', strrep(' ', p_len), '\\r')  # a string to clear the progress\n  p_out = getOption('litedown.progress.output', stderr())\n  p_yes = FALSE; t0 = Sys.time(); td = getOption('litedown.progress.delay', 2)\n  p_bar = function(x) {\n    if (!quiet && (p_yes || Sys.time() - t0 > td)) {\n      cat(x, sep = '', file = p_out)\n      p_yes <<- TRUE\n    }\n  }\n  # if error occurs, print error location with a clickable file link\n  k = 0  # when exiting, k should be n + 1\n  on_error = function() {\n    if (k > n) return()  # blocks have been successfully fused\n    p_bar(p_clr)\n    ansi_link(input)\n    message('Quitting from ', get_loc(nms[k]))\n  }\n  # suppress tidyverse progress bars and use cairo for bitmap devices (for\n  # smaller plot files and possible parallel execution)\n  opt = options(rstudio.notebook.executing = TRUE, bitmapType = bitmap_type())\n  on.exit({ options(opt); on_error() }, add = TRUE)\n\n  # the chunk option `order` determines the execution order of chunks\n  o = block_order(blocks, n)\n  res = character(n)\n  for (i in seq_len(n)) {\n    k = o[i]; b = blocks[[k]]; save_pos(b$lines)\n    p_bar(c(as.character(round((i - 1)/n * 100)), '%', ' | ', get_loc(nms[k])))\n    # record timing if requested\n    if (!isFALSE(time <- timing_path())) t1 = Sys.time()\n    res[k] = if (b$type == 'code_chunk') {\n      one_string(fuse_code(b, blocks))\n    } else {\n      one_string(fuse_text(b), '')\n    }\n    if (!isFALSE(time)) record_time(Sys.time() - t1, b$lines, nms[k])\n    p_bar(p_clr)\n  }\n  k = n + 1\n  res\n}\n\n# add ANSI link on input path if supported\nansi_link = function(x) {\n  if (length(x) && isTRUE(as.logical(Sys.getenv('RSTUDIO_CLI_HYPERLINKS'))))\n    .env$input2 = sprintf(\n      '\\033]8;%s;file://%s\\a%s\\033]8;;\\a', link_pos(), normalize_path(x), x\n    )\n}\n\n# use options(bitmapType = 'cairo') for bitmap devices on macOS if possible\nbitmap_type = function() {\n  # xquartz has to be installed for cairo to work (I don't know why)\n  if (xfun::is_macos() && capabilities('cairo') && Sys.which('xquartz') != '')\n    'cairo' else .Options$bitmapType\n}\n\ntime_frame = function(s = NA_character_, l = integer(), lab = NA_character_, t = NA_real_) {\n  data.frame(source = s, line1 = l[1], line2 = l[2], label = lab, time = t)\n}\n\nrecord_time = function(x, lines, label) {\n  x = as.numeric(x)\n  gc(FALSE)\n  d = time_frame(.env$input %||% '#text', lines, label, x)\n  .env$time = append(.env$time, list(d))\n}\n\n#' Get the timing data of code chunks and text blocks in a document\n#'\n#' Timing can be enabled via the chunk option `time = TRUE` (e.g., set\n#' [litedown::reactor]`(time = TRUE)` in the first code chunk). After it is\n#' enabled, the execution time for code chunks and text blocks will be recorded.\n#' This function can be called to retrieve the timing data later in the document\n#' (e.g., in the last code chunk).\n#' @param threshold A number (time in seconds) to subset data with. Only rows\n#'   with time above this threshold are returned.\n#' @param sort Whether to sort the data by time in the decreasing order.\n#' @param total Whether to append the total time to the data.\n#' @note By default, the data will be cleared after each call of [fuse()] and\n#'   will not be available outside [fuse()]. To store the data persistently, you\n#'   can set the `time` option to a file path. This is necessary if you want to\n#'   get the timing data for multiple input documents (such as all chapters of a\n#'   book). Each document needs to point the `time` option to the same path.\n#'   When you do not need timing any more, you will need to delete this file by\n#'   yourself.\n#' @return A data frame containing input file paths, line numbers, chunk labels,\n#'   and time. If no timing data is available, `NULL` is returned.\n#' @export\ntiming_data = function(threshold = 0, sort = TRUE, total = TRUE) {\n  d = .env$time\n  if (!is.null(d)) d = do.call(rbind, d)\n\n  if (is.character(path <- timing_path())) {\n    if (file_exists(path)) {\n      d2 = readRDS(path)\n      if (length(input <- .env$input)) d2 = subset(d2, source != input)\n      d = rbind(d2, d)\n    }\n    saveRDS(d, path)\n  }\n  if (is.null(d)) return(invisible(d))\n\n  total = if (total) sum(d$time)\n  # add edit links in the roam() mode\n  if (is_roaming() && !all(i <- d$source == '#text')) {\n    d$source = ifelse(i, '', sprintf(\n      '%s [&#9998;](?path=%s&line=%d)', d$source, URLencode(d$source, TRUE), d$line1\n    ))\n  }\n  d = d[d$time > threshold, ]\n  if (sort) d = d[order(d$time, decreasing = TRUE), ]\n  if (!is.null(total)) d = rbind(d, time_frame('Total', t = total))\n  d\n}\n\ntiming_path = function() {\n  p = xfun::env_option('litedown.time')\n  if (is.character(p) && tolower(p) %in% c('true', 'false')) p = as.logical(p)\n  if (is.logical(p) || (is.character(p) && p != '')) p else reactor('time')\n}\n\n# an internal function for RStudio IDE to recognize the custom knit function\n# when users hit the Knit button\nknit = function(input, ...) fuse(input, envir = parent.frame())\n\nfuse_code = function(x, blocks) {\n  # merge local chunk options into global options\n  old = reactor(x$options); on.exit(reactor(old), add = TRUE)\n  opts = reactor()\n\n  # delayed assignment to evaluate a chunk option only when it is actually used\n  lapply(setdiff(names(opts), 'order'), function(i) {\n    # skip the `order` option since it needs to be eval()ed in block_order()\n    if (i != 'order' && is_lang(o <- opts[[i]]))\n      delayedAssign(i, eval(o, fuse_env()), environment(), opts)\n  })\n  # set the working directory before evaluating anything else\n  change_wd(opts$wd)\n\n  # fuse child documents (empty the `child` option to avoid infinite recursion)\n  if (length(opts$child)) return(uapply(reactor(child = NULL)$child, function(.) {\n    child = .env$child; .env$child = TRUE; on.exit(.env$child <- child)\n    fuse(., output = 'markdown', envir = fuse_env(), quiet = TRUE)\n  }))\n\n  lab = opts$label; lang = opts$engine\n\n  # the source code could be from chunk options 'file' or 'code'\n  test_source = function(name) {\n    if (length(opts[[name]]) == 0) return(FALSE)\n    if (cond <- length(x$source) > 0) warning(\n      \"The chunk option '\", name, \"' is ignored for the non-empty chunk:\\n\\n\",\n      one_string(x$source)\n    )\n    !cond\n  }\n  if (test_source('file')) {\n    x$source = read_all(opts$file)\n  } else if (test_source('code')) {\n    x$source = opts$code\n  } else {\n    # use code from other chunks of the same label\n    labs = if (length(x$source) == 0) which(names(blocks) == lab)\n    if (length(labs)) x$source = uapply(blocks[labs], `[[`, 'source')\n  }\n\n  # resolve inline chunk references and do code interpolation\n  x$source = fill_source(x$source, opts$fill, blocks)\n\n  res = if (isFALSE(opts$eval)) list(new_source(x$source)) else {\n    if (is.function(eng <- engines(lang))) eng(x) else list(\n      new_source(x$source),\n      new_warning(sprintf(\"The engine '%s' is not supported.\", lang))\n    )\n  }\n\n  if (!opts$include) return('')\n\n  # if the engine result contains new chunk options, apply the new options, and\n  # make sure they will be properly restored via `old` on exit\n  if (length(opts_new <- attr(res, 'options'))) {\n    old2 = reactor(opts_new)\n    for (i in setdiff(names(old2), names(old))) old[i] = old2[i]\n  }\n\n  # decide the number of backticks to wrap up output\n  fence = xfun::make_fence(c(unlist(res), x$fences))\n\n  # deal with figure alt text, captions, and environment\n  env = opts$fig.env; alt = opts$fig.alt; cap = opts$fig.cap\n  att = if (is.null(att <- opts$attr.plot)) '' else paste0('{', att, '}')\n  if (is.null(alt)) alt = cap\n  p1 = Filter(function(x) !is_plot(x), res)\n  p2 = Filter(is_plot, res)\n  p3 = unlist(p2)  # vector of plot paths\n  # get the relative path of the plot directory\n  fig.dir = if (length(p3)) tryCatch(\n    sub('^[.]/', '', paste0(dirname(relative_path(p3[1], .env$wd_out)), '/')),\n    error = function(e) NULL\n  )\n\n  # record plot paths so they can be cleaned up if option embed_cleanup = true;\n  # however, when cache = true, we shouldn't clean up plots since they won't be\n  # regenerated next time (then they won't be found)\n  if (!isTRUE(opts$cache)) .env$plot_files = c(.env$plot_files, p3)\n  # recycle alt and attributes for all plots\n  pn = length(p3)\n  if (pn && is.null(alt)) {\n    # reminder about missing alt text if this option is set to TRUE\n    if (getOption('litedown.fig.alt', FALSE)) message(\n      \"\\nPlease provide a 'fig.alt' option to the code chunk at \", get_loc(lab)\n    )\n    alt = ''\n  }\n  alt = rep(alt, length.out = pn)\n  att = rep(att, length.out = pn)\n  # if figure caption is provided, merge all plots in one env\n  if (pn && length(cap)) res = c(xfun:::merge_record(p1), list(new_plot(p3)))\n  i = 0  # plot counter\n\n  res_show = opts$results  # normalize the 'results' option\n  if (is.logical(res_show)) res_show = if (res_show) 'markup' else 'hide'\n\n  l1 = x$code_start  # starting line number of the whole code chunk\n  # filter the results if desired\n  if (!is.null(opts$filter)) res = match.fun(opts$filter)(res)\n  # generate markdown output\n  out = lapply(res, function(x) {\n    type = grep_sub('^record_', '', class(x))[1]\n    if (is.na(type)) type = 'output'\n    if (type == 'source') {\n      if (!opts$echo) return()\n      l2 = attr(x, 'lines')[1]  # starting line number of a code block\n      x = one_string(x)\n      if (opts$strip.white) x = trim_blank(x)\n    }\n    asis = if (type %in% c('output', 'asis')) {\n      if (res_show == 'hide') return()\n      any(c(res_show, type) == 'asis')\n    } else FALSE\n    if (type == 'warning' && !isTRUE(opts$warning)) return()\n    if (type == 'message' && !isTRUE(opts$message)) return()\n    if (type == 'plot') {\n      n = length(x); i2 = i + seq_len(n); i <<- i + n\n      img = sprintf(\n        '![%s](<%s>)%s', alt[i2],\n        if (is.null(fig.dir)) x else gsub('^.*/', fig.dir, x), att[i2]\n      )\n      add_cap(img, cap, lab, opts$cap.pos, env)\n    } else {\n      a = opts[[paste0('attr.', type)]]\n      if (type == 'source') {\n        # use engine name as class name when `a` is not provided\n        if (is.null(a)) a = paste0('.',  lang)\n        # add line numbers\n        if (is_roaming()) a = c(a, lineno_attr(NA, l1 + l2 - 1))\n      } else {\n        if (type == 'message') x = sub('\\n$', '', x)\n        if (!asis) {\n          opt2 = attr(x, 'opts')\n          cmt = opts$comment %||% opt2$comment %||% '#> '\n          if (cmt != '') {\n            x = split_lines(x)\n            x = paste0(cmt, x)  # comment out text output\n          }\n          if (is.null(a)) if (!is.null(a <- opt2$attr)) a = c(a, '.plain')\n        }\n      }\n      if (asis) {\n        if (is.null(a)) x else fenced_div(x, a)\n      } else fenced_block(x, a, fence)\n    }\n  })\n  out = dropNULL(out)\n\n  # collapse a code block without attributes into previous adjacent code block\n  # (also try to collapse for results = 'hide')\n  collapse = isTRUE(opts$collapse) || res_show == 'hide'\n  if (collapse && (n <- length(out)) > 1) {\n    i1 = 1; k = NULL  # indices of elements to be removed from `out`\n    for (i in 2:n) {\n      if (i - i1 > 1) i1 = i - 1  # make sure blocks are adjacent\n      o1 = out[[i1]]; n1 = length(o1); e1 = o1[n1]\n      # previous block should have a closing fence ```+\n      if (n1 < 3 || !grepl('^```+$', e1)) next\n      o2 = out[[i]]\n      if (continue_block(o1[2], e1, head(o2, 2))) {\n        out[[i]] = c(o1[-n1], o2[-(1:2)])\n        k = c(k, i1)  # merge previous block into current and remove previous\n      }\n    }\n    if (length(k)) out = out[-k]\n  }\n\n  a = opts$attr.chunk\n  if (length(x$fences) == 2) {\n    # add a class name to the chunk output so we can style it differently\n    a = c(a, '.fenced-chunk')\n    out = add_fences(out, x, fence)\n  }\n  out = unlist(out)\n  if (!is.null(a)) out = fenced_div(out, a)\n  # if first line of chunk output is empty, remove it (the chunk should have had\n  # an empty line before it in the source)\n  if (length(out) && out[1] == '') out = out[-1]\n  # add prefix (possibly indentation and > quote chars)\n  if (!is.null(x$prefix)) out = gsub('^|(?<=\\n)', x$prefix, out, perl = TRUE)\n  out\n}\n\n# resolve `<label>` to chunk source, and evaluate `{code}` to string (to\n# interpolate original source)\nfill_source = function(x, fill, blocks) {\n  if (isFALSE(fill)) return(x)\n  x = fill_label(x, blocks)\n  fill_code(x, fill)\n}\n\nfill_label = function(x, blocks) {\n  r = '`<(.+?)>`'\n  for (i in grep(r, x)) {\n    ind = sub('^(\\\\s*).*', '\\\\1', x[i])  # possible indent\n    x[i] = match_replace(x[i], r, function(z) {\n      labs = sub(r, '\\\\1', z)  # chunk label\n      j = labs %in% names(blocks)\n      if (any(j)) z[j] = uapply(blocks[labs[j]], function(b) {\n        s = b$source\n        if ((n <- length(s)) > 0) {\n          paste0(c('', rep(ind, n - 1)), s, collapse = '\\n')\n        } else ''\n      })\n      z\n    })\n  }\n  # recursion for possible more `<label>` markers\n  if (is.null(i)) x else fill_label(split_lines(x), blocks)\n}\n\nfill_code = function(x, fill) {\n  r = '`\\\\{(.+?)}`'\n  match_replace(x, r, function(z) {\n    code = sub(r, '\\\\1', z)\n    uapply(code, function(s) {\n      v = eval_code(s)\n      if (is.function(fill)) v = fill(v)\n      one_string(v)\n    })\n  })\n}\n\n# temporarily change the working directory inside a function call\nchange_wd = function(dir) {\n  if (is.character(dir)) {\n    owd = setwd(dir); exit_call(function() setwd(owd))\n  }\n}\n\n# add caption to an element (e.g., figure/table)\nadd_cap = function(x, cap, lab, pos, env, type = 'fig') {\n  if (length(cap) == 0 || length(lab) == 0) return(x)\n  cap = fenced_div(add_ref(lab, type, cap), '.caption')\n  pos = pos %||% 'bottom'\n  x = if (pos == 'top') c(cap, '', x) else c(x, '', cap)\n  fenced_div(x, c(sub('^[.]?', '.', env), sprintf('#%s:%s', type, lab)))\n}\n\n# if original chunk header contains multiple curly braces (e.g., ```{{lang}}),\n# include chunk fences in the output (and also pipe comments if exist)\nadd_fences = function(out, x, fence) {\n  # remove last line of pipe comments if empty\n  if ((n <- length(o <- x$comments)) > 1 && o[n] == '') o = o[-n]\n  fences = list(c(x$fences[1], o), x$fences[2])\n  append(lapply(fences, fenced_block, c('.md', '.code-fence'), fence), out, 1)\n}\n\n# attributes for code blocks with line numbers\nlineno_attr = function(lang = NA, start = 1, auto = TRUE) c(\n  if (!is.na(lang)) paste0('.', sub('^[rq]md$', 'md', tolower(lang))),\n  '.line-numbers', if (auto) '.auto-numbers', sprintf('data-start=\"%d\"', start)\n)\n\n# two blocks are continuous if first 2 elements of next block are '' and\n# previous block's closing or opening fence (after removing data-start attribute)\ncontinue_block = function(e1_open, e1_end, e2) {\n  if (length(e2) != 2 || e2[1] != '') return(FALSE)\n  if ((e2_open <- e2[2]) == e1_end) return(TRUE)\n  e3 = sub(' data-start=\"[0-9]+\"', '', c(e1_open, e2_open))\n  if (e3[1] == e3[2]) return(TRUE)\n  # remove attributes of message blocks and see if they can be collapsed\n  sub(' \\\\{[.]plain [.](message|warning|error)}', '', e2_open) == e1_end\n}\n\nnew_source = function(x) {\n  len = length(x)\n  structure(new_record(x, 'source'), lines = if (len) c(1L, len) else c(0L, 0L))\n}\nnew_output = function(x) new_record(x, 'output')\nnew_warning = function(x) new_record(x, 'warning')\nnew_plot = function(x) new_record(x, 'plot')\nnew_asis = function(x, raw = FALSE) {\n  res = new_record(x, 'asis')\n  if (raw) raw_string(res) else res\n}\n\n# interleave text and plot output, e.g., t t t p p p -> t p t p t p, or t t t t\n# p p -> t t p t t p\ninterleave = function(res) {\n  p = match(c('record_output', 'record_plot'), sapply(res, function(x) class(x)[1]))\n  if (any(is.na(p))) {\n    warning('Both text and plot output must be present in results to be interleaved.')\n    return(res)\n  }\n  x1 = res[[p[1]]]; n1 = length(x1)\n  x2 = res[[p[2]]]; n2 = length(x2)\n  n = n1 / n2\n  if (n < 1) return(res)\n  if (n1 %% n2 != 0) stop(\n    'Length of text output (', n1, ') is not multiple of the number of plots (', n2, ').'\n  )\n  # interleave text and plot output: one (chunk of) text followed by one plot\n  mix = unlist(lapply(seq_len(n2), function(i) {\n    list(new_output(x1[(i - 1) * n + 1:n]), new_plot(x2[i]))\n  }), recursive = FALSE)\n  append(res[-p], mix, p[1] - 1)\n}\n\n#' Mark a character vector as raw output\n#'\n#' This function should be called inside a code chunk, and its effect is the\n#' same as the chunk option `results = \"asis\"`. The input character vector will\n#' be written verbatim to the output (and interpreted as Markdown).\n#' @param x A character vector (each element will be treated as a line).\n#' @param format An output format name, e.g., `html` or `latex`. If provided,\n#'   `x` will be wrapped in a fenced code block, e.g., ```` ```{=html}````.\n#' @return A character vector with a special class to indicate that it should be\n#'   treated as raw output.\n#' @export\n#' @examples\n#' litedown::raw_text(c('**This**', '_is_', '[Markdown](#).'))\n#' litedown::raw_text('<b>Bold</b>', 'html')\n#' litedown::raw_text('\\\\textbf{Bold}', 'latex')\nraw_text = function(x, format = NULL) {\n  if (length(fmt <- sprintf('=%s', format)) == 1) x = fenced_block(x, fmt)\n  new_asis(x, TRUE)\n}\n\nis_plot = function(x) inherits(x, 'record_plot')\n\nfuse_text = function(x) {\n  if (is.character(src <- x$source)) return(one_string(src))\n  res = lapply(src, function(s) {\n    if (is.character(s)) s else exec_inline(s)\n  })\n  unlist(res)\n}\n\nexec_inline = function(x) {\n  save_pos(x$pos)\n  o = reactor(x$options); on.exit(reactor(o), add = TRUE)\n  opts = reactor()\n  if (isFALSE(opts$eval)) return('')\n  change_wd(opts$wd)\n  lang = x$options$engine\n  if (is.function(eng <- engines(lang))) {\n    one_string(eng(x, inline = TRUE))\n  } else {\n    warning(\"The inline engine '\", lang, \"' is not supported.\")\n    sprintf('`{%s} %s`', lang, x$source)\n  }\n}\n\nfmt_inline = function(x, ...) {\n  if (is.numeric(x) && length(x) == 1 && !inherits(x, 'AsIs'))\n    sci_num(x, ...) else as.character(x)\n}\n\n# change scientific notation to LaTeX math\nsci_num = function(x, math = NULL) {\n  opts = reactor()\n  s = x != 0 && abs(log10(abs(x))) >= opts$power\n  x = format(signif(x, opts$signif), scientific = s)\n  r = '^(-)?([0-9.]+)e([-+])0*([0-9]+)$'\n  s = grepl(r, x)\n  if (s) {\n    n = match_one(x, r)[[1]]\n    x = sprintf(\n      '%s%s10^{%s%s}', n[2], if (n[3] == '1') '' else paste(n[3], '\\\\times '),\n      if (n[4] == '+') '' else n[4], n[5]\n    )\n  }\n  if (is.na(d <- opts$dollar)) d = s && !isTRUE(math)\n  if (d) paste0('$', x, '$') else x\n}\n\n# similar to the base R options() interface but for litedown options / engines /\n# ..., and is based on environments, which are *mutable*\nnew_opts = function() {\n  # global chunk options\n  .opts = structure(new_env(), class = c('litedown_env', 'environment'))\n\n  opt_get = function(x, drop = length(x) == 1) {\n    vs = mget(x, .opts, ifnotfound = list(NULL))\n    if (drop) vs[[1]] else vs\n  }\n  # setter: fun(opt = val); getter: fun('opt')\n  function(...) {\n    v = list(...)\n    n = length(v)\n    if (n == 0) return(.opts)\n    if (is.null(nms <- names(v))) {\n      if (all(vapply(v, is.character, TRUE))) return(opt_get(unlist(v)))\n      if (n > 1) warning(\n        'When not all unnamed arguments are character, only the first argument is used (',\n        n, ' were received).'\n      )\n      if (is.null(nms <- names(v <- v[[1]])) || any(nms == '')) stop(\n        'When the first unnamed argument is not character, it must be a named list.'\n      )\n    }\n    if (any(nms == '')) stop('All arguments must be either named or unnamed.')\n    old = opt_get(nms, drop = FALSE)\n    for (i in nms) assign(i, v[[i]], envir = .opts)\n    invisible(old)\n  }\n}\n\n#' Get and set chunk options\n#'\n#' Chunk options are stored in an environment returned by `reactor()`. Option\n#' values can be queried by passing their names to `reactor()`, and set by\n#' passing named values.\n#' @param ... Named values (for setting) or unnamed values (for getting).\n#' @return With no arguments, `reactor()` returns an environment that stores the\n#'   options, which can also be used to get or set options. For example, with\n#'   `opts = reactor()`, `opts$name` returns an option value, and `opts$name =\n#'   value` sets an option to a value.\n#'\n#'   With named arguments, `reactor()` sets options and returns a list of their\n#'   old values (e.g., `reactor(echo = FALSE, fig.width = 8)`). The returned\n#'   list can be passed to `reactor()` later to restore the options.\n#'\n#'   With unnamed arguments, `reactor()` returns option values after received\n#'   option names as input. If one name is received, its value is returned\n#'   (e.g., `reactor('echo')`). If multiple names are received, a named list of\n#'   values is returned (e.g., `reactor(c('echo', 'fig.width'))`). A special\n#'   case is that if only one unnamed argument is received and it takes a list\n#'   of named values, the list will be used to set options, e.g.,\n#'   `reactor(list(echo = FALSE, fig.width = 8))`, which is equivalent to\n#'   `reactor(echo = FALSE, fig.width = 8)`.\n#' @export\n#' @examples\n#' # get options\n#' litedown::reactor('echo')\n#' litedown::reactor(c('echo', 'fig.width'))\n#'\n#' # set options\n#' old = litedown::reactor(echo = FALSE, fig.width = 8)\n#' litedown::reactor(c('echo', 'fig.width'))\n#' litedown::reactor(old)  # restore options\n#'\n#' # use the environment directly\n#' opts = litedown::reactor()\n#' opts$echo\n#' mget(c('echo', 'fig.width'), opts)\n#' ls(opts)  # built-in options\nreactor = new_opts()\nreactor(\n  eval = TRUE, echo = TRUE, fill = TRUE, results = TRUE, comment = NULL,\n  warning = TRUE, message = TRUE, error = NA, include = TRUE,\n  strip.white = TRUE, collapse = FALSE, order = 0,\n  attr.source = NULL, attr.output = NULL, attr.plot = NULL, attr.chunk = NULL,\n  attr.message = '.plain .message', attr.warning = '.plain .warning', attr.error = '.plain .error',\n  cache = FALSE, cache.path = NULL,\n  dev = NULL, dev.args = NULL, fig.path = NULL, fig.ext = NULL, fig.keep = TRUE,\n  fig.width = 7, fig.height = 7, fig.dim = NULL, fig.cap = NULL, fig.alt = NULL, fig.env = '.figure',\n  tab.cap = NULL, tab.env = '.table', cap.pos = NULL,\n  print = NULL, print.args = NULL, time = FALSE,\n  code = NULL, file = NULL, child = NULL, purl = TRUE,\n  wd = NULL,\n  signif = 3, power = 6, dollar = NA\n)\n\neval_code = function(code, error = NA) {\n  expr = parse_only(code)\n  if (is.na(error)) eval(expr, fuse_env()) else tryCatch(\n    eval(expr, fuse_env()), error = function(e) if (error) e$message else ''\n  )\n}\n\n# the R engine\neng_r = function(x, inline = FALSE, ...) {\n  opts = reactor()\n  if (inline) {\n    res = eval_code(x$source, opts$error)\n    return(fmt_inline(res, x$math))\n  }\n  args = reactor(\n    'fig.path', 'fig.ext', 'fig.keep', 'dev', 'dev.args', 'message', 'warning', 'error',\n    'cache', 'print', 'print.args', 'verbose'\n  )\n  if (is.character(args$fig.path)) args$fig.path = paste0(args$fig.path, opts$label)\n  size = list(width = opts$fig.width, height = opts$fig.height)\n  # if fig.dim is provided, override fig.width and fig.height\n  if (length(dm <- opts$fig.dim) == 2) size[] = as.list(dm)\n  # map chunk options to record() argument names\n  names(args)[1:3] = c('dev.path', 'dev.ext', 'dev.keep')\n  args = dropNULL(args)\n  args$dev.args = merge_list(size, opts$dev.args)\n  args$cache = list(\n    path = if (args$cache) opts$cache.path, vars = opts$cache.vars,\n    hash = opts$cache.hash, extra = opts$cache.extra, keep = opts$cache.keep,\n    id = opts$label, rw = opts$cache.rw\n  )\n  do.call(xfun::record, c(list(code = x$source, envir = fuse_env()), args))\n}\n\n# the Markdown engine: echo Markdown source verbatim, and also output it as-is\neng_md = function(x, inline = FALSE, ...) {\n  s = x$source\n  if (inline) {\n    f = unlist(match_all(s, '`+'))  # how many backticks to quote the text?\n    f = if (length(f)) paste0('`', max(f)) else '`'\n    one_string(c(f, s, f, s), ' ')\n  } else list(new_source(s), new_asis(s))\n}\n\n# the Mermaid engine: put code in ```mermaid and add caption if necessary\neng_mermaid = function(x, inline = FALSE, ...) {\n  code = fenced_block(src <- x$source, 'mermaid')\n  opts = reactor()\n  code = add_cap(code, opts$fig.cap, opts$label, opts$cap.pos, opts$fig.env)\n  if (inline) one_string(c(code, '')) else {\n    list(new_source(src), new_asis(code))\n  }\n}\n\n# embed a file verbatim\neng_embed = function(x, ...) {\n  s = x$source; opts = reactor()\n  # if chunk option `file` is empty, use source code as the list of files\n  if (is.null(f <- opts$file)) {\n    f = gsub('^[\"\\']|[\"\\']$', '', s)  # in case paths are quoted\n    if (length(f) == 0) return()\n    s = read_all(f)\n  }\n  opts_new = list(comment = '')  # don't comment out file content\n  # use the filename extension as the default language name\n  if (is.null(opts$attr.output) && nchar(lang <- file_ext(f[1])) > 1) {\n    lang = sub('^R', '', lang)  # Rmd -> md, Rhtml -> html, etc.\n    if (lang == 'nw') lang = 'tex'\n    opts_new$attr.output = paste0('.', lang)\n  }\n  structure(list(new_output(s)), options = opts_new)\n}\n\neng_html = function(x, inline = FALSE, html = NULL) {\n  out = fenced_block(html, '=html')\n  if (inline) one_string(c(out, '')) else list(new_source(x$source), new_asis(out))\n}\n\neng_css = function(x, inline = FALSE, ...) {\n  if (is.character(h <- reactor('href'))) {\n    res = sprintf('<link rel=\"stylesheet\" href=\"%s\">', h)\n    if (inline) one_string(res) else list(new_asis(res))\n  } else {\n    eng_html(x, inline, c('<style type=\"text/css\">', x$source, '</style>'))\n  }\n}\n\neng_js = function(x, inline = FALSE, ...) {\n  opts = reactor()\n  a = list(type = opts$type, src = opts$src)\n  for (i in c('type', 'src')) a[[i]] = a[[i]]  # remove NULL\n  if (is.character(s <- a$src)) {\n    if (!isFALSE(opts$defer)) a['defer'] = list(NULL)\n    res = html_tag('script', NULL, a)\n    if (inline) one_string(res) else list(new_asis(res))\n  } else {\n    if (identical(a$type, 'module')) a$defer = NULL\n    eng_html(x, inline, html_tag('script', html_value(x$source), a))\n  }\n}\n\n#' Language engines\n#'\n#' Get or set language engines for evaluating code chunks and inline code.\n#'\n#' An engine function should have three arguments:\n#'\n#' - `x`: An element in the [crack()] list (a code chunk or a text block).\n#'\n#' - `inline`: It indicates if `x` is from a code chunk or inline code.\n#'\n#' - `...`: Currently unused but recommended for future compatibility (more\n#'   arguments might be passed to the function).\n#'\n#' The function should return a character value.\n#' @inheritParams reactor\n#' @return The usage is similar to [reactor()]: `engines('LANG')` returns an\n#'   engine function for the language `LANG`, and `engines(LANG = function(x,\n#'   inline = FALSE, ...) {})` sets the engine for a language.\n#' @export\n#' @examples\n#' litedown::engines()  # built-in engines\nengines = new_opts()\nengines(\n  r = eng_r, md = eng_md, mermaid = eng_mermaid, embed = eng_embed,\n  css = eng_css, js = eng_js\n)\n\n#' @export\nprint.litedown_env = function(x, ...) {\n  str(as.list(x, all.names = TRUE, sorted = TRUE), ...)\n  invisible(x)\n}\n"
  },
  {
    "path": "R/mark.R",
    "content": "#' Render Markdown, R Markdown, and R scripts\n#'\n#' The function `mark()` renders Markdown to an output format via the\n#' \\pkg{commonmark} package.\n#' @param input A character vector to provide the input file path or text. If\n#'   not provided, the `text` argument must be provided instead. The `input`\n#'   vector will be treated as a file path if it is a single string, and points\n#'   to an existing file or has a filename extension. In other cases, the vector\n#'   will be treated as the `text` argument input. To avoid ambiguity, if a\n#'   string should be treated as `text` input when it happens to be an existing\n#'   file path or has an extension, wrap it in [I()], or simply use the `text`\n#'   argument instead.\n#' @param output An output file path or a filename extension (e.g., `.html`,\n#'   `.tex`, `.xml`, `.man`, `.markdown`, or `.txt`). In the latter case, the\n#'   output file path will use the extension on the same base filename as the\n#'   input file if the `input` is a file. If `output` is not character (e.g.,\n#'   `NA`), the results will be returned as a character vector instead of being\n#'   written to a file. If `output` is `NULL` or an extension, and the input is\n#'   a file path, the output file path will have the same base name as the input\n#'   file, with an extension corresponding to the output format. The output\n#'   format is retrieved from the first value in the `output` field of the YAML\n#'   metadata of the `input` (e.g., `html` will generate HTML output). The\n#'   `output` argument can also take an output format name (possible values are\n#'   `html`, `latex`, `xml`, `man`, `commonmark`, and `text`). If no output\n#'   format is detected or provided, the default is HTML.\n#' @param text A character vector as the text input. By default, it is read from\n#'   the `input` file if provided.\n#' @param options Options to be passed to the renderer. See [markdown_options()]\n#'   for details. This argument can take either a character vector of the form\n#'   `\"+option1 option2-option3\"` (use `+` or a space to enable an option, and\n#'   `-` to disable an option), or a list of the form `list(option1 = value1,\n#'   option2 = value2, ...)`. A string `\"+option1\"` is equivalent to\n#'   `list(option1 = TRUE)`, and `\"-option2\"` means `list(option2 = FALSE)`.\n#'   Options that do not take logical values must be specified via a list, e.g.,\n#'   `list(width = 30)`.\n#' @param meta A named list of metadata. Elements in the metadata will be used\n#'   to fill out the template by their names and values, e.g., `list(title =\n#'   ...)` will replace the `$title$` variable in the template. See the Section\n#'   \\dQuote{YAML metadata} [in the\n#'   documentation](https://yihui.org/litedown/#sec:yaml-metadata) for supported\n#'   variables.\n#' @return The output file path if output is written to a file, otherwise a\n#'   character vector of the rendered output (wrapped in [xfun::raw_string()]\n#'   for clearer printing).\n#' @seealso The spec of GitHub Flavored Markdown:\n#'   <https://github.github.com/gfm/>\n#' @import utils\n#' @export\n#' @examples\n#'\n#' mark(c('Hello _World_!', '', 'Welcome to **litedown**.'))\n#' # if input appears to be a file path but should be treated as text, use I()\n#' mark(I('This is *not* a file.md'))\n#' # that's equivalent to\n#' mark(text = 'This is *not* a file.md')\n#'\n#' # output to a file\n#' (mark('_Hello_, **World**!', output = tempfile()))\n#'\n#' # convert to other formats\n#' mark('Hello _World_!', '.tex')\n#' mark('Hello _**`World`**_!', 'xml')\n#' mark('Hello _**`World`**_!', 'text')\nmark = function(input, output = NULL, text = NULL, options = NULL, meta = list()) {\n  text = read_input(input, text); input = attr(text, 'input')\n  part = yaml_body(text)\n  yaml = part$yaml; yaml2 = yaml_text(part, text)  # unparsed YAML\n  text = part$body\n\n  full = is_output_full(output)\n  format = detect_format(output, yaml)\n  output = auto_output(input, output, format)\n  out_dir = dirname(output_path(input, output) %||% '.')\n\n  # title/author/date can be provided as top-level YAML options\n  meta = merge_list(\n    get_option('meta', format),\n    yaml[intersect(names(yaml), top_meta)],\n    yaml_field(yaml, format),\n    list(generator = I(paste('litedown', packageVersion('litedown')))),\n    meta\n  )\n  meta = normalize_meta(meta)\n\n  render_fun = tryCatch(\n    getFromNamespace(paste0('markdown_', tolower(format)), 'commonmark'),\n    error = function(e) {\n      stop(\"Output format '\", format, \"' is not supported in commonmark.\")\n    }\n  )\n\n  options = merge_list(yaml_field(yaml, format, 'options'), option2list(options))\n  options = normalize_options(options, format)\n  options$extensions = intersect(\n    names(Filter(isTRUE, options)), commonmark::list_extensions()\n  )\n\n  # build PDF for LaTeX output when the output file is .pdf or latex_engine is specified\n  is_pdf = is_output_file(output) && format == 'latex' &&\n    (is.character(latex_engine <- yaml_field(yaml, format, 'latex_engine')) ||\n       file_ext(output) == 'pdf')\n\n  # whether to write YAML metadata to output\n  keep_yaml = isTRUE(options[['keep_yaml']])\n\n  # if keep_yaml or format is not html/latex, don't use template; otherwise\n  # check the `template` value in litedown::(html|latex)_format in YAML\n  template = if (keep_yaml || !format %in% c('html', 'latex')) FALSE else\n    yaml_field(yaml, format, 'template')\n  # if not set there, check global option; if not set, disable template if no\n  # YAML was provided (i.e., generate a fragment)\n  if (is.null(template))\n    template = get_option('template', format, full || 'yaml' %in% names(part) || is_pdf)\n  # template = FALSE means no template; other values mean the default template\n  if (!is.character(template)) template = if (!isFALSE(template))\n    pkg_file('resources', sprintf('litedown.%s', format))\n\n  render_args = options[intersect(names(formals(render_fun)), names(options))]\n  render = function(x, clean = FALSE) {\n    if (length(x) == 0) return(x)\n    res = do.call(render_fun, c(list(text = x), render_args))\n    if (clean) res = sans_p(res)\n    I(res)\n  }\n\n  if (isTRUE(options[['smartypants']])) text = smartypants(text)\n\n  # test if a feature needs to be enabled\n  test_feature = function(name, pattern) {\n    isTRUE(options[[name]]) && format %in% c('html', 'latex') &&\n      length(grep(pattern, text, perl = TRUE))\n  }\n\n  # protect $ $ and $$ $$ math expressions for html/latex output\n  if (has_math <- test_feature('latex_math', '[$]')) {\n    id = id_string(text); maths = NULL\n    text = xfun::protect_math(text, id)\n    if (has_math <- any(grepl(paste0('`', id), text, fixed = TRUE))) {\n      # temporarily replace math expressions with tokens so render() won't seem\n      # them (to avoid issues like #33) and restore them later\n      text = one_string(text)\n      text = match_replace(text, sprintf('`%s(?s).{3,}?%s`', id, id), function(x) {\n        # replace math with !id-n-id! where n is the index of the math\n        tokens = sprintf('!%s-%d-%s!', id, length(maths) + seq_along(x), id)\n        math = gsub(sprintf('`%s|%s`', id, id), '', x)\n        maths <<- c(maths, set_names(math, tokens))\n        tokens\n      })\n      if (format == 'html') maths = html_escape(maths)\n      text = split_lines(text)\n    }\n  }\n\n  p = NULL  # indices of prose\n  find_prose = local({\n    t = NULL\n    function() {\n      # return early if text has not changed\n      if (!is.null(p) && identical(text, t)) return(p)\n      t <<- text\n      p <<- prose_index(text)\n    }\n  })\n  # ensure a blank line after an HTML tag if followed by a code block,\n  # otherwise the code block may be considered part of HTML, e.g.,\n  # for <p></p>\\n```\\n```, the code block after </p> won't be recognized\n  find_prose()\n  if (length(i <- grep('</[a-z0-9]+>\\\\s*$', text[p]))) {\n    # if the next line is not prose but code block, append \\n\n    k = p[!(p[i] + 1) %in% p]\n    if (length(k)) text[k] = paste0(text[k], '\\n')\n  }\n\n  # superscript and subscript; for now, we allow only characters alnum|*|(|) for\n  # script text but can consider changing this rule upon users' request\n  r2 = '(?<!`)\\\\^([[:alnum:]*(),.]+?)\\\\^(?!`)'\n  if (has_sup <- test_feature('superscript', r2)) {\n    id2 = id_string(text)\n    find_prose()\n    text[p] = match_replace(text[p], r2, function(x) {\n      # place superscripts inside !id...id!\n      x = gsub('^\\\\^|\\\\^$', id2, x)\n      sprintf('!%s!', x)\n    })\n  }\n  r3 = '(?<![~`[:space:]])~([[:alnum:]*(),.]+?)~(?!`)'\n  if (has_sub <- test_feature('subscript', r3)) {\n    id3 = id_string(text)\n    find_prose()\n    text[p]= match_replace(text[p], r3, function(x) {\n      # place subscripts inside !id...id!\n      x = gsub('^~|~$', id3, x)\n      sprintf('!%s!', x)\n    })\n  }\n  find_prose()\n  # add line breaks before/after fenced Div's to wrap ::: tokens into separate\n  # paragraphs or code blocks\n  text[p] = sub('^([ >]*:::+ )([^ {]+)$', '\\\\1{.\\\\2}', text[p]) # ::: foo -> ::: {.foo}\n  text[p] = sub(\n    '^([ >]*)((:::+)( \\\\{.*\\\\})?)$',\n    if (format == 'latex') '\\\\1\\n\\\\1```\\n\\\\1\\\\2 \\\\3\\n\\\\1```\\n\\\\1' else '\\\\1\\n\\\\1\\\\2\\n\\\\1',\n    text[p]\n  )\n\n  id4 = id_string(text)\n  if (format == 'latex') {\n    # put info string inside code blocks so the info won't be lost, e.g., ```r -> ```\\nr\n    text = gsub(\n      '^([> ]*)(```+)([^`].*)$', sprintf('\\\\1\\\\2\\n\\\\1%s\\\\3%s', id4, id4), text\n    )\n  } else if (format == 'html' && length(p) < length(text)) {\n    # hide spaces so that attributes won't be dropped: {.lang foo} -> {.lang!id!foo}\n    r4 = '^([> ]*```+\\\\s*)(\\\\{.+})\\\\s*$'\n    text = match_replace(text, r4, function(x) {\n      x1 = sub(r4, '\\\\1', x)\n      x2 = sub(r4, '\\\\2', x)\n      x2 = gsub(' ', id4, x2, fixed = TRUE)\n      paste0(x1, x2)\n    })\n  }\n\n  # turn @ref into [@ref](#ref) and resolve cross-references later in JS; for\n  # latex output, turn @ref to \\ref{}\n  r_ref = '(([a-z]+)[-:][-_[:alnum:]]+)'  # must start with letters followed by - or :\n  r5 = paste0('(^|(?<=\\\\s|\\\\())@', r_ref, '(?!\\\\])')\n  if (test_feature('cross_refs', r5)) {\n    text[p] = match_replace(text[p], r5, function(x) {\n      sprintf('[%s](%s)', x, sub('^@', '#', x))\n    })\n  }\n\n  ret = render(text)\n  ret = move_attrs(ret, format)  # apply attributes of the form {attr=\"value\"}\n\n  if (has_math) ret = match_replace(ret, sprintf('!%s-\\\\d+-%s!', id, id), function(x) {\n    if (length(maths) != length(x)) warning(\n      'LaTeX math expressions cannot be restored correctly (expected ',\n      length(maths), ' expression(s) but found ', length(x), ' in the output).'\n    )\n    maths[x]\n  })\n\n  has_mermaid = FALSE\n\n  if (format == 'html') {\n    # don't disable check boxes\n    ret = gsub('(<li><input type=\"checkbox\" [^>]*?)disabled=\"\" (/>)', '\\\\1\\\\2', ret)\n    # replace <a> with <span> if href is empty but other attrs exist, so we have\n    # a way to create SPANs with attributes, e.g., [text](){.foo} -> <span\n    # class=\"foo\"></span>\n    ret = gsub('<a href=\"\" ([^>]+>.*?</)a>', '<span \\\\1span>', ret)\n    if (has_sup)\n      ret = gsub(sprintf('!%s(.+?)%s!', id2, id2), '<sup>\\\\1</sup>', ret)\n    if (has_sub)\n      ret = gsub(sprintf('!%s(.+?)%s!', id3, id3), '<sub>\\\\1</sub>', ret)\n    r4 = '<pre><code class=\"language-\\\\{=([^}]+)}\">(.+?)</code></pre>\\n'\n    ret = match_replace(ret, r4, function(x) {\n      lang = gsub(r4, '\\\\1', x)\n      code = gsub(r4, '\\\\2', x)\n      # restore raw html content from ```{=html}\n      i1 = lang == 'html'\n      x[i1] = restore_html(code[i1])\n      # possible math environments\n      i2 = (lang %in% c('tex', 'latex')) &\n        grepl('^\\\\\\\\begin\\\\{[a-zA-Z*]+\\\\}.+\\\\\\\\end\\\\{[a-zA-Z*]+\\\\}\\n$', code)\n      if (any(i2)) {\n        x[i2] = sprintf('<p>\\n%s</p>\\n', code[i2])\n        has_math <<- TRUE\n      }\n      # discard other types of raw content blocks\n      x[!(i1 | i2)] = ''\n      x\n    }, perl = FALSE)  # for perl = TRUE, we'd need (?s) before (.+?)\n    # support mermaid\n    r_mmd = '<pre><code class=\"language-mermaid\">(.*?)</code></pre>'\n    if (has_mermaid <- length(grep(r_mmd, ret))) {\n      ret = gsub(r_mmd, '<pre class=\"mermaid\">\\\\1</pre>', ret)\n    }\n    r4 = '(<pre><code class=\"language-)\\\\{([^\"]+)}\">'\n    # deal with ```{.class1 .class2 attrs}, which is not supported by commonmark\n    ret = convert_attrs(ret, r4, '\\\\2', function(r, z, z2) {\n      z1 = sub(r, '\\\\1', z)\n      # make sure `class` is the first attribute\n      z2 = gsub('^(.+?)( +)(class=\"[^\"]+\")(.*)$', '\\\\3 \\\\1\\\\4', z2)\n      i = grepl('^class=\"', z2)\n      z2 = ifelse(i, sub('^class=\"', '', z2), paste0('\"', z2))\n      paste0(z1, z2, '>')\n    }, 'html', function(z2) gsub(id4, ' ', restore_html(z2)))\n    # some code blocks with \"attributes\" are verbatim ones\n    ret = match_replace(ret, '```+\\\\s*\\\\{.+}', function(x) gsub(id4, ' ', x, fixed = TRUE))\n    # remove empty table header\n    ret = gsub('<thead>\\n<tr>\\n(<th[^>]*></th>\\n)+</tr>\\n</thead>\\n', '', ret)\n    # table caption: a paragraph that starts with 'Table: ' or ': ' after </table>\n    ret = gsub(\n      '(<table>)(?s)(.+?</table>)\\n<p>(Table)?: (?s)(.+?)</p>',\n      '\\\\1\\n<caption>\\\\4</caption>\\\\2', ret, perl = TRUE\n    )\n    # auto identifiers\n    if (isTRUE(options[['auto_identifiers']])) ret = auto_identifier(ret)\n    # number sections\n    if (isTRUE(options[['number_sections']])) ret = number_sections(ret)\n    # build table of contents\n    ret = add_toc(ret, options)\n    # math\n    if (!has_math) has_math = length(ret) && (\n      grepl('$$</p>', ret, fixed = TRUE) || grepl('\\\\)</span>', ret, fixed = TRUE)\n    )  # math may be from pkg_manual()'s HTML\n    is_katex = TRUE\n    if (has_math && length(js_math <- js_options(options[['js_math']], 'katex'))) {\n      is_katex = js_math$package == 'katex'\n    }\n    # number figures and tables, etc.\n    ret = number_refs(ret, r_ref, is_katex)\n  } else if (format == 'latex') {\n    if (isTRUE(options[['footnotes']])) ret = fix_footnotes(ret)  # fix footnotes\n    if (has_sup)\n      ret = gsub(sprintf('!%s(.+?)%s!', id2, id2), '\\\\\\\\textsuperscript{\\\\1}', ret)\n    if (has_sub)\n      ret = gsub(sprintf('!%s(.+?)%s!', id3, id3), '\\\\\\\\textsubscript{\\\\1}', ret)\n    r4 = sprintf(\n      '(\\\\\\\\begin\\\\{verbatim}\\n)%s(.+?)%s\\n(.*?\\n)(\\\\\\\\end\\\\{verbatim}\\n)', id4, id4\n    )\n    ret = match_replace(ret, r4, function(x) {\n      info = gsub(r4, '\\\\2', x)\n      info = gsub('^\\\\{|}$', '', info)\n      i = info %in% c('=latex', '=tex')\n      x[i] = gsub(r4, '\\\\3', x[i])  # restore raw ```{=latex} content\n      i = !i & grepl('^=', info)\n      x[i] = ''  # discard other raw content\n      # TODO: support code highlighting for latex (listings or highr::hi_latex)\n      x = gsub(r4, '\\\\1\\\\3\\\\4', x)\n      x\n    }, perl = FALSE)\n    # for nested verbatim code blocks, the inner blocks may have leftover ```\\nid4\n    ret = gsub(sprintf('(```)\\n%s(.*?)%s', id4, id4), '\\\\1\\\\2', ret)\n    # fix horizontal rules from --- (\\linethickness doesn't work)\n    ret = gsub('{\\\\linethickness}', '{1pt}', ret, fixed = TRUE)\n    ret = redefine_level(ret, options[['top_level']])\n    if (isTRUE(options[['toc']])) ret = paste0('\\\\tableofcontents\\n', ret)\n  }\n\n  pkg_cite = yaml_field(yaml, format, 'citation_package')\n  if (length(pkg_cite) != 1) pkg_cite = 'natbib'\n  bib = yaml[['bibliography']]\n  # temporarily save the bib values when previewing a book because bib may only\n  # be specified in index.Rmd but not other chapters\n  if (is.character(b <- .env$current_book)) {\n    if (length(bib)) .env$bib[[b]] = bib else bib = .env$bib[[b]]\n  }\n  if (length(bib) == 1 && grepl(',', bib)) bib = strsplit(bib, ',\\\\s*')[[1]]\n  # add [@citation] (.bib files are assumed to be under output dir)\n  if (length(bib)) {\n    ret = in_dir(out_dir, add_citation(ret, bib, format))\n    if (format == 'latex') meta = bib_meta(meta, bib, pkg_cite)\n  }\n\n  # convert some meta variables in case they use Markdown syntax\n  if (is.character(template)) for (i in top_meta) if (meta_len <- length(meta[[i]])) {\n    # if author is of length > 1, render them individually\n    m_author = i == 'author' && meta_len > 1\n    meta[[i]] = if (m_author) uapply(meta[[i]], render) else {\n      render(meta[[i]], clean = i != 'abstract')\n    }\n    # also provide *_ version of top-level meta variables, containing tags/commands\n    meta[[paste0(i, '_')]] = I(if (format == 'html') {\n      tag = tag_meta[i]\n      sprintf(\n        '<div class=\"%s\">%s</div>', i, if (tag == '') meta[[i]] else {\n          one_string(sprintf('<%s>%s</%s>', tag, meta[[i]], tag))\n        }\n      )\n    } else if (format == 'latex') {\n      sprintf(cmd_meta[i], if (m_author) one_string(meta[[i]], ' \\\\and ') else meta[[i]])\n    })\n  }\n\n  # cross references (\\ref or clever \\cref)\n  clever = isTRUE(options[['cleveref']])\n  if (format == 'latex') ret = latex_refs(ret, r_ref, clever) else clever = FALSE\n\n  # use the template (if provided) to create a standalone document\n  if (is.character(template)) {\n    meta$body = I(ret)\n    if (format == 'html') {\n      # reset the internal js/css stored in acc_var() on exit\n      on.exit(acc_var(), add = TRUE)\n      # add js/css for math\n      if (has_math) set_math(js_math, is_katex)\n      # add js/css for syntax highlighting\n      set_highlight(options, ret)\n      # add js for mermaid\n      if (has_mermaid && length(grep('mermaid', meta[['js']])) == 0)\n        acc_var(js = '@npm/mermaid/dist/mermaid.min.js')\n    }\n    ret = build_output(\n      format, options, template, meta, test = c(if (length(input)) dirname(input), '.')\n    )\n    # load the cleveref package if not loaded\n    if (clever && !any(grepl('\\\\\\\\usepackage.*\\\\{cleveref\\\\}', ret, perl = TRUE)))\n      ret = sub('(?=\\\\\\\\begin\\\\{document\\\\})', '\\\\\\\\usepackage{cleveref}\\n', ret, perl = TRUE)\n  }\n\n  if (format == 'html') {\n    ret = in_dir(out_dir, embed_resources(ret, options))\n    ret = clean_html(ret)\n  } else if (format == 'latex') {\n    # remove \\maketitle if \\title is absent\n    if (!grepl('\\n\\\\title{', ret, fixed = TRUE))\n      ret = gsub('\\n\\\\maketitle\\n', '\\n', ret, fixed = TRUE)\n  }\n\n  if (keep_yaml) ret = one_string(c(yaml2, '', ret))\n\n  ret = sub('\\n$', '', ret)\n  if (is_output_file(output)) {\n    if (is_pdf) {\n      tex = with_ext(output, '.tex')\n      if (!isTRUE(yaml_field(yaml, format, 'keep_tex')))\n        on.exit(file.remove(tex), add = TRUE)\n      write_utf8(ret, tex)\n      output = tinytex::latexmk(\n        tex, latex_engine %||% 'xelatex',\n        if (pkg_cite == 'biblatex') 'biber' else 'bibtex'\n      )\n    }\n    # for RStudio to capture the output path when previewing the output\n    if (is_rmd_preview()) message('\\nOutput created: ', output)\n    if (is_pdf) invisible(output) else write_utf8(ret, output)\n  } else raw_string(ret, lang = paste0('.', format))\n}\n\n# insert body and meta variables into a template\nbuild_output = function(format, options, template, meta, ...) {\n  tpl = one_string(template, ...)\n  if (format == 'html') {\n    defaults = list(\n      'css' = 'default',\n      'lang' = locale_lang(),\n      'plain-title' = I(str_trim(commonmark::markdown_text(meta[['title']])))\n    )\n    for (i in setdiff(names(defaults), names(meta))) meta[[i]] = defaults[[i]]\n    # special handling for css/js \"files\" that have no extensions\n    for (i in c('css', 'js')) {\n      i2 = paste0(i, '2')  # treat css2/js2 as global base (e.g. for sites)\n      meta[[i]] = resolve_files(c(meta[[i2]], meta[[i]], acc_var(i)), i)\n    }\n  }\n  sub_vars(tpl, meta, ...)\n}\n\n# substitute all variables in template with their values\nsub_vars = function(tpl, meta, ...) {\n  # find all variables in the template\n  vars = unlist(match_full(tpl, '[$][-_[:alnum:]]+[$]'))\n  # insert $body$ at last in case the body contain any $variables$ accidentally\n  if (!is.na(i <- match('$body$', vars))) vars = c(vars[-i], vars[i])\n  for (v in vars) {\n    tpl = sub_var(tpl, v, meta[[gsub('[$]', '', v)]], ...)\n  }\n  tpl\n}\n\ntop_meta = c('title', 'subtitle', 'author', 'date', 'abstract')\ntag_meta = c('h1', 'h2', 'h2', 'h3', '')\nnames(tag_meta) = top_meta\ncmd_meta = c(sprintf('\\\\%s{%%s}', top_meta[-5]), '\\\\begin{abstract}\\n%s\\\\end{abstract}')\nnames(cmd_meta) = top_meta\n\nyaml_text = function(part, text) if (length(l <- part$lines) == 2) text[l[1]:l[2]]\n\n#' Markdown rendering options\n#'\n#' A list of all options to control Markdown rendering. Options that are enabled\n#' by default are marked by a `+` prefix, and those disabled by default are\n#' marked by `-`.\n#'\n#' See <https://yihui.org/litedown/#sec:markdown-options> for the full list of\n#' options and their documentation.\n#' @return A character vector of all available options.\n#' @export\n#' @examples\n#' # all available options\n#' litedown::markdown_options()\nmarkdown_options = function() {\n  # options enabled by default\n  x1 = c(\n    'smart', 'embed_resources', 'embed_cleanup', 'js_math', 'js_highlight', 'footnotes',\n    'superscript', 'subscript', 'latex_math', 'auto_identifiers', 'cross_refs',\n    setdiff(commonmark::list_extensions(), 'tagfilter')\n  )\n  # options disabled by default\n  x2 = c(\n    'toc', 'hardbreaks', 'tagfilter', 'number_sections', 'cleveref', 'offline',\n    'smartypants'\n  )\n  sort(c(paste0('+', x1), paste0('-', x2)))\n}\n"
  },
  {
    "path": "R/package.R",
    "content": "#' A lightweight version of R Markdown\n#'\n#' Markdown is a plain-text format that can be converted to HTML and other\n#' formats. This package can render R Markdown to Markdown, and then to an\n#' output document format. The main differences between this package and\n#' \\pkg{rmarkdown} are that it does not use Pandoc or \\pkg{knitr} (i.e., fewer\n#' dependencies), and it also has fewer Markdown features.\n#' @importFrom xfun alnum_id base64_uri csv_options del_empty_dir dir_create\n#'   divide_chunk download_cache exit_call fenced_block fenced_div file_exists\n#'   file_ext grep_sub html_escape html_tag html_value in_dir is_abs_path\n#'   is_blank is_rel_path loadable mime_type new_app new_record normalize_path\n#'   parse_only prose_index raw_string read_all read_utf8 record_print\n#'   relative_path Rscript_call same_path sans_ext set_envvar split_lines\n#'   try_error try_silent with_ext write_utf8\n'_PACKAGE'\n\n# an internal environment to store some intermediate objects\n.env = new_env()\n\n#' The `fuse()` environment\n#'\n#' Get the environment passed to the `envir` argument of [fuse()], i.e., the\n#' environment in which code chunks and inline code are evaluated.\n#' @return When called during `fuse()`, it returns the `envir` argument value of\n#'   `fuse()`. When called outside `fuse()`, it returns the global environment.\n#' @export\nfuse_env = function() .env$global %||% globalenv()\n\n#' @export\nrecord_print.data.frame = function(x, ...) {\n  asis = inherits(x, 'AsIs')\n  if (is.null(getOption('xfun.md_table.limit')) && !'limit' %in% names(list(...))) {\n    opts = options(xfun.md_table.limit = if (!asis) 10)\n    on.exit(options(opts), add = TRUE)\n  }\n  if (asis) class(x) = setdiff(class(x), 'AsIs')\n  if (inherits(x, 'tbl_df')) x = as.data.frame(x)\n  tab = xfun::md_table(x, ...)\n  opt = reactor()\n  tab = add_cap(tab, opt$tab.cap, opt$label, opt$cap.pos %||% 'top', opt$tab.env, 'tab')\n  new_record(c(tab, ''), 'asis')\n}\n\n#' @export\nrecord_print.matrix = record_print.data.frame\n\n#' @export\nrecord_print.tbl_df = record_print.data.frame\n\n#' @export\nrecord_print.knitr_kable = function(x, ...) {\n  if ((fmt <- attr(x, 'format')) %in% c('html', 'latex'))\n    x = fenced_block(x, paste0('=', fmt))\n  new_record(c(x, ''), 'asis')\n}\n\n# register vignette engines\n.onLoad = function(lib, pkg) {\n  vig_add('vignette', vig_fun(TRUE), vig_fun(FALSE))\n  vig_add('book', vig_fun(TRUE, TRUE), vig_fun(FALSE, TRUE))\n}\n\nvig_add = function(name, weave, tangle) {\n  tools::vignetteEngine(\n    name, weave, tangle, '[.]R?md$', aspell = list(filter = vig_filter)\n  )\n}\n\n# weave or tangle?\nvig_fun = function(weave = TRUE, book = FALSE) {\n  function(file, quiet = FALSE, ...) {\n    empty_file = function() write_utf8(character(), with_ext(file, '.R'))\n\n    # call fuse_book() to build multiple input files into a book\n    if (book) return(if (weave) {\n      if (quiet) {\n        opt = options(litedown.progress.delay = Inf); on.exit(options(opt))\n      }\n      fuse_book(dirname(file), envir = globalenv())\n    } else empty_file())\n\n    # fuse() .Rmd and mark() .md\n    if (grepl('[.]Rmd$', file)) {\n      if (weave) fuse(file, quiet = quiet, envir = globalenv()) else {\n        if (getRversion() <= '3.2.5') empty_file() else fiss(file)\n      }\n    } else {\n      if (weave) mark(file) else empty_file()\n    }\n  }\n}\n\n# filter out code from document so aspell() won't spell check code\nvig_filter = function(ifile, encoding) {\n  res = crack(ifile)\n  res = lapply(res, function(x) {\n    if (x$type == 'code_chunk') return(rep('', length(x$source)))\n    if (is.character(x$source)) x$source else {\n      one_string(uapply(x$source, function(s) {\n        if (is.character(s)) s else ''\n      }), '')\n    }\n  })\n  structure(split_lines(unlist(res)), control = '-H -t')\n}\n\n#' Print the package description, news, citation, manual pages, and source code\n#'\n#' Helper functions to retrieve various types of package information that can be\n#' put together as the full package documentation like a \\pkg{pkgdown} website.\n#' These functions can be called inside any R Markdown document.\n#' @param name The package name (by default, it is automatically detected from\n#'   the `DESCRIPTION` file if it exists in the current working directory or\n#'   upper-level directories).\n#' @param type The HTML tag for the description: `table` (`<table>` with `<tr>`\n#'   and `<td>`) or `dl` (definition list `<dl>` with `<dt>` and `<dd>`).\n#' @return A character vector (HTML or Markdown) that will be printed as is\n#'   inside a code chunk of an R Markdown document.\n#'\n#'   `pkg_desc()` returns an HTML table containing the package metadata.\n#' @export\n#' @examples\n#' \\dontrun{\n#' litedown::pkg_desc()\n#' litedown::pkg_news()\n#' litedown::pkg_citation()\n#' }\npkg_desc = function(name = detect_pkg(), type = c('table', 'dl')) {\n  fields = c(\n    'Title', 'Version', 'Description', 'Depends', 'Imports', 'Suggests',\n    'License', 'URL', 'BugReports', 'VignetteBuilder', 'Authors@R', 'Author'\n  )\n  # read the DESCRIPTION file if pkg root is found, otherwise use installed info\n  d = if (is.character(p <- attr(name, 'path'))) {\n    as.list(read.dcf(file.path(p, 'DESCRIPTION'))[1, ][fields])\n  } else {\n    packageDescription(name, fields = fields)\n  }\n  names(d) = fields\n  # remove single quotes on words (which are unnecessary IMO)\n  for (i in c('Title', 'Description')) d[[i]] = sans_sq(d[[i]])\n  d[['Author']] = one_string(pkg_authors(d), ', ')\n  d[['Authors@R']] = NULL\n  # convert URLs to <a>, and escape HTML in other fields\n  for (i in names(d)) d[[i]] = if (!is.na(d[[i]])) {\n    if (i %in% c('Description', 'URL', 'BugReports', 'Author')) {\n      sans_p(commonmark::markdown_html(d[[i]], extensions = 'autolink'))\n    } else html_escape(d[[i]])\n  }\n  d = unlist(d)\n  res = one_string(switch(\n    type[1],\n    table = c(\n      '<table class=\"table-full\"><tbody>',\n      sprintf('<tr>\\n<td>%s</td>\\n<td>%s</td>\\n</tr>', names(d), d),\n      '</tbody></table>'\n    ),\n    dl = c('<dl class=\"pkg-desc\">', sprintf('<dt>%s</dt>\\n<dd>%s</dd>', names(d), d), '</dl>')\n  ))\n\n  new_asis(c(res, vest(css = '@manual')))\n}\n\n# format authors, adding URL and ORCID links as appropriate\npkg_authors = function(desc, role = NULL, extra = TRUE) {\n  if (is.null(a <- desc[['Authors@R']])) return(desc[['Author']])\n  a = eval(parse_only(a))\n  a = uapply(a, function(x) {\n    if (length(role) && !any(role %in% x$role)) return()\n    role = if (extra && length(x$role)) paste0('[', one_string(x$role, ', '), ']')\n    name = paste(x$given, x$family)\n    comment = as.list(x$comment)\n    orcid = if (extra) sprintf(\n      '[![ORCID iD](https://cloud.r-project.org/web/orcid.svg){.orcid}](https://orcid.org/%s)',\n      comment[[\"ORCID\"]]\n    )\n    link = comment[['URL']]\n    if (length(link)) name = sprintf('[%s](%s)', name, link)\n    one_string(c(name, orcid, role), ' ')\n  })\n  a\n}\n\n#' @param path For [pkg_news()], path to the `NEWS.md` file. If empty, [news()]\n#'   will be called to retrieve the news entries. For [pkg_code()], path to the\n#'   package root directory that contains `R/` and/or `src/` subdirectories.\n#' @param recent The number of recent versions to show. By default, only the\n#'   latest version's news entries are retrieved. To show the full news, set\n#'   `recent = 0`.\n#' @param toc Whether to add section headings to the document TOC (when TOC has\n#'   been enabled for the document).\n#' @param number_sections Whether to number section headings (when sections are\n#'   numbered in the document).\n#' @param ... Other arguments to be passed to [news()].\n#' @return `pkg_news()` returns the news entries.\n#' @rdname pkg_desc\n#' @export\npkg_news = function(\n  name = detect_pkg(), path = detect_news(name), recent = 1, toc = TRUE,\n  number_sections = TRUE, ...\n) {\n  a = header_class(toc, number_sections)\n  if (length(path) != 1 || path == '') {\n    db = news(package = name, ...)\n    if (recent > 0) db = head(db, recent)\n    res = NULL\n    for (v in unique(db$Version)) {\n      df = db[db$Version == v, ]\n      res = c(\n        res, paste('##', name, v, a), '',\n        if (all(df$Category == '')) paste0(df$HTML, '\\n') else paste0(\n          '### ', df$Category, a, '\\n\\n', df$HTML, '\\n\\n'\n        ), ''\n      )\n    }\n  } else {\n    res = read_utf8(path)\n    if (recent > 0 && length(h <- grep('^# ', res)) >= 2)\n      res = res[h[1]:(h[1 + recent] - 1)]\n    # lower heading levels: # -> ##, ## -> ###, etc, and add attributes\n    for (i in 2:1) res = sub(sprintf('^(#{%d} .+)', i), paste0('#\\\\1', a), res)\n    # shorten headings\n    res = gsub('^## CHANGES IN ([^ ]+) VERSION( .+)', '## \\\\1\\\\2', res)\n    # link Github @username and #issue\n    if (length(u <- github_link(dirname(path)))) {\n      r1 = '([[:alnum:]-]+)'; r2 = '([0-9]+)'\n      res = gsub(paste0(' @', r1), ' [@\\\\1](https://github.com/\\\\1)', res)\n      res = gsub(paste0(' #', r2), sprintf(' [#\\\\1](%sissues/\\\\1)', u), res)\n      res = gsub(\n        sprintf(' %s/%s#%s', r1, r1, r2),\n        ' [\\\\1/\\\\2#\\\\3](https://github.com/\\\\1/\\\\2/issues/\\\\3)', res\n      )\n    }\n  }\n  new_asis(res)\n}\n\n# classes for section headings in news, code, and manual\nheader_class = function(toc, number_sections, md = TRUE) {\n  a = c(if (!toc) 'unlisted', if (!number_sections) 'unnumbered')\n  if (md && length(a)) a = paste0('.', a)\n  a = one_string(a, ' ')\n  if (a != '') a = if (md) paste0(' {', a, '}') else paste0(' class=\"', a, '\"')\n  a\n}\n\n#' @param pattern A regular expression to match filenames that should be treated\n#'   as source code.\n#' @param link Whether to add links on the file paths of source code. By\n#'   default, if a GitHub repo link is detected from the `BugReports` field of\n#'   the package `DESCRIPTION`, GitHub links will be added to file paths. You\n#'   can also provide a string template containing the placeholder `%s` (which\n#'   will be filled out with the file paths via `sprintf()`), e.g.,\n#'   `https://github.com/yihui/litedown/blob/main/%s`.\n#' @return `pkg_code()` returns the package source code under the `R/` and\n#'   `src/` directories.\n#' @rdname pkg_desc\n#' @export\npkg_code = function(\n  path = attr(detect_pkg(), 'path'), pattern = '[.](R|c|h|f|cpp)$', toc = TRUE,\n  number_sections = TRUE, link = TRUE\n) {\n  if (!isTRUE(dir.exists(path))) {\n    if (missing(path)) message('Please run this function in the package source directory.')\n    return()\n  }\n  a = header_class(toc, number_sections)\n  if (isTRUE(link) && length(u <- github_link(path))) link = paste0(u, 'blob/HEAD/%s')\n  ds = c('R', 'src')\n  ds = ds[ds %in% list.dirs(path, FALSE, FALSE)]\n  flat = length(ds) == 1  # if only one dir exists, list files in a flat structure\n  code = in_dir(path, lapply(ds, function(d) {\n    fs = list.files(d, pattern, full.names = TRUE, recursive = TRUE)\n    if (length(fs) == 0) return()\n    x = uapply(fs, function(f) c(\n      sprintf('##%s %s%s', if (flat) '' else '#', if (is.character(link)) {\n        sprintf('[`%s`](%s)', f, sprintf(link, f))\n      } else sprintf('`%s`', f), a), '',\n      fenced_block(read_utf8(f), lineno_attr(file_ext(f), auto = FALSE)), ''\n    ))\n    e = unique(file_ext(fs))\n    c(if (!flat) paste0('## ', paste0('`*.', e, '`', collapse = ' / '), a), '', x)\n  }))\n  new_asis(unlist(code))\n}\n\n# retrieve Github repo link from DESCRIPTION\ngithub_link = function(path) {\n  u = read.dcf(file.path(path, 'DESCRIPTION'), 'BugReports')[1, 1]\n  grep_sub('^(https://github.com/[^/]+/[^/]+/).*', '\\\\1', u)\n}\n\n#' @return `pkg_citation()` returns the package citation in both the plain-text\n#'   and BibTeX formats.\n#' @rdname pkg_desc\n#' @export\npkg_citation = function(name = detect_pkg()) {\n  res = uapply(citation(name), function(x) {\n    x = tweak_citation(x)\n    unname(c(format(x, style = 'text'), fenced_block(toBibtex(x), 'latex')))\n  })\n  new_asis(res)\n}\n\n# dirty hack to add year if missing\ntweak_citation = function(x) {\n  cls = class(x)\n  x = unclass(x)\n  if (is.null(x[[1]]$year)) x[[1]]$year = format(Sys.Date(), '%Y')\n  class(x) = cls\n  x\n}\n\n#' @param overview Whether to include the package overview page, i.e., the\n#'   `{name}-package.Rd` page.\n#' @param examples A list of arguments to be passed to [xfun::record()] to run\n#'   examples each help page, e.g., `list(dev = 'svg', dev.args = list(height =\n#'   6))`. If not a list (e.g., `FALSE`), examples will not be run.\n#' @return `pkg_manual()` returns all manual pages of the package in HTML.\n#' @rdname pkg_desc\n#' @export\npkg_manual = function(\n  name = detect_pkg(), toc = TRUE, number_sections = TRUE, overview = TRUE,\n  examples = list()\n) {\n  links = tools::findHTMLlinks('')\n  # resolve internal links (will assign IDs of the form sec:man-ID to all h2)\n  r = sprintf('^[.][.]/[.][.]/(%s)/html/(.+)[.]html$', name)\n  i = grep(r, links)\n  links[i] = paste0('#sec:man-', alnum_id(sub(r, '\\\\2', links[i])))\n  # resolve external links to specific man pages on https://rdrr.io\n  r = sprintf('^[.][.]/[.][.]/(%s)/html/', paste(xfun::base_pkgs(), collapse = '|'))\n  links = sub(r, 'https://rdrr.io/r/\\\\1/', links)\n\n  db = tools::Rd_db(name)  # all Rd pages\n  intro = paste0(name, '-package.Rd')  # the name-package entry (package overview)\n  entries = setdiff(names(db), intro)\n  entries = entries[!vapply(entries, function(i) {\n    kwd = uapply(db[[i]], function(x) if (attr(x, 'Rd_tag') == '\\\\keyword') x)\n    'internal' %in% kwd\n  }, logical(1))]\n  db = db[c(if (overview && intro %in% names(db)) intro, entries)]\n  al = lapply(db, Rd_aliases)\n\n  cl = header_class(toc, number_sections, FALSE)\n  r1 = '<code class=\"reqn\">\\\\s*([^<]+?)\\\\s*</code>'  # inline math\n  r2 = sprintf('<p[^>]*>\\\\s*%s\\\\s*</p>', r1)  # display math\n  res = uapply(names(db), function(i) {\n    txt = ''\n    con = textConnection('txt', 'w', local = TRUE, encoding = 'UTF-8')\n    tryCatch(\n      tools::Rd2HTML(db[[i]], Links = links, out = con), error = function(e) {\n        warning(\"The Rd file '\", i, \"' appears to be malformed.\", call. = FALSE)\n        stop(e)\n      }, finally = close(con))\n    # extract body, which may end at </main> (R 4.4.x) or </div></body> (R 4.3.x)\n    txt = gsub('(?s).*?(?=<h2)', '', one_string(txt), perl = TRUE)\n    txt = gsub('(</main>|</div>\\\\s*</body>).*', '', txt)\n    # free math from <code>\n    txt = gsub(r2, '<p>$$\\\\1$$</p>', txt)\n    txt = gsub(r1, '<span>\\\\\\\\(\\\\1\\\\\\\\)</span>', txt)\n    # run examples\n    if (is.list(examples)) {\n      xfun::pkg_attach(name)\n      default = list(print = function(x, ...) {\n        if (inherits(x, 'xfun_raw_string')) record_print(x) else\n          xfun:::record_print.default(x)\n      }, dev.path = 'manual/', dev.args = list(width = 9, height = 7))\n      txt = run_examples(txt, merge_list(default, examples), sans_ext(i))\n    }\n    # remove existing ID and class\n    for (a in c('id', 'class')) txt = gsub(sprintf('(<h2[^>]*?) %s=\"[^\"]+\"', a), '\\\\1', txt)\n    if (cl != '') txt = sub('<h2', paste0('<h2', cl), txt, fixed = TRUE)\n    sub('<h2', sprintf('<h2 id=\"sec:man-%s\"', sans_ext(i)), txt, fixed = TRUE)\n  })\n\n  # extract all aliases and put them in the beginning (like a TOC)\n  env = asNamespace(name)\n  toc = unlist(.mapply(function(topics, target) {\n    fn = uapply(topics, function(x) {\n      if (is.function(env[[x]])) paste0(x, '()') else x  # add () after function names\n    })\n    sprintf('<a href=\"#sec:man-%s\"><code>%s</code></a>', target, fn)\n  }, al, sans_ext(names(al))))\n\n  g = toupper(substr(unlist(al), 1, 1))\n  g[!g %in% LETTERS] = 'misc'\n  g = factor(g, intersect(c(LETTERS, 'misc'), g))\n  toc = split(toc, g)  # group by first character\n  toc = unlist(mapply(function(x, g) {\n    c('<p>', sprintf('<b>-- <kbd>%s</kbd> --</b>', g), x, '</p>')\n  }, toc, names(toc)))\n\n  r = '(<a href=\")[.][.]/[.][.]/([^/]+)/help/'\n  res = gsub(r, '\\\\1https://rdrr.io/cran/\\\\2/man/', res)\n  res = gsub(\" (id|class)='([^']+)'\", ' \\\\1=\"\\\\2\"', res)  # ' -> \"\n  res = gsub('<h3>', '<h3 class=\"unnumbered unlisted\">', res, fixed = TRUE)\n  res = match_replace(res, '<(h[1-6])[^>]*>(?s).+?</\\\\1>', function(x) {\n    gsub('\\n', ' ', x)  # remove \\n in headings so build_toc() can be correctly build TOC\n  })\n  res = gsub('<code style=\"[^\"]+\">', '<code>', res)\n  res = gsub('<code id=\"[^\"]+\">', '<code>', res)\n  res = gsub('(<code[^>]*>)\\\\s+', '\\\\1', res)\n  res = gsub('\\\\s+(</code>)', '\\\\1', res)\n  res = gsub('<div class=\"sourceCode\"><pre>(.+?)</pre></div>', '<pre><code>\\\\1</code></pre>', res)\n  res = gsub('<div class=\"sourceCode ([^\"]+)\"><pre>(.+?)</pre></div>', '<pre><code class=\"language-\\\\1\">\\\\2</code></pre>', res)\n  res = gsub('<code class=\"language-R\"', '<code class=\"language-r\"', res, fixed = TRUE)\n  res = gsub('&#8288;', '', res, fixed = TRUE)\n  res = gsub('<img src=\"../help/figures/', '<img src=\"man/figures/', res, fixed = TRUE)\n  new_asis(c(toc, res, vest(css = '@manual')))\n}\n\nrun_examples = function(html, config, path) {\n  config$dev.path = path = paste0(config$dev.path, path)\n  on.exit(del_empty_dir(dirname(path)), add = TRUE)\n  r = '(?s).*?<pre><code[^>]*>(?s)(.+?)</code></pre>'\n  match_replace(html, paste0('(?<=<h3>Examples</h3>)', r), function(x) {\n    code = gsub(r, '\\\\1', x, perl = TRUE)\n    code = restore_html(str_trim(code))\n    nr1 = 'if (FALSE) {  ## Not run'\n    nr2 = '}  ## Not run'\n    code = gsub('\\n?## Not run:\\\\s*?\\n', paste0('\\n', nr1, '\\n'), code)\n    code = gsub('\\n+## End[(]Not run[)]\\n*', paste0('\\n', nr2, '\\n'), code)\n    res = do.call(xfun::record, merge_list(config, list(code = code, envir = globalenv())))\n    idx = seq_along(res); cls = class(res)\n    for (i in idx) {\n      ri = res[[i]]; ci = class(ri)\n      # disable asis output since it may contain raw HTML\n      if ('record_asis' %in% ci) class(res[[i]]) = 'record_output'\n      # split the dontrun block\n      if ('record_source' %in% ci && !any(is.na(nr <- match(c(nr1, nr2), ri)))) {\n        i1 = nr[1]; i2 = nr[2]\n        new_block = function(i, ...) {\n          b = trim_blank(one_string(ri[i]))\n          if (is_blank(b)) b = character()\n          list(structure(b, class = c(ci, ...)))\n        }\n        if (i1 > 1) {\n          res = c(res, new_block((i1 + 1):(i2 - 1), 'fade'))\n          idx = c(idx, i)\n        }\n        n = length(ri)\n        if (i2 < n) {\n          res = c(res, new_block((i2 + 1):n))\n          idx = c(idx, i)\n        }\n        res[i] = if (i1 > 1) new_block(1:(i1 - 1)) else\n          new_block((i1 + 1):(i2 - 1), 'fade')\n      }\n    }\n    res = res[order(idx)]\n    class(res) = cls\n    res = one_string(c('', format(res, 'markdown')))\n    res\n  })\n}\n\n# detect package name and root path from current and upper dirs\ndetect_pkg = local({\n  res = NULL  # cache the detection\n  function(...) {\n    if (is.null(res) || !same_path('.', attr(res, 'wd'))) res <<- .detect_pkg(...)\n    res\n  }\n})\n\n.detect_pkg = function(error = TRUE) {\n  ds = if (xfun::is_R_CMD_check()) {\n    # R CMD check's working directory is PKG_NAME.Rcheck by default\n    name = grep_sub('[.]Rcheck$', '', basename(getwd()))\n    # when running R CMD check, DESCRIPTION won't be under working directory but\n    # ../PKG_NAME/ (on CRAN's *nix) or ./00_pkg_src/\n    c(file.path('..', name), if (dir.exists('00_pkg_src'))\n      dirname(list.files('.', '^DESCRIPTION$', recursive = TRUE)))\n  } else name = NULL\n  for (d in c(ds, './')) {\n    if (!is.null(root <- xfun::proj_root(d, head(xfun::root_rules, 1)))) break\n  }\n  if (is.null(root)) {\n    root = if (length(name)) system.file(package = name)\n    if (identical(root, '')) root = NULL\n  }\n  if (is.null(root)) {\n    if (error) stop(\n      \"Cannot automatically detect the package root directory from '\", getwd(), \"'. \",\n      \"You must provide the package name explicitly.\"\n    ) else return()\n  } else if (is.null(name)) {\n    desc = read_utf8(file.path(root, 'DESCRIPTION'))\n    name = grep_sub('^Package: (.+?)\\\\s*$', '\\\\1', desc)[1]\n  }\n  structure(name, path = root, wd = getwd())\n}\n\ndetect_news = function(name) {\n  if (isTRUE(file_exists(path <- file.path(attr(name, 'path'), 'NEWS.md'))))\n    path else system.file('NEWS.md', package = name)\n}\n\n# get \\alias{} names in an Rd object\nRd_aliases = function(x) {\n  uapply(x, function(x) if (attr(x, 'Rd_tag') == '\\\\alias') as.character(x))\n}\n"
  },
  {
    "path": "R/preview.R",
    "content": "#' Preview Markdown and R Markdown files\n#'\n#' Launch a web page to list and preview files under a directory.\n#'\n#' Markdown files will be converted to HTML and returned to the web browser\n#' directly without writing to HTML files, to keep the directory clean during\n#' the preview. Clicking on a filename will bring up an HTML preview. To see its\n#' raw content, click on the link on its file size instead.\n#' @param dir A directory path.\n#' @param live Whether to enable live preview. If enabled, the browser page will\n#'   be automatically updated upon modification of local files used by the page\n#'   (e.g., the Markdown file or external CSS/JS/image files). If disabled, you\n#'   can manually refresh the page to fully re-render it.\n#' @param ... Other arguments to be passed to [xfun::new_app()].\n#' @return A URL (invisibly) for the preview.\n#' @export\nroam = function(dir = '.', live = TRUE, ...) in_dir(dir, {\n  # a proxy server to return files under inst/resources/\n  s = new_app('.litedown', function(path, ...) {\n    file_raw(pkg_file('resources', path))\n  }, open = FALSE)\n  # the URL needs to be translated on RStudio Server\n  if (Sys.getenv('RSTUDIO_PROGRAM_MODE') == 'server' && loadable('rstudioapi'))\n    s = rstudioapi::translateLocalUrl(s, TRUE)\n  # load litedown assets via http://127.0.0.1:port/custom/.litedown/assets\n  asset_url = function(path) paste0(s, path)\n\n  t1 = list()  # store modification times of files\n  check_time = function(path) {\n    t2 = if (dir.exists(path)) {\n      file.info(list.files(path, full.names = TRUE))[, 'mtime', drop = FALSE]\n    } else file.mtime(path)\n    t = t1[[path]]; t1[[path]] <<- t2\n    !is.null(t) && !(is.null(dim(t2)) && is.na(t2)) && !identical(t2, t)\n  }\n\n  new_app('litedown', function(path, query, post, headers) {\n    # set up proper default options for mark()\n    opt = options(\n      litedown.html.meta = list(\n        css = asset_url(c('default.css', if (dir.exists(path)) 'listing.css'))\n      ),\n      litedown.html.options = list(embed_resources = FALSE),\n      litedown.roaming = TRUE\n    )\n    on.exit(options(opt), add = TRUE)\n    # capture errors in fuse() because I don't know if it's possible to capture\n    # general errors in the handler; without capturing errors, users will see a\n    # plain-text error page, which may be hard to understand\n    opts = reactor(error = TRUE); on.exit(reactor(opts), add = TRUE)\n    query = as.list(query)\n    ext = tolower(file_ext(path))\n    # we keep POSTing to the page assets' URLs, and if an asset file has been\n    # modified, we return a response telling the browser to update it\n    type = if (length(headers)) grep_sub(\n      '.*\\nlitedown-data: ([^[:space:]]+).*', '\\\\1', rawToChar(headers)\n    )\n    if (length(type) != 1) type = ''\n    # we may need to check rawToChar(headers) to decide what to do for the\n    # request; for now, we simply ignore request headers, and treat the POST\n    # body as the type of request\n    if (type == 'open') {\n      xfun:::open_path(\n        query[['path']] %||% path, !dir.exists(path) && is_text_file(file = path),\n        as.integer(query[['line']]) %|% -1L\n      )\n      return(list(payload = 'done'))\n    }\n    # create a new file with selected features\n    if (grepl('^new($|:)', type))\n      return(list(payload = if (type == 'new') feature_form(path) else new_file(path, ext, type)))\n    # render Rmd in new R sessions and save to file\n    if (type == 'save') {\n      return(if (is_lite_ext(ext)) {\n        # check if the file is for a book or site\n        info = proj_info(path)\n        list(payload = paste('Rendered and saved:', switch(\n          info$type,\n          book = Rscript_call(fuse_book, list(info$root)),\n          site = { Rscript_call(fuse_site, list(info$root)); info$root },\n          if (ext == 'md') mark(path) else Rscript_call(fuse, list(path))\n        )))\n      } else {\n        list(payload = paste0(\n          \"Unable to render '\", path, \"' (only \",\n          paste0('.', lite_exts, collapse = ', '), \" are supported).\"\n        ))\n      })\n    }\n    # clean up __files/\n    if (type == 'cleanup') {\n      if (dir.exists(fig <- fig_path(path)) &&\n          !is.null(fig_time <- .env$roam_files[[fig]]) &&\n          !dir.exists(aux_path(, 'cache.path', path))) {\n        fig_time2 = file.mtime(fig)\n        # remove fig dir if it has not been modified since its mtime was\n        # recorded last time (in file_resp()); if it has, update to new mtime so\n        # we can clean it up next time when we receive the request; checking\n        # mtime is necessary because the Ajax cleanup request (sent from the\n        # 'beforeunload' event) may arrive _after_ file_resp() rebuilds the\n        # current page when the page is refreshed, which may be counterintuitive\n        # but Ajax is _asynchronous_ anyway (this took me hours to figure out)\n        .env$roam_files[[fig]] = if (fig_time2 <= fig_time) {\n          unlink(fig, recursive = TRUE); NULL\n        } else fig_time2\n      }\n      return(list(payload = ''))\n    }\n    # TODO: should we implement Hugo's --navigateToChanged?\n    if (live && type != '') {\n      resp = ''\n      if (type %in% c('asset', 'page')) {\n        if (check_time(path)) resp = '1'\n      } else if (grepl('^book:', type) && check_time(f <- sub('^book:', '', type))) {\n        store_book(dirname(path), f)\n        # the book file path to preview is encoded in `type = book:foo/bar.Rmd`\n        resp = fuse_book(c(dirname(path), f), 'html', globalenv())\n      }\n      return(list(payload = resp))\n    }\n    res = lite_handler(path, query, post, headers)\n    # inject js to communicate with the R server via POST for live preview\n    p = res$payload\n    if (is.null(p) || (res[['content-type']] %||% 'text/html') != 'text/html')\n      return(res)\n    p = sub(\n      '</head>', one_string(c(\n        if (live) '<meta name=\"live-previewer\" content=\"litedown::roam\">',\n        gen_tags(asset_url('server.css')), '</head>'\n      )), p, fixed = TRUE\n    )\n    p = sub(\n      '</body>', one_string(c(gen_tags(asset_url('server.js')), '</body>')), p,\n      fixed = TRUE\n    )\n    res$payload = p\n    res\n  }, ...)\n})\n\n# a handler returns list(payload, file, `content-type`, header, `status code`)\nlite_handler = function(path, query, post, headers) {\n  if (dir.exists(path)) list(payload = dir_page(path)) else {\n    file_page(path, query[['preview']] %||% '0')\n  }\n}\n\ndir_page = function(dir = '.') {\n  files = list.files(dir, full.names = TRUE)\n  # index.* files should appear first\n  files = files[order(!sans_ext(basename(files)) == 'index')]\n  # show file size and mtime\n  info = function(f, b, extra = '') {\n    sprintf(\n      '_( [%s](<%s>){title=\"Raw file\"} %s%s%s)_', file_size(f), b, file_time(f),\n      if (is_text_file(file = f)) btn('.open', b) else '', extra\n    )\n  }\n  # create link to preview a file\n  p_link = function(f, t = f, n = 1, a = NULL) {\n    btn(t, sprintf('%s?preview=%d', f, n), a)\n  }\n  i1 = is_lite_ext(file = files)\n  # order first by folder, then by .Rmd/.R, and other files go to the end\n  res = lapply(files[i1][order(grepl('^[_.]', basename(files[i1])))], function(f) {\n    b = basename(f)\n    fenced_div(c(\n      fenced_div(c(\n        p_link(b, a = NULL), info(f, b, btn('.save', b)),\n        p_link(b, '.run', 2, 'title=\"Run in memory\"')\n      ), '.caption .name'),\n      fenced_block(readLines(f, n = 10, encoding = 'UTF-8', warn = FALSE))\n    ), '.box')\n  })\n  files = files[!i1]\n  i2 = dir.exists(files)\n  res = c(res, '', lapply(files[order(!i2, files)], function(f) {\n    b = basename(f)\n    if (d <- dir.exists(f)) b = paste0(b, '/')\n    sprintf(\n      '- [%s](<%s%s>) %s', b, b, if (d) '' else '?preview=1',\n      if (d) '' else info(f, b)\n    )\n  }))\n  mark_full(unlist(res), meta = list(title = dir_title(dir)))\n}\n\n# add directory navigation to the top of the page\nfile_page = function(x, preview) {\n  res = file_resp(x, preview)\n  if (is.null(p <- res$payload)) return(res)\n  # inject navigation links to the top of the page\n  nav = dir_title(x, preview)\n  nav = sub('<p>', '<p class=\"nav-path\">', nav, fixed = TRUE)\n  res$payload = sub('<body>', paste0('<body>\\n', nav), p, fixed = TRUE)\n  res\n}\n\n# extensions that litedown can render\nlite_exts = c('md', 'rmd', 'qmd', 'r')\n\nis_lite_ext = function(ext = file_ext(file), file) tolower(ext) %in% lite_exts\n\n# render the path to HTML if possible\nfile_resp = function(x, preview) {\n  raw = preview == '0'  # 0: send raw response; 1: render verbatim; 2: fuse()/mark()\n  ext = if (raw) '' else tolower(file_ext(x))\n  if (preview == '2' && is_lite_ext(ext)) {\n    # to clean up the __files/ dir if requested (via options()) and the dir\n    # didn't exist before\n    if (getOption('litedown.roam.cleanup', FALSE)) {\n      fig = fig_path(x)\n      if (!dir.exists(fig)) on.exit({\n        if (dir.exists(fig)) .env$roam_files[[fig]] = file.mtime(fig)\n      })\n    }\n    # check if the file is for a book or site\n    info = proj_info(x)\n    list(payload = switch(\n      info$type,\n      book = {\n        store_book(info$root, x, info$index)\n        fuse_book(if (info$index) info$root else x, full_output, globalenv())\n      },\n      site = fuse_site(x),\n      if (ext == 'md') mark_full(x) else fuse(x, full_output, envir = globalenv())\n    ))\n  } else {\n    type = mime_type(x)\n    if (!raw && is_text_file(ext, type) &&\n        !inherits(txt <- try_silent(read_utf8(x, error = TRUE)), 'try-error')) {\n      list(payload = mark_full(\n        fenced_block(txt, lineno_attr(if (ext == '') 'plain' else ext))\n      ))\n    } else {\n      file_raw(x, type)\n    }\n  }\n}\n\n# store book dir for books to resolve number_refs() because the book may be\n# partially rendered (in which case we can't resolve refs to other chapters)\nstore_book = function(dir, x, index = FALSE) {\n  f = function(...) .mapply(\n    function(n, v) .env[[paste0('current_', n)]] = v,\n    c('book', 'file', 'index'), list(...)\n  )\n  f(dir, x, index)\n  exit_call(function() f(NULL))\n}\n\n# detect project type for a directory (_litedown.yml may be in an upper-level dir)\nproj_info = function(x, d = dirname(x)) {\n  while (length(yaml <- yml_config(d)) == 0) {\n    if (same_path(d, d2 <- file.path(d, '..'))) break\n    d = d2\n  }\n  # use the field 'type' if provided, otherwise look for 'book' or 'site'\n  type = yaml[['type']] %||% head(intersect(c('book', 'site'), names(yaml)), 1)\n  root = if (length(type)) d else NA\n  if (is.na(root)) type = 'default'\n  # a file doesn't belong to a site if it doesn't match the site file pattern\n  if (type != 'default' && x != '') {\n    p = yaml[[type]][['pattern']] %||% site_pattern\n    if (!grepl(p, x)) type = 'default'\n  }\n  list(\n    type = type, root = root, yaml = yaml,\n    index = !is.na(root) && is_index(x) && same_path(x, file.path(root, basename(x)))\n  )\n}\n\nfull_output = structure('html', full = TRUE)\n# generate full HTML output (instead of fragments)\nmark_full = function(...) mark(..., output = full_output)\n\n# guess if a file is a text file\nis_text_file = function(ext = file_ext(file), type = mime_type(file), file) {\n  (ext %in% c('js', 'latex', 'qmd', 'tex', 'xml') || grepl('^text/', type))\n}\n\nis_roaming = function() isTRUE(getOption('litedown.roaming'))\n\n# return a raw file response\nfile_raw = function(x, type = mime_type(x)) {\n  list(file = normalizePath(x), `content-type` = type)\n}\n\nfile_size = function(x) xfun::format_bytes(file.size(x))\nfile_time = function(x) format(file.mtime(x))\ndir_title = function(f, preview = '1') {\n  links = if (f != '.') {\n    d = if (file_exists(f)) dirname(f) else f\n    d = if (d == '.') character() else unlist(strsplit(d, '/'))\n    b = basename(f)\n    c(\n      sprintf('[%s/](%s)', c('.', d), c(rev(strrep('../', seq_along(d))), './')),\n      if (file_exists(f)) b, if (is_lite_ext(file = f)) c(\n        btn('.save'), if (preview != '2')\n          btn('.run', sprintf('%s?preview=2', b), 'title=\"Run\"')\n      ),\n      if (is_text_file(file = f)) btn('.open')\n    )\n  }\n  txt = one_string(c(sprintf('_%s:_', normalize_path('.')), links), ' ')\n  move_attrs(commonmark::markdown_html(txt, smart = TRUE))\n}\n\nbtn = function(t, u = '#', a = character()) {\n  if (startsWith(t, '.')) {\n    a = c(t, a); t = .icons[t]\n  }\n  a = if (is.character(a)) one_string(c('.btn-lite', a), ' ')\n  a = if (is.null(a)) '' else paste0('{', a, '}')\n  sprintf(' [%s](<%s>)%s ', t, u, a)\n}\n\n.icons = c(.open = '&#9998;', .run = '&#9205;', .save = '&#8623;')\n\nnew_file = function(path, ext, type, css = NULL, js = NULL) {\n  if (file.exists(path)) return(sprintf(\"The file '%s' already exists.\", path))\n  on.exit(xfun:::open_path(path), add = TRUE)\n  if (!is_lite_ext(ext)) return(tolower(file.create(path)))\n  features = strsplit(sub('^new:', '', type), ',')[[1]]\n  a = assets[features, , drop = FALSE]\n  m = list(\n    title = 'Comfortably Untitled',\n    author = tools::toTitleCase(Sys.getenv('USERNAME', Sys.getenv('USER'))),\n    date = format(Sys.Date())\n  )\n  if (nrow(a)) m$output$html = list(meta = list(\n    css = I(na_omit(a[, 'css'])), js = I(na_omit(a[, 'js']))\n  ))\n  txt = c('---', xfun::taml_save(m), '---', '', 'Relax. I need some information first.')\n  if (ext == 'r') txt = paste(\"#'\", split_lines(txt))\n  write_utf8(txt, path)\n  'view'\n}\n"
  },
  {
    "path": "R/site.R",
    "content": "#' Fuse R Markdown documents individually under a directory\n#'\n#' Run [fuse()] on R Markdown documents individually to generate a website.\n#'\n#' If a directory contains a config file `_litedown.yml`, which has a YAML field\n#' `site`, the directory will be recognized as a site root directory. The YAML\n#' field `output` will be applied to all R Markdown files (an individual R\n#' Markdown file can provide its own `output` field in YAML to override the\n#' global config). For example:\n#'\n#' ```yaml\n#' ---\n#' site:\n#'   rebuild: \"outdated\"\n#'   pattern: \"[.]R?md$\"\n#' output:\n#'   html:\n#'     meta:\n#'       css: [\"@default\"]\n#'       include_before: \"[Home](/) [About](/about.html)\"\n#'       include_after: \"&copy; 2024 | [Edit]($input$)\"\n#' ---\n#' ```\n#'\n#' The option `rebuild` determines whether to rebuild `.Rmd` files. Possible\n#' values are:\n#'\n#' - `newfile`: Build an input file if it does not have a `.html` output file.\n#'\n#' - `outdated`: Rebuild an input file if the modification time of its `.html`\n#' output file is older than the input.\n#' @param input The root directory of the site, or a vector of input file paths.\n#' @return Output file paths (invisibly).\n#' @export\nfuse_site = function(input = '.') {\n  info = NULL; preview = FALSE\n  inputs = if (length(input) == 1 && dir.exists(input)) {\n    info = proj_info('', input)\n    find_input(input, TRUE, info$yaml[['site']][['pattern']])\n  } else {\n    info = proj_info(input[1])\n    preview = is_roaming() && length(input) == 1\n    input\n  }\n  root = info$root\n  output = with_ext(inputs, '.html')\n  cfg = merge_list(list(rebuild = 'outdated'), info$yaml[['site']])\n  b = cfg[['rebuild']]\n  if (b == 'outdated') b = 0\n  i = if (is.numeric(b)) filter_outdated(inputs, output, b) else {\n    if (b == 'newfile') !file_exists(output) else TRUE\n  }\n  opts = yaml_field(info$yaml, 'html', c('meta', 'options'))\n  opts[['meta']] = merge_list(list(\n    css2 = c(site_css, '@site'), js2 = site_js,\n    include_before = nav_menu(info, inputs), include_after = format(Sys.Date(), '&copy; %Y')\n  ), opts[['meta']])\n  opts[['options']] = merge_list(\n    list(embed_resources = FALSE, toc = TRUE), opts[['options']]\n  )\n  out = lapply(inputs[i], function(x) {\n    res = if (grepl('[.]md$', x)) {\n      opts = set_site_options(opts, x, root); on.exit(options(opts))\n      mark(x, full_output)\n    } else {\n      Rscript_call(\n        function(x, opts, set, root, flag, output) {\n          set(opts, x, root, list(litedown.roaming = flag))\n          litedown::fuse(x, output, envir = globalenv())\n        },\n        list(x, opts, set_site_options, root, is_roaming(), full_output),\n        fail = paste('Failed to run litedown::fuse() on', x)\n      )\n    }\n    # resolve / to relative paths\n    if (!is.na(info$root)) {\n      up = relative_path(info$root, dirname(x))\n      if (up == '.') up = ''\n      res = match_replace(res, ' (href|src)(=\")/', function(z) {\n        gsub('/$', up, z)\n      })\n    }\n    if (preview) res else write_utf8(res, with_ext(x, '.html'))\n  })\n  if (preview) {\n    if (i) out[[1]] else xfun::file_string(output)\n  } else invisible(output)\n}\n\n# common css/js for sites/books\nsite_css = c('@default', '@article', '@copy-button', '@heading-anchor', '@pages')\nsite_js = c('@sidenotes', '@appendix', '@toc-highlight', '@copy-button', '@heading-anchor', '@pages')\n\n# set global options litedown.html.[meta|options] read from _litedown.yml\nset_site_options = function(opts, input, root, extra = NULL) {\n  m = opts[['meta']]\n  for (i in c('include_before', 'include_after')) {\n    if (!is.character(m[[i]])) next\n    tag = if (i == 'include_before') 'nav' else 'footer'\n    x = mark(I(one_string(m[[i]], test = c(dirname(input), root))))\n    x = sprintf('<%s>%s</%s>', tag, x, tag)\n    m[[i]] = sub_vars(x, list(input = I(input)))\n  }\n  opts[['meta']] = m\n  options(c(set_names(opts, paste0('litedown.html.', names(opts))), extra))\n}\n\nfilter_outdated = function(x, x2, n) {\n  m1 = file.mtime(x); m2 = file.mtime(x2); is.na(m2) | m1 - m2 > n\n}\n\n# build a nav menu from filenames under root directory\nnav_menu = function(info, inputs) {\n  if (is.na(info$root)) return('[Home](/index.html)')\n  root = sub('/+$', '', info$root)  # strip any trailing slash for reliable dirname() comparison\n  b = basename(inputs[dirname(inputs) == root])\n  links = sprintf(\n    '[%s](/%s)', menu_name(sans_ext(ifelse(is_index(b), 'home', b))),\n    if (is_roaming()) paste0(b, '?preview=2') else with_ext(b, '.html')\n  )\n  # add subdirs that already have an index.html (same exclusion as find_input)\n  sub_dirs = list.dirs(root, recursive = FALSE)\n  sub_dirs = sub_dirs[file.exists(file.path(sub_dirs, 'index.html'))]\n  dnames = basename(sub_dirs)\n  c(links, sprintf('[%s](/%s/)', menu_name(dnames), dnames))\n}\n\nmenu_name = function(x) {\n  tools::toTitleCase(gsub('[-_]', ' ', x))\n}\n\n#' Fuse multiple R Markdown documents to a single output file\n#'\n#' This is a helper function to [fuse()] `.Rmd` files and convert all their\n#' Markdown output to a single output file, which is similar to\n#' `bookdown::render_book()`, but one major differences is that all HTML output\n#' is written to one file, instead of one HTML file per chapter.\n#'\n#' If the output format needs to be customized, the settings should be written\n#' in the config file `_litedown.yml`, e.g.,\n#'\n#' ```yaml\n#' ---\n#' output:\n#'   html:\n#'     options:\n#'       toc:\n#'         depth: 4\n#'   latex:\n#'     meta:\n#'       documentclass: \"book\"\n#' ```\n#'\n#' In addition, you can configure the book via the `book` field, e.g.,\n#'\n#' ```yaml\n#' ---\n#' book:\n#'   new_session: true\n#'   subdir: false\n#'   pattern: \"[.]R?md$\"\n#'   chapter_before: \"Information before a chapter.\"\n#'   chapter_after: \"This chapter was generated from `$input$`.\"\n#' ---\n#' ```\n#'\n#' The option `new_session` specifies whether to render each input file in the\n#' current R session or a separate new R session; `chapter_before` and\n#' `chapter_after` specify text to be added to the beginning and end of each\n#' file, respectively, which accepts some variables (e.g., `$input$` is the\n#' current input file path).\n#' @inheritParams fuse\n#' @param input A directory or a vector of file paths. By default, all\n#'   `.Rmd`/`.md` files under the current working directory are used as the\n#'   input, except for filenames that start with `.` or `_` (e.g., `_foo.Rmd`),\n#'   or `.md` files with the same base names as `.Rmd` files (e.g., `bar.md`\n#'   will not be used if `bar.Rmd` exists). For a directory `input`, the file\n#'   search will be recursive if `input` ends with a slash (i.e.,\n#'   sub-directories will also be searched). If a file named `index.Rmd` or\n#'   `index.md` exists, it will always be treated as the first input file. Input\n#'   files can also be specified in the config file `_litedown.yml` (in the\n#'   `input` field under `book`).\n#' @return An output file path or the output content, depending on the `output`\n#'   argument.\n#' @export\nfuse_book = function(input = '.', output = NULL, envir = parent.frame()) {\n  # when input is c(dir, file1, file2, ...), we find book files under dir, but\n  # only preview file1, file2, ...\n  if (dir.exists(input[1])) {\n    preview = input[-1]; input = input[1]\n  } else preview = NULL\n\n  yaml = NULL\n  # search for book files or read from config if input is a dir\n  if (length(input) == 1 && dir.exists(input)) {\n    yaml = yml_config(input)\n    cfg = yaml[['book']]\n    input = file.path(input, cfg[['input']]) %|%\n      find_input(input, cfg[['subdir']] %||% grepl('/$', input), cfg[['pattern']])\n  } else {\n    # if input files are provided directly, read config from the dir of first file\n    cfg = if (length(input)) {\n      yaml = yml_config(dirname(input[1]))\n      yaml[['book']]\n    }\n  }\n  if (length(input) == 0) stop('No input was provided or found.')\n  input = sub('^[.]/+', '', input)  # clean up the leading ./ in paths\n\n  full = is_output_full(output)\n  format = detect_format(output, yaml)\n  output = auto_output(input[1], output, format)\n  cfg = merge_list(list(\n    new_session = FALSE, chapter_before = '', chapter_after = \"Source: `$input$`\"\n  ), cfg)\n\n  # provide a simpler way to configure timing in YAML; only env vars are\n  # inherited in new R sessions, so we attach the timing path to R_LITEDOWN_TIME\n  if (is.character(p <- cfg$time)) {\n    # treat relative path as a path relative to the first input's cache dir\n    if (is_rel_path(p))\n      p = file.path(paste0(sans_ext(normalize_path(input[1])), '__cache'), p)\n    vars = set_envvar(c(R_LITEDOWN_TIME = p))\n    on.exit(set_envvar(vars), add = TRUE)\n    if (file_exists(p)) {\n      # filter out data from input files that do not belong to the book\n      d = readRDS(p)\n      if (!all(i <- d$source %in% input)) {\n        d = d[i, ]; saveRDS(d, p)\n      }\n    } else dir_create(dirname(p))\n  }\n\n  res = lapply(preview %|% input, function(x) {\n    out = if (grepl('[.]md$', x)) read_utf8(x) else {\n      fmt = paste0('markdown:', format)  # generate intermediate markdown output\n      if (cfg$new_session) {\n        Rscript_call(fuse, list(x, fmt))\n      } else {\n        fuse(x, fmt, NULL, envir)\n      }\n    }\n    # remove YAML in the preview mode or for non-index chapters since we only need the body\n    if (length(preview) || isFALSE(.env$current_index) || x != input[1])\n      out = sans_yaml(out)\n\n    if (format != 'html') return(out)\n    # add input filenames to the end for HTML output and wrap each file in a div\n    info = function(cls) c(\n      sprintf('::: {.chapter-%s .side .side-right}', cls),\n      sub_vars(cfg[[sprintf('chapter_%s', cls)]], list(input = I(x))), ':::'\n    )\n    # for the first input, the fenced Divs should be inserted after YAML\n    h = if (length(preview) == 0 && x == input[1]) {\n      out = split_lines(out)\n      if (length(i <- xfun:::locate_yaml(out)) >= 2) {\n        i = seq_len(i[2]); h = out[i]; out = out[-i]\n        h\n      }\n    }\n    c(\n      h, sprintf('::: {.chapter .body data-source=\"%s\"}', x),\n      info('before'), '', out, '', info('after'), ':::'\n    )\n  })\n  tweak_options(format, yaml, list(\n    body_class = '', css2 = c(site_css, '@book'), js2 = site_js\n  ), toc = length(preview) == 0)\n  fuse_output(input[1], output, unlist(res), full)\n}\n\n# read the config file _litedown.yml\nyml_config = function(d) {\n  if (file_exists(cfg <- file.path(d, '_litedown.yml'))) {\n    yaml = xfun::taml_file(cfg)\n    if (!is.null(yaml2 <- normalize_yaml(yaml))) yaml = yaml2\n    yaml\n  }\n}\n\nsite_pattern = '[.][Rq]?md$'\n\n# find input files under a directory\nfind_input = function(d, deep = grepl('/$', d), pattern = NULL) {\n  if (!is.character(pattern)) pattern = site_pattern\n  x = list.files(d, pattern, full.names = TRUE, recursive = deep)\n  # exclude .* and _* files/dirs\n  x = x[!grepl('(^|/)[_.]', gsub('^([.]+/)+', '', x))]\n  # exclude readme\n  x = x[tolower(basename(sans_ext(x))) != 'readme']\n  # for .md files, don't include them if they have .Rmd/.qmd files\n  b = sans_ext(x); i = file_ext(x) == 'md'\n  x = x[!i | !(b %in% sub('[.][Rq]md$', '', x))]\n  x = reorder_input(x)\n  x = sub('^[.]/+', '', x)\n  x\n}\n\n# move index.[Rq]md to the first\nreorder_input = function(x) {\n  i = is_index(x)\n  index = x[i][which.min(nchar(x[i]))]  # take the shortest path\n  c(index, setdiff(x, index))\n}\n\nis_index = function(x) sans_ext(basename(x)) == 'index'\n\n# temporarily set global metadata and options (inheriting from index.Rmd)\ntweak_options = function(format, yaml, meta = NULL, toc = TRUE, options = NULL) {\n  nms = paste0('litedown.', format, c('.meta', '.options'))\n  defaults = list(\n    merge_list(\n      .Options[[nms[1]]], meta, yaml_field(yaml, format, 'meta')\n    ),\n    merge_list(\n      .Options[[nms[2]]], options,\n      list(toc = toc, number_sections = TRUE, embed_resources = FALSE),\n      yaml_field(yaml, format, 'options')\n    )\n  )\n  names(defaults) = nms\n  opts = options(defaults)\n  exit_call(function() options(opts))\n}\n"
  },
  {
    "path": "R/utils.R",
    "content": "# reset an environment using \"objects\" in a list\nreset_env = function(x = list(), envir) {\n  rm(list = ls(envir, all.names = TRUE), envir = envir)\n  list2env(x, envir)\n}\n\n# counters for elements to be cross-referenced (e.g., fig, tab)\ncounters = local({\n  db = NULL\n  list(\n    get = function() db,\n    inc = function(name) db[[name]] <<- (db[[name]] %||% 0L) + 1L,\n    del = function(name) db <<- NULL\n  )\n})\n\n# use PCRE by default (which seems to handle multibyte chars better)\ngregexpr = function(..., perl = TRUE) base::gregexpr(..., perl = perl)\nattr = function(...) base::attr(..., exact = TRUE)  # exact attr() please\n`%|%` = function(x, y) if (length(x)) x else y\nif (getRversion() < '4.4.0') `%||%` = function(x, y) if (is.null(x)) y else x\nset_names = function(x, nm) {\n  names(x) = nm; x\n}\n\ndropNULL = function(x) x[!vapply(x, is.null, logical(1))]\n\n# remove the <p> tag from HTML\nsans_p = function(x) gsub('^<p[^>]*>|(</p>)?\\n$', '', x)\n\n# remove ugly single quotes, e.g., 'LaTeX' -> LaTeX\nsans_sq = function(x) gsub(\"(^|\\\\W)'([^']+)'(\\\\W|$)\", '\\\\1\\\\2\\\\3', x)\n\n# remove YAML header (if title exists, convert it to h1)\nsans_yaml = function(x) {\n  if (length(x) && grepl('^---\\\\s*?($|\\n)', x[1])) {\n    res = xfun::yaml_body(split_lines(x))\n    x = c(if (is.character(t <- res$yaml$title)) paste('#', t), res$body)\n  }\n  x\n}\n\nsplit_chunk = function(...) xfun::divide_chunk(..., use_yaml = FALSE)\n\nis_lang = function(x) is.symbol(x) || is.language(x)\n\nuapply = function(..., recursive = TRUE) unlist(lapply(...), recursive = recursive)\n.mapply = function(fun, ...) base::.mapply(fun, list(...), NULL)\n\n# convert the system locale to a BCP 47 language tag for use in the HTML lang attribute\nlocale_lang = function() {\n  lc = Sys.getlocale('LC_CTYPE')\n  # remove encoding part (e.g., \".UTF-8\", \".1252\")\n  lc = sub('[.][^.]*$', '', lc)\n  # extract language (and optional region) from POSIX-style locale (case-insensitive)\n  m = regmatches(lc, regexpr('^[a-zA-Z]{2,3}([_-][a-zA-Z]{2,3})?', lc))\n  if (length(m) == 0) return('')\n  # normalize to BCP 47: language lowercase, region uppercase, hyphen separator\n  parts = strsplit(m, '[_-]')[[1]]\n  parts[1] = tolower(parts[1])\n  if (length(parts) > 1) parts[2] = toupper(parts[2])\n  paste(parts, collapse = '-')\n}\n\n#' Convert some ASCII strings to HTML entities\n#'\n#' Transform ASCII strings `(c)` (copyright), `(r)` (registered trademark),\n#' `(tm)` (trademark), and fractions `n/m` into *smart* typographic HTML\n#' entities.\n#' @param text A character vector of the Markdown text.\n#' @return A character vector of the transformed text.\n#' @keywords internal\nsmartypants = function(text) {\n  text = split_lines(text)\n  i = prose_index(text)\n  r = '(?<!`)\\\\((c|r|tm)\\\\)|(\\\\d+/\\\\d+)(?!`)'\n  text[i] = match_replace(text[i], r, function(z) {\n    y = pants[z]\n    i = is.na(y)\n    y[i] = z[i]\n    y\n  })\n  text\n}\n\n# Represent some fractions with HTML entities\nfracs = local({\n  n1 = c(\n    '1/2', '1/3', '2/3', '1/4', '3/4', '1/5', '2/5', '3/5', '4/5', '1/6', '5/6',\n    '1/8', '3/8', '5/8', '7/8'\n  )\n  n2 = c('1/7', '1/9', '1/10')\n  x2 = seq_along(n2) + 8527  # &#8528;, 8529, 8530\n  set_names(c(sprintf('&frac%s;', gsub('/', '', n1)), sprintf('&#%d;', x2)), c(n1, n2))\n})\n\npants = c(fracs, c('(c)' = '&copy;', '(r)' = '&reg;', '(tm)' = '&trade;'))\n\n# merge a later list in arguments into a former one by name\nmerge_list = function(...) {\n  dots = list(...)\n  res  = dots[[1]]\n  for (i in seq_along(dots) - 1L) {\n    if (i == 0) next\n    x = dots[[i + 1]]\n    if (!is.list(x)) next\n    res[names(x)] = x\n  }\n  res\n}\n\nCHARS = c(letters, LETTERS, 0:9, '!', ',', '/', ':', ';', '=', '@')\n\n# generate a random string that is not present in provided text\nid_string = function(text, lens = c(5:10, 20), times = 20) {\n  for (i in lens) {\n    for (j in seq_len(times)) {\n      id = paste(sample(CHARS, i, replace = TRUE), collapse = '')\n      if (length(grep(id, text, fixed = TRUE)) == 0) return(id)\n    }\n  }\n  # failure should be very rare\n  stop('Failed to generate a unique ID string. You may try again.')\n}\n\n# a shorthand for gregexpr() and regmatches()\nmatch_replace = function(x, r, replace = identity, ...) {\n  m = gregexpr(r, x, ...)\n  regmatches(x, m) = lapply(regmatches(x, m), function(z) {\n    if (length(z)) replace(z) else z\n  })\n  x\n}\n\n# gregexec() + regmatches() to greedy-match all substrings in regex groups\nmatch_all = function(x, r, ...) {\n  regmatches(x, base::gregexec(r, x, ...))\n}\n# for R < 4.1.0\nif (!exists('gregexec', baseenv(), inherits = TRUE)) match_all = function(x, r, ...) {\n  lapply(match_full(x, r, ...), function(z) {\n    if (length(z)) do.call(cbind, match_one(z, r, ...)) else z\n  })\n}\n\n# regexec() + regmatches() to match the regex once and capture substrings\nmatch_one = function(x, r, ...) regmatches(x, regexec(r, x, ...))\n\n# gregexpr() + regmatches() to match full strings but not substrings in regex groups\nmatch_full = function(x, r, ...) regmatches(x, gregexpr(r, x, ...))\n\n# if `text` is NULL and `input` is a file, read it; otherwise use the `text`\n# argument as input\nread_input = function(input, text) {\n  if (missing(input)) input = NULL\n  if (is.null(text)) {\n    if (is.null(input)) stop(\"Either 'input' or 'text' must be provided.\")\n    text = if (is_file(input)) read_utf8(input) else input\n  }\n  structure(split_lines(text), input = if (is_file(input)) input)\n}\n\n# test if an input is a file path; if shouldn't be treated as file, use I()\nis_file = function(x) {\n  length(x) == 1 && !inherits(x, 'AsIs') && is.character(x) &&\n    (file_ext(x) != '' || suppressWarnings(file_exists(x)))\n}\n\nis_output_file = function(x) {\n  is.character(x) && !(x %in% names(md_formats) || is_ext(x))\n}\n\nis_ext = function(x) grepl('^[.]', x) && sans_ext(x) == ''\n\n# if output has an attribute full = TRUE\nis_output_full = function(x) isTRUE(attr(x, 'full'))\n\n# test if input is R code or not (this is based on heuristics and may not be robust)\nis_R = function(input, text) {\n  if (is_file(input)) grepl('[.][Rrs]$', input) else {\n    # if R code, it must not contain ```{, be syntactically valid, and contain\n    # at least one expression (i.e., not all comments)\n    !length(grep('^\\\\s*```+\\\\{', text)) && !try_error(res <- parse_only(text)) && length(res)\n  }\n}\n\n# make an output filename with the format and input name\nauto_output = function(input, output, format = NULL) {\n  # change NULL to a filename extension\n  if (is.null(output) && !is.null(format)) {\n    output = md_formats[format]\n    if (is.na(output)) stop(\n      \"The output format '\", format, \"' is not supported (must be \",\n      xfun::join_words(names(md_formats), and = ' or ', before = \"'\"), \").\"\n    )\n  }\n  # non-character `output` means the output shouldn't be written to a file\n  if (is.character(output)) {\n    if (startsWith(output, 'markdown:')) output = 'markdown'\n    # if `output` is an extension, make a full file path based on input\n    if (is_ext(output)) {\n      if (is_file(input)) output = with_ext(input, output)\n    }\n    if (is_file(input)) check_output(input, output)\n  }\n  output\n}\n\n# fall back to input path if output path is not available\noutput_path = function(input, output) {\n  if (is_output_file(output)) output else if (is_file(input)) input\n}\n\n# make sure not to overwrite input file inadvertently\ncheck_output = function(input, output) {\n  if (file_exists(input) && same_path(input, output))\n    stop('The output file path is the same as input: ', input)\n  output\n}\n\n# substitute a variable in template `x` with its value; the variable may have\n# more than one possible name, in which case we try them one by one\nsub_var = function(x, name, value, ...) {\n  for (i in name) {\n    if (any(grepl(i, x, fixed = TRUE))) {\n      return(sub(i, one_string(value, ...), x, fixed = TRUE))\n    }\n  }\n  x\n}\n\n# unescape HTML code\nrestore_html = function(x) {\n  x = gsub('&quot;', '\"', x, fixed = TRUE)\n  x = gsub('&amp;', '&', x, fixed = TRUE)\n  x = gsub('&lt;', '<', x, fixed = TRUE)\n  x = gsub('&gt;', '>', x, fixed = TRUE)\n  x\n}\n\n#' Add CSS/JS assets to HTML output\n#'\n#' While CSS/JS assets can be set via the `css`/`js` keys under the `meta` field\n#' of the `html` output format in YAML, this function provides another way to\n#' add them, which can be called in a code chunk to dynamically add assets.\n#' @param feature A character vector of features supported by CSS/JS, e.g.,\n#'   `c('article', 'callout')`. See the row names of `litedown:::assets` for all\n#'   available features. Each feature will be mapped to CSS/JS.\n#' @param css,js Character vectors of CSS/JS assets.\n#' @return A vector of `<link>` (CSS) or `<script>` (JS) tags.\n#' @export\n#' @examples\n#' litedown:::assets[, -1]\n#' # add features\n#' litedown::vest(c('copy-button', 'tabsets'))\n#' # add css/js directly\n#' litedown::vest(css = '@tabsets', js = c('@tabsets', '@fold-details'))\nvest = function(feature = NULL, css = NULL, js = NULL) {\n  if (length(feature)) {\n    a = assets[feature, , drop = FALSE]\n    css = c(a[, 'css'], css); js = c(a[, 'js'], js)\n  }\n  new_asis(c(resolve_files(css, 'css'), resolve_files(js, 'js')), raw = TRUE)\n}\n\nassets = t(data.frame(\n  article = c('side notes, floats, and full-width elements for articles', '@article', '@sidenotes, @appendix'),\n  book = c('cover and chapter pages for books', '@book', NA),\n  callout = c('frames with legends', '@callout', '@callout'),\n  'center-img' = c('center images in paragraphs', NA, '@center-img'),\n  'chapter-toc' = c('add TOC to each chapter', NA, '@chapter-toc'),\n  'copy-button' = c('copy buttons', '@copy-button', '@copy-button'),\n  default = c('default CSS', '@default', NA),\n  'external-link' = c('open external links in new windows', NA, '@external-link'),\n  'fold-details' = c('fold elements (e.g., code blocks)', NA, '@fold-details'),\n  'heading-anchor' = c('add anchor links to headings', '@heading-anchor', '@heading-anchor'),\n  'key-buttons' = c('style keyboard shortcuts', '@key-buttons', '@key-buttons'),\n  pages = c('paginate HTML for printing', '@pages', '@pages'),\n  'right-quote' = c('right-align quote footers', NA, '@right-quote'),\n  snap = c('snap slides', '@snap', '@snap'),\n  tabsets = c('create tabsets from bullet lists or sections', '@tabsets', '@tabsets'),\n  'toc-highlight' = c('highlight TOC items on scroll', NA, '@toc-highlight'),\n  row.names = c('description', 'css', 'js'), check.names = FALSE\n))\n\n# an HTML form for creating new files in roam()\nfeature_form = function(path) {\n  nms = rownames(assets)\n  files = list.files(if (dir.exists(path)) path else dirname(path), full.names = TRUE)\n  files = basename(files[file_exists(files)])\n  one_string(c(\n    '<h3>New File</h3>',\n    '<p><label>Filename: <input type=\"text\" id=\"filename-input\" list=\"file-list\" placeholder=\"enter a new filename (not in the list)\" /></label></p>',\n    if (length(files)) c(\n      '<datalist id=\"file-list\">',\n      sprintf('<option value=\"&#10060; %s\">', html_escape(files, TRUE)),\n      '</datalist>'\n    ),\n    '<p><b>Select HTML features</b></p>', '<p style=\"columns:20em;\">',\n    sprintf(\n      '<label><input name=\"%s\" type=\"checkbox\" /> <a href=\"https://yihui.org/litedown/#sec:%s\" target=\"_blank\"><code>%s</code></a>: %s</label>',\n      nms, nms, nms, html_escape(assets[, 'description'])\n    ), '</p>'\n  ))\n}\n\nna_omit = function(x) x[!is.na(x)]\n\n# accumulate variable values\nacc_var = local({\n  db = list()\n  function(...) {\n    v = list(...)\n    if (is.null(nms <- names(v))) {\n      v = unlist(v)\n      switch(length(v) + 1, db <<- list(), db[[v]], db[v])\n    } else {\n      for (i in nms) db[[i]] <<- c(db[[i]], v[[i]])\n    }\n  }\n})\n\n# set js/css variables according to the js_math option\nset_math = function(o, is_katex) {\n  if (is_katex) o$js = c(o$js, 'dist/contrib/auto-render.min.js')\n  js = js_combine(sprintf('npm/%s%s/%s', o$package, o$version, o$js))\n  js = if (is_katex) c(js, '@render-katex') else c('@mathjax-config', js)\n  css = sprintf('@npm/%s%s/%s', o$package, o$version, o$css)\n  acc_var(js = js, css = css)\n}\n\n# use jsdelivr's combine feature\njs_combine = function(...) {\n  if (length(x <- c(...))) paste0('@', paste(x, collapse = ','))\n}\n\njs_options = function(x, default) {\n  d = js_default(x, default)\n  x = if (is.list(x)) merge_list(d, x) else d\n  if (length(x) == 0) return()\n  if (x$version != '') x$version = sub('^@?', '@', x$version)\n  x\n}\n\njs_default = function(x, default) {\n  if (is.list(x)) x = x$package\n  if (is.null(x) || isTRUE(x)) x = default\n  if (is.character(x)) merge_list(js_libs[[x]], list(package = x))\n}\n\njs_libs = list(\n  highlight = list(\n    version = '11.7.0', style = 'xcode', js = 'build/highlight.min.js'\n  ),\n  katex = list(version = '', css = 'dist/katex.min.css', js = 'dist/katex.min.js'),\n  mathjax = list(version = '3', js = 'es5/tex-mml-chtml.js'),\n  prism = list(\n    version = '1.29.0', js = 'components/prism-core.min.js'\n  )\n)\n\n# set js/css variables according to the js_highlight option\nset_highlight = function(options, html) {\n  # if the class .line-numbers is present, add js/css for line numbers\n  if (any(grepl('<code class=\"[^\"]*line-numbers', html)))\n    acc_var(js = '@code-line-numbers', css = '@code-line-numbers')\n\n  r = '(?<=<code class=\"language-)([^\"]+)(?=\")'\n  if (!any(grepl(r, html, perl = TRUE))) return()\n  if (!length(o <- js_options(options[['js_highlight']], 'prism'))) return()\n\n  p = o$package\n  # return jsdelivr subpaths\n  get_path = function(path) {\n    t = switch(\n      p, highlight = 'gh/highlightjs/cdn-release%s/%s', prism = 'npm/prismjs%s/%s'\n    )\n    sprintf(t, o$version, path)\n  }\n  # add the `prism-` prefix if necessary\n  normalize_prism = function(x) {\n    if (length(x) == 1 && x == 'prism') x else sub('^(prism-)?', 'prism-', x)\n  }\n\n  # if resources need to be embedded, we need to work harder to figure out which\n  # js files to embed (this is quite tricky and may not be robust)\n  embed = ('https' %in% options[['embed_resources']]) || is.character(options[['offline']])\n\n  # style -> css\n  css = c(if (is.null(s <- o$style)) {\n    if (p == 'prism') '@prism-xcode'  # use prism-xcode.css in the lite.js repo\n  } else if (is.character(s)) js_combine(get_path(switch(\n    p,\n    highlight = sprintf('build/styles/%s.min.css', s),\n    prism = sprintf('themes/%s.min.css', normalize_prism(s))\n  ))))\n\n  # languages -> js\n  get_lang = function(x) switch(\n    p,\n    highlight = sprintf('build/languages/%s.min.js', x),\n    prism = sprintf('components/%s.min.js', normalize_prism(x))\n  )\n  autoloader = 'plugins/autoloader/prism-autoloader.min.js'\n  o$js = c(o$js, if (!is.null(l <- o$languages)) get_lang(l) else {\n    # detect <code> languages in html and load necessary language components\n    lang = unlist(match_full(html, r))\n    lang = gsub(' .*', '', lang)  # only use the first class name\n    lang = setdiff(lang, 'plain')  # exclude known non-existent names\n    f = switch(p, highlight = js_libs[[c(p, 'js')]], prism = autoloader)\n    if (!embed && p == 'prism') f else {\n      get_lang(lang_files(p, get_path(f), lang))\n    }\n  })\n  js = get_path(o$js)\n  if (p == 'highlight') js = c(js, 'npm/@xiee/utils/js/load-highlight.js')\n  # do not combine js when they are automatically detected (this will make\n  # embedding faster because each js is a separate URL that has been downloaded)\n  js = if (is.null(l)) paste0('@', js) else js_combine(js)\n\n  acc_var(js = js, css = css)\n}\n\n# figure out which language support files are needed for highlight.js/prism.js\nlang_files = function(package, path, langs) {\n  u = jsdelivr(path, '')\n  x = download_cache$get(u, 'text')\n  x = one_string(x)\n\n  warn = function(l1, l2, url) warning(\n    \"Unable to recognize code blocks with language(s): \", comma_list(l2),\n    \". They will not be syntax highlighted by \", package, \".js. If you can find \",\n    \"the right language files at \", url, \", you may mannually specify their names \",\n    \"in the 'languages' field of the 'js_highlight' option.\",\n    if (length(l1)) c(\" Also remember to add \", comma_list(l1))\n  )\n\n  if (package == 'highlight') {\n    # first figure out all languages bundles in highlight.js (starting with grmr_)\n    x = unlist(strsplit(x, ',\\n?grmr_'))\n    r = '^([[:alnum:]_-]+):.+'\n    x = grep(r, x, value = TRUE)\n    l = gsub(r, '\\\\1', x)\n    # then find their aliases\n    a = lapply(match_full(x, '(?<=aliases:\\\\[)[^]]+(?=\\\\])'), function(z) {\n      z = unlist(strsplit(z, '[\",]'))\n      z[!is_blank(z)]\n    })\n    l = c(l, unlist(a))  # all possible languages that can be highlighted\n    l = setdiff(langs, l)  # languages not supported by default\n    if (length(l) == 0) return()\n    # check if language files exist on CDN\n    d = paste0(dirname(u), '/languages/')\n    l1 = uapply(l, function(z) {\n      if (downloadable(sprintf('%s%s.min.js', d, z))) z\n    })\n    l2 = setdiff(l, l1)\n    if (length(l2)) warn(l1, l2, d)\n    l1\n  } else {\n    # dependencies and aliases (the arrays should be more than 1000 characters)\n    x = unlist(match_full(x, '(?<=\\\\{)([[:alnum:]_-]+:\\\\[?\"[^}]{1000,})(?=\\\\})'))\n    if (length(x) < 2) {\n      warning(\n        \"Unable to process Prism's autoloader plugin (\", u, \") to figure out \",\n        \"language components automatically. Please report this message to \",\n        packageDescription('litedown')$BugReports, \".\"\n      )\n      return()\n    }\n    x = x[1:2]\n    x = lapply(match_full(x, '([[:alnum:]_-]+):(\\\\[\"[^]]+\\\\]|\"[^\"]+\")'), function(z) {\n      z = gsub('[][\"]', '', z)\n      uapply(strsplit(z, '[:,]'), function(y) {\n        set_names(list(y[-1]), y[1])\n      }, recursive = FALSE)\n    })\n    # x1 is dependencies; x2 is aliases\n    x1 = x[[1]]; x2 = unlist(x[[2]])\n    # normalize aliases to canonical names\n    i = langs %in% names(x2)\n    langs[i] = x2[langs[i]]\n    # resolve dependencies via recursion\n    resolve_deps = function(lang) {\n      deps = x1[[lang]]\n      c(lapply(deps, resolve_deps), lang)\n    }\n    # all languages required for this page\n    l1 = unique(uapply(langs, resolve_deps))\n    # languages that are officially supported\n    l2 = c(names(x1), unlist(x1), x2)\n    # for unknown languages, check if they exist on CDN\n    d = sub('/plugins/.+', '/components/', u)\n    l3 = uapply(setdiff(l1, l2), function(z) {\n      if (!downloadable(sprintf('%sprism-%s.min.js', d, z))) z\n    })\n    l4 = setdiff(l1, l3)\n    if (length(l3)) warn(l4, l3, d)\n    l4\n  }\n}\n\n# test if a URL can be downloaded\ndownloadable = function(u, type = 'text') {\n  !try_error(download_cache$get(u, type))\n}\n\n# quote a vector and combine by commas\ncomma_list = function(x) paste0('\"', x, '\"', collapse = ', ')\n\n# get an option specific to an output format\nget_option = function(name, format, ...) {\n  getOption(sprintf('litedown.%s.%s', format, name), ...)\n}\n\n# if a string is a file path found under test dirs, read the file; then concatenate elements by \\n\none_string = function(x, by = '\\n', test = NULL) {\n  if (length(test) && is_file(x)) {\n    p = if (is_abs_path(x)) x else xfun::existing_files(file.path(c(test, '.'), x))\n    if (length(p)) x = read_utf8(p[1]) else stop(\"The file '\", x, \"' is not found\")\n  }\n  paste(x, collapse = by)\n}\n\n# find @citation and resolve references\nadd_citation = function(x, bib, format = 'html') {\n  if (!format %in% c('html', 'latex')) return(x)\n  bib = do.call(c, lapply(bib, rbibutils::readBib, direct = TRUE, texChars = 'convert'))\n  if (length(bib) == 0) return(x)\n  cited = NULL\n  is_html = format == 'html'\n  r = if (is_html) '(?<!<code>)(\\\\[@[-;@ [:alnum:]]+\\\\]|@[-[:alnum:]]+)' else\n    '(?<!\\\\{)(\\\\{\\\\[\\\\}@[-;@ [:alnum:]]+\\\\{\\\\]\\\\}|@[-[:alnum:]]+)'\n  # [@key] for citep, and @key for citet\n  x = match_replace(x, r, function(z) {\n    z2 = uapply(strsplit(z, '[;@ {}]+'), function(keys) {\n      bracket = any(grepl('^\\\\[', keys))\n      if (bracket) keys = gsub('^\\\\[|\\\\]$', '', keys)\n      keys = keys[keys != '']\n      if (length(keys) == 0 || !all(keys %in% names(bib))) return(NA)\n      if (is_html) {\n        cited <<- c(cited, keys)\n        cite_html(keys, bib, bracket)\n      } else {\n        sprintf('\\\\cite%s{%s}', if (bracket) 'p' else 't', one_string(keys, ','))\n      }\n    })\n    ifelse(is.na(z2), z, z2)\n  })\n  if (is_html) {\n    b = bib_html(bib, cited)\n    d = '<div id=\"refs\">'\n    if (any(grepl(d, x, fixed = TRUE))) {\n      x = sub(d, paste0(d, one_string(b)), x, fixed = TRUE)\n    } else {\n      x = one_string(c(x, d, b, '</div>'))\n    }\n  }\n  x\n}\n\n# fall back to given name if family name is empty\nauthor_name = function(x) {\n  a = paste(x$family %|% x$given, collapse = ' ')\n  a = gsub('\\\\{\\\\\\\\(.)}', '\\\\1', a)  # un-escape special latex chars\n  html_escape(a)\n}\n\n# mimic natbib's author-year citation style for HTML output\ncite_html = function(keys, bib, bracket = TRUE) {\n  x = NULL; N = length(keys)\n  for (i in seq_len(N)) {\n    key = keys[i]; b = bib[[key]]; a = b$author; n = length(a)\n    z = paste0(c(\n      author_name(a[[1]]),\n      if (n == 2) c('<span class=\"ref-and\"></span>', author_name(a[[2]])),\n      if (n > 2) '<span class=\"ref-et-al\"></span>', ' ',\n      if (bracket) b$year else\n        c('<span class=\"ref-paren-open ref-paren-close\">', b$year, '</span>')\n    ), collapse = '')\n    cls = if (bracket) c(\n      if (i == 1) 'ref-paren-open', if (i == N) 'ref-paren-close',\n      if (i < N) 'ref-semicolon'\n    )\n    z = cite_link(key, z, one_string(c('', cls), ' '))\n    x = c(x, z)\n  }\n  one_string(x, '')\n}\n\ncite_link = function(key, text, class = '') {\n  sprintf('<a href=\"#ref-%s\" class=\"citation%s\">%s</a>', key, class, text)\n}\n\n# html bibliography\nbib_html = function(bib, keys) {\n  bib = sort(bib[unique(keys)])\n  keys = uapply(bib, function(x) attr(unclass(x)[[1]], 'key'))\n  res = format(bib, 'html')\n  paste0('<p id=\"ref-', keys, '\"', sub('^<p', '', res))\n}\n\n# add meta variables bib-preamble and bib-end for LaTeX output\nbib_meta = function(meta, bib, package) {\n  bib = one_string(bib, ',')\n  if (is.null(meta[['bib-preamble']])) meta[['bib-preamble']] = switch(\n    package,\n    none = '\\\\bibliographystyle{apalike}\\\\let\\\\citep\\\\cite\\\\let\\\\citet\\\\cite',\n    natbib = '\\\\usepackage{natbib}\\\\bibliographystyle{abbrvnat}',\n    biblatex = paste0(\n      '\\\\usepackage[style=authoryear]{biblatex}\\\\addbibresource{', bib,\n      '}\\\\let\\\\citep\\\\parencite\\\\let\\\\citet\\\\cite'\n    )\n  )\n  if (is.null(meta[['bib-end']])) meta[['bib-end']] = if (package == 'biblatex')\n    '\\\\printbibliography' else paste0('\\\\bibliography{', bib, '}')\n  meta\n}\n\n# find headings and build a table of contents as an unordered list\nbuild_toc = function(html, n = 3) {\n  if (n <= 0) return()\n  if (n > 6) n = 6\n  r = sprintf('<(h[1-%d])( id=\"[^\"]+\")?[^>]*>(.+?)</\\\\1>', n)\n  items = unlist(match_full(html, r))\n  # ignore headings with class=\"unlisted\"\n  items = items[!has_class(items, 'unlisted')]\n  if (length(items) <= 1) return()  # require at least 2 items in TOC\n  x = gsub(r, '<toc\\\\2>\\\\3</toc>', items)  # use a tag <toc> to protect heading text\n  x = gsub('<a[^>]+>|</a>', '', x)  # clean up <a>\n  h = as.integer(gsub('^h', '', gsub(r, '\\\\1', items)))  # heading level\n  s = strrep('  ', seq_len(n) - 1)  # indent\n  x = paste0(s[h], '- ', x)  # create an unordered list\n  x = commonmark::markdown_html(x)\n  # add anchors on TOC items\n  x = gsub('<toc id=\"([^\"]+)\">(.+?)</toc>', '<a href=\"#\\\\1\">\\\\2</a>', x)\n  x = gsub('</?toc>', '', x)\n  # add class 'numbered' to the first <ul> if any heading is numbered\n  if (length(grep('<span class=\"section-number[^\"]*\">', x)))\n    x = sub('<ul>', '<ul class=\"numbered\">', x)\n  paste0('<div id=\"TOC\">\\n', x, '</div>')\n}\n\n# add TOC to the html body\nadd_toc = function(html, options) {\n  o = options[['toc']]\n  if (is.null(o) || isFALSE(o)) return(html)\n  if (isTRUE(o)) o = list()\n  if (!is.numeric(o$depth)) o$depth = 3\n  one_string(c(build_toc(html, o$depth), html))\n}\n\nsec_levels = c('subsubsection', 'subsection', 'section', 'chapter', 'part')\n# raise section levels: redefine section to chapter or part, and so on\nredefine_level = function(x, top) {\n  n = switch(top, chapter = 1, part = 2, 0)\n  if (n == 0) return(x)\n  for (i in 3:1) {\n    x = gsub(sprintf('(^|\\n)\\\\\\\\%s', sec_levels[i]), sprintf('\\\\\\\\%s', sec_levels[i + n]), x)\n  }\n  x\n}\n\n# move image attributes like `![](){#id .class width=\"20%\"}`, heading attributes\n# `# foo {#id .class}`, and fenced Div's `::: {#id .class}` into HTML tags and\n# LaTeX commands\nmove_attrs = function(x, format = 'html') {\n  if (format == 'html') {\n    # images\n    x = convert_attrs(x, '(<img src=\"[^>]+ )/>\\\\{([^}]+)\\\\}', '\\\\2', function(r, z, z2) {\n      z1 = sub(r, '\\\\1', z)\n      paste0(z1, z2, ' />')\n    })\n    # headings\n    x = convert_attrs(x, '(<h[1-6])(>.+?) \\\\{([^}]+)\\\\}(</h[1-6]>)', '\\\\3', function(r, z, z3) {\n      z1 = sub(r, '\\\\1 ', z)\n      z24 = sub(r, '\\\\2\\\\4', z)\n      paste0(z1, z3, z24)\n    })\n    # links\n    x = convert_attrs(x, '(<a[^>]+)(>(?s).*?</a>)(\\\\{([^}]+)\\\\})?', '\\\\4', function(r, z, z3) {\n      z1 = sub(r, '\\\\1', z, perl = TRUE)\n      z2 = sub(r, '\\\\2', z, perl = TRUE)\n      paste0(z1, ifelse(z3 == '', '', ' '), z3, z2)\n    })\n    # fenced Div's\n    x = convert_attrs(x, '<p>:::+ \\\\{(.*?)\\\\}</p>', '\\\\1', function(r, z, z1) {\n      # add attributes to the div but remove the data-latex attribute\n      z1 = str_trim(gsub('(^| )data-latex=\"[^\"]*\"( |$)', ' ', z1))\n      sprintf('<div%s%s>', ifelse(z1 == '', '', ' '), z1)\n    })\n    x = gsub('<p>:::+</p>', '</div>', x)\n  } else if (format == 'latex') {\n    # only support image width\n    x = convert_attrs(x, '(\\\\\\\\includegraphics)(\\\\{[^}]+\\\\})\\\\\\\\\\\\{([^}]+)\\\\\\\\\\\\}', '\\\\3', function(r, z, z3) {\n      r2 = '(^|.* )width=\"([^\"]+)\"( .*|$)'\n      j = grepl(r2, z3)\n      w = gsub(r2, '\\\\2', z3[j])\n      w = gsub('\\\\\\\\', '\\\\', w, fixed = TRUE)\n      k = grep('%$', w)\n      w[k] = paste0(as.numeric(sub('%$', '', w[k])) / 100, '\\\\linewidth')\n      z3[j] = paste0('[width=', w, ']')\n      z3[!j] = ''\n      z1 = sub(r, '\\\\1', z)\n      z2 = sub(r, '\\\\2', z)\n      paste0(z1, z3, z2)\n    }, format)\n    # discard most attributes for headings\n    r = sprintf('(\\\\\\\\(%s)\\\\{.+?) \\\\\\\\\\\\{([^}]+)\\\\\\\\\\\\}(\\\\})', paste(sec_levels, collapse = '|'))\n    x = convert_attrs(x, r, '\\\\3', function(r, z, z3) {\n      z = gsub(r, '\\\\1\\\\4', z)\n      k = has_class(z3, 'unnumbered')\n      z[k] = sub('{', '*{', z[k], fixed = TRUE)\n      k = has_class(z3, 'appendix')\n      z[k] = '\\\\appendix'\n      r2 = '(^|.* )id=\"([^\"]+)\".*'\n      k = grepl(r2, z3)\n      id = gsub(r2, '\\\\2', z3[k])\n      z[k] = paste0(z[k], '\\\\label{', id, '}')\n      z\n    }, format)\n    # fenced Div's: first class name is the environment name; options from data-latex\n    r = '\\n\\\\\\\\begin\\\\{verbatim\\\\}\\n(:::+)( \\\\{([^\\n]+?)\\\\})? \\\\1\\n\\\\\\\\end\\\\{verbatim\\\\}\\n'\n    x = convert_attrs(x, r, '\\\\3', function(r, z, z3) {\n      r1 = '(^|.*? )class=\"([^\" ]+)[\" ].*'\n      r2 = ' data-latex=\"([^\"]*)\".*$'\n      r3 = paste0(r1, '?', r2)\n      i3 = grepl(r3, z3)\n      z4 = ifelse(i3, gsub(r3, '{\\\\2}\\\\3', z3), ifelse(z3 == '', '', '{@}'))\n      cls = gsub(r1, '\\\\2', z3)\n      # fig/tab environments don't need the data-latex attribute\n      i4 = !i3 & cls %in% c('figure', 'caption', 'table')\n      z4[i4] = sprintf('{%s}', cls[i4])\n      z3 = latex_envir(gsub('\\\\\\\\', '\\\\', z4, fixed = TRUE))\n      z3[z3 %in% c('\\\\begin{@}', '\\\\end{@}')] = ''\n      i = grep('^\\\\\\\\begin', z3)\n      z3[i] = paste0('\\n', z3[i])\n      i = grep('^\\\\\\\\end', z3)\n      z3[i] = paste0(z3[i], '\\n')\n      # put fig/tab captions in \\caption{}\n      z3 = gsub('\\\\begin{caption}', '\\\\caption{', z3, fixed = TRUE)\n      z3 = gsub('\\\\end{caption}', '}', z3, fixed = TRUE)\n      z3\n    }, format)\n    # remove table env generated from commonmark and use those from fenced Divs\n    x = gsub('\\\\\\\\begin\\\\{table\\\\}\\n(?=\\\\\\\\begin\\\\{tabular\\\\})', '', x, perl = TRUE)\n    x = gsub('(?<=\\\\\\\\end\\\\{tabular\\\\}\\n)\\\\\\\\end\\\\{table}', '', x, perl = TRUE)\n  } else {\n    # TODO: remove attributes for other formats\n  }\n  x\n}\n\nconvert_attrs = function(x, r, s, f, format = 'html', f2 = identity) {\n  r2 = '(?<=^| )[.#]([-:[:alnum:]]+)(?= |$)'  # should we allow other chars in ID/class?\n  match_replace(x, r, function(y) {\n    z = sub(r, s, y, perl = TRUE)\n    if (format == 'html') {\n      z = gsub('[\\u201c\\u201d]', '\"', z)\n    } else {\n      z = gsub('=``', '=\"', z, fixed = TRUE)\n      z = gsub(\"''( |\\\\\\\\})\", '\"\\\\1', z)\n      z = gsub('\\\\\\\\([#%])', '\\\\1', z)\n    }\n    z2 = f2(z)\n    # {-} is a shorthand of {.unnumbered}\n    z2[z2 == '-'] = '.unnumbered'\n    # convert #id to id=\"\" and .class to class=\"\"\n    z2 = match_replace(z2, r2, function(a) {\n      i = grep('^[.]', a)\n      if ((n <- length(i))) {\n        # merge multiple classes into one class attribute\n        a[i] = sub('^[.]', '', a[i])\n        a[i] = c(sprintf('class=\"%s\"', paste(a[i], collapse = ' ')), rep('', n - 1))\n        a = c(a[i], a[-i])\n      }\n      if (length(i <- grep('^#', a))) {\n        a[i] = gsub(r2, 'id=\"\\\\1\"', a[i], perl = TRUE)\n        a = c(a[i], a[-i])  # make sure id is the first attribute\n      }\n      a\n    })\n    # remove spaces after class=\"...\" (caused by merging multiple classes)\n    z2 = sub('(^| )(class=\"[^\"]+\")  +', '\\\\1\\\\2 ', z2)\n    f(r, y, str_trim(z2))\n  })\n}\n\nstr_trim = function(x) gsub('^\\\\s+|\\\\s+$', '', x)\n# trim blank lines from both ends\ntrim_blank = function(x) gsub('^(\\\\s*\\n)+|\\n\\\\s*$', '', x)\n\n# {A}, '', {B}, {C}, '', '' -> \\begin{A}\\end{A}\\begin{B}\\begin{C}\\end{C}\\end{B}\nlatex_envir = function(x, env = NULL) {\n  n = length(x)\n  if (n == 0) return()\n  x1 = x[1]\n  env2 = tail(env, 1)  # the most recent env is in the end\n  env = if (x1 == '') head(env, -1) else c(env, sub('^(\\\\{[^}]+}).*$', '\\\\1', x1))\n  c(if (x1 == '') paste0('\\\\end', env2) else paste0('\\\\begin', x1), latex_envir(x[-1], env))\n}\n\n# fix footnotes for LaTeX output: convert `\\footnotemark[1] \\footnotetext[1]{*}`\n# to `\\footnote{*}` (see r-lib/commonmark#32)\nfix_footnotes = function(x) {\n  f1 = f2 = NULL\n  r = '\\n\\\\\\\\footnotetext\\\\[(.+?)]\\\\{(.+?)\\n\\n}\\n'\n  x = match_replace(x, r, function(z) {\n    f1 <<- c(f1, sub(r, '\\\\1', z))\n    f2 <<- c(f2, sub(r, '\\\\2', z))\n    ''\n  }, perl = FALSE)\n  f1 = sprintf('\\\\footnotemark[%s]', f1)\n  f2 = sprintf('\\\\footnote{%s}', f2)\n  for (i in seq_along(f1)) {\n    x = sub(f1[i], f2[i], x, fixed = TRUE)\n  }\n  x\n}\n\n# add auto identifiers to headings\nauto_identifier = function(x) {\n  r = '<(h[1-6])([^>]*)>(.+?)</\\\\1>'\n  match_replace(x, r, function(z) {\n    z1 = sub(r, '\\\\1', z)  # tag\n    z2 = sub(r, '\\\\2', z)  # attrs\n    z3 = sub(r, '\\\\3', z)  # content\n    i = !grepl(' id=\"[^\"]*\"', z2)  # skip headings that already have IDs\n    p = ifelse(z1 == 'h1', 'chp:', 'sec:')  # h1 is chapter; h2+ are sections\n    id = unique_id(paste0(p[i], alnum_id(z3[i])), 'section')\n    z[i] = sprintf('<%s id=\"%s\"%s>%s</%s>', z1[i], id, z2[i], z3[i], z1[i])\n    z\n  })\n}\n\n# add a number suffix to an id if it is duplicated\nunique_id = function(x, empty) {\n  x[x == ''] = empty\n  i = duplicated(x)\n  for (d in unique(x[i])) {\n    k = x == d\n    x[k] = paste0(x[k], '_', seq_len(sum(k)))\n  }\n  x\n}\n\n# test if a class name exists in attributes\nhas_class = function(x, class) {\n  grepl(sprintf('(^| )class=\"([^\"]+ )?%s( [^\"]+)?\"', class), x)\n}\n\n# number sections in HTML output\nnumber_sections = function(x) {\n  h = sub('</h([1-6])>', '\\\\1', unlist(match_full(x, '</h[1-6]>')))\n  if (length(h) == 0) return(x)  # no headings\n  h = min(as.integer(h))  # highest level of headings\n  r = '<h([1-6])([^>]*)>(?!<span class=\"section-number)'\n  n = rep(0, 6)  # counters for all levels of headings\n  # when previewing a book chapter, set the start number if possible\n  is_appendix = FALSE  # the start \"number\" for appendix is A-Z\n  if (length(f <- .env$current_file)) {\n    if (length(n_start <- grep_sub('^([0-9]+|[A-Z])[-_].+', '\\\\1', basename(f))))\n      if (!(is_appendix <- grepl('^[A-Z]$', n_start))) n[1] = as.integer(n_start) - 1\n  }\n  k0 = 6  # level of last unnumbered heading\n  match_replace(x, r, function(z) {\n    z1 = as.integer(sub(r, '\\\\1', z, perl = TRUE))  # heading levels\n    z2 = sub(r, '\\\\2', z, perl = TRUE)  # heading attributes\n    num_sections = identity  # generate appendix numbers\n    for (i in seq_along(z)) {\n      k = z1[i]\n      if (k < 6) n[(k + 1):6] <<- 0\n      # skip unnumbered sections\n      if (has_class(z2[i], 'unnumbered')) {\n        k0 <<- k; next\n      } else {\n        # don't number headings with level lower than last unnumbered heading\n        if (k > k0) next else k0 <<- 6\n      }\n      if (has_class(z2[i], 'appendix')) {\n        if (k != h) stop(\n          \"The 'appendix' attribute must be on the top-level heading (\",\n          strrep('#', h), ').'\n        )\n        num_sections = local({\n          a = n[k]  # an offset (highest top-level heading number before appendix)\n          # number headings with A-Z or roman numerals\n          num = if (sum(z1[i:length(z)] == h) - 1 > 26) as.roman else {\n            function(i) LETTERS[i]\n          }\n          function(s) {\n            if (s[1] <= a) stop(\n              'An appendix section must start with the top-level heading (',\n              strrep('#', h), ').'\n            )\n            s[1] = num(s[1] - a)\n            s\n          }\n        })\n        next\n      }\n      n[k] <<- n[k] + 1\n      # remove leading 0's\n      s = if (h > 1) n[-(1:(h - 1))] else n\n      if (is_appendix) s[1] = n_start else s = num_sections(s)\n      s = paste(s, collapse = '.')\n      s = gsub('([.]0)+$', '', s)  # remove trailing 0's\n      # if section number doesn't contain '.', assign a class 'main-number' to the number\n      z[i] = paste0(z[i], sprintf(\n        '<span class=\"section-number%s\">%s</span> ',\n        ifelse(grepl('[.]', s), '', ' main-number'), s\n      ))\n    }\n    z\n  })\n}\n\n# number elements such as headings and figures, etc and resolve cross-references\nnumber_refs = function(x, r, katex = TRUE) {\n  if (length(x) == 0) return(x)\n  db = list()  # element numbers\n\n  # first, find numbered section headings\n  r2 = '<h[1-6][^>]*? id=\"([^\"]+)\"[^>]*><span class=\"section-number[^\"]*\">([A-Z0-9.]+)</span>'\n  m = match_all(x, r2)[[1]]\n  if (length(m)) {\n    ids = m[2, ]\n    db = as.list(set_names(m[3, ], ids))\n  }\n\n  # retrieve refs from all chapters for fuse_book()\n  if (is.character(b <- .env$current_book)) db = merge_list(.env$refs[[b]], db)\n\n  # then find and number other elements\n  r2 = sprintf('<a href=\"#@%s\"> ?</a>', r)\n  db2 = list()\n  x = match_replace(x, r2, function(z) {\n    type = sub(r2, '\\\\2', z)\n    id = sub(r2, '\\\\1', z)\n    ids = split(id, type)\n    db2 <<- unlist(unname(lapply(ids, function(id) set_names(seq_along(id), id))))\n    sprintf('<span class=\"ref-number-%s\">%d</span>', type, db2[id])\n  })\n  db = unlist(if (is.character(b)) merge_list(db, as.list(db2)) else c(db, db2))\n  ids = names(db)\n  if (any(i <- duplicated(ids))) warning('Duplicated IDs: ', one_string(ids[i], ', '))\n\n  # save refs db for fuse_book()\n  if (is.character(b)) .env$refs[[b]] = db\n\n  # finally, resolve cross-references\n  r2 = sprintf('<a href=\"#(%s)\">@\\\\1</a>', r)\n  match_replace(x, r2, function(z) {\n    type = sub(r2, '\\\\3', z)\n    id = sub(r2, '\\\\2', z)\n    # equation numbers will be resolved in JS later\n    i1 = grepl('^eq[-:]', id)\n    z[i1] = sprintf('\\\\ref{%s}', id[i1])\n    i2 = grepl('^eqn[-:]', id)\n    z[i2] = sprintf('\\\\eqref{%s}', sub('eqn', 'eq', id[i2]))\n    i3 = i1 | i2\n    # KaTeX requires references to be in math, e.g., \\(\\ref{ID}\\)\n    if (katex) z[i3] = paste0('\\\\(', z[i3], '\\\\)')\n    i = id %in% ids\n    # for backward compatibility, if fig-id is not found, also look for fig:id\n    if (any(i4 <- !i & !i3)) {\n      id2 = sub('-', ':', id)\n      j = i4 & (id2 %in% ids)\n      id[j] = id2[j]; i[j] = TRUE; i4[j] = FALSE\n    }\n    if (any(i)) z[i] = sprintf(\n      '<a class=\"cross-ref-%s\" href=\"#%s\">%s</a>', type[i], id[i], db[id[i]]\n    )\n    if (any(i4)) warning('Reference key(s) not found: ', one_string(id[i4], ', '))\n    z\n  })\n}\n\n# add a special anchor [](#@id) to text, to be used to resolved cross-references\nadd_ref = function(id, type, x = NULL) {\n  c(sprintf('[](#@%s:%s)', type, id), x)\n}\n\n# make cross-refs for LaTeX output, to be resolved by a LaTeX engine\nlatex_refs = function(x, r, clever = FALSE) {\n  ar = paste0('@', r)\n  r0 = function(a, b) sprintf('\\\\\\\\protect\\\\\\\\hyperlink\\\\{%s\\\\}\\\\{%s\\\\}', a, b)\n  r1 = r0(ar, '\\\\s*')  # \\label{}\n  r2 = r0(r, ar)  # \\ref{}\n  x = gsub(r1, '\\\\\\\\label{\\\\1}', x)\n  x = gsub(r2, sprintf('\\\\\\\\%sref{\\\\1}', if (clever) 'c' else ''), x)\n  x\n}\n\nembed_resources = function(x, options) {\n  if (length(x) == 0) return(x)\n  r = '\\n<link[^>]*? rel=\"stylesheet\" [^>]*>|\\n<script[^>]*? src=\"[^\"]+\"[^>]*>\\\\s*</script>'\n  x = match_replace(x, r, function(z) {\n    # de-dup assets (e.g., when vest() is called multiple times on the same asset)\n    z[duplicated(z)] = ''\n    z\n  })\n\n  embed = c('https', 'local') %in% options[['embed_resources']]\n  offline = options[['offline']]\n  if (!any(embed, is.character(offline))) return(x)\n  clean = options[['embed_cleanup']]\n\n  # find images in <img> and (for slides only) comments\n  rs = c(\n    '(<img src=\")([^\"]+)(\"[^>]*?/>)',\n    '(<!--#[^>]*? style=\"background-image: url\\\\(\"?)([^\"]+?)(\"?\\\\);)'\n  )\n  for (r in rs) x = match_replace(x, r, function(z) {\n    z1 = sub(r, '\\\\1', z)\n    z2 = sub(r, '\\\\2', z)\n    z3 = sub(r, '\\\\3', z)\n    # skip images already base64 encoded\n    for (i in grep('^data:.+;base64,.+', z2, invert = TRUE)) {\n      is_svg = grepl('[.]svg$', f <- z2[i]) && grepl('^<img', z1[i])\n      a = if (is_svg) str_trim(gsub('^\"|/>$', '', z3[i])) else ''\n      f = download_url(f, offline)\n      if (is_https(f)) {\n        if (embed[1]) z2[i] = if (!is_svg) download_cache$get(f, 'base64') else {\n          download_cache$get(f, 'text', function(xml) process_svg(xml, a))\n        }\n      } else if (embed[2]) {\n        if (file_exists(f <- URLdecode(f))) {\n          z2[i] = if (is_svg) process_svg(read_utf8(f), a) else base64_uri(f)\n          if (clean && normalize_path(f) %in% .env$plot_files) file.remove(f)\n        } else {\n          warning(\"File '\", f, \"' not found (hence cannot be embedded).\")\n        }\n      }\n    }\n    ifelse(grepl('<svg', z2), z2, paste0(z1, z2, z3))\n  })\n\n  # CSS and JS\n  r = paste0(\n    '<link[^>]*? rel=\"stylesheet\" href=\"([^\"]+)\"[^>]*>|',\n    '<script([^>]*?) src=\"([^\"]+)\"([^>]*)>\\\\s*</script>'\n  )\n  x2 = NULL  # js to be appended before </body>\n  x = match_replace(x, r, function(z) {\n    z1 = sub(r, '\\\\1', z)  # css\n    z2 = sub(r, '\\\\3', z)  # js\n    js = z2 != ''\n    z3 = paste0(z1, z2)\n    at = sub(r, '\\\\2\\\\4', z)  # attributes for js\n    # skip resources already base64 encoded\n    i1 = !grepl('^data:.+;base64,.+', z3)\n    z3[i1] = gen_tags(\n      z3[i1], ifelse(js[i1], 'js', 'css'), embed[1], embed[2], offline, at\n    )\n    # js with the defer attribute and has been embedded (i.e., no src attribute)\n    i = grepl(' defer($| )', at) & !grepl('^<script([^>]*?) src=\"', z3) & i1\n    x2 <<- c(x2, z3[i])\n    z3[i] = ''\n    z3\n  })\n  # move deferred scripts to the end of <body>\n  if (length(x2)) {\n    x = if (length(grep('</body>', x)) != 1) {\n      one_string(c(x, x2))\n    } else {\n      sub('</body>', one_string(c(x2, '</body>')), x, fixed = TRUE)\n    }\n  }\n  x\n}\n\n# remove the xml/doctype declaration in svg, and add attributes\nprocess_svg = function(x, attr) {\n  while (length(x) > 0 && !grepl('^\\\\s*<svg .+', x[1])) x = x[-1]\n  if (length(x) > 0 && !attr %in% c('', 'alt=\"\"')) {\n    x[1] = if (grepl(r <- '\\\\s*>\\\\s*$', x[1])) {\n      paste0(gsub(r, ' ', x[1]), attr, '>')\n    } else {\n      paste(x[1], attr)\n    }\n  }\n  one_string(x)\n}\n\nnormalize_options = function(x, format = 'html') {\n  g = get_option('options', format)\n  x = option2list(x)\n  n = names(x)\n  # default options\n  d = option2list(markdown_options())\n  g = option2list(g)\n  d[names(g)] = g  # merge global options() into default options\n  d[n] = x  # then merge user-provided options\n  if (!is.character(d[['top_level']])) d$top_level = 'section'\n  if (isTRUE(o <- d[['offline']])) o = 'assets'\n  if (!is.character(o)) o = FALSE\n  d$offline = o\n  d = normalize_embed(d)\n  d\n}\n\nnormalize_embed = function(x) {\n  v = x[['embed_resources']]\n  if (is.logical(v)) {\n    v = if (v) 'local'\n  } else {\n    if (length(v) == 1 && v == 'all') v = c('local', 'https')\n  }\n  x[['embed_resources']] = v\n  x\n}\n\nnamed_bool = function(x, val = TRUE) as.list(set_names(rep(val, length(x)), x))\n\n# normalize metadata variable names: change _ to -\nnormalize_meta = function(x) {\n  # make sure some variables are available in metadata\n  x = merge_list(list(classoption = '', documentclass = 'article', body_class = 'body'), x)\n  names(x) = gsub('_', '-', names(x))\n  x\n}\n\n# turn '+a-b c' to list(a = TRUE, b = FALSE, c = TRUE)\noption2list = function(x) {\n  if (!is.character(x)) return(as.list(x))\n  x = unlist(strsplit(x, '\\\\b(?=[+-])', perl = TRUE))\n  x = unlist(strsplit(x, '\\\\s+'))\n  x = setdiff(x, '')\n  i = grepl('^-', x)\n  c(named_bool(sub('^[-]', '', x[i]), FALSE), named_bool(sub('^[+]', '', x[!i])))\n}\n\npkg_file = function(...) {\n  system.file(..., package = 'litedown', mustWork = TRUE)\n}\n\njsdelivr = function(file, dir = 'npm/@xiee/utils/') {\n  ifelse(is_https(file), file, sprintf('https://cdn.jsdelivr.net/%s%s', dir, file))\n}\n\n# get the latest version of jsdelivr assets\njsd_version = local({\n  vers = list()  # cache versions in current session\n  # find version from local cache\n  p_cache = function() {\n    d = if (getRversion() >= '4.0') tools::R_user_dir('litedown', 'cache') else {\n      file.path(dirname(tempdir()), 'R', 'cache', 'litedown')\n    }\n    file.path(d, 'jsd_versions.rds')\n  }\n  # update cache to a specific version\n  u_cache = function(info, pkg, version, file) {\n    if (!grepl('^@', version)) version = paste0('@', version)\n    info[[pkg]] = list(version = version, time = Sys.time())\n    saveRDS(info, file)\n    version\n  }\n  # cache expires after one week by default\n  v_cache = function(pkg, force, delta = getOption('litedown.jsdelivr.cache', 604800)) {\n    if (!isTRUE(force) && file.exists(f <- p_cache())) {\n      info = readRDS(f)\n      if (is.character(force)) {\n        u_cache(info, pkg, force, f)\n      } else {\n        info = info[[pkg]]\n        if (!is.null(t <- info$time) && Sys.time() - t <= delta) info$version\n      }\n    }\n  }\n  # query version from jsdelivr api\n  v_api = function(pkg) {\n    x = tryCatch(\n      read_utf8(paste0('https://data.jsdelivr.com/v1/packages/', pkg, '/resolved?specifier=latest')),\n      error = function(e) v_cache(pkg, FALSE, Inf)  # fall back to local cache\n    )\n    v = grep_sub('.*\"version\":\\\\s*\"([0-9.]+)\".*', '@\\\\1', x)\n    if (length(v)) {\n      if (dir_create(dirname(f <- p_cache()))) {\n        info = if (file.exists(f)) readRDS(f) else list()\n        u_cache(info, pkg, v[1], f)\n      }\n      v[1]\n    }\n  }\n  # force can be TRUE/FALSE/version number\n  function(pkg, force = FALSE) {\n    if (isFALSE(force) && is.character(v <- vers[[pkg]])) return(v)\n    v = v_cache(pkg, force) %||% v_api(pkg)\n    (vers[[pkg]] <<- if (length(v)) v[1] else '')\n  }\n})\n\njsd_versions = function(pkgs) uapply(pkgs, jsd_version)\n\n# resolve the implicit latest version to current latest version\njsd_resolve = function(x) {\n  if (!getOption('litedown.jsd_resolve', TRUE)) return(x)\n  rs = paste0(c(\n    '((?<=https://cdn.jsdelivr.net/combine/)|(?<=,))',\n    '(?<=https://cdn.jsdelivr.net/)(?!combine/)'\n  ), '([^/]+/(@[^/]+/)?[^/@]+)((?=/)|(?=@latest$)|$)')\n  for (r in rs) x = match_replace(x, r, function(z) {\n    paste0(z, jsd_versions(z))\n  })\n  x = sub('@latest$', '', x)\n  x\n}\n\n# if both @foo and foo are present, remove foo\nresolve_dups = function(x) {\n  x = unique(x)\n  for (i in grep('^@', x, value = TRUE)) {\n    x = x[x != sub('^@', '', i)]\n  }\n  x\n}\n\n# add filename extensions to paths without extensions: the path should contain\n# no slashes (@xiee/utils assets) or at least 2 slashes when the npm package\n# name doesn't start with @ (otherwise the path should contain >= 3 slashes),\n# e.g. don't add ext for npm/simple-datatables\nadd_ext = function(x, ext) {\n  n = vapply(strsplit(x, ''), function(z) sum(z == '/'), 0)\n  i = file_ext(x) == '' & (n == 0 | n > grepl('^[^/]+/@', x) + 2)\n  x[i] = paste0(x[i], ext)\n  x\n}\n\n# resolve CSS/JS shorthand filenames to actual paths (e.g., 'default' to 'default.css')\nresolve_files = function(x, ext = 'css') {\n  x = resolve_dups(x)\n  if (length(x) == 0) return(x)\n  min_ext = paste0('.min.', ext)\n\n  # @foo -> jsdelivr.net/npm/@xiee/utils/ext/foo.min.ext\n  i0 = grepl('^@', x)\n  x[i0] = sub('^@', '', x[i0])\n  # @foo@version -> @npm/@xiee/utils@version/foo\n  i = i0 & !grepl('[/,]', x)\n  x[i] = sub('^(.+?)@(.+)$', sprintf('npm/@xiee/utils@\\\\2/%s/\\\\1', ext), x[i])\n\n  # if no extension is specified, use .min.ext\n  x[i0] = add_ext(x[i0], min_ext)\n  i = i0 & !grepl('[/,]', x)\n  x[i] = jsdelivr(paste0(ext, '/', x[i]))\n\n  # @foo/bar -> jsdelivr.net/foo/bar\n  i = i0 & !grepl(',', x)\n  x[i] = jsdelivr(x[i], '')\n\n  # @foo/bar,baz -> jsdelivr.net/combine/foo/bar,foo/baz\n  i = i0 & grepl(',', x)\n  if (any(i)) x[i] = sapply(strsplit(x[i], ',\\\\s*'), function(z) {\n    d = dirname(z[1])\n    if (d == '.') d = paste0('npm/@xiee/utils/', ext)\n    for (j in seq_along(z)) {\n      if (grepl('/', z[j])) {\n        d = dirname(z[j])\n      } else {\n        z[j] = paste(d, z[j], sep = '/')\n      }\n    }\n    z = add_ext(z, min_ext)\n    paste0('combine/', paste(z, collapse = ','))\n  })\n  x[i] = jsdelivr(x[i], '')\n  x[i0] = jsd_resolve(x[i0])\n\n  i = dirname(x) == '.' & file_ext(x) == '' & !file_exists(x)\n  x[i] = map_assets(x[i], ext)\n  x = if (ext %in% c('css', 'js')) gen_tags(x, ext) else read_all(x)\n  I(x)\n}\n\nmap_assets = function(x, ext) {\n  if (length(x) == 0) return(x)\n  x[x == 'slides'] = 'snap'  # alias slides.css -> snap.css\n  # built-in resources in this package\n  b1 = c(if (ext != 'js') 'default', 'snap')\n  i1 = x %in% b1\n  x[i1] = file.path(pkg_file('resources'), sprintf('%s.%s', x[i1], ext))\n  # in case users forgot to type @ for jsdelivr assets\n  b2 = sub('@', '', assets[, ext])\n  i2 = x %in% b2\n  x[i2] = jsdelivr(sprintf('%s/%s.min.%s', ext, x[i2], ext))\n  x[i2] = jsd_resolve(x[i2])\n  if (any(i3 <- !(i1 | i2))) stop(\n    \"Invalid '\", ext, \"' option: \", paste0(\"'\", x[i3], \"'\", collapse = ', '),\n    \" (possible values are: \", paste0(\"'\", unique(c(b1, na_omit(b2))), \"'\", collapse = ', '), \")\"\n  )\n  x\n}\n\n# generate tags for css/js depending on whether they need to be embedded or offline\ngen_tag = function(\n  x, ext = file_ext(x), embed_https = FALSE, embed_local = FALSE,\n  offline = FALSE, attr = ' defer'\n) {\n  if (ext == 'css') {\n    t1 = '<link rel=\"stylesheet\" href=\"%s\">'\n    t2 = c('<style type=\"text/css\">', '</style>')\n  } else if (ext == 'js') {\n    t1 = paste0('<script src=\"%s\"', attr, '></script>')\n    t2 = c('<script>', '</script>')\n  } else stop(\"The file extension '\", ext, \"' is not supported.\")\n  is_web = is_https(x)\n  is_rel = !is_web && is_rel_path(x)\n  if (is_web && embed_https && xfun::url_filename(x) == 'MathJax.js') {\n    warning('MathJax.js cannot be embedded. Please use MathJax v3 instead.')\n    embed_https = FALSE\n  }\n  # linking for 1) local rel paths that don't need to be embedded, or 2) web\n  # resources that don't need to be accessed offline\n  link1 = is_rel && !embed_local\n  link2 = is_web && !embed_https\n  if (link1 || link2) {\n    if (link2) x = download_url(\n      x, offline, handler = function(code) resolve_url(x, code, ext, FALSE)\n    )\n    sprintf(t1, x)\n  } else {\n    # embedding for other cases\n    one_string(c(t2[1], resolve_external(x, is_web, ext), t2[2]))\n  }\n}\n\n# check if path starts with https://, but also tolerate http://\nis_https = function(x) grepl('^https?://', x)\n\n# a vectorized version\ngen_tags = function(...) mapply(gen_tag, ..., USE.NAMES = FALSE)\n\n# read CSS/JS and embed external fonts/background images, etc.\nresolve_external = function(x, web = TRUE, ext = '') {\n  # download and cache web resources\n  if (web) download_cache$get(x, 'text', handler = function(code) {\n    # remove jsdelivr comments\n    if (grepl('^https://cdn[.]jsdelivr[.]net/', x)) {\n      code = gsub(\n        '^/[*][*]\\n( [*][^\\n]*\\n)+ [*]/\\n|\\n/[*/]# sourceMappingURL=.+[.]map( [*]/)?$',\n        '', one_string(code)\n      )\n      code = resolve_url(x, code, ext)\n    }\n    code\n  }) else {\n    resolve_url(x, read_utf8(x), ext)\n  }\n}\n\n# find url(\"path\") in JS/CSS and base64 encode or download the resources\nresolve_url = function(url, code, ext, encode = TRUE) {\n  d = dirname(url)\n  # embed fonts in mathjax's js\n  if (grepl('^https://cdn[.]jsdelivr[.]net/npm/mathjax.+[.]js$', url)) {\n    r = '.*?fontURL:[^\"]+\\\\(\"([^\"]+)\".*'  # output/chtml/fonts/woff-v2\n    p = grep_sub(r, '\\\\1', code)\n    if (length(p) == 1) code = match_replace(\n      code, '(?<=src:\\'url\\\\(\")(%%URL%%/[^\"]+)(?=\"\\\\))', function(u) {\n        f = sub('%%URL%%/', '', u, fixed = TRUE)\n        u2 = paste(d, p, f, sep = '/')\n        if (encode) {\n          uapply(u2, download_cache$get, 'base64')\n        } else {\n          .mapply(function(u, f) download_url(u, p, f), u2, f)\n          u\n        }\n      }\n    ) else warning(\n      'Unable to determine the font path in MathJax. Please report an issue to ',\n      'https://github.com/yihui/litedown/issues and mention the URL ', url, '.'\n    )\n  }\n  # find `attr: url(resource)` and embed url resources in CSS\n  if (ext == 'css') {\n    r = '((: ?| )url\\\\([\"\\']?)([^\"\\')]+)([\"\\']?\\\\))'\n    code = match_replace(code, paste0('(?<!behavior)', r), function(z) {\n      z1 = gsub(r, '\\\\1', z)\n      z2 = gsub(r, '\\\\3', z)\n      z3 = gsub(r, '\\\\4', z)\n      i = is_https(z2)\n      u = ifelse(i, z2, sprintf('%s/%s', d, z2))\n      z2 = unlist(if (encode) {\n        lapply(u, function(x) {\n          if (is_https(x)) download_cache$get(x, 'base64') else base64_uri(x)\n        })\n      } else {\n        .mapply(function(u, i, f) download_url(u, '.', if (!i) f), u, i, z2)\n      })\n      paste0(z1, z2, z3)\n    })\n  } else if (ext == 'js') {\n    # the literal sequence '</script>' inside <script> tag needs to be escaped;\n    # to avoid problems in general caused by closing tags, we escape them all\n    code = gsub('</script>', '<\\\\/script>', code, fixed = TRUE)\n  }\n  code\n}\n\n# download a file to a local dir if the local file doesn't exist\ndownload_url = function(url, dir = '.', file = NULL, handler = NULL) {\n  if (!is.character(dir) || !is_https(url) || grepl('^http://127.0.0.1', url))\n    return(url)\n  f = file %||% gsub('^https?://|[?#].*$', '', url)\n  p = URLdecode(f)\n  # see if a previous version of the jsd asset exists\n  if (dir.exists(dir) && startsWith(p, 'cdn.jsdelivr.net/')) in_dir(dir, {\n    p2 = sub('@[0-9.]+/', '@*/', p)\n    if (n <- length(p2 <- Sys.glob(p2))) p = p2[n]\n  })\n  if (dir != '.') p = file.path(dir, p)\n  if (!file_exists(p)) {\n    xfun::download_file(url, p)\n    if (is.function(handler)) xfun::process_file(p, function(x) {\n      x  # force eval before changing wd\n      in_dir(dirname(p), handler(x))\n    })\n  }\n  URLencode(p)\n}\n\n# compact HTML code\nclean_html = function(x) {\n  x = gsub('\\n+(\\n<[a-z1-6]+[^>]*>|\\n</(body|div|head|html)>)', '\\\\1', x)\n  # can also merge <style>/<script> tags (<style type=\"text/css\">).+?</style>\\\\s*\\\\1\n  x\n}\n"
  },
  {
    "path": "R/zzz.R",
    "content": "# for R versions < 4.0\n\n# ignore the perl argument for regexec() if it's not supported (in R < 3.3); we\n# use perl = TRUE only for speed in crack()\nif (!'perl' %in% names(formals(regexec)))\n  regexec = function(..., perl) base::regexec(...)\n\nhas_fun = function(name, envir = baseenv()) exists(name, envir, inherits = FALSE)\n\nif (!has_fun('isFALSE')) isFALSE = function(x) {\n  is.logical(x) && length(x) == 1 && !is.na(x) && !x\n}\nif (!has_fun('startsWith')) startsWith = function(...) xfun:::startsWith(...)\nif (!has_fun('endsWith')) endsWith = function(...) xfun:::endsWith(...)\nif (!has_fun('strrep')) strrep = function(...) xfun:::strrep(...)\n"
  },
  {
    "path": "README.md",
    "content": "```         \n  ______  \n /   ⚡  \\\n/litedown\\\n\\   ⚡    /\n \\______/\n```\n\n# R Markdown Reimagined\n\n<!-- badges: start -->\n\n[![R-CMD-check](https://github.com/yihui/litedown/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/yihui/litedown/actions/workflows/R-CMD-check.yaml)\n[![CRAN\nrelease](https://www.r-pkg.org/badges/version/litedown)](https://cran.r-project.org/package=litedown)\n[![litedown on\nr-universe](https://yihui.r-universe.dev/badges/litedown)](https://yihui.r-universe.dev/litedown)\n[![Codecov test\ncoverage](https://codecov.io/gh/yihui/litedown/branch/main/graph/badge.svg)](https://app.codecov.io/gh/yihui/litedown?branch=main)\n\n<!-- badges: end -->\n\nThis package provides a trimmed-down and reimagined implementation of [R\nMarkdown](https://rmarkdown.rstudio.com). It is much more lightweight, at the\nprice of dropping some features. It does not depend on the R package **knitr**\nor the system package Pandoc.\n\nPlease consider this package experimental for now. The documentation is also\nvery incomplete and still under development.\n\n## Usage\n\n### Installation\n\nAt the moment, you are recommended to install the development version from\nr-universe:\n\n``` r\ninstall.packages('litedown', repos = c('https://yihui.r-universe.dev', 'https://cloud.r-project.org'))\n```\n\n### Markdown rendering\n\nThe function `litedown::mark()` is based on the R package\n[**commonmark**](https://github.com/r-lib/commonmark), and renders Markdown to\nvarious output formats supported by **commonmark**, which are primarily HTML and\nLaTeX. MS Office formats are not supported.\n\n### Knitting\n\nR Markdown documents need to be knitted to Markdown before being rendered to a\ntarget output format. The function `litedown::fuse()` plays a role similar to\n`knitr::knit()` and `rmarkdown::render()`. It fuses program code with\nnarratives, i.e., it executes code in the source document and interweaves\nresults with narratives in the output document.\n\n### Previewing\n\nTry `litedown::roam()`.\n\n## Scope\n\nWe want to limit the scope of this package. Most planned features have been\n(re-)implemented so far, such as reports, slides, books, websites (including\npackage sites), and paged HTML documents, etc. See the set $R$ below:\n\n$$\\mathrm{litedown} = \\min{\\{R\\}} + \\{D_i\\} - \\{D_e\\} + \\{J\\}$$\n\n-   $R$ = **knitr** + **evaluate** + **rmarkdown** + **bookdown** +\n    **blogdown** + **pagedown** + **pkgdown** + **xaringan** + **tufte** +\n    **distill** + **htmlwidgets**\n\n-   $D_i$ = (internal dependencies) **commonmark** + **xfun**\n\n-   $D_e$ = (external dependencies) Pandoc + Bootstrap + jQuery + GitBook +\n    Hugo + paged.js + remark.js + tufte.css + distill.js/.css + ...\n\n-   $J$ = Lightweight [vanilla JavaScript/CSS](https://github.com/yihui/lite.js)\n\nPlease feel free to file feature requests anyway, but we may be a little\nconservative when considering them (we will take votes into consideration, so\nplease upvote features you like).\n\n## License\n\nThe **litedown** package is licensed under MIT.\n"
  },
  {
    "path": "docs/01-start.Rmd",
    "content": "# Get Started {#chp:start}\n\n::: epigraph\n> Things are in the saddle,\\\n> And ride mankind.\n>\n> ---Ralph Waldo Emerson, *Ode, Inscribed to William H. Channing*\n:::\n\nR Markdown documents need to be compiled to Markdown before being rendered to a\ntarget output format such as HTML and LaTeX.\n\nFor those who are familiar with R Markdown, you can think of the function\n`litedown::fuse()` as the new `knitr::knit()`, `litedown::fiss()` as the new\n`knitr::purl()`, and `litedown::mark()` as the new Pandoc. @fig:fuse-flow shows\nthe conversion process from R Markdown to a target format.\n\n```{mermaid}\n#| fuse-flow, echo = FALSE,\n#| fig.cap = 'A diagram illustrating how **litedown** converts R Markdown documents to target output formats.'\n\nflowchart LR\n  A@{ shape: doc, label: \"*.Rmd\" }-. <code>fuse()</code> .-o F@{ shape: bolt }\n  F o--- B@{ shape: doc, label: \"*.md\" }\n  B-. <code>mark()</code> .-o G@{ shape: framed-circle }\n  G --> C@{ shape: doc, label: \"*.html\" }\n  G --> D@{ shape: doc, label: \"*.tex\" }\n  G --> E@{ shape: doc, label: \"...\" }\n  A-. <code>fiss()</code> .-o H@{ shape: cross-circ } --> I@{ shape: lin-doc, label: \"*.R\" }\n```\n\nThe function `fuse()` \"fuses\" program code with narratives, i.e., it executes\nall code in the source document and interweaves results with narratives in the\noutput document; `fiss()` splits the document and extracts the code. Similar to\n**knitr**, **litedown** supports code chunks (@sec:code-chunks) and inline code\n(@sec:inline-code).\n\nWe will first introduce `fuse()` in @chp:fuse, and then `mark()` in @chp:syntax\nand @chp:mark.\n\n::: callout-tip\nIf you do not do computing in Markdown, you do not need to learn\n`fuse()`---`mark()` will be enough.\n:::\n\n## A new road to `roam()` {#sec:new-road}\n\nBefore entering the **litedown** world, I suggest you forget the `Knit` button\nif it has become your muscle memory to click on that button to view results\nafter writing every single line of code. The recommended way to work with\n**litedown** is `litedown::roam()`, which allows you to preview and render\ndocuments through a web interface.\n\nTo help you bid goodbye to the `Knit` button, please take 5 minutes to practice\nthe following mouse-clicking exercise (but try not to be addicted):\n\n::: new-road\n```{=html}\n<p><button class=\"old-knit\">Knit</button></p>\n<p><button class=\"new-fuse\">Render</button> <button class=\"new-fuse new-preview\">Run</button></p>\n```\n:::\n\nIf the `Knit` button still sticks to your mind after you have practiced for a\nfew hundred times, I'd like to thank you for your loyalty to **knitr**, and\nplease see @sec:knit-button for how to continue to use the `Knit` button with\n**litedown**.\n\nYou can learn more about the `Render` and `Run` buttons in @sec:live-preview.\nBasically, the `Render` button renders a document to an output file, whereas the\n`Run` button renders a document in memory without writing to disk.\n\n::: callout-tip\nUnless you use **litedown** programmatically (i.e., in scripts), you should\nrarely need to call `fuse()` or `mark()` directly---`litedown::roam()` will call\n`fuse()` and `mark()` for you behind the scenes.\n:::\n\nYou can also create new documents with the [+](){.larger} button on the toolbar.\n\n## A minimal example\n\nTo understand what `fuse()` does, we look at a minimal example:\n\n`{r} .ex2(1)`\n\nWhen we `fuse()` the source document, the program code will be executed, writing\nresults to the output.\n\nYou can see that the value `2` was assigned to the object `x` in a code chunk,\nand the inline R expression `pi * x^2` was also evaluated to a number.\n\nThe advantage of interweaving computing code with text is that you can freely\nupdate the source code according to the purpose of computing, and you will get\nthe up-to-date results after rebuilding the document. For example, you can\nchange `x` to `3`, and the area of the circle will be automatically updated\nafter you re-run `fuse()`. You do not need to copy and paste computational\noutcome manually from one place to another.\n\n## Basic concepts\n\nA computational document contains both text narratives and code for computing.\n@fig:flow-concepts shows the basic components of a document and how they work\ntogether.\n\n```{mermaid}\n#| flow-concepts, echo = FALSE,\n#| fig.cap = 'An illustration of the structure of a computational document and how its components are split, computed, and merged.'\nflowchart TD\n  O@{ shape: lin-doc, label: \"Input document\" }--\"crack()\"--> A@{ shape: f-circ } -.-o B[[YAML]]\n  A -.-o C[[Text chunk]]\n  C -.- C1>Text] --- C12{ } --> H[[Text output]]\n  C -.- C2>Inline code]--> F{Value} --> H\n  C -.- C3>More...] --- C32{ } --> H\n  A -.-o D[[Code chunk]]\n  D -.- D1>Chunk options]\n  D -.- D2>Code]--> G{Results} --> I[[Chunk output]]\n  A -.-o E[[More chunks...]] -.- K>...] --> L{...} --> M[[More output...]] -.-> J\n  H -.-> J@{ shape: doc, label: \"Markdown\" }\n  I -.-> J\n  B -.-> P>...] --> Q{...} --> R[[YAML]] -.-> J\n  J--\"mark()\" --> N@{ shape: tag-doc, label: \"Output document\" }\n  subgraph \"fuse()\"\n  C1\n  C2\n  C3\n  D1\n  D2\n  K\n  P\n  end\n  class O,J,N document\n  classDef document stroke-width:3px\n  class C12,F,C32,G,L,Q fuse-out\n  classDef fuse-out fill:lightyellow\n  class H,I,J,M,N,R output\n  classDef output fill:none\n```\n\nFirst, the input is split into three types of blocks: the YAML metadata, text\nchunks, and code chunks.\n\nThe *YAML metadata* contains information such as the title, author, date, and\nother configurations for the document.\n\nA *text chunk* could consist entirely of plain-text narratives that do not\nrequire computing, or contain both text fragments and *inline code expressions*.\nIn the latter case, the inline code can be executed to return its value to the\noutput.\n\nA *code chunk* contains code that often does more sophisticated computing than\ninline code. Optionally, a code chunk can contain *chunk options* to control the\nbehavior of the computing and output.\n\nAfter all code has been executed, the output blocks will be merged to a Markdown\ndocument, which can be further rendered to a target output format such as HTML\nand LaTeX.\n\n:::: {#fig:humpty-dumpty .figure}\n![Humpty\nDumpty](https://upload.wikimedia.org/wikipedia/commons/b/b2/Humpty_Dumpty_1_-_WW_Denslow_-_Project_Gutenberg_etext_18546.jpg){width=\"250px\"}\n\n::: caption\n[ ](#@fig:humpty-dumpty) After raising two kids for a few years, I have been so\ndeeply brainwashed by nursery rhymes that it was Humpty Dumpty that came to my\nmind when I was thinking about an example to explain computational documents.\nDon't be surprised if you run across Blippi and garbage trucks somewhere in this\nbook.\n:::\n::::\n\nTake the following document for example:\n\n```` {.md data-file=\"*.Rmd\"}\n---\ntitle: \"A Report on the Wall\"\nauthor: \"`{r} who`\"\ndate: \"`{r} Sys.Date()`\"\n---\n\n```{r, order = 0}\nwho = 'Humpty Dumpty'\n```\n\n`{r} who` sat on a wall.\n\n`{r} who` had a great fall.\n\n```{r}\nbroken = strsplit(who, ' ') |> unlist()\n```\n\nAll the king's horses and all the king's men\n\nCouldn't put `{r} broken[1]` together again.\n\n<!--\nIf the king's horses and men knew R, they could've put\nHumpty together again by paste(broken, collapse = ' ').\n-->\n````\n\nThe YAML metadata is provided between `---` and `---` in the beginning. Note\nthat it can also contain code to be evaluated, such as the variable `who` and\nthe function call `Sys.Date()`.\n\nThe document has two code chunks in triple-backtick (```` ``` ````) fences. The\nfirst code chunk contains a chunk option `order = 0` (you may see\n@sec:option-order for its meaning later). You can do any computing in code\nchunks and generate text results or graphics.\n\nThere are two text chunks: one between the two code chunks, and one after the\nsecond code chunk. These text chunks contain inline R code expressions in\n`` `{r} ` ``. An inline code expression is typically used to get a single value.\n\nIn the end, there is a comment in `<!-- -->`. Comments will not be displayed in\nthe output document.\n\n## A few words on naming\n\nWhen I started writing the **litedown** package, I was thinking of nuclear\nfusion reactions (although my knowledge in physics is quite limited): text and\ncode chunks are like atomic nuclei, and they can be combined to form a report.\nThe process is controlled by a reactor. That was how I came up with the function\nnames `fuse()` and `reactor()`.\n\nTo prepare the atomic nuclei, I need to `crack()` the source document. When the\ninput is not a document but a script, I will `sieve()` out the elements from\ncomments. The fusion reaction can take place after `crack()` or `sieve()` is\ndone.\n\nMaybe the neurons in my brain are wired in an odd way, but I feel mysteriously\nhappy that the names `crack()` and `sieve()` weakly resembles my friend Carson\nSievert's name. For that reason, Carson will live in my heart forever, even if\nhe would never send a pull request to **litedown**.\n\nSince there also exist fission reactions, the function `fiss()` is meant to\ndecompose a document.\n\nAs for the package name \"litedown\", it has three meanings:\n\n1.  The obvious meaning is the lightweight of the package.\n\n2.  The less obvious meaning is that light comes down from the fusion reactor\n    like the Sun. This meaning is represented by the symbol for `fuse()`, which\n    is a zigzag arrow (↯).\n\n3.  Finally, it is a play on the phrase \"lie down\". I intended to write this\n    package as my last \"down\" package. After it is done, I wish to lie down.\n\nLet there be light.\n"
  },
  {
    "path": "docs/02-fuse.Rmd",
    "content": "# Computing {#chp:fuse}\n\n::: epigraph\n> If you give someone a program, you will frustrate them for a day; if you teach\n> them how to program, you will frustrate them for a lifetime.\n>\n> ---David Leinweber\n:::\n\nIn this chapter, we introduce how to manage computing in computational\ndocuments. The computing may be done in code chunks, inline code expressions,\nscript files, and in R or other languages.\n\n## Code chunks\n\nA code chunk is a fenced code block that consists of: 1) the language (or\nengine) name, 2) chunk options that control the behavior of computing and\noutput, and 3) the program code. It is of the following form:\n\n```` md\n```{lang}\n#| chunk options\n\ncode\n```\n````\n\nWhitespaces are allowed between the opening ```` ``` ```` and `{`. The language\nname should begin with only alphanumeric characters (`a-z`, `A-Z`, `0-9`) and\nunderscores (`_`). If any other character is used or there are no curly braces\nin the chunk header, the chunk will be treated as a normal fenced code block in\nMarkdown, instead of a code chunk for computing. Below are some examples of\nnormal code blocks:\n\n```` md\n```r\n# lack of {} in the header\n```\n\n``` {.r, echo = FALSE}\n# the language name should not start with \".\"\n```\n````\n\nIn this book, we use the term \"code block\" to refer to *plain Markdown* code\nblocks, and \"code chunk\" to refer to *executable* code chunks.\n\n### Syntax for chunk options {#sec:option-syntax}\n\nThe chunk options can be provided in the chunk header after the language name,\nor in the chunk body as pipe comments (`#|`).\n\n-   Chunk options in the chunk header must be provided in the comma-separated\n    tag-value pairs, except for the chunk label, of which the tag `label` can be\n    omitted.\n\n-   Chunk options in the chunk body must be provided in `#|` comments in the\n    beginning. They can use either the comma-separated or YAML syntax. When\n    using the comma-separated syntax, you can wrap the comments freely.\n\nFor the comma-separated syntax, the option value can be provided via arbitrary R\ncode as long as it is syntactically valid. The code will be evaluated when the\nvalue is needed (i.e., the evaluation is lazy).\n\n::: callout-note\nYou can write chunk options in both the chunk header and pipe comments, but\nplease note that if you provide the same option in both places, the option in\npipe comments will override the one in the chunk header, e.g.,\n\n```` md\n```{r, echo = TRUE}\n#| echo = FALSE, fig.height = 6\n```\n````\n\nThe option `echo` will be `FALSE` for this chunk.\n:::\n\nBelow are examples showing different ways to write chunk options:\n\n-   Options in the chunk header (the chunk label is `foo`, which can also be\n    written as `label = \"foo\"`):\n\n    ```` md\n    ```{r, foo, echo = FALSE, fig.height = 6, fig.cap = \"A scatterplot.\"}\n    plot(cars)\n    ```\n    ````\n\n-   Comma-separated options in pipe comments:\n\n    ```` md\n    ```{r}\n    #| foo, echo = FALSE, fig.height = 6,\n    #| fig.cap = \"A scatterplot.\"\n\n    plot(cars)\n    ```\n    ````\n\n-   YAML options in pipe comments:\n\n    ```` md\n    ```{r}\n    #| label: \"foo\"\n    #| echo: false\n    #| fig.height: 6\n    #| fig.cap: \"A scatterplot.\"\n\n    plot(cars)\n    ```\n    ````\n\nYou can use `#|` for any language, although not all languages use `#` as the\ncomment character. If you prefer, you can use language-specific comments and add\npipes after the comment characters to write chunk options, e.g., `//|` for\nJavaScript. You can find the supported comment characters for different\nlanguages in `xfun:::comment_chars`. Here are a few of them:\n\n```{r}\nstr(xfun:::comment_chars[c('css', 'fortran', 'js', 'r')])\n```\n\nFor languages that use opening and closing comment delimiters such as C and CSS,\nyou do not need to repeat the comment pipe on every line, e.g.,\n\n```` md\n```{css}\n/*| label = 'foo',\n    echo = FALSE   */\n\np { color: red; }\n```\n````\n\nIn this book, I will write options in the chunk header when they are relatively\nshort, and write them in pipe comments when they are too long to fit one line.\n\nSee @sec:chunk-options for the full list of possible chunk options.\n\n### The chunk option manager {#sec:reactor}\n\nAll chunk options are managed internally via `litedown::reactor()`. To get an\noption value, call `reactor(\"NAME\")`, where `NAME` is the option name (e.g.,\n`fig.width`). To set an option globally for all code chunks, call\n`reactor(NAME = VALUE)`.\n\n::: callout-note\nIf an option is set in both `reactor()` and a code chunk (@sec:option-syntax),\nthe value in the chunk wins. For example, if you have set\n`litedown::reactor(echo = FALSE)` previously, and `echo = TRUE` in a chunk\nheader, the chunk option `echo` will be `TRUE` for this chunk.\n:::\n\nAlternatively, you can manipulate the value returned by `reactor()` directly,\nwhich is essentially an environment:\n\n``` r\nopts = litedown::reactor()\nopts$fig.width  # get an option\nopts$echo = FALSE  # set an option via assignment\n```\n\nYou can call `reactor()` anywhere to get or set chunk options. For example, you\ncan call it inside a code chunk. Please note that when you call it to change\nglobal chunk options *outside* an R Markdown document, we recommend that you\nrestore the original options afterwards to limit the scope of the changes,\notherwise the changes may affect the behavior of other documents unexpectedly.\nTo restore options, you need to save the old option values, e.g.,\n\n``` r\n# reactor() returns old values after setting new values\nold_opts = litedown::reactor(echo = FALSE, fig.height = 6)\n\n# fuse a document with new global chunk options\nlitedown::fuse(\"foo.Rmd\")\n\n# restore old option values\nlitedown::reactor(old_opts)\n```\n\nThis is unnecessary if you call `reactor()` inside a document, since global\nchunk options are always restored when `fuse()` finishes compiling the document,\ne.g.,\n\n```` md\n```{r, setup}\nlitedown::reactor(echo = FALSE)\n```\n\n```{r}\n# echo = FALSE will be applied to this chunk\n1 + 1\n```\n````\n\nThe chunk option `echo` will be restored to `TRUE` after `fuse()` finishes this\ndocument.\n\n### Skipped code chunks {#sec:skipped-chunks}\n\nCode chunks inside other code blocks are not parsed or evaluated, which provides\na way to write verbatim code chunks, e.g.,\n\n````` md\n````md\nSome verbatim content.\n\n```{r}\n1 + 1\n```\n````\n`````\n\nSimilarly, code in HTML comments (`<!-- -->`) will not be recognized, either,\ne.g.,\n\n```` html\n<!--\nDo not run this chunk:\n\n```{r}\n1 + 1\n```\n\nor the inline code `{r} pi`.\n-->\n````\n\n### Show chunk fences in output\n\nIf `N + 1` pairs of curly braces are used in the opening fence, the chunk fences\n(with `N` pairs of curly braces) and chunk options will be shown in the output,\nwhich can be useful for telling readers the full source of a chunk, e.g.,\n\n::: callout-example\n```` md\n```{{r}}\n#| echo = TRUE, eval = FALSE\n\n1 + 1\n```\n````\n:::\n\n::: callout-output\n```{{r}}\n#| echo = TRUE, eval = FALSE\n\n1 + 1\n```\n:::\n\n## Chunk options {#sec:chunk-options}\n\nAll supported chunk options are listed alphabetically below.\n\n### `attr.*`: attributes for output elements {#sec:option-attr}\n\nThe `attr.*` options can be used to customize different types of output\nelements, such as source code blocks, messages, and plots, etc. To understand\nthese options, you need to learn the Markdown syntax in @sec:attributes and\n@sec:fenced-divs.\n\n@fig:attr-structure illustrates the structure of Markdown output from a code\nchunk (the class names in the figure such as `.chunk` are not names used in the\nactual output but for illustration only).\n\n```{css, echo = FALSE}\n.attr-blocks {\n  margin: 1em auto;\n  max-width: 30em;\n  border-right: 1px solid;\n  pre { margin: 0 2em; }\n  code { padding: .5em; border-left: 1px solid; }\n  .source-block { border-left-color: orangered; }\n  pre:has(.open-fence, .close-fence) {\n    code { border-left-width: 2px; }\n  }\n  pre:has(.open-fence) {\n    margin-top: 1em;\n    &:first-child { margin: 0 0 1em 0; }\n  }\n  pre:has(.close-fence) {\n    margin-bottom: 1em;\n    &:last-child { margin: 1em 0 0; }\n  }\n}\n```\n\n::::: {#fig:attr-structure .figure}\n::: {.attr-blocks .fenced-chunk}\n``` {.md .open-fence .code-fence}\n:::: {.chunk}\n```\n\n``` {.md .open-fence .code-fence .source-block}\n``` {.source}\n```\n\n``` {.r .source-block}\n1:2 + 1:3\n```\n\n```` {.md .close-fence .code-fence .source-block}\n```\n````\n\n``` {.md .open-fence .code-fence}\n``` {.output}\n```\n\n```         \n#> [1] 2 4 4\n```\n\n```` {.md .close-fence .code-fence}\n```\n````\n\n``` {.md .open-fence .code-fence}\n``` {.warning}\n```\n\n``` {.plain .warning}\n#> longer object length is not a multiple of ...\n```\n\n```` {.md .close-fence .code-fence}\n```\n````\n\n``` {.md .open-fence .code-fence .source-block}\n``` {.source}\n```\n\n``` {.r .source-block}\nplot(cars)\n```\n\n```` {.md .close-fence .code-fence .source-block}\n```\n````\n\n``` md\n![alt](*__files/*.png){.plot}\n```\n\n``` {.md .close-fence .code-fence}\n::::\n```\n:::\n\n::: caption\n[ ](#@fig:attr-structure) An illustration of the output structure of a code\nchunk.\n:::\n:::::\n\nThe source code, text output, and messages (including warnings and errors) are\nformatted as fenced code blocks. Plots are written in `![]()`. The whole chunk\ncan be wrapped in a fenced `Div` (@sec:fenced-divs). The `attr.*` options will\nadd attributes to these fenced code blocks, plots, and fenced `Div`s.\n\n-   `attr.asis`: When provided and the chunk option `results = 'asis'`, text\n    output will be wrapped in a fenced `Div` using attributes from this option.\n    By default, this option is empty, and the fenced `Div` is not generated.\n\n-   `attr.chunk`: When provided, the whole chunk will be wrapped in a fenced\n    `Div` with attributes from this option. By default, it is empty.\n\n-   `attr.error`: Attributes for error message blocks. The default is\n    `.plain .error` (i.e., two class names `plain` and `error` will be added to\n    error blocks). The class name `plain` is to avoid syntax highlighting.\n\n-   `attr.message`: Attributes for message blocks generated by `message()`. The\n    default is `.plain .message`.\n\n-   `attr.output`: Attributes for text output blocks. By default, it is empty.\n\n-   `attr.plot`: Attributes for plots. If a code chunk produces multiple plots,\n    you may provide a vector to `attr.plot` so that *i*-th element of the vector\n    is applied to the *i*-th plot. For example, if a code chunk has two plots,\n    you may use `attr.plot = c('width=\"40%\"', 'width=\"60%\"')` to set the width\n    of the first plot to `40%`, and the second plot to `60%`.\n\n-   `attr.source`: Attributes for the source blocks. By default, it uses the\n    language name of the code chunk as the class name. For example, for\n    ```` ```{r} ````, the attribute `.r` will be used.\n\n-   `attr.warning`: Attributes for warning blocks. The default is\n    `.plain .warning`.\n\nBelow is an example of using options `attr.chunk`, `attr.source`, and\n`attr.plot`:\n\n`{r} .ex(2)`\n\nHere is an example of creating a callout (@sec:callout) from a code chunk:\n\n`{r} .ex(3)`\n\n### `cache`: speed up the computing {#sec:option-cache}\n\n> There are only two hard things in Computer Science: cache invalidation and\n> naming things.\n>\n> ---Phil Karlton\n\nYou can cache the computing of code chunks via the chunk option `cache = TRUE`.\nWhen a code chunk is cached, the computing will be skipped when the document is\ncompiled on the next time if the source code in the chunk has not\nchanged[^02-fuse-1] and the external dependencies used by the chunk have not\nchanged, either, otherwise the cache will be invalidated and the results will be\nre-computed.\n\n[^02-fuse-1]: Cosmetic changes such as adding/deleting spaces and blank lines or\n    modifying comments do not matter. The code is considered unchanged if it\n    still parses to the same expressions via `parse()`. For example,\n    `parse(text = '1+1')` and `parse(text = '1 + 1')` generate the same result.\n\nThe key to understand cache (in)validation is understanding the \"external\ndependencies\" of a code chunk. A common dependency is *external variables*. For\nexample, `x` is an external (or global) variable and `y` is an internal (or\nlocal) variable in the following chunk:\n\n```` md\n```{r}\ny = x + 1\n```\n````\n\nThat is because `y` is defined inside the code chunk, and `x` must come from\nelsewhere, otherwise the code chunk will throw an error (\"object `x` not\nfound\").\n\nWhen cache is enabled on a code chunk, the global and local variables will be\nautomatically inferred from the code. If the value of any global variable has\nchanged, the cache will be invalidated. Local variables will be saved in a\ncurrent run, and (lazy-)loaded[^02-fuse-2] in the next run, so they can be\navailable to later code chunks in the document.\n\n[^02-fuse-2]: Lazy-loading means that the value of a variable will not be read\n    from the cache until the variable is actually used somewhere. This can save\n    the read time, especially if a large variable is not actually used later in\n    the document.\n\nAnother common dependency is external data sources. For example, if the data\nfile `foo.csv` has been updated in the following chunk, we may want to\ninvalidate the cache and read the file again:\n\n```` md\n```{r, cache = TRUE}\nz = read.csv('foo.csv')\n```\n````\n\n@fig:flow-cache illustrates how the cache system works. The code expression,\nglobal variables, and other dependencies will be summarized into a hash (a\ncharacter string), which will be used to check if the cache exists. Any change\nin the input of the hash will lead to a change in the hash value, which in turn\ninvalidates the old cache and creates new cache.\n\n```{mermaid}\n#| flow-cache, echo = FALSE,\n#| fig.cap = 'The cache (in)validation process.'\n\nflowchart TD\n  A[[Code chunk]]--\"parse()\"--> B1>Expression]\n  A--\"xfun::find_globals()\"--> B2>Global variables]\n  A--\"cache.extra\"--> B3>Other dependencies]\n  B1--\"deparse()\"--> C((Hash))\n  B2 --> C\n  B3 --> C\n  C--find cache--> D@{ shape: f-circ }\n  D--\"No (re-run code)\"--> E@{ shape: lin-cyl, label: \"New cache\" }\n  D--\"Yes (skip running)\"--> F[(Load cache)]--import--> G@{ shape: bow-rect, label: \"Local variables\" }\n  E--\"export\"--> G\n  style C stroke-dasharray:5 5,stroke-width:3px,color:orangered\n  style E fill:lightyellow\n```\n\nBesides the option `cache`, below are other chunk options that allow you to\ncustomize the cache system:\n\n-   `cache.path`: The path to save the cache. If the path is intended to be a\n    directory path, please add a trailing slash. By default, the path is set to\n    `INPUT__cache/`, where `INPUT` is the input file path to `fuse()`. For\n    example, if you `fuse('foo.Rmd')`, the default cache path will be\n    `foo__cache/`. If the input is not a file, `litedown__cache/` will be used.\n\n    The special value `:memory:` means in-memory caching, meaning that the cache\n    will be stored in memory, which is faster than writing to disk but the cache\n    will be lost once the R session quits.\n\n-   `cache.vars`: Names of local variables. By default, local variables are\n    automatically detected from the expression via `xfun::find_locals()`.\n    Locally created variables are cached along with the full output of the code\n    chunk. They will be re-loaded the next time the code chunk is recompiled,\n    unless the cache is invalidated.\n\n-   `cache.hash`: Names of R variables to be used to determine if cache should\n    be loaded or invalidated. By default, it is the names of global variables\n    automatically detected by `xfun::find_locals()` from the code chunk. You can\n    provide a vector of names to override the automatic detection if you want\n    some specific global variables to affect caching, or if the automatic\n    detection is not reliable.\n\n    Alternatively, you can also pass a list object containing values that should\n    affect caching (i.e., if any of these values change, the cache should be\n    invalidated). Please avoid using too large objects, since hashing them may\n    be slow.\n\n-   `cache.extra`: Additional information to validate the cache. For example, if\n    the code reads an external file `foo.csv`, and you want the cache to be\n    invalidated after the file is modified, you may use\n    `extra = file.mtime(\"foo.csv\")` (i.e., use the file modification time for\n    validation).\n\n-   `cache.keep`: By default, only one copy of the cache is kept, and all other\n    copies are automatically purged.\n\n    -   If `TRUE`, all copies of the cache are kept, which can be useful when\n        you are experimenting with different changes and have not decided the\n        version of the change to use. You can always go back to a certain\n        version, and the cache corresponding to that version still exists.\n\n    -   If `FALSE`, all copies are removed, which means the cache is *always*\n        invalidated, and can be useful to force re-executing the code, after\n        which you can clear this option to resume the default behavior of\n        keeping one copy.\n\n-   `cache.rw`: A list of functions to read/write the cache files. The list is\n    of the form\n    `list(name = 'xxx', load = function(file) {}, save = function(x, file) {})`.\n    This argument can also take a character string to use some built-in\n    read/write methods. currently including:\n\n    -   `rds`: The default, using `readRDS()` and `saveRDS()`.\n\n    -   `raw`: Using `serialize()` and `unserialize()`). Note that the `rds`\n        method generates smaller files because it uses compression, but is often\n        slower than the `raw` method, which does not use compression.\n\n    -   `qs2`: Using `qs2::qs_read()` and `qs2::qs_save()`. It requires the\n        **qs2** package, which can be much faster than the above base R methods\n        and also supports compression.\n\nA special application of the `cache.hash` option is to freeze the computation of\na code chunk, meaning that the cache will not be affected by any variable, even\nif the chunk uses global variables. To do this, you can set `cache.hash` to any\nnon-character constant, e.g., `cache.hash = FALSE`. Since `FALSE` is a constant\nthat cannot be changed by any variable, the cache will not be invalidated by\nchanges in any variable.\n\n::: callout-caution\nWith great power comes great responsibility. Freezing the cache can make it\nfaster to compile the document, but you may get outdated and/or inconsistent\ncomputational results when the old cache should have been invalidated. Please\nmake sure you understand what you are doing before freezing the cache.\n:::\n\n### `cap.pos`: caption position {#sec:option-cap-pos}\n\nPossible values are `'top'` and `'bottom'`. By default, figure captions are\nplaced below figures, and table captions are placed above tables.\n@tab:tab-bottom shows an example of a table caption at the bottom.\n\n```{{r}}\n#| tab-bottom, cap.pos = 'bottom',\n#| tab.cap = 'A table caption at the bottom via `cap.pos = \"bottom\"`.'\nhead(cars, 4)\n```\n\nYou can test different positions for both figures and tables in the following\nexample.\n\n`{r} .ex(4)`\n\n### `child`: child documents {#sec:option-child}\n\nThe `child` option can take a vector of file paths to other `.Rmd` files, which\nwill be compiled and included as the output the current chunk. This offers a way\nto organize a large R Markdown report as smaller child documents.\n\n### `code` and `file`: alternative ways to provide code {#sec:option-code}\n\nWhile you can write source code directly in a code chunk, there are two more\nways to provide the code through the chunk options:\n\n-   `code`: A character vector of the source code, e.g.,\n    `code = c('x = 1 + 1', 'x')`.\n\n-   `file`: A vector of file paths to read the code from, e.g.,\n    `file = c('foo.R', 'bar.R')`. If both `file` and `code` options are provided\n    and non-empty, the `code` option will be ignored.\n\nThese options will be ignored (with a warning) if the code chunk is not empty.\nThey can be used in any code chunks (not necessarily R code chunks).\n\n`{r} c(.ex(5), .ex(5, '.md'))`\n\n### `collapse`: collapse source code and text output {#sec:option-collapse}\n\nWhen the option `collapse = TRUE` (it is `FALSE` by default), adjacent source\ncode blocks and verbatim text output blocks will be merged, which can make the\noutput a little more compact, e.g.,\n\n```{{r, collapse = TRUE}}\nx = 1:5\nx\nx + 100\n```\n\nNote that if a source block and an output block are not adjacent to each other,\nthey will not be merged, e.g., when there is a plot between the source and text\noutput. Message blocks (warnings, messages, and errors) are also treated as text\nblocks, and will be merged if they are next to a source block.\n\n### `comment`: comment out text output {#sec:option-comment}\n\nBy default, verbatim text output is commented out with a prefix `#>`. This\ncomment prefix can be set via the chunk option `comment`, e.g.,\n\n```{{r, comment = '#~~> ', print = NA}}\n1:9\nmatrix(1:9, 3)\n```\n\nIf you do not want the comment prefix, you may set `comment = ''`.\n\n```{{r, comment = '', print = NA}}\nmatrix(1:9, 3)\n```\n\nThe reason to use comments is that readers will be able to copy multiple code\nblocks from the chunk output in one go and directly run the copied text as code\nelsewhere when desired (all text output will be ignored as comments).\n\n### `dev`: graphics device {#sec:option-dev}\n\nFor a code chunk to generate graphics output, it will need a graphics device to\nrecord the plots. The device can be set via the `dev` option. The value can be a\nfunction (e.g., `svg`), a function name as a string (e.g., `\"svg\"`), or a string\nthat can be evaluated to a function (e.g., `\"grDevices::svg\"`).\n\nThe default device is `cairo_pdf` for LaTeX output, and `png` for other types of\noutput (such as HTML).\n\nThe plot file path (specified via the chunk option `fig.path`) will be passed to\nthe first argument of the device function. The chunk option `dev.args` can be\nused to pass a list of additional arguments to the device, and the default list\nis:\n\n```{r, echo = FALSE, attr.output = '.r', comment = ''}\ndput(xfun:::dev_args)\n```\n\nNote that the default unit for `width` and `height` is inches instead of pixels.\n\nAny argument in `dev.args` that is not available in a device function will be\nignored. For example, the `png()` device does not have the `onefile` argument,\nso it will not be passed to `png()`, whereas you can use\n`dev.args = list(bg = 'yellow')` to pass a custom `bg` value (yellow background)\nto `png()`. Please read the help page of the device function (e.g., `?png`) to\nlearn the possible arguments that you can use.\n\n`{r} .ex(8)`\n\n### `echo`: visibility of source code {#sec:option-echo}\n\nBy default, the source code blocks are displayed in the output with the chunk\noption `echo = TRUE`. To suppress source code blocks, you can use\n`echo = FALSE`.\n\n### `error`: error behavior {#sec:option-error}\n\nYou can specify how errors in a code chunk should be handled with the `error`\noption. Its possible values are:\n\n-   `NA` (default): No special handling, i.e., if an error occurs, just throw it\n    and halt the process.\n\n-   `TRUE`: Capture errors with `tryCatch()` and show the error messages in the\n    output.\n\n-   `FALSE`: Suppress errors.\n\nIf a certain error cannot be captured by `tryCatch()`, the chunk option\n`error = TRUE` or `FALSE` will not work.\n\nNote that in addition to errors in executing the code, syntax errors in the\nsource code can also be captured, e.g.,\n\n```{{r, error = TRUE}}\nx = 1 + 2 +\n```\n\n### `eval`: code evaluation {#sec:option-eval}\n\nIf you do not want to evaluate a certain code chunk, you can use the chunk\noption `eval = FALSE`, which is `TRUE` by default.\n\n### `fig.*`: figure options {#sec:option-fig}\n\nThe `fig.*` options fall into two categories: one for decorating the images in\nthe output, and the other for customizing the plot files. Chunk options for\ndecoration include:\n\n-   `fig.alt`: The alt text for plots, which will be used in image tags\n    (`<img alt=\"...\" />`) in HTML output. By default, it takes value from the\n    `fig.cap` option, which defaults to `NULL`. You are encouraged to provide\n    the alt text, since it improves the accessibility of images on HTML pages.\n\n-   `fig.cap`: The figure caption. By default, it is empty (`NULL`).\n\n-   `fig.env`: Attributes for the figure environment. By default, it is a class\n    name `.figure`.\n\n::: callout-tip\nTo avoid omitting the alt text inadvertently, you can set\n`options(litedown.fig.alt = TRUE)` in your `.Rprofile`. When this option is set\nand the chunk option `fig.alt` is unset, `fuse()` will emit reminders about the\nmissing alt text for code chunks containing plots. You can also set this option\ninside a particular R Markdown document to receive reminders only for that\ndocument.\n:::\n\nWhen all these options are provided, the Markdown output of figures will be of\nthis form:\n\n``` md\n:::: {fig.env}\n![fig.alt](fig.path){attr.plot}\n\n:::\nfig.cap\n:::\n::::\n```\n\nWhen a code chunk generates multiple plots, the options `fig.alt` and\n`attr.plot` (@sec:option-attr) are vectorized, i.e., they will be recycled to a\nlength equal to the number of plots, and each value in the vectors will be\napplied to each plot. For example, for `fig.alt = c('aaa', 'bbb')`, the first\nvalue will be the alt text for the first plot, and the second value is for the\nsecond plot.\n\nWhen `fig.cap` is provided and a chunk generates multiple plots, all plots will\nbe moved into the same figure environment at the end of the chunk output. As a\nresult, one code chunk can produce at most one figure environment, which may\ncontain one or more plots. If you need multiple figure environments, you have to\nwrite separate code chunks.\n\n`{r} .ex(9)`\n\nChunk options for plot files include:\n\n-   `fig.ext`: The plot file extension. By default, it will be automatically\n    inferred from the first argument of the graphics device function\n    (@sec:option-dev). For example, the extensions `.png` and `.svg` can be\n    detected from the `png()` and `svg()` devices, respectively:\n\n    ```{r, collapse = TRUE}\n    formals(png)[[1]]\n    formals(svg)[[1]]\n    ```\n\n    You can manually provide the extension via `fig.ext` if the automatic\n    inference fails.\n\n-   `fig.height` and `fig.width`: The physical size of plots to be passed to the\n    graphics device. By default, a plot is 8 x 8 inches with a resolution of\n    84ppi (i.e., 672 x 672 pixels). The chunk option `fig.dim` (figure\n    dimension) can be used as a shorthand to provide `fig.width` and\n    `fig.height` at the same time, e.g., `fig.dim = c(10, 6)` means\n    `fig.width = 10` and `fig.height = 6`. These options can be overridden by\n    the option `dev.args` (@sec:option-dev), e.g.,\n    `dev.args = list(width = 9, height = 7)`.\n\n-   `fig.keep`: Indices of plots to be kept. The indices can be either numeric\n    (positive or negative integers) or logical. Negative integers and false\n    values will remove the corresponding plots. For example, if a code chunk\n    generates 3 plots, `c(1, 3)`, `-2`, and `c(T, F, T)` are equivalent ways for\n    `fig.keep` to remove the second plot. The special value `fig.keep = 'last'`\n    means to keep the last plot only.\n\n-   `fig.path`: A path prefix for plot files. This prefix, joined by the chunk\n    label and the plot number, will be the actual plot file path. For example,\n    for `fig.path = 'figures/'` and a chunk with the label `foo`, the *i*-th\n    plot in the chunk will be `figures/foo-i.ext`, with the extension `.ext`\n    from the chunk option `fig.ext`. The default `fig.path` is the input file\n    path with its extension substituted by `__files/`, e.g., when running\n    `fuse('path/foo.Rmd')`, the `fig.path` will be set to `path/foo__files/` by\n    default.\n\n    If `fig.path` is not a character value or `NULL` (e.g., `fig.path = NA`),\n    plots will not be captured for output.\n\n`{r} .ex(10)`\n\n### `fill`: code interpolation {#sec:option-fill}\n\nThe source code in a chunk can be dynamically constructed via interpolation. Two\ntypes of placeholders are supported, which will be filled out by the character\nstrings associated with them if the chunk option `fill = TRUE` (the default):\n\n1.  `` `<label>` ``: embed the source code of another chunk into the current\n    chunk via the label of that chunk.\n\n2.  `` `{code}` ``: run R code to return a string to be inserted into the\n    current code chunk.\n\nInline chunk references via `` `<label>` `` make it possible to avoid copying\ncode from one chunk to another manually. You can compose code freely from other\nchunks. Generating code dynamically via `` `{code}` `` offers even more\nflexibility, since the code does not have to come from other code chunks but can\nbe from anything. It is similar to the chunk option `code` (@sec:option-code),\nbut the placeholder syntax may make the chunk source more readable in the source\ndocument.\n\nThese placeholders can be used anywhere in a code chunk. For `` `<label>` ``, if\nit is on a line that is indented, and the referred chunk has multiple lines, all\nsubsequent lines will be indented by the same amount of white spaces. For\nexample, suppose that we have a chunk with the label `chunk-a`:\n\n```` md\n```{r, chunk-a}\n{\n  cat('x is positive')\n}\n```\n````\n\nand we embed it in another chunk (the line is indented by four spaces):\n\n```` md\n```{r, chunk-b}\nf = function(x) {\n    if (x > 0) `<chunk-a>`\n}\n```\n````\n\nThe source code of this chunk will be resolved to:\n\n``` r\nf = function(x) {\n    if (x > 0) {\n      cat('x is positive')\n    }\n}\n```\n\nNote that the whole function body above has been indented by four spaces.\n\nInline chunk references can be recursive. For example, `chunk-a` may embed\n`chunk-b` while `chunk-b` contains more references to other chunks. Of course,\nthe recursion must be finite (e.g., a chunk cannot reference itself).\n\nThe `` `{code}` `` placeholder can be useful when you want to run or present the\ncode differently according to different scenarios. The chunk option `fill` can\nalso take a function to convert the value returned by the code.\n\n`{r} c(.ex(15), .ex(15, '.md'))`\n\nIn the above example, we are simulating the value of $\\pi$, but the number of\nsimulations `N` is dynamic. We can generate different copies of code by changing\nthe value of `N` (note that `N` can be defined outside the document before you\nrun `litedown::fuse()`). For example, if you are an R package developer and you\nhave a package vignette that involves time-consuming simulations, you may want\nto reduce the number of simulations during `R CMD check` via:\n\n``` r\nN = if (xfun::is_R_CMD_check()) 10 else 100000\n```\n\nThe above example also showed the possibility of passing R data to a JS code\nchunk—we set `fill = xfun::tojson` so that the data frame is converted to JSON\nand embedded into the JS code.\n\n### `filter`: filter output elements {#sec:option-filter}\n\nA code chunk is evaluated via `xfun::record()`, which captures the results in a\nlist containing elements such as the source code, text output, plots, and\nmessages (see @fig:attr-structure for an illustration). The chunk option\n`filter` can take a function to filter post-process this list. Please make sure\nto read the help page of `xfun::record()` and understand the returned data\nstructure before using this option.\n\nWith the `filter` option, you can achieve the same effects as chunk options like\n`echo = FALSE` or `message = FALSE`. For example, `echo = FALSE` means to\nexclude the `record_source` elements from the list, which can be achieved with\nthe following function:\n\n``` r\nfilter = function(res) res[!sapply(res, inherits, 'record_source')]\n```\n\nOne potentially useful application is to re-arrange the output elements, e.g.,\nthis function\n\n``` r\nfilter = function(res) res[c(1, 3, 2)]\n```\n\nwill re-order the output elements by swapping the second and third element.\n\nThe example below demonstrates a technical limitation of **litedown** (from\n`xfun::record()`) and provides a solution via the `filter` option. That is,\n`xfun::record()` evaluates *top-level* expressions in a code chunk one by one,\nand captures all results after each top-level evaluation. Normally that wouldn't\nbe a problem, but if you want to interleave results in a lower-level expression,\nyou will run into trouble. In the following example, we want to interleave text\noutput and plots inside a `for` loop. Without using the `filter` option, the\n`for` loop will first produce three text output elements, followed by three\nplots. With `filter = 'interleave'`, the output order will be revised so that\neach plot follows one text output.\n\n`{r} c(.ex(25), .ex(25, '.md'))`\n\nNote that although we assigned a character value to `filter`, it will be\nresolved to a function (via `match.fun()`). The string `'interleave'` is\nresolved to the function `litedown:::interleave` (you can also pass the latter\nto `filter`), which finds the first text output element and the first plot\nelement in the result list, and inserts the plots evenly into text elements. For\nexample, if the original list is of the form\n`[ [text, text, text], [plot, plot, plot] ]`, the filtered list will be\n`[text, plot, text, plot, text, plot]`.\n\n### `include`: visibility of whole code chunk {#sec:option-include}\n\nWhen you want to hide the full output of a code chunk, you can use the option\n`include = FALSE`, instead of trying to hide elements individually like\n`echo = FALSE` (source), `results = 'hide'` (text output), `message = FALSE`,\nand `warning = FALSE`, etc.\n\nNote that even with `include = FALSE`, the code is still executed, unless you\nalso set `eval = FALSE`.\n\n### `label`: chunk label {#sec:option-label}\n\nThe chunk label is an identifier of a code chunk. It is used in plot/cache\nfilenames and figure/table cross-references. If two code chunks have the same\nlabel, their plots and cache will overwrite each other, which may lead to\nunexpected output. If one of these chunks does not produce plots/tables or use\ncache, it is fine for them to use the same label.\n\nIf two code chunks share the same label, and one of the chunks is empty, the\nempty chunk will copy code from the non-empty one.\n\n`{r} c(.ex(11), .ex(11, '.md'))`\n\nIf the label is not provided in a chunk, a label of the form `chunk-i` will be\nassigned to the chunk, where `i` is the chunk number in the document. For\nexample, the labels for the following code chunks will be `chunk-a`, `chunk-2`,\n`chunk-js`, and `project-flowchart`:\n\n```` md\n```{r, chunk-a}\n```\n\n```{css}\n```\n\n```{js}\n//| label: chunk-js\n```\n\n```{mermaid, label = 'project-flowchart'}\n```\n````\n\n### `message`: message behavior {#sec:option-message}\n\nThe `message` option is similar to the `error` option (@sec:option-error) but is\nfor handling `message()`. Possible values are:\n\n-   `NA`: No special handling (messages will be written to the R console by\n    default).\n\n-   `TRUE` (default): Capture messages and show them in the output document.\n\n-   `FALSE`: Suppress messages.\n\n### `order`: order of execution {#sec:option-order}\n\nCode chunks and inline code expressions do not have to be executed sequentially.\nThe chunk option `order` can be used to customize the order of execution. It\ntakes a numeric value and defaults to the chunk number (e.g., `3` for the 3rd\nchunk), therefore all chunks are executed in the natural linear order by\ndefault.\n\nA lower `order` value indicates earlier execution of the chunk, and *vice\nversa*. If you want to delay the execution of a chunk, assign a higher `order`\nvalue to it. If you want to prioritize the execution, you may assign a lower\nvalue.\n\nIt is the order of these values that matters, instead of the specific values.\nFor example, if the input contains three chunks in total, order values `1`,\n`1000`, and `888` for these chunks will be equivalent to `1`, `3`, and `2`,\nsince the order is calculated via the `order()` function:\n\n```{r, collapse=TRUE}\norder(c(1, 1000, 888))\norder(c(1, 3, 2))  # same order\n```\n\nYou may use two variables, `i` (the chunk number) and `N` (the total number of\nchunks), in the `order` value, which can make it easier to specify the relative\norder. For example, if a chunk has `order = i + 1.5`, its next chunk will be\nexecuted before it, because the order of the next chunk is `i + 1` (unless its\norder has also been changed), which is smaller than `i + 1.5`. Without the\nvariable `i`, you would have to figure out the chunk number by yourself and\nassign a fixed value like `8.5` in this case.\n\nIf you want an earlier chunk to be executed last, you may use `order = N + 1`.\nSimilarly, to execute a later chunk first, you may use `order = 0`.\n\nNote that the `order` option also works for text chunks that contain inline code\nexpressions. To specify the order of a text chunk, set the `order` option in any\ninline code expression in the chunk.\n\nIn the following example, we execute the first text chunk in the end by setting\n`order = N + 1`, so that the variables `x` and `n_cyl` will be available\n(calculated from later chunks), and we move the last chunk one step earlier via\n`order = i - 1.5`, so the variable `m` will be ready for the text chunk before.\nWithout the custom order, this example will either throw errors (objects not\nfound) or use wrong values of these variables (from elsewhere in the session).\n\n`{r} c(.ex(12), .ex(12, '.md'))`\n\nYou can change the value of `n_cyl` to 4 or 6, re-run the example, and get a new\nreport of cars with 4 or 6 cylinders.\n\n### `print`: printing function {#sec:option-print}\n\nIn a code chunk, if the value of an expression is visible, it will be printed.\nYou may read the help pages `?invisible` and `?withVisible` to learn more about\nthe visibility of values.\n\nTo print a value, a print function needs to be called. The function can be\nprovided via the chunk option `print`, which defaults to the S3 generic function\n`xfun::record_print()`, with the following methods:\n\n```{r, message = FALSE}\nmethods(xfun::record_print)\n```\n\nThese methods are mainly for generating tables from rectangular data objects,\nsuch as matrices and data frames.\n\nIf a non-function value (such as `NA`) is passed to the `print` option,\n`base::print()` (or `methods::show()` for S4 objects) will be called, which will\ngenerate text output that you would normally see in the R console. Therefore if\nyou wish to avoid printing data objects to tables, you may use the chunk option\n`print = NA`.\n\nThe chunk option `print.args` can be used to pass additional arguments to the\nprint function. It should be of the form\n`list(class_a = args_a, class_b = args_b, ...)`, where `class_x` is a class\nname, and `args_x` is a list of arguments. If the (first) class name of a\nvisible value in a code chunk is `class_x`, `args_x` will be passed to the print\nfunction.\n\nIn the following example, objects are printed through `base::print()` (by\nsetting the chunk option `print = NA`). The argument `zero.print = '.'` (see\n`?print.table`) is used for `table` objects, and arguments `quote` /\n`max.levels` (see `?print.factor`) are used for `factor` objects.\n\n`{r} c(.ex(13), .ex(13, '.md', last = -12))`\n\nIf you do not want to pass arguments by class names but pass a list of arguments\nto the print function regardless of the object classes, you can wrap the list in\n`I()`. For example, `print.args = I(list(zero.print = '.'))` means that\n`print(..., zero.print = '.')` is called to print *any* objects in the chunk in\nthe above example. However, please note that this \"universal\" argument list may\nnot work for all print functions or methods---some arguments may be ignored, and\nsome may cause errors if the print function does not have certain arguments. It\nis often better to define `print.args` by class names.\n\n### `purl`: code extraction {#sec:option-purl}\n\nWhen using `litedown::fiss()` to extract code from a document, all code chunks\nare extracted by default. To skip a code chunk, set `purl = FALSE` for that\nchunk.\n\n`{r} c(.ex(14), .ex(14, '.R'))`\n\nYou can see that the `setup` chunk with the option `purl = FALSE` was omitted in\nthe R script output (generated by `fiss()`).\n\n### `results`: text output behavior {#sec:option-results}\n\nText output from code chunks can be shown verbatim, hidden, or just written out\nas is. The behavior is controlled by the option `results`, with possible values:\n\n-   `TRUE` (default): Show text output verbatim (in fenced code blocks).\n\n-   `FALSE` (or `\"hide\"`): Hide text output.\n\n-   `\"asis\"`: Write raw text (interpreted as Markdown) to the output.\n\n`{r} c(.ex(16), .ex(16, '.md'))`\n\n### `strip.white`: trim blank lines in code {#sec:option-strip-white}\n\nBy default, the leading and trailing blank lines of individual code expressions\nin a code chunk are removed. To keep them, you can use the chunk option\n`strip.white = FALSE`.\n\n`{r} c(.ex(17), .ex(17, '.md'))`\n\n### `tab.*`: table options {#sec:option-tab}\n\nUnless the default `print` option (@sec:option-print) is changed, common\nrectangular data objects are printed to Markdown tables through\n`xfun::record_print()`, and tables are generated by a simple function\n`xfun::md_table()`, which is even much simpler than `knitr::kable()`.\n\nThe chunk option `tab.cap` can be used to provide the table caption. The option\n`tab.env` can be used to customize the attributes (@sec:fenced-divs) of the\ntable environment, and it defaults to `.table`, i.e., a class name `table`.\nBelow is a quick example showing where `tab.cap` and `tab.env` are used in the\nMarkdown output:\n\n`{r} c(.ex(18), .ex(18, '.md'))`\n\nThe position of the caption can be changed via the chunk option `cap.pos`\n(@sec:option-cap-pos).\n\nTo customize the table content, you can pass arguments to `xfun::md_table()` via\nthe chunk option `print.args`. Below is an example that limits data frames to at\nmost 4 rows and 6 columns:\n\n```{{r}}\n#| print.args = list(data.frame = list(limit = c(4, 6)))\nmtcars\n```\n\nBy default, tables are limited to 10 rows. If you want to get rid of this limit,\nyou may set `options(xfun.md_table.limit = Inf)` or `limit = Inf` in\n`print.args` or wrap the data object in `I()`.\n\nPlease read the help page `?xfun::md_table` to learn the possible arguments, and\nalso see @sec:option-print to learn how the chunk option `print.args` works.\n\n### `time`: code timing {#sec:option-time}\n\nWhen the chunk option `time` is set to `TRUE`, the execution time of the chunk\nwill be recorded. You can print out `litedown::timing_data()` at the end of a\ndocument to check the timing data. The data is sorted by default, so you can\nquickly know which code chunks are slow.\n\nYou can time specific code chunks by applying `time = TRUE` to them, or time all\nchunks in the document by setting `litedown::reactor(time = TRUE)` in the first\ncode chunk.\n\n### `verbose`: printing verbosity {#sec:option-verbose}\n\nBy default, invisible values are not printed (@sec:option-print). However, you\ncan use the chunk option `verbose` to change this behavior. Its possible values\nare:\n\n-   `0` (default): Do not print invisible values.\n\n-   `1`: Always print the last value in the code chunk, no matter if it is\n    visible.\n\n-   `2`: Print all invisble values in the code, except for invisible `NULL`\n    (which is often not useful).\n\nBelow are examples for different values of `verbose`:\n\n```{{r, test-verbose, collapse = TRUE}}\n1:5\nx = 1 + 1\ny = x^2\n```\n\n```{{r, test-verbose, verbose = 1, collapse = TRUE}}\n```\n\n```{{r, test-verbose, verbose = 2, collapse = TRUE}}\n```\n\nIn case you do not know, an assignment (e.g., `x = 1 + 1`) in R returns the\nvalue being assigned invisibly, so the value will not be printed by default. You\ncan set `verbose = 2` to reveal the value. Normally you would have to explicitly\nprint the variable or use the `()` trick to make it visible:\n\n``` r\nx = 1 + 1\nx  # print x explicitly\n(x = 1 + 1)  # or the () trick\n```\n\n### `warning`: warning behavior {#sec:option-warning}\n\nThe `warning` option is similar to the `error` option (@sec:option-error) but is\nfor handling `warning()`. Possible values are:\n\n-   `NA`: No special handling (warnings will be sent to the R console by\n    default).\n\n-   `TRUE` (default): Capture warnings and show them in the output document.\n\n-   `FALSE`: Suppress warnings. Warnings often exist for good reasons. Please\n    make sure the warnings can be safely suppressed before using\n    `warning = FALSE`.\n\n### `wd`: working directory {#sec:option-wd}\n\nThe working directory when evaluating code chunks and inline code will be\ntemporarily changed to the directory of the input file to `fuse()`. If the input\nis not a file path, the working directory will not be changed.\n\n::: callout-note\nWhen using relative paths to read/write files in code chunks, these paths are\nrelative to the directory of the input file by default. I know some people hate\nthis default. It is a matter of thinking inside or outside the (R Markdown) box.\nI tend to think *inside*—everything is relative to the document that I'm working\ninside. It is just a habit (following the conventions of HTML and CSS), not\nnecessarily right or wrong.\n:::\n\nYou are free to specify any directory as the working directory for the code via\nthe chunk option `wd`, e.g., `wd = '../'` (up one level from the directory of\nthe input file) or `wd = 'C:/Documents/Project/'`. Please note that using a\nhard-coded absolute directory may affect others if they need to re-run your\ndocument on their computers, since the absolute directory may not exist there.\n\nIf you have to change the working directory to an absolute directory, you may\nconsider using functions like `xfun::proj_root()` to dynamically find the\nabsolute directory.\n\n## Inline code\n\n### The syntax {#sec:inline-syntax}\n\nThe syntax for inline code expressions is `` `{lang} code` ``, where `lang` is\nthe language name, e.g., `r`. Spaces are allowed after the opening backtick and\nbefore the closing backtick. If the `code` happens to contain `N` backticks, you\nwill need to use at least `N + 1` backticks outside, e.g.,\n``` ``{r} paste(\"`\", rnorm(1), \"`\")`` ```. An inline code expression inside\nanother piece of inline code is not parsed or evaluated, which provides a way to\nwrite verbatim inline code expressions, e.g., ``` `` `{lang} code` `` ```.\n\nComma-separated chunk options can also be provided to inline code expressions\nafter the language name, e.g., `` `{r, eval = FALSE} code` ``. Currently, only\n`eval` (@sec:option-eval) and `error` (@sec:option-error) are supported, and\nmost other chunk options in @sec:chunk-options are not supported for inline\ncode. A few additional options are provided for formatting inline numeric output\n(@sec:numeric-output).\n\nThe inline code is expected to return a value of length 1. If it returns an\nobject of length greater than 1, all elements in the object will be concatenated\nby line breaks (`\\n`) to form a single string.\n\n### Numeric output\n\nIf the inline expression returns a single number, the number will be formatted.\nTo bypass the formatting, wrap the inline expression in `I()`. We denote the\nnumber by $x$ for now.\n\n-   First, $x$ will be rounded to $n$ significant digits, where $n$ is\n    controlled by the option `signif` and its default value is `3`.\n\n-   Then $x$ will be formatted using the scientific notation $\\pm m\\times 10^n$\n    if $|x| \\geq 10^p$ or $|x| \\leq 10^{-p}$, where $p$ is controlled by the\n    option `power`, which defaults to `6`. If you want to avoid the scientific\n    notation, you can set this option to `Inf`. When $m = 1$, it will be omitted\n    (i.e., the output will be $10^n$ instead of $1 \\times 10^n$.\n\nWhen a number is formatted in the scientific notation, it will be enclosed with\n`$ $` as an inline LaTeX math expression. For example, `` `{r} 1234567` `` will\nbe rendered to $`{r} 1234567`$.\n\nOptions to control the formatting include:\n\n-   `dollar`: Whether to add `$ $` to numbers in the scientific notation. By\n    default (`dollar = NA`), dollar signs will be added automatically if the\n    inline code expression in the source is not directly enclosed with dollar\n    signs. `TRUE` means to always add dollar signs, and `FALSE` means to never\n    add dollar signs. You will need to use `dollar = FALSE` for numbers in a\n    LaTeX math expression because you should not use `$ $` inside a math\n    expression (see the example below).\n\n-   `power`: The threshold for scientific notation.\n\n-   `signif`: The desired number of significant digits.\n\n`{r} c(.ex(20), .ex(20, '.md'))`\n\nTo make it easier to read the numbers in the Markdown table above, we render the\ntable to @tab:inline-numbers:\n\n:::: table\n::: caption\n[ ](#@tab:inline-numbers) Inline numbers formatted with different `signif` and\n`power` options.\n:::\n\n```{r, echo = FALSE, results = 'asis'}\nex20 = .ex(20, '.md', verbatim = FALSE)\ncat(grep('^[|][- ]', ex20, value = TRUE), sep = '\\n')\n```\n::::\n\n### Compatibility with knitr\n\nFor **knitr** users, please note that the syntax `` `r code` `` is not supported\nby default. You have to wrap the language name `r` in curly braces. As a\ntemporary workaround, you may set `options(litedown.enable.knitr_inline = TRUE)`\nin your `.Rprofile` to make **litedown** recognize `` `r code` ``, but we\nrecommend that you convert the document via `litedown:::convert_knitr()` instead\nif you decide to stay with **litedown** in the long run.\n\n## R scripts\n\nBesides R Markdown, you can also pass an R script to `fuse()`. You can write\nMarkdown content in `#'` comments, and group lines of code into chunks by `#|`\ncomments, e.g.,\n\n``` r\n#' ---\n#' title: An R script\n#' output: latex\n#' ---\n#' \n#' A _paragraph_.\n\n#| eval = FALSE\n1:10\n1 + 1\n\n#| fig.width = 10, dev = 'pdf'\nplot(cars)\n```\n\nBoth `#'` and `#|` comments are optional. If no `#'` or `#|` comments are found\nin the script, the whole script will be treated as a single code chunk.\n\n```{mermaid}\n#| flow-sieve, echo = FALSE,\n#| fig.cap = 'An illustration of how scripts are rendered.'\nflowchart TD\n  O@{ shape: lin-doc, label: \"Input script\" }--\"sieve()\"--> A@{ shape: f-circ } -.-o B[[\"#' YAML\"]]\n  A -.-o C[[\"#' Text\"]]\n  C ---> H[[Text]]\n  A -.-o D[[Code chunk]]\n  D -.- D1>\"#| Chunk options\"]\n  D -.- D2>Code]--> G{Results} --> I[[Chunk output]]\n  A -.-o E[[More chunks...]] -.- K>...] --> L{...} --> M[[More output...]] -.-> J\n  H -.-> J@{ shape: doc, label: \"Markdown\" }\n  I -.-> J\n  B --> R[[YAML]] -.-> J\n  J--\"mark()\" --> N@{ shape: tag-doc, label: \"Output document\" }\n  class O,J,N document\n  classDef document stroke-width:3px\n  class G,L fuse-out\n  classDef fuse-out fill:lightyellow\n  class H,I,M,R output\n  classDef output fill:none\n```\n\n## Language engines {#sec:engines}\n\nCurrently, the main computing language supported by **litedown** is R. You can\ncheck all supported languages via:\n\n```{r}\nsort(names(litedown::engines()))\n```\n\nYou can get a language engine definition via `litedown::engines('LANG')`, where\n`LANG` is the language name, e.g.,\n\n``` r\nlitedown::engines('md')\n```\n\nYou can also set a new language engine via:\n\n``` r\nlitedown::engines(LANG = function(x, inline = FALSE, ...) {\n\n})\n```\n\nThe function's argument `x` is an element in the list returned by\n`litedown::crack()`.\n\n### The Markdown engine\n\nThe `md` engine will output Markdown text both verbatim and as-is, which can be\nuseful for showing Markdown examples, e.g.,\n\n```` md\n```{md}\nYou can see both the _source_ and _output_ of\nthis `md` chunk.\n```\n\nYou can also use `{md} the engine **inline**`.\n````\n\n### The CSS/JS engines {#sec:engine-css}\n\nYou can insert CSS/JS to the output via the `css`/`js` engines, e.g.,\n\n```` md\n```{css}\na {\n  color: red;\n}\n```\n\n```{js}\ndocument.body.classList.add('dark');\n```\n````\n\nThe `css` engine will output its content in the `<style>` tag. The `js` engine\nwill output its content in the `<script>` tag. If the chunk option `type` is\npresent, it will be included as an attribute of the `<script>` tag. For example,\n`type = \"module\"` indicates that the script should be treated as a [JS\nmodule](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules).\n\nBy default, the chunk source is also displayed verbatim in the output. In most\ncases, you may want to hide the source via the chunk option `echo = FALSE`\n(@sec:option-echo).\n\nThe `css` engine can also accept the chunk option `href` to include a stylesheet\nvia the `<link>` tag, e.g.,\n\n```` md\n```{css, href=\"https://unpkg.com/mvp.css\"}\n```\n````\n\nThe `js` engine can also include external scripts via the chunk option `src`,\ne.g.,\n\n```` md\n```{js, src=\"https://cdn.jsdelivr.net/npm/@xiee/utils/js/math-code.js\"}\n```\n````\n\nBy default, external scripts are\n[deferred](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer)\n(i.e., they will not run until the full document has been parsed). If you want\nto run an external script immediately, you may set the chunk option\n`defer = FALSE`.\n\n### The Mermaid engine\n\nThe `mermaid` engine can be used to draw diagrams with\n[mermaid.js](https://mermaid.js.org), e.g.,\n\n```` md\n```{mermaid, flow-abcd, echo = FALSE, fig.cap = 'A flowchart.'}\nflowchart TD\n    A[WEB] --> B[noweb] --> D[Sweave] --> E[knitr]\n    A --> C[CWEB]\n    D --> F[litedown]\n```\n````\n\nThe JS library mermaid.js has to be loaded for the diagrams to be rendered. By\ndefault, the latest version of mermaid.js will be added to the `js` variable\n(@sec:the-js-variable) in the YAML metadata automatically. If you prefer using a\nspecific version, you can add it manually to the YAML metadata, e.g.,\n\n```` md\n---\ntitle: \"A mermaid diagram\"\noutput:\n  html:\n    meta:\n      js: [\"@npm/mermaid@11.3.0/dist/mermaid.min.js\"]\n---\n\n```{mermaid}\n\n```\n````\n\n### The embed engine\n\nThe `embed` engine can be used to embed text files verbatim in the output. You\ncan pass the file paths to the chunk option `file` or write the paths in the\nchunk body (with optional quotes), e.g.,\n\n```` md\n```{embed, file = \"foo.txt\"}\n```\n\n```{embed}\n\"foo.txt\"\n```\n````\n\nYou can use multiple paths in either case, e.g.,\n\n```` md\n```{embed, file = c(\"foo.txt\", \"bar.R\")}\n```\n\n```{embed}\n\"foo.txt\"\nbar.R\n```\n````\n\nThe content of the file(s) will be included in a fenced code block in the\noutput. For example, if `foo.txt` contains a single line \"hello world\", the\noutput will be:\n\n```` md\n``` {.txt}\nhello world\n```\n````\n\nBy default, the language name of the code block is from the file extension. You\ncan use the chunk option `attr.output` to customize the name, e.g.,\n\n```` md\n```{embed, file = \"foo.txt\", attr.output = \".md\"}\n```\n````\n\nThen the output will be in a Markdown (`.md`) block:\n\n```` md\n``` {.md}\nhello world\n```\n````\n\nYou can use the chunk option `results = \"hide\"` to hide the output, or\n`results = \"asis\"` to output the file content as is (i.e., not wrapping it in a\ncode block).\n\n## Comparison to **knitr**\n\nMajor differences between **knitr** and **litedown** include:\n\n-   **knitr** accepts a variety of input document formats, such as `*.Rnw`,\n    `*.Rhtml`, `*.Rmd`, `*.Rtex`, `*.Rrst`, and `*.Rasciidoc`,[^02-fuse-3]\n    whereas **litedown** only supports Markdown.\n\n-   **knitr** supports multiple graphics devices for a chunk via the `dev`\n    option, and **litedown** only supports one device for a chunk (but there are\n    multiple choices for this device). I hate the\n    [ugly](https://github.com/yihui/knitr/blob/4510c0/R/block.R#L393-L426)\n    [hacks](https://github.com/yihui/knitr/blob/69b063/R/plot.R#L288-L306) in\n    **knitr** for drawing a plot in one device and rendering it with other\n    devices later. This feature is not worth the effort due to the daunting\n    complexity of implementation, not to mention that it doesn't always work. If\n    you need multiple file formats for the same plot in **litedown**, you should\n    start a new chunk with the same code (@sec:option-fill) and a different\n    `dev` option instead.\n\n-   **knitr** may output images in Markdown (`![]()`) or raw HTML (`<img>`) /\n    LaTeX (`\\includegraphics{}`), depending on certain chunk options;\n    **litedown** always uses the Markdown syntax for figures, which is cleaner\n    and more portable.\n\n-   The document parser of **knitr** is based solely on regular expressions and\n    not robust. Code chunks and inline expressions are not aware of their\n    contexts. The parser of **litedown** is based on **commonmark**, which is\n    more robust and makes it straightforward to write verbatim code or comment\n    out code (@sec:skipped-chunks).\n\n-   **knitr** supports a large number of chunk options and language engines, and\n    **litedown** only supports a limited number of chunk options and engines\n    (this number may grow in the future but perhaps not significantly).\n\n-   Inline code for **knitr** does not support options or non-R languages, and\n    **litedown** is much more flexible in this regard.\n\n-   All code chunks in **knitr** are executed in the linear order, and\n    **litedown** can execute code in an arbitrary custom order\n    (@sec:option-order).\n\n-   **knitr** supports chunk hooks and output hooks; **litedown** doesn't\n    support hooks but the `attr.*` (@sec:option-attr) and `print`\n    (@sec:option-print) options have provided some flexibility in customizing\n    the chunk output.\n\n-   **knitr** is more than 12 years old and quite mature, and **litedown** is\n    new and still experimental.\n\n[^02-fuse-3]: Perhaps Paul Murrell is the only person in the world who uses the\n    `.Rhtml` format. I wonder if anyone uses any of the rest of formats, except\n    for `.Rnw` and `.Rmd`.\n\nIf you feel any indispensable **knitr** features are missing in **litedown**,\nplease feel free to suggest them in [Github\nissues](https://github.com/yihui/litedown). However, please remember that the\ngoal of **litedown** is not to fully re-implement **rmarkdown**, **knitr** or\nPandoc. Some features may never be re-implemented, especially when the\nimplementation is not simple enough.\n"
  },
  {
    "path": "docs/03-syntax.Rmd",
    "content": "# Markdown Syntax {#chp:syntax}\n\n::: epigraph\n> All the tired horses in a run; how’m I gonna get any writing done!\n>\n> ---[Michael\n> Friendly](https://bsky.app/profile/datavisfriendly.bsky.social/post/3lapaume4m22k)\n> (and Bob Dylan)\n:::\n\nA Markdown document consists of a YAML frontmatter providing the document\nmetadata, and a Markdown body. The frontmatter is optional, and (if provided)\nshould be written using the syntax introduced in @sec:yaml-syntax. The syntax\nfor the body is introduced in @sec:markdown-basic and @sec:markdown-addon.\n\n## YAML syntax\n\nYAML stands for \"YAML Ain't Markup Language\", which aims at being a\n\"human-friendly data serialization language\". You may judge how \"human-friendly\"\nit is[^03-syntax-1] by yourself after reading its specifications at\n<https://yaml.org>.\n\n[^03-syntax-1]: At least [it was not quite friendly to\n    Norwegian](https://news.ycombinator.com/item?id=36745212) until YAML 1.2.\n\nWhat we introduce in this section is a significantly trimmed-down version of\nYAML. Its official name is \"TRACY\", a recursive acronym for \"TRACY Really Ain't\nComplete YAML\" and also in honor of [Tracy\nTeal](https://en.wikipedia.org/wiki/Tracy_Teal) since she was once a nice boss\nof mine. However, to follow the naming convention of `*ML`, we use the name\n\"TAML\" (with apologies for the further recursion) when the audience has some\nsoftware background.\n\nThe following example shows all types of data that TAML supports:\n\n``` yaml\na: 1\nb: \"string\"\nc: true\nd: null\n# e is also null\ne:\n# an expression\nf: !expr 1 + 1\n# vectors\ng: [1, 2, 3]\nh: [\"foo\", \"bar\"]\n# a nested list\ni:\n  i1: -1e8\n  i2: \"another string\"\n  i3:\n    i31: [true, false, true]\nany_character.is-okay: string not quoted\n```\n\nThe TAML specifications are:\n\n1.  Each line should be of the form `field: value`. The `field` name can use any\n    character (except a leading `#` or a colon followed by a space). The `value`\n    cannot span across multiple lines. A line starting with `#` is treated as a\n    comment. Any line not of the form `field: value` is ignored.\n\n2.  Values must be character, numeric, logical, expressions, null, or vectors.\n\n    -   Character strings are quoted in either single or double quotes. Quotes\n        are optional when the values cannot be interpreted as other types of\n        values (e.g., numeric or logical).\n\n    -   Numeric values must consist of numbers (0-9), `+`, `-`, and `e`, and can\n        be converted to numbers.\n\n    -   Logical values must be either `true` or `false`. No other values are\n        recognized, such as `yes`, `YES`, `no`, `NO`,[^03-syntax-2] `on`, `off`,\n        `TRUE`, `FALSE`, `T`, `F`, `to be`, or `not to be` .\n\n    -   Expressions start with `!expr` followed by R code.\n\n    -   Null values can be either literally `null` or empty.\n\n    -   A vector consists of comma-separated character/numeric/logical values in\n        `[ ]`.\n\n3.  If `value` is empty, and the next line is of the form `field: value`\n    indented by $n$ white spaces ($n \\geq 1$),[^03-syntax-3] the next line will\n    be treated as a child member of the current line. One field can have\n    multiple children indented to the same level, and each child can have its\n    children (further indented by $n$ spaces).\n\n[^03-syntax-2]: TAML is a good friend of Norwegian.\n\n[^03-syntax-3]: Most people may want to use 2 or 4 spaces. Avoid 8, because that\n    has been reserved for a VIP in the R club, [Roger\n    Peng](https://simplystatistics.org/posts/2018-07-27-why-i-indent-my-code-8-spaces/).\n    You may also choose to use tabs. If you *really* want to, you can mix tabs\n    with spaces (then $n$ is the total number of them). If people tell you that\n    you are crazy by mixing up things, don't listen and never follow. TAML is\n    human-friendly and respects the space right of all human beings. Guess what\n    happens if you indent one line by 2 spaces and the next line by 3 spaces\n    when you meant to use 4 spaces?\n\nTAML is implemented in `xfun::taml_load()` and used by **litedown**. See\n@sec:yaml-metadata for all possible fields supported in **litedown**.\n\n## Basic Markdown {#sec:markdown-basic}\n\nIf you cannot learn the basic Markdown in this section (i.e.,\n@sec:markdown-basic) in a few minutes, please feel free to file [a GitHub\nissue](https://github.com/yihui/litedown/issues) and I will send you a\nsponsorship of \\$20 on GitHub.\n\n### Inline elements\n\nYou can write these elements inline: `**strong**`, `_emphasis_`, `` `code` ``,\n`~~strikethrough~~`, `[text](link)`, and `![alt](image/path)`.\n\n::: callout-note\nFor links and images, their URLs [should not contain\nspaces](https://spec.commonmark.org/current/#link-destination). If they do, the\nURLs must be enclosed in `<>`, e.g., `![alt](<some dir/a subdir/foo.png>).`\n:::\n\nThe rest of elements in this section are block-level elements.\n\n### Headings\n\nHeadings start with a number of `#`'s, e.g., `## level-two heading`.\n\nYou are recommended to use `##` (level-two) as the top heading level in a\ndocument, except for books, in which case each document is a chapter and you\nshould use `#` (level-one) as the top level.\n\n### Paragraphs\n\nParagraphs are separated by blank lines.\n\n### Code blocks\n\nCode blocks can be indented by 4 spaces, or fenced by ```` ``` ````. In the\nlatter case, the code block can have a class name (typically the language name).\nFor example, below is a JS code block:\n\n```` md\n```js\nx < 0 ? -x : x;\n```\n````\n\n### Lists\n\nThere are three types of lists: unordered, ordered, and task lists.\n\nAn unordered list item starts with `-`, `+`, or `*`, e.g., `- item`.\n\nA task list item is a regular list item with `[ ]` (unchecked) or `[x]`\n(checked) in the beginning, e.g., `- [ ] item`.\n\nAn ordered list item starts with a number followed by a period. The number of\nthe first item is used as the starting number of the list, and the rest of\nnumbers can be arbitrary. The examples on the left below are equivalent to those\non the right:\n\n::: flex-col\n``` md\n1. Get up!\n1. Write a book!\n1. Go to bed!\n```\n\n``` md\n1. Get up!\n2. Write a book!\n3. Go to bed!\n```\n:::\n\n::: flex-col\n``` md\n5. Get up!\n100. Write a book!\n1. Go to bed!\n```\n\n``` md\n5. Get up!\n6. Write a book!\n7. Go to bed!\n```\n:::\n\n### Block quotes\n\nBlock quotes start with `>` followed by a space and then the quote. A quote can\ncontain any number of any Markdown elements. If it contains multiple block-level\nelements, each line needs to start with `>`. For example, below is a block quote\nwith multiple paragraphs:\n\n``` md\n> \"You're an oaf. I told you quite distinctly to make his discharge papers out.\"\n>\n> And all the bile which had accumulated in the judge advocate's soul in the course of that day because of Captain Linhart and Švejk poured out like a wild torrent on the head of the staff warder. At the end of it Bernis said:\n>\n> \"And now do you understand that you are a prize royal oaf?\"\n>\n> This is something which should only be said to kings and emperors, but even this simple staff warder, who was no royal personage, was not very pleased about it.\n>\n> ---Jaroslav Hašek, _The Good Soldier Švejk_ (Chapter 9)\n```\n\n### Tables\n\nTables are created with `|` as the column separator, i.e., Pandoc's pipe table,\nwhich can be generated by `xfun::md_table(x)` or `knitr::kable(x, \"pipe\")` if\nyou want to create a table from a rectangular object `x` in R.\n\n``` md\n| col 1 | col 2 | col 3 |\n|:------|------:|:-----:|\n| row 1 | row 1 | row 1 |\n| row 2 | row 2 | row 2 |\n```\n\nThe position of the colon below the table header controls the column alignment,\ne.g., a colon on the right means the column is right-aligned, and colons on both\nends means the column is center-aligned.\n\n## Add-on features {#sec:markdown-addon}\n\nIn addition to the basic features above (supported by **commonmark**), the\n**litedown** package also supports the following features.\n\n### Raw LaTeX/HTML blocks\n\nRaw LaTeX and HTML blocks can be written as fenced code blocks with language\nnames `=latex` and `=html`, e.g.,\n\n```` markdown\n```{=latex}\nThis only appears in \\LaTeX{} output.\n```\n````\n\nRaw LaTeX blocks will only appear in LaTeX output, and will be ignored in other\noutput formats. Similarly, raw HTML blocks will only appear in HTML output. One\nexception is raw LaTeX blocks that are LaTeX math environments, which also work\nfor HTML output (see the next section).\n\n### LaTeX math {#sec:math}\n\nYou can write both `$inline$` and `$$display$$` LaTeX math, e.g.,\n$\\sin^{2}(\\theta)+\\cos^{2}(\\theta) = 1$.\n\n$$\\bar{X} = \\frac{1}{n} \\sum_{i=1}^n X_i$$\n\n$$|x| = \\begin{cases}\nx &\\text{if } x \\geq 0 \\\\\n-x &\\text{if } x < 0\n\\end{cases}$$\n\nFor expressions in pairs of single or double dollar signs to be recognized as\nLaTeX math, there must be no spaces after the opening dollar sign, or before the\nclosing dollar sign. The math expression should either start from the very\nbeginning of a line, or have a space or `(` before the opening dollar sign.\n\n-   For a pair of single dollar signs, they must be on the same line in the\n    text, and the closing dollar sign should not be followed by a number (to\n    avoid detecting math mistakenly from text like \"a \\$5 bill and a \\$10\n    bill\"). The inner math expression should not be wrapped in backticks.\n\n-   For `$$ $$` expressions, they can span over multiple lines, in which case\n    the closing `$$` must have at least one non-space character before it, and\n    no spaces after it.\n\nValid examples:\n\n``` tex\n$x + y$\n($x + y$)\n  $x + y$\ntext $x + y$ text\n\n$$x + y$$\n  $$x + y$$\ntext $$x + y$$ text\n$$x +\n  y$$\n```\n\nInvalid examples:\n\n``` md\n$ x + y$  <- space after the opening `$`\ntext$x + y$  <- lack of space before the opening `$`\ntext $x + y$10 text  <- number after closing `$`\n$x +\ny$  <- multi-line `$ $` expressions\n$`x + y`$  <- expression wrapped in backticks\n\n$$x +\n  y\n$$\n^- lack of non-space character before closing `$$`\n```\n\nLaTeX math environments are also supported, e.g., below are an `align`\nenvironment and an `equation` environment:\n\n```{md, attr.source = '.tex', attr.asis = '.callout-output'}\n\\begin{align}\na^{2}+b^{2} & =  c^{2}\\\\\n\\sin^{2}(\\theta)+\\cos^{2}(\\theta) & =  1\n\\label{eq:pyth-identity}\n\\end{align}\n\n\\begin{equation}\n  \\begin{split}\n  (a+b)^2 &=(a+b)(a+b)\\\\\n    &=a^2+2ab+b^2\n  \\end{split}\n\\end{equation}\n```\n\nThese math environments can be written as either nake LaTeX code or raw LaTeX\nblocks (```` ```{=latex} ````), but we recommend that you use raw LaTeX blocks\nbecause they are more robust. LaTeX math environments work for both LaTeX and\nHTML output.\n\nFor HTML output, it is up to the JavaScript library (MathJax or KaTeX) whether a\nmath environment can be rendered (@sec:js-math).\n\n### Superscripts and subscripts\n\nWrite superscripts in `^text^` and subscripts in `~text~` (same syntax as\nPandoc's Markdown), e.g., 2^10^ and H~2~O. Currently only alphanumeric\ncharacters, `*`, `(`, and `)` are allowed in the scripts. For example, `a^b c^`\nwill not be recognized as a superscript (because the space is not allowed). Note\nthat GFM supports striking out text via `~text~`, but this feature has been\ndisabled and replaced by the feature of subscripts in **litedown**. To strike\nout text, you must use a pair of *double* tildes.\n\n### Footnotes\n\nInsert footnotes via `[^n]`, where `n` is a unique footnote\nidentifier.[^03-syntax-4] The footnote content should be defined in a separate\nblock starting with `[^n]:`. For example:\n\n[^03-syntax-4]: Typically the identifier is a number, but it doesn't have to be.\n    The specific number doesn't matter, as long as it's unique for the footnote\n    in the document. For example, the first footnote can be `[^100]` and the\n    second can be `[^64]`. Eventually they will appear as `[1]` and `[2]`. If\n    you use the RStudio visual editor to edit Markdown documents, the footnote\n    numbers will be automatically generated and updated when new footnotes are\n    inserted before existing footnotes.\n\n``` markdown\nInsert a footnote here.[^1]\n\n[^1]: This is the footnote.\n```\n\nYou can write arbitrary elements in footnotes and not necessarily a single\nparagraph.\n\n### Attributes\n\nAttributes on images, links, fenced code blocks, and section headings can be\nwritten in `{}`. ID and class are two common attributes. An ID can be written\nafter the `#` character, and a class can be written after `.` . Attributes are\ntypically written in the form `name=\"value\"`, and separated by spaces (in fact,\nyou can also write IDs and classes explicitly like other attributes, e.g.,\n`id=\"foo\" class=\"bar\"`). Certain attributes do not require values, and you can\nprovide the attribute name only, e.g., `disabled` or `contenteditable`, although\nit is harmless to write `disabled=\"true\"`.\n\n::: callout-note\nOnly lowercase letters (a-z), digits (0-9), hyphens (`-`), and colons (`:`) are\nallowed in ID and class strings. For example, `sec:intro` and `fig-cars` are\nvalid IDs, but `sec_intro` and `tab cars` are not.\n:::\n\nFor example, `![text](path){.foo #bar width=\"50%\"}` will generate an `<img>` tag\nwith attributes in HTML output:\n\n``` html\n<img src=\"path\" alt=\"text\" id=\"bar\" class=\"foo\" width=\"50%\" />\n```\n\nand `## Heading {#baz}` will generate:\n\n``` html\n<h2 id=\"baz\">Heading</h2>\n```\n\nLinks of the form `[text](url){...}` will generate:\n\n``` html\n<a href=\"url\" ...></a>\n```\n\nWhen the `url` is empty, `<a>` will be converted to `<span>`, e.g.,\n`[text](){.foo .bar}` will generate:\n\n``` html\n<span class=\"foo bar\">text</span>\n```\n\nThis provides a way to create `<span>` elements, which is similar to [bracketed\n`Span`s](https://pandoc.org/MANUAL.html#extension-bracketed_spans) (i.e.,\n`[text]{...}`) in Pandoc's Markdown.\n\nFor fenced code blocks, a special rule is that the first class name will be\ntreated as the language name for a block, and the `class` attribute of the\nresult `<code>` tag will have a `language-` prefix. For example, the following\ncode block\n\n```` markdown\n```{.foo .bar #my-code style=\"color: red;\"}\n```\n````\n\nwill generate the HTML output below:\n\n``` html\n<pre>\n  <code class=\"language-foo bar\" id=\"my-code\" style=\"color: red;\">\n  </code>\n</pre>\n```\n\nMost attributes in `{}` are ignored for LaTeX output except for:\n\n-   The `width` attribute for images, e.g., `![text](path){width=\"50%\"}` will be\n    converted to `\\includegraphics[width=.5\\linewidth]{path}`.\n\n-   The `.unnumbered` attribute, which will make a heading unnumbered, e.g.,\n    `# Hello {.unnumbered}` will be converted to `\\section*{Hello}`.\n\n-   The `.appendix` attribute on a heading, which will start the appendix, e.g.,\n    `# Appendix {.appendix}` will be converted to `\\appendix`.\n\n-   The ID attributes on headings, which will be converted to `\\label{}`, e.g.,\n    `# Introduction {#sec:intro}` will be converted to\n    `\\section{Introduction}\\label{sec:intro}`.\n\n### Appendices\n\nWhen a top-level heading has the attribute `.appendix`, the rest of the document\nwill be treated as the appendix. If section numbering is enabled\n(@sec:number-sections), the appendix section headings will be numbered\ndifferently.\n\n### Fenced `Div`s\n\nA fenced `Div` can be written in `:::` fences. Note that the opening fence must\nhave at least one attribute, such as the class name. For example:\n\n``` markdown\n::: foo\nThis is a fenced Div.\n:::\n\n::: {.foo}\nThe syntax `::: foo` is equivalent to `::: {.foo}`.\n:::\n\n::: {.foo #bar style=\"color: red;\"}\nThis div has more attributes.\n\nIt will be red in HTML output.\n:::\n```\n\nA fenced `Div` will be converted to `<div>` with attributes in HTML output,\ne.g.,\n\n``` html\n<div class=\"foo\" id=\"bar\" style=\"color: red;\">\n</div>\n```\n\nFor LaTeX output, it can be converted to a LaTeX environment if both the class\nname and an attribute `data-latex` are present. For example,\n\n``` markdown\n::: {.tiny data-latex=\"\"}\nThis is _tiny_ text.\n:::\n```\n\nwill be converted to:\n\n``` latex\n\\begin{tiny}\nThis is \\emph{tiny} text.\n\\end{tiny}\n```\n\nThe `data-latex` attribute can be used to specify arguments to the environment\n(which can be an empty string if the environment doesn't need an argument). For\nexample,\n\n``` markdown\n::: {.minipage data-latex=\"{.5\\linewidth}\"}\n```\n\nwill be converted to:\n\n``` latex\n\\begin{minipage}{.5\\linewidth}\n```\n\nThe `data-latex` attribute is optional for fenced `Div`s with class names\n`figure` or `table`. They will be converted to `figure` or `table` environments.\nFor example,\n\n``` markdown\n:::: {.figure}\n![](foo.png)\n\n::: {.caption}\nThis is a caption.\n:::\n::::\n```\n\nwill be converted to:\n\n``` tex\n\\begin{figure}\n  \\includegraphics{foo.png}\n  \\caption{This is a caption.}\n\\end{figure}\n```\n\nOther fenced `Div`s will be ignored if they don't have the `data-latex`\nattribute, and their inner content will be written out normally without a\nsurrounding environment.\n\nIf a fenced `Div` has multiple class names (e.g., `{.a .b .c}`), only the first\nclass name will be used as the LaTeX environment name. However, all class names\nwill be used if the output format is HTML (e.g., `<div class=\"a b c\">`).\n\n### Cross-references {#sec:cross-references}\n\nTo cross-reference an element, it must be numbered first. Then we can refer to\nit by its ID.\n\n#### Sections, figures, and tables\n\nSection heading IDs can be either manually assigned or automatically generated\n(@sec:auto-identifiers). Section numbers are automatically generated if the\n`number_sections` option is true (@sec:number-sections).\n\nFigures and tables are automatically numbered if their captions are provided\n(via the chunk options `fig.cap` / `tab.cap`), e.g.,\n\n```` md\n```{r}\n#| nice-plot, fig.cap = \"A nice caption\"\n\nplot(cars)\n```\n````\n\nTo refer to an element in the text, use the syntax `@ID`, where `ID` is the ID\nof the element to be referenced, which typically consists of a prefix (e.g.,\n`sec:`, `fig:`, `tab:`, or `eq:`) and a label. For example:\n\n``` md\nPlease see @fig:nice-plot for an overview of the `cars` data.\n```\n\nHyphens (`-`) are also allowed in place of colons in the ID prefix, e.g.,\n`@fig-nice-plot`.\n\n#### LaTeX equations\n\nLaTeX math environments such as `align` and `equation` are numbered by default.\nTo refer to an expression (e.g., an equation) in a math environment, a label\nneeds to be assigned to the expression first via `\\label{}`, and it must start\nwith the prefix `eq:` or `eq-`, e.g.,\n\n```` md\n```{=latex}\n\\begin{equation}\n\\sin^2(x) + \\cos^2(x) = 1 \\label{eq:pyth-identity}\n\\end{equation}\n```\n````\n\nThen we can use either `@eq:*` or `@eqn:*` to cross-reference the equation,\ne.g., `@eqn:pyth-identity`. Under the hood, the prefix `@eq` will be resolved to\n`\\ref{}`, and `@eqn` will be resolved to `\\eqref{}`. If you are not familiar\nwith LaTeX commands `\\ref{}` and `\\eqref{}`, the main difference is that\n`\\eqref{}` will render the equation number in parentheses, e.g.,\n@eqn:pyth-identity, whereas `\\ref{}` will only generate the equation number,\ne.g., @eq:pyth-identity.\n\nIn HTML output, `\\eqref{}` will also add a label \"Equation\" before the number by\ndefault. If you prefer writing the label manually and having control over the\nparentheses, you can use `@eq:` instead of `@eqn:`, e.g., `Eq. [@eq:*]` (using\nthe label \"Eq.\" and square brackets).\n\nIn addition to equation numbers, you can also specify a tag for an equation via\n`\\tag{}` and refer to the equations by its tag, e.g.,\n\n```{md, attr.source = '.tex', attr.asis = '.callout-output'}\nFor a right-angled triangle, we are all familiar with @eqn:pyth-theorem.\n\n\\begin{equation}\na^2 + b^2 = c^2 \\label{eq:pyth-theorem} \\tag{PT}\n\\end{equation}\n```\n\n#### Arbitrary elements\n\nYou can cross-reference any other type of elements by adding empty anchors of\nthe form `[](#@ID)` into them, e.g.,\n\n````{md, attr.asis = '.callout-output'}\n:::: {#lst:example .box}\n::: caption\n[](#@lst:example) A code listing demonstrating how to fold code blocks with JS.\n:::\n\n```js\ndocument.querySelectorAll('pre').forEach(pre => {\n  const d = document.createElement('details');\n  d.innerHTML = '<summary>Details</summary>';\n  pre.before(d);  // insert <details> before <pre>\n  d.append(pre);  // move <pre> into <details>\n});\n```\n::::\n\nWe can cross-reference @lst:example.\n````\n\n```{css, echo = FALSE}\n[id^=\"lst:\"] code[class] { background-color: inherit; }\n```\n\nNote that we also added the ID (`#lst:example`) to the fenced Div to make it the\ntarget of the cross reference, i.e., when you click on the reference, the\nbrowser will navigate to the Div.\n\nFor HTML output, we can style the numbers and references with CSS. Element\nnumbers are wrapped in `<span class=\"ref-number-*\"></span>`, and references are\nwrapped in `<span class=\"cross-ref-*\"></span>`, where `*` is the ID prefix\n(e.g., `fig`, `tab`, and `eqn`). For example, we can add the label \"Listing\" to\nthe number and reference of the block above:\n\n```{css}\n.ref-number-lst::before, .cross-ref-lst::before {\n  content: \"Listing \";\n}\n.ref-number-lst {\n  font-style: italic;\n}\n.ref-number-lst::after {\n  content: \". \"\n}\n```\n\n### Citations\n\nThis feature requires the R package **rbibutils** [@R-rbibutils]. Please make\nsure it is installed before using citations.\n\n``` r\nxfun::pkg_load2(\"rbibutils\")\n```\n\nTo insert citations, you have to first declare one or multiple bibliography\ndatabases in the YAML metadata, e.g.,\n\n``` yaml\nbibliography: [\"papers.bib\", \"books.bib\"]\n```\n\nEach `.bib` file contains entries that start with keywords. For example,\n`R-base` is the keyword for the following entry:\n\n``` plain\n@Manual{R-base,\n  title = {R: A Language and Environment for Statistical Computing},\n  author = {{R Core Team}},\n  organization = {R Foundation for Statistical Computing},\n  address = {Vienna, Austria},\n  year = {2024},\n  url = {https://www.R-project.org/},\n}\n```\n\nThen you can use `[@R-base]` or `@R-base` to cite this item. The keywords must\nconsist of only alphanumeric characters (`a-z`, `A-Z`, `0-9`) and `-`. You can\ninclude multiple keywords in `[ ]` separated by semicolons.\n\nFor HTML output, the citation uses the author-year style. The syntax\n`[@keyword]` generates the citation in parentheses (e.g., `(Author, 2024)`), and\n`@keyword` only puts the year in parentheses (e.g., `Author (2024)`). All\nreferences will be put in a Div with the id `refs`, i.e.,\n`<div id=\"refs\"></div>`. This Div will appear at the end of the document by\ndefault. If you want it to appear elsewhere, you can provide an empty Div there.\n\nFor LaTeX output, the citation style depends on the LaTeX package, which you can\nset via the `citation_package` option of the output format in YAML metadata,\ne.g.,\n\n``` yaml\noutput:\n  latex:\n    citation_package: biblatex\n```\n\nThe default is `natbib`. The table below shows the LaTeX commands corresponding\nto the Markdown citation syntax:\n\n| citation package | `[@key-1; @key-2]`         | `@key`        |\n|------------------|----------------------------|---------------|\n| none             | `\\cite{key-1, key-2}`      | `\\cite{key}`  |\n| natbib           | `\\citep{key-1, key-2}`     | `\\citet{key}` |\n| biblatex         | `\\parencite{key-1, key-2}` | `\\cite{key}`  |\n\nNote that **litedown** actually generates `\\citep` and `\\citet` regardless of\nthe citation package, but will redefine them to `\\cite` or `\\parencite`\naccording to the citation package. For example, it will insert\n`\\let\\citep\\parencite` in the LaTeX preamble when `citation_package` is\n`biblatex`.\n\n### Smart HTML entities\n\n\"Smart\" HTML entities can be represented by ASCII characters, e.g., you can\nwrite fractions in the form `n/m`. Below are some example entities:\n\n```{r, smartypants, echo = FALSE}\np = litedown:::pants[-(4:14)]\nt(as.matrix(setNames(p, sprintf('`%s`', names(p)))))\n```\n\n## Comparison to Pandoc\n\nAs mentioned earlier, a lot of features in Pandoc's Markdown are not supported\nin the **litedown** package. Any feature that you find missing in previous\nsections is likely to be unavailable. In addition, a lot of R Markdown and\nQuarto (both are based on Pandoc) features are not supported, either. Some HTML\nfeatures have been implemented via JavaScript and CSS.\n\nPandoc can convert Markdown to many output formats, such as Word, PowerPoint,\nLaTeX beamer, and EPUB. The **litedown** package is unlikely to support output\nformats beyond HTML and LaTeX.\n"
  },
  {
    "path": "docs/04-mark.Rmd",
    "content": "# Markdown Rendering {#chp:mark}\n\n::: epigraph\n> Mature mental health demands, then, an extraordinary capacity to flexibly\n> strike and continually restrike a delicate balance between conflicting needs,\n> goals, duties, responsibilities, directions, et cetera. The essence of this\n> discipline of balancing is \"giving up.\"\n>\n> ---M. Scott Peck, *The Road Less Traveled*\n:::\n\nThe main function to convert Markdown to other formats is `litedown::mark()`.\n\nThe output format can be specified in the `output` (or `format`) field in YAML\nmetadata (@sec:yaml-metadata), e.g.,\n\n``` yaml\n---\noutput:\n  html:\n    options:\n      js_math:\n        package: \"katex\"\n        version: \"0.16.4\"\n      number_sections: true\n      embed_resources: [\"local\", \"https\"]\n    meta:\n      css: \"custom.css\"\n---\n```\n\n## Markdown options\n\nThe `options` argument of `mark()` can be used to enable/disable/set options to\ncontrol Markdown rendering. This argument can take either a list, e.g.,\n`list(toc = TRUE, smart = FALSE)`, or a character vector, e.g.,\n`c(\"+toc\", \"-smart\")`, or equivalently, `+toc-smart`, where `+` means to enable\nan option, and `-` means to disable an option. The options can also be set in\nYAML metadata in @sec:yaml-metadata (recommended). Available options are listed\nbelow.\n\n### `auto_identifiers`\n\nWhether to add automatic IDs to headings (`true` by default), e.g., convert\n\n``` markdown\n# Hello world!\n\n## Introduction\n```\n\nto\n\n``` html\n<h1 id=\"chp:hello-world\">Hello world!</h1>\n\n<h2 id=\"sec:introduction\">Introduction</h2>\n```\n\nThe prefix `chp:` (chapter) will be added to the automatic IDs of level-one\nheadings, and `sec:` (section) will be added for other levels of headings.\n\nYou can override the automatic ID by providing an ID manually via the ID\nattribute, e.g.,\n\n``` markdown\n# Hello world! {#hello}\n```\n\nAn automatic ID is generated by substituting non-alphanumeric characters in the\nheading text with hyphens. If the result is empty, the ID will be `section`. If\nany ID is duplicated, a numeric suffix will be added to the ID, e.g.,\n`example_1` and `example_2`.\n\n### `cleveref`\n\nWhether to use the LaTeX package [**cleveref**](https://ctan.org/pkg/cleveref)\nfor \"clever\" cross-references (@sec:cross-references). This option is for LaTeX\noutput only and `false` by default. If enabled, **cleveref** will be loaded and\nreferences will use the command `\\cref{}` instead of `\\ref{}`, which will\nautomatically add the type of reference before the reference number, e.g.,\n`\\cref{sec:intro}` may generate `Section 1`, so you do not have to write\n`Section \\ref{sec:intro}`.\n\n### `embed_cleanup`\n\nWhether to clean up plot files after they have been embedded in HTML output (see\n@sec:embed-resources). This option is `true` by default.\n\n### `embed_resources`\n\nEmbed resources (images, CSS, and JS) in the HTML output using their\nbase64-encoded data (all images except for SVG) or raw content (SVG/CSS/JS).\nPossible values are:\n\n-   `null` or `false`: Do not embed any resources.\n\n-   `\"local\"` or `true`: Embed local image/CSS/JS files.\n\n-   `\"https\"`: Embed web resources (links that start with `https://`).\n\n-   `\"all\"`: An alias to the union of `\"local\"` and `\"https\"`.\n\nThe default is `\"local\"`, i.e., local resources are embedded, whereas `https`\nresources are not. This means the output document may not work offline. If you\nhave to view the output offline, you may either use the option value `\"https\"`\n(or `\"all\"`) or set the option `offline` (@sec:offline) to `true`, and render\nthe document at least once before you go offline.\n\nFor `https` resources, after you have embedded them successfully once, they will\nbe cached locally (via `xfun::download_cache`) and will not require an Internet\nconnection again.\n\n::: callout-note\nAll images are base64 encoded, except for SVG images, of which the raw XML\ncontent is embedded, which makes it possible to manipulate SVG elements via JS\nor style them via CSS. Without embedding the raw content, SVG images are\nincluded via the `<img>` tag, and their elements cannot be manipulated or\nstyled.\n:::\n\nWhen embedding `https` resources, only these elements are considered:\n\n``` html\n<img src=\"...\" />\n<link rel=\"stylesheet\" href=\"...\">\n<script src=\"...\"></script>\n```\n\nIf an external CSS file contains `url()` resources, these resources will also be\ndownloaded and embedded.\n\n### `js_highlight`\n\nSpecify the JavaScript library to syntax highlight code blocks. Possible values\nare `highlight` ([highlight.js](https://highlightjs.org)) and `prism`\n([Prism.js](https://prismjs.com)). The default is `prism`. This option can also\ntake a list of the form `list(package, version, style, languages)`, which\nspecifies the package name (`highlight` or `prism`), version, CSS style/theme\nname, and names of languages to be highlighted.\n\n-   You can find information about Prism.js from its CDN at\n    <https://cdn.jsdelivr.net/npm/prismjs/>. Available styles are under the\n    `themes/` directory (e.g., `prism-dark`), and languages are under the\n    `components/` directory (e.g., `prism-c`). You can omit the prefix `prism-`,\n    e.g.,\n\n    ``` yaml\n    js_highlight:\n      package: prism\n      style: dark\n      languages: [r, latex, yaml]\n    ```\n\n-   The CDN of highlight.js is at\n    <https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/>. Themes are\n    under the `styles/` directory (e.g., `github`), and you can find demos of\n    themes at <https://highlightjs.org/static/demo/>. Supported language are\n    under the `languages/` directory (e.g., `latex`).\n\nBy default, languages are automatically detected and the required JS files are\nautomatically loaded. Normally you need to specify the `languages` array only if\nthe automatic detection fails.\n\nTechnically this option is a shorthand for setting the metadata variables `css`\nand `js` in @sec:yaml-metadata. If you want full control, you may disable this\noption (set it to `false` or `null`) and use metadata variables directly, which\nrequires more familiarity with the JS libraries and the jsdelivr CDN.\n\n### `js_math`\n\nSpecify the JavaScript library for rendering math expressions in HTML output.\nPossible values are `\"mathjax\"` and `\"katex\"` (the default). Like the\n`js_highlight` option, this option is also essentially a shorthand for setting\nthe metadata variables `css` and `js`.\n\n-   For [MathJax](https://www.mathjax.org/#gettingstarted), the `js` variable is\n    set to `tex-mml-chtml.js`.\n\n-   For [KaTeX](https://katex.org/docs/browser.html), the `js` variable is set\n    to `katex.min.js` and the `css` variable is set to `katex.min.css`. KaTeX's\n    `auto-render` extension (`auto-render.min.js`) is also enabled by default,\n    so math expressions can be immediately rendered when the page is loaded.\n\nIf you want finer control, you can provide a list of the form\n`list(package, version, css, js)`. This will allow you to specify the package\nname, version, and css/js files. For example, if you want to use MathJax's\n`tex-chtml.js` instead, you may set:\n\n``` yaml\njs_math:\n  package: mathjax\n  version: 3\n  js: es5/tex-chtml.js\n```\n\nBy default, MathJax version 3 is used. If you want to use the older v2, you may\nset:\n\n``` yaml\njs_math:\n  package: mathjax\n  version: 2\n  js: MathJax.js?config=TeX-AMS-MML_CHTML\n```\n\nPlease visit [the MathJax CDN](https://cdn.jsdelivr.net/npm/mathjax/) to know\nwhich versions and JS files are available.\n\nFor KaTeX, the version is not specified by default, which means the latest\nversion from [the CDN](https://cdn.jsdelivr.net/npm/katex/). Below is an example\nof specifying the version 0.16.4 and using the `mhchem` extension:\n\n``` yaml\njs_math:\n  package: katex\n  version: 0.16.4\n  js: [dist/katex.min.js, dist/contrib/mhchem.min.js]\n```\n\nNote that if you want the HTML output to be self-contained via the\n`embed_resources` option, KaTeX can be embedded and used offline, but MathJax\ncannot be fully embedded due to its complexity. MathJax v3 can be partially\nembedded and used offline, but currently only its fonts can be embedded, and\nextensions cannot. If you must view HTML output offline, we recommend using\nKaTeX, but please also note that KaTeX and MathJax do not fully cover each\nother's features.\n\n### `keep_yaml`\n\nWhether to keep the YAML metadata in the output (`false` by default). When\n`true`, the original YAML in the Markdown input (if exists) will be written to\nthe output. Note that when this option is enabled, templates (@sec:templates)\nwill be disabled and `mark()` will only generate a document fragment. This\noption was introduced mainly for Hugo websites to use **litedown** instead of\nHugo's Markdown engines to render pages.\n\n### `latex_math`\n\nWhether to identify LaTeX math expressions in pairs of single (`$ $`) or double\ndollar signs (`$$ $$`), and transform them so that they could be correctly\nrendered by MathJax/KaTeX (HTML output) or LaTeX. This option is `true` by\ndefault.\n\n### `number_sections`\n\nWhether to number section headings (`false` by default). To skip numbering a\nspecific heading, add a class attribute `.unnumbered` (or use the shorthand `-`)\nto it. For example:\n\n``` md\n## Preface {.unnumbered}\n\n## About the author {-}\n```\n\n### `offline`\n\nWhether to download web resources (e.g., images and CSS/JS files) used in the\nHTML output so that the HTML file can be viewed offline later. This option is\n`false` by default. If enabled (`true`), resources are downloaded to the\ndirectory `assets/`. If you want to use a different directory name, you can set\nthis option to a character value, e.g.,\n\n``` yaml\noffline: \"resources\"\n```\n\nThe resources will not be downloaded again unless you have deleted them on\npurpose.\n\nThe `offline` option can also be useful for those who do have Internet\nconnection but the connection forbids access to external web resources (e.g., in\ncertain corporate environments). In this case, you can ship the resources\ntogether with the HTML output so it will not rely on external resources.\n\nThe option `embed_resources` in @sec:embed-resources provides another way to\ndownload and cache resources locally. The differences between these two options\nare:\n\n1.  The `offline` option downloads resources but does not embed their content\n    into the HTML file, so the HTML file is not self-contained (e.g., JS\n    resources are included via `<script src=\"assets/*.js\">`).\n\n2.  The `offline` option downloads (and caches) resources to the directory of\n    the HTML file, whereas the `embed_resources` option downloads them to a\n    system cache directory. For the `offline` option, the cached resources are\n    tied to a specific output directory, and cannot be shared or reused by HTML\n    files in other directories. For `embed_resources`, the cache can be shared\n    among any documents to be rendered via `fuse()` or `mark()` in the same\n    system.\n\n### `smartypants`\n\nWhether to translate certain ASCII strings into smart typographic characters\n(see `?litedown::smartypants`). This option is `false` by default.\n\n### `superscript`\n\nWhether to translate strings between two carets into superscripts, e.g.,\n`text^foo^` to `text<sup>foo</sup>`. This option is `true` by default.\n\n### `subscript`\n\nWhether to translate strings between two tildes into subscripts, e.g.,\n`text~foo~` to `text<sub>foo</sub>`. This option is `true` by default.\n\n### `toc`\n\nWhether to generate a table of contents (TOC) from section headings (`false` by\ndefault). If a heading has an `id` attribute, the corresponding TOC item will be\na link to this heading. You can also set a sub-option:\n\n-   `depth`: The number of section levels to include in the TOC (`3` by\n    default). Setting `toc` to `true` is equivalent to:\n\n    ``` yaml\n    toc:\n      depth: 3\n    ```\n\nTo exclude a certain heading from the TOC, assign a class name `unlisted` to it.\nFor example:\n\n``` md\n## Acknowledgments {.unlisted}\n```\n\n### `top_level`\n\nThe desired type of the top-level headings in LaTeX output. Possible values are\n`'chapter'` and `'part'`. For example, if `top_level = 'chapter'`, `# heading`\nwill be rendered to `\\chapter{heading}` instead of the default\n`\\section{heading}`.\n\n### Other options\n\nOptions not described above can be found on the help pages of **commonmark**,\ne.g., the `hardbreaks` option is for the `hardbreaks` argument of\n`commonmark::markdown_*()` functions, and the `table` option is for the `table`\nextension in **commonmark**'s extensions.\n\n```{r}\n#| collapse = TRUE\n\nlitedown::markdown_options()\n# commonmark's arguments\nopts = formals(commonmark::markdown_html)\nopts = opts[setdiff(names(opts), c('text', 'extensions'))]\nunlist(opts)\n# commonmark's extensions\ncommonmark::list_extensions()\n```\n\n## Templates\n\nBy default, `mark()` generates a document fragment (i.e., the body) if the input\ndoes not contain YAML metadata at the beginning. To generate a full document,\nyou need to specify YAML metadata. A full document is generated with a template.\nBelow is a simple HTML template example:\n\n``` html\n<html>\n  <head>\n    <title>$title$</title>\n  </head>\n\n  <body>\n  $body$\n  </body>\n</html>\n```\n\nIt contains two variables, `$title$` and `$body$`. All variables will be\nsubstituted by metadata values, except for `$body$`, which is from the body of\nthe input document (after conversion to a target output format).\n\nThe **litedown** has provided default templates for\n[HTML](https://github.com/yihui/litedown/blob/main/inst/resources/litedown.html)\nand\n[LaTeX](https://github.com/yihui/litedown/blob/main/inst/resources/litedown.latex)\noutput. To pass metadata to templates, use the `meta` argument, e.g.,\n\n``` r\nlitedown::mark(..., meta = list(title = \"My Title\"))\n```\n\nIf you want to use a custom template file, you can set the path in the global\noption `litedown.FORMAT.template` (where `FORMAT` is the output format name\n(`html` or `latex`), e.g., in `.Rprofile`:\n\n``` r\noptions(litedown.html.template = 'path/to/my/template.html')\n```\n\nThe global option will be applied to all documents to be converted by `mark()`.\nAlternatively, you can pass a template path to the `template` argument of the\noutput format `html` or `latex` in an individual document, e.g.,\n\n``` yaml\n---\noutput:\n  html:\n    template: \"path/to/my/template.html\"\n---\n```\n\nThe template path can also take a logical value: `TRUE` means to use the default\ntemplate, and `FALSE` means to generate only a fragment document without using\nany template.\n\n## YAML metadata {#sec:yaml-metadata}\n\nAlternatively, the `meta` argument can read YAML metadata in the Markdown\ndocument.\n\n### Top-level variables\n\nThe following variables can be set in the top-level fields in YAML:\n\n-   `abstract`: The abstract.\n\n-   `author`: The document author(s).\n\n-   `date`: The date.\n\n-   `subtitle`: The document subtitle.\n\n-   `title`: The document title.\n\nFor example:\n\n``` yaml\n---\ntitle: \"My Title\"\nauthor: \"[Frida Gomam](https://example.com)\"\ndate: \"2023-01-09\"\n---\n```\n\nThe values are treated as Markdown text, and will be converted to the target\noutput format before being passed to the template. For the above example, the\nvariable `$author$` will be `<a href=\"https://example.com\">Frida Gomam</a>` in\nthe HTML template.\n\nFor top-level variables, `mark()` will also create the \"underscore\" versions for\ntemplates, which contain HTML and LaTeX markups. For example, for the `$title$`\nvariable, `$title_$` will also be available to the template. The following table\nshows the values of the underscore variables, assuming the original value of a\nvariable is `TEXT`:\n\n| Variables | HTML | LaTeX |\n|----|----|----|\n| `$title_$` | `<div class=\"title\"> <h1>TEXT</h1> </div>` | `\\title{TEXT}` |\n| `$subtitle_$` | `<div class=\"subtitle\"> <h2>TEXT</h2> </div>` | `\\subtitle{TEXT}` |\n| `$author_$` | `<div class=\"author\"> <h2>TEXT</h2> </div>` | `\\author{TEXT}` |\n| `$date_$` | `<div class=\"date\"> <h3>TEXT</h3> </div>` | `\\date{TEXT}` |\n| `$abstract_$` | `<div class=\"abstract\"> <p>TEXT</p> </div>` | `\\begin{abstract} TEXT \\end{abstract}` |\n\nIf an original variable is empty or missing, its underscore version will also be\nempty. For the `$author_$` variable, if the `$author$` variable contains\nmultiple author names as an array, each name will be in a separate `<h2>` in\nHTML output, and all names will be concatenated by `\\and` in LaTeX output, e.g.,\nfor\n\n``` yaml\nauthor: [\"Jane X\", \"John Y\"]\n```\n\nin YAML, the HTML output will be:\n\n``` html\n<div class=\"author\">\n  <h2>Jane X</h2>\n  <h2>John Y</h2>\n</div>\n```\n\nand the LaTeX output will be:\n\n``` tex\n\\author{Jane X \\and John Y}\n```\n\nIf you design your own template, you are free to use either the original or the\nunderscore versions of these variables. For example, you could put the title in\nan `<h1>` without the `<div>` wrapper via `<h1 class=\"title\">$title$</h1>`\ninstead of using `$title_$`.\n\nWhen these top-level variables are also provided as `meta` variables for an\noutput format, the latter will override the former, e.g.,\n\n``` yaml\ntitle: \"Global Title\"\noutput:\n  html:\n    meta:\n      title: \"Title for HTML output\"\n  latex:\n    meta:\n      title: \"Title for LaTeX output\"\n```\n\n### Format-specific variables\n\nOther variables need to be specified under `output -> * -> meta`, where `*` can\nbe `html` or `latex`, e.g.,\n\n``` yaml\n---\ntitle: \"My Title\"\noutput:\n  html:\n    meta:\n      css: \"style.css\"\n      js: \"script.js\"\n  latex:\n    meta:\n      documentclass: \"book\"\n      header_includes: \"\\usepackage{microtype}\"\n---\n```\n\nThe following metadata variables are supported for both HTML and LaTeX\ntemplates:\n\n-   `header-includes`, `include-before`, `include-after`: Either a vector of\n    (HTML/LaTeX) code or a code file to be included in the header, before the\n    body, or after the body of the output.\n\n--------------------------------------------------------------------------------\n\nThe following variables are for HTML templates:\n\n#### The `css` variable {#sec:meta-css}\n\nA vector of CSS files to be included in the output. If the variable is not\nprovided, the\n[`default.css`](https://github.com/yihui/lite.js/blob/main/css/default.css) will\nbe used.\n\nIf you want to use built-in CSS files in this package, you can only specify the\nbase name, e.g., `default` means `default.css` in this package, and `snap` means\n`snap.css`.\n\nYou can also use web resources via a full URL, e.g.,\n`https://example.org/style.css`. One special case is\n[jsdelivr](https://www.jsdelivr.com) resources: if a `css` value starts with\n`@`, it will be treated as a jsdelivr resource. if you are not familiar with\njsdelivr, you may read its documentation to understand the following example\nURLs. The shorthand syntax is as follows (`*` stands for\n`https://cdn.jsdelivr.net`) and summarized in @tab:at-syntax:\n\n-   `@foo` (without a filename extension) will be converted to\n    `*/npm/@xiee/utils/css/foo.min.css`, e.g., `@default` means\n    `*/npm/@xiee/utils/css/default.min.css`. If you prefer the `.css` extension\n    over `.min.css`, you can use `@default.css`.\n\n-   `@foo@version` (a filename followed by a version number) will be converted\n    to `*/npm/@xiee/utils@version/css/foo.min.css`, e.g., `@article@1.12.0`\n    means `*/npm/@xiee/utils@1.12.0/css/article.min.css`.\n\n-   `@path/to/file` (i.e., a value that contains slashes) will be converted to\n    `*/path/to/file`, e.g., `@npm/@xiee/utils/js/center-img.js` will be\n    converted to `*/npm/@xiee/utils/js/center-img.min.js`.\n\n-   `@path/to/file-1,file-2` (comma-separated values and later values do not\n    contain slashes) will be converted to\n    `*/combine/path/to/file-1,path/to/file-2` (this can be useful to\n    [combine](https://www.jsdelivr.com/documentation#id-combine-multiple-files)\n    multiple resources and load all at once).\n\n-   `@path-1/to/file-1,path-2/to/file-2` (comma-separated values and later\n    values contain slashes) will be converted to\n    `*/combine/path-1/to/file-1,path-2/to/file-2`.\n\n:::: table\n::: caption\n[ ](#@tab:at-syntax) The `@` syntax for using jsdelivr resources in the `css`\nvariable.\n:::\n\n| syntax | actual URL |\n|----|----|\n| `@foo` | `*/npm/@xiee/utils/css/foo.min.css` |\n| `@foo.css` | `*/npm/@xiee/utils/css/foo.css` |\n| `@foo@version` | `*/npm/@xiee/utils@version/css/foo.min.css` |\n| `@path/to/file` | `*/path/to/file` |\n| `@path/to/file-1,file-2` | `*/combine/path/to/file-1,path/to/file-2` |\n| `@path-1/to/file-1,path-2/to/file-2` | `*/combine/path-1/to/file-1,path-2/to/file-2` |\n::::\n\nThis provides a way to reduce the output HTML file size by loading CSS from the\nweb instead of embedding inside HTML, at the cost of requiring Internet\nconnection when viewing the HTML file. If you need the external web resources to\nwork after you go offline, please see @sec:embed-resources.\n\n#### The `js` variable\n\nA vector of JavaScript files to be included in the output. The syntax is the\nsame as the `css` variable, e.g., `snap` means `snap.js` in this package,\n`@snap` means a \"jsdelivr\" resource, and you can use arbitrary paths or URLs to\nother JS files.\n\n#### The `body-class` variable\n\nA class name for the main body (the default value is `body`).\n\n#### The `lang` variable\n\nThe language of the HTML document, used to set the `lang` attribute of the\n`<html>` tag. It should be a [BCP 47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt)\nlanguage tag such as `en`, `en-US`, or `zh-CN`. The default value is\nautomatically detected from the system locale (via `Sys.getlocale('LC_CTYPE')`)\nif the locale is in the POSIX format (e.g., `en_US.UTF-8`). You can override it\nin YAML, e.g.,\n\n``` yaml\noutput:\n  html:\n    meta:\n      lang: \"zh-CN\"\n```\n\n--------------------------------------------------------------------------------\n\nThe following variables are for LaTeX templates:\n\n#### The `classoption` variable\n\nA string containing options for the document class.\n\n#### The `documentclass` variable\n\nThe document class (by default, `article`).\n\n### Naming convention of variables\n\nA variable name must consist of alphanumeric characters and hyphens only. You\ncould use underscores in variable names in the metadata, but please note that\nunderscores will be normalized to hyphens internally, e.g., `header_includes`\nwill be converted to `header-includes`. This means if you use a custom template,\nyou must use hyphens instead of underscores as separators in variable names in\nthe template.\n\n### Using custom variables\n\nThe above are variables supported in the default templates. If you use a custom\ntemplate, you can use arbitrary variable names following the naming convention\n(except for `body`, which is a reserved name and cannot be used in metadata),\nand the values of variables in the metadata will be passed to your template.\n\n### Setting options in YAML\n\nBesides metadata variables, the aforementioned Markdown options\n(@sec:markdown-options) can also be set in YAML under `output -> * -> options`,\ne.g.,\n\n``` yaml\noutput:\n  html:\n    options:\n      toc: true\n      js_highlight:\n        package: highlight\n        theme: github\n        languages: [diff, latex]\n```\n\n### Other fields in YAML\n\nSee the help page `?litedown::html_format` for possible fields in addiction to\n`meta` and `options` that can be specified under the format name, e.g.,\n\n``` yaml\noutput:\n  latex:\n    latex_engine: xelatex\n    keep_md: true\n    template: custom-template.tex\n```\n"
  },
  {
    "path": "docs/05-assets.Rmd",
    "content": "# CSS/JS assets {#chp:assets}\n\n::: epigraph\n> And it seemed as though in a little while the solution would be found, and\n> then a new and splendid life would begin; and it was clear to both of them\n> that they had still a long, long road before them, and that the most\n> complicated and difficult part of it was only just beginning.\n>\n> ---Anton Chekhov, *The Lady with the Dog*\n:::\n\nThe **litedown** package aims at lightweight with a minimal number of features\nat its core, but you are free to add more features by yourself. In this chapter,\nwe introduce some CSS/JS assets from the GitHub repository\n<https://github.com/yihui/lite.js>. You can load arbitrary external JS and CSS\nfiles via the `js` and `css` meta variables. There are numerous JS libraries and\nCSS frameworks on the web, and you do not have to use the ones mentioned in this\nchapter. You can also write CSS/JS by yourself to enrich your HTML applications.\n\nRemember that the CSS and JS are introduced under the output format `html` in\nYAML metadata, e.g.,\n\n``` yaml\n---\noutput:\n  html:\n    meta:\n      css: [\"one.css\", \"two.css\"]\n      js: [\"three.js\", \"four.js\"]\n---\n```\n\nFor the sake of brevity, we will omit the full YAML fields in examples\nthroughout this chapter but only use the `css` and `js` fields. A file name\n`foo.js` denotes the file under the `js/` directory of the aforementioned\n[`lite.js` repository](https://github.com/yihui/lite.js). Similarly, `foo.css`\nis under the `css/` directory.\n\nAll these CSS/JS resources can be used offline, and there are several ways to do\nit. One way is to clone the GitHub repo to your working directory, and use the\nfiles you need, e.g.,\n\n``` yaml\njs: [\"repo/path/js/callout.js\"]\n```\n\nYou may also see @sec:offline and @sec:embed-resources for other ways.\n\n## A quick reference {#sec:asset-reference}\n\n@tab:assets provides a quick reference of existing CSS/JS assets in the\n`lite.js` repository. See @sec:meta-css for the meaning of the `@` character in\n`css` / `js` names.\n\n```{r}\n#| assets, echo = FALSE, print.args = I(list(limit = Inf)),\n#| tab.env = '.table .no-ref-label',\n#| tab.cap = 'A reference of CSS/JS assets in the `lite.js` repository.'\na = litedown:::assets\nfor (i in c('css', 'js')) a[, i] = ifelse(is.na(a[, i]), NA, paste0('`', a[, i], '`'))\na = cbind(feature = rownames(a), a, section = paste0(' @sec:', rownames(a)))\nrownames(a) = NULL\na\n```\n\nIf the source document is R Markdown instead of Markdown, an alternative way (in\naddition to configuring YAML metadata) to add assets is to call the function\n`litedown::vest()` in a code chunk. You can either pass vectors of `css` / `js`\nasset names to the `css` / `js` arguments, or the `feature` names in @tab:assets\nto the `feature` argument, e.g.,\n\n```` md\n```{r, echo = FALSE}\n# add assets by feature\nlitedown::vest(feature = c('callout', 'copy-button'))\n# or add css/js directly\nlitedown::vest(css = c('@callout', '@book'), js = '@callout')\n```\n````\n\nYou can call `vest()` multiple times in a document to keep adding assets.\n\n## The default CSS {#sec:default}\n\nThe default stylesheet defines the basic styles of the document body, lists,\ncode, figures, tables, block quotes, TOC, links, and cross-references, etc. For\nexample, the maximum width for the body is set to 800px. If you want to override\nit, you can provide your own stylesheet, e.g.,\n\n``` css\nbody {\n  max-width: 1000px;\n}\n```\n\nThe default CSS is included when no `css` is provided in the YAML metadata. You\ncan use either `@default` or `default` to apply the default CSS.\n\n``` yaml\ncss: [\"@default\"]\n# or\ncss: [\"default\"]\n```\n\nThe former uses the jsdelivr resource and will not be embedded in HTML by\ndefault. The latter points to the `default.css` shipped with the **litedown**\npackage, and will always be embedded.\n\n## Copy buttons {#sec:copy-button}\n\nNormally you would have to select the text before copying it, and copy buttons\ncan save the effort of text selection when you want to copy the text from an\nHTML element. You just click on the button, and the text is automatically\ncopied. Copy buttons can be added via CSS/JS:\n\n``` yaml\ncss: [\"@copy-button\"]\njs: [\"@copy-button\"]\n```\n\nBy default, this will add copy buttons to all code blocks (`<code>` in `<pre>`)\nand all elements with the class name `copy-this`. For example, below are two\nquotes, and the right quote is inside a fenced Div `::: copy-this`, so you will\nsee a copy button in the right quote if you hover over it with the cursor:\n\n:::: flex-col\n> Love like you've never been hurt\\\n> Dance like nobody's watching\\\n> Sing like nobody's listening\\\n> Work like you don't need the money\\\n> Live like it's heaven on earth\n>\n> ---Alfred D'Souza\n\n::: copy-this\n> Copy like you've never been hurt\\\n> Copy like nobody's watching\\\n> Copy like nobody's listening\\\n> Copy like you don't need the money\\\n> Copy like it's heaven on earth\n>\n> ---Yihui (Ode to the [Copy\n> Ninja](https://en.wikipedia.org/wiki/Kakashi_Hatake))\n:::\n::::\n\nAll code blocks in this book also have copy buttons in them.\n\nIn fact, you can add the copy button to any element on an HTML page. You may\nread [this post](https://yihui.org/en/2023/09/copy-button/) if you are\ninterested in generalizing the copy button to other elements (without using the\n`copy-this` class).\n\n## Tabsets {#sec:tabsets}\n\nA tabset provides a compact way to arrange content in tabs, which can save a lot\nof vertical space on the page. To create a tabset, you need to load the\nfollowing assets:\n\n``` yaml\ncss: [\"@tabsets\"]\njs: [\"@tabsets\"]\n```\n\nThere are two ways to create a tabset in Markdown.\n\n### From a bullet list\n\nThe first way is to write a bullet inside a fenced Div with the class name\n`tabset`. The first element of each bullet item will become the tab title, and\nthe rest of elements will become the tab pane, e.g.,\n\n```{md}\n::: tabset\n- Tab one\n\n  Content: -OOOO0OOO⍥\n  \n  Another paragraph.\n\n- Tab two <!--active-->\n\n  Content: -OOOOOOOO⍥\n  \n  - A normal bullet list.\n:::\n```\n\nBy default, the first tab is the active tab initially. To specify a different\ninitial active tab, add a comment `<!--active-->` to the bullet item.\n\nYou can use the `Left` (Left) and `Right` (Right) Arrows on your keyboard to\nnavigate through the tabs when the tabset is on focus (e.g., after you have\nclicked on it once). Now keep pressing the Left or Right Arrow key, and you will\nrealize that there is a caterpillar crawling in the example tabset above (and\nalso looking at you). It's cute, isn't it?\n\nSince bullet lists can be nested, you can also nest one tabset in another. To do\nthat, you just wrap a sub-list in a `tabset` div, e.g.,\n\n``` md\n::: tabset\n- Tab one\n\n  ::: tabset\n  - Child tab one\n  \n  - Child tab two\n  :::\n\n- Tab two\n:::\n```\n\nI'm not sure why anyone would want to create nested tabsets, but it sounds like\na great way to create a maze. Perhaps you can write a detective story or game\nwith it?\n\nWait a minute. I just came up with an idea! We can use nested tabsets to\nnavigate through a recursive list. I have implemented this idea in the function\n`xfun::tabset()`, and felt for the first time of my life that I dare to peruse\nthe huge `recordPlot()` list now! That said, I don't want to scare you with\n`recordPlot()`, but let's take a look at the config file of this book as an\nexample instead:\n\n```{r}\ncfg = xfun::taml_file('_litedown.yml')\nxfun::tabset(cfg, dput)\n```\n\nI have never loved YAML, but this time, I feel that YAML is adorable.\n\n### From sections\n\nThe second way to create a tabset is through section headings. If you add a\nclass name `tabset` to a heading, headings of the next (lower) level will be\nused as the tab titles, e.g.,\n\n``` md\n## Demo tabs {.tabset}\n\n### Tab one\n\nContent.\n\n### Tab two {.active}\n\nContent.\n```\n\nIf a heading has the `active` class, it will be the active tab initially.\n\nYou can also create a tabset by providing an empty fenced Div with the class\nname `tabset`, and subsequent headings will be converted to tabs, e.g.,\n\n``` md\n::: tabset\n:::\n\n## Tab one\n\nContent.\n\n## Tab two\n\nContent.\n```\n\nThe tabset will end before it hits a higher-level heading. The tabset in the\nfirst example above can be ended by a `##` heading, and the second example can\nbe ended by a `#` heading.\n\nTo explicitly end a tabset without using a higher-level heading, you can insert\na comment of the form `<!--tabset:ID-->` to the end of the tabset, where `ID` is\nthe ID of the starting element of the tabset, e.g.,\n\n``` md\n## Demo tabs {.tabset #foo}\n\n### Tab one\n\n### Tab two\n\n<!--tabset:foo-->\n\n### Not a tab\n```\n\n``` md\n::: {.tabset #foo}\n:::\n\n## Tab one\n\n## Tab two\n\n<!--tabset:foo-->\n\n## Not a tab\n```\n\nWhen a tabset is created from sections, the tab titles (from section headings)\nwill appear in the TOC of the document if TOC is enabled (@sec:toc).\n\n## Code folding {#sec:fold-details}\n\n``` yaml\njs: [\"@fold-details\"]\n```\n\nYou can fold any element with the JS. See [this\npost](https://yihui.org/en/2023/09/code-folding/) for how to configure the\nscript.\n\n## Callout blocks {#sec:callout}\n\nA callout block is a fenced Div with the class name `callout-*`. Callouts\nrequire `callout.css` and `callout.js`:\n\n``` yaml\ncss: [\"@callout\"]\njs: [\"@callout\"]\n```\n\nFor example:\n\n```{md, attr.asis = '.callout-output'}\n::: callout-tip\nThis is a tip.\n\n> You can write arbitrary content, such as a blockquote.\n\nYou can even _nest another callout_ into this one!\n\nIs that cool?\n:::\n```\n\n### Built-in callouts\n\nThe stylesheet `callout.css` supports styling `tip`, `note`, `caution`,\n`warning`, `important`, and `example` callouts. For example:\n\n::: callout-note\nThank you for your notice! Your notice has been noted.\n:::\n\n::: callout-caution\nBe careful when testing for strict equality of floating point numbers.\n\n```{r}\nseq(0, 1, .2) == c(0, .2, .4, .6, .8, 1)\n```\n:::\n\n::: callout-warning\nNever try to out-stubborn a cat!\n:::\n\n::: callout-important\nFor the sake of reproducibility, please remember to render an R Markdown\ndocument in a *new R session* before publishing or submitting the output\ndocument.\n:::\n\n::: callout-example\n```{md}\n::: {.callout-example data-legend=\"Demo\"}\nChange the title of this example to \"Demo\".\n:::\n```\n:::\n\n### Customizing callouts\n\nYou do not have to use `callout.css` but can define your own CSS rules, e.g.,\n\n``` css\n.callout-important {\n  background: red;\n  color: yellow;\n}\n```\n\nUnder the hood, `callout.js` turns the fenced Div into a `<fieldset>` for the\nform:\n\n``` html\n<fieldset class=\"callout-*\">\n  <legend>Title</legend>\n\n  Content.\n</fieldset>\n```\n\nThe content comes from the original fenced Div. The title comes from the class\nname (converted to uppercase) by default. You can provide a custom title via the\n`data-legend` attribute of the Div, e.g.,\n\n``` md\n::: {.callout-tip data-legend=\"Information\"}\n:::\n```\n\nThe icons before the callout titles can be defined via CSS, e.g., you can add\ntwo exclamation marks before the title of `important` callouts:\n\n``` css\n.callout-important legend::before {\n  content: \"!! \";\n}\n```\n\nThe default icons defined in `callout.css` are essentially [UTF-8\ncharacters](https://www.w3schools.com/charsets/ref_utf_symbols.asp). In theory,\nthere are hundreds of thousands of characters that you can choose from. Each\ncharacter is 1 to 4 bytes. For example, you can define a `music` callout with\nthe music note symbol ♫ in the CSS:\n\n``` css\n.callout-music {\n  background: springgreen;\n  border-color: yellow;\n}\n.callout-music legend::before {\n  content: \"♫ \";\n}\n```\n\nThen you can insert a `music` callout in your document:\n\n``` md\n::: callout-music\nPlease listen to this lovely song.\n:::\n```\n\nAlternatively, if you are using `callout.css`, you can also use CSS variables to\ndefine the border color, background, and icon of a callout, e.g.,\n\n``` css\n.callout-music {\n  --callout-background: springgreen;\n  --callout-border: yellow;\n  --callout-icon: \"♫ \";\n}\n```\n\n## Add anchor links to headings {#sec:heading-anchor}\n\nThe CSS is necessary only if you want to hide the anchors by default and reveal\nthem on hover.\n\n``` yaml\ncss: [\"@heading-anchor\"]\njs: [\"@heading-anchor\"]\n```\n\n## HTML articles {#sec:article}\n\nYou can style an HTML page in an article format via the following CSS and JS:\n\n``` yaml\ncss: [\"@default\", \"@article\"]\njs: [\"@sidenotes\", \"@appendix\"]\n```\n\nThe `article.css` is mainly for styling the article frontmatter, body, and side\ncontent.\n\n-   The `sidenotes.js` is required only if you want to place certain elements on\n    the left or right side, such as the table of contents (TOC), footnotes, and\n    sidenotes.\n\n-   The `appendix.js` is required only if you have an appendix in the article.\n\nThe web version of this book is also based on the article format, so you know\nwhat an article format looks like when you read the HTML version of the book.\n\n### The overall style\n\nThe maximum width of the article body is 800px. For larger screens, this means\nthere will be extra space in the left/right margin, where we can place auxiliary\ninformation, such as the TOC and footnotes. On smaller screens, the side content\nwill be collapsed into the body.\n\nThe article frontmatter, body, and optionally the appendix are placed in\nseparate boxes.\n\nThe default typeface is sans-serif, and you can customize it by supplying an\nexternal CSS file (via the `css` meta variable) or just embedding CSS in the\ndocument body, e.g.,\n\n```` md\n```{css, echo = FALSE}\nbody {\n  font-family: Palatino, \"Book Antiqua\", Georgia, serif;\n  font-size: 1em;\n}\n```\n````\n\n### Side elements\n\nThe TOC and footnotes are automatically placed in the margin if space permits.\nYou can also write arbitrary content in the margin via a fenced `Div`.\n\n#### The TOC\n\nThe TOC is sticky on the left side as you scroll down the article. If you do not\nlike this behavior, you may cancel it via CSS:\n\n``` css\n#TOC {\n  top: unset;\n}\n```\n\n#### Footnotes on the side\n\nFootnotes are moved to the right side. When you move your cursor over a footnote\nnumber in the body, the footnote will be moved next to your cursor. This can be\nconvenient when you have multiple footnotes on a line, since you do not need to\nlook for a specific footnote in the margin.\n\n#### Arbitrary sidenotes\n\nYou can write anything in the margin by using a fenced `Div` with the classes\n`.side` and `.side-left` or `.side-right`.\n\n::: {.side .side-left}\n**Notice**\n\nHere is a note on the left side. Anything permitted by law is permitted here.\nMath? No problem!\n\n$$e^{i\\theta}=\\sin{\\theta}+i\\cos{\\theta}$$\n\nWhen you have this sidenote \"hammer\", I'm sure you will hit a lot of nails into\nthe margin, even if you do not have to.\n:::\n\n``` markdown\n::: {.side .side-left}\n**Anything** on the left.\n:::\n```\n\n``` markdown\n::: {.side .side-right}\n_Anything_ on the right.\n:::\n```\n\nIn case you are interested in the technical detail, it's quite simple to move an\nelement into the margin using CSS. For example, the `.side-right` class above is\nroughly defined as:\n\n``` css\n.side-right {\n  width: 200px;\n  float: right;\n  margin-right: -200px\n}\n```\n\nThat basically means the width of the element is 200px and it floats to the\nright. Now its right side will touch the right margin of its parent element (the\narticle body). What we need to do next is move it further to the right by 200px\n(i.e., its width), which is done by the `-200px` right margin. Remember, a\npositive right margin in CSS moves an element to the left, and a negative right\nmargin moves it to the right.\n\n### Body elements\n\nInside the article body, you can write a few special elements.\n\n#### Full-width elements\n\nWhen an element is wider than the article body, you can show it in its full\nwidth by enclosing the element in a fenced `Div` with the class `.fullwidth`,\ne.g.,\n\n``` markdown\n::: {.fullwidth}\n![text](path/to/image)\n:::\n```\n\n::: fullwidth\n![Sunspots](https://prose.yihui.org/post/2020/11/10/r-markdown-demo/index_files/figure-html/sunspots-1.svg)\n:::\n\nIf you use R Markdown, you can generate a wide plot or table from an R code\nchunk, e.g.,\n\n```` markdown\n::: {.fullwidth}\n```{r}\n#| sunspots, echo = FALSE, fig.dim = c(14, 4),\n#| fig.cap = 'Monthly mean relative sunspot numbers from 1749 to 1983.'\npar(mar = c(4, 4, .1, .1), bg = 'lightyellow', fg = 'red', las = 1)\nplot(sunspots, col = 'red')\ngrid()\n```\n:::\n````\n\nIf you want to show code (`echo = TRUE`) but do not want the code to be in the\nfull-width container, you can apply the `.fullwidth` class to the plot only,\ne.g.,\n\n```` md\n```{r}\n#| sunspots, fig.dim = c(14, 4), fig.env = '.fullwidth .figure',\n#| fig.cap = 'Monthly mean relative sunspot numbers from 1749 to 1983.'\npar(mar = c(4, 4, .1, .1), bg = 'lightyellow', fg = 'red', las = 1)\nplot(sunspots, col = 'red')\ngrid()\n```\n````\n\n#### Left/right quotes\n\n::: quote-right\n> Whenever you find that you are on the side of the majority, it is time to\n> pause and reflect.\n>\n> ---[Mark Twain](http://www.quotationspage.com/quotes/Mark_Twain/81)\n:::\n\nSometimes you may want to add a quote but do not want it to take the full width\nin the body. You may use a fenced `Div` with the class `.quote-left` or\n`.quote-right`.\n\nDespite the class names, the content does not have to be a quote. If you do want\na quote, just use the blockquote syntax `>`, e.g.,\n\n``` markdown\n::: {.quote-right}\n> This is a boring quote.\n>\n> ---Someone\n:::\n```\n\n#### Margin embedding\n\nYou can embed elements on the left or right margin using a fenced `Div` with the\nclass `.embed-left` or `.embed-right`. These elements will float to the left or\nright and exceed the margin by about 200px, which can save some space in the\narticle body. You can use the extra space to explain the embedded element with\ntext.\n\n::: embed-right\n```{r, echo = FALSE}\nmtcars[1:4, 1:8]\n```\n:::\n\nWe have embedded a table of the first 4 rows of the `mtcars` data on the right\nmargin, which you can see if the browser window is wide enough.\n\n## HTML books {#sec:book}\n\n``` yaml\ncss: [\"@book\"]\n```\n\n## Chapter TOC {#sec:chapter-toc}\n\n``` yaml\njs: [\"@chapter-toc\"]\n```\n\n## Highlight TOC items {#sec:toc-highlight}\n\n``` yaml\njs: [\"@toc-highlight\"]\n```\n\n## Paged HTML {#sec:pages}\n\n``` yaml\ncss: [\"@pages\"]\njs: [\"@pages\"]\n```\n\n## HTML slides {#sec:snap}\n\nWith `snap.css` and `snap.js`, you can create lightweight HTML slides:\n\n``` yaml\ncss: [\"@default\", \"@snap\"]\njs: [\"@snap\"]\n```\n\nYou can learn more in `vignette('slides', package = 'litedown')`.\n\n## Right-align a quote footer {#sec:right-quote}\n\nYou can use the script `right-quote.js` to right-align a blockquote footer if it\nstarts with an em-dash (`---`).\n\n``` yaml\njs: [\"@right-quote\"]\n```\n\n> Without a little madness, life is not worth living. Let us follow the guidance\n> of our inner voice. Why should we fry each of our actions like a piece of cake\n> on a sensible frying pan?\n>\n> ---Milan Kundera, *Immortal*\n\n## Center images {#sec:center-img}\n\n``` yaml\njs: [\"@center-img\"]\n```\n\n## Open external links in new windows {#sec:external-link}\n\n``` yaml\njs: [\"@external-link\"]\n```\n\n## Style keyboard shortcuts {#sec:key-buttons}\n\nThe script `key-button.js` identifies keys and the CSS styles them, which can be\nuseful for [showing keyboard\nshortcuts](https://yihui.org/en/2023/02/key-buttons/).\n\n``` yaml\ncss: [\"@key-buttons\"]\njs: [\"@key-buttons\"]\n```\n\nOf course, you can combine any number of JS scripts and CSS files if you want\nmultiple features.\n\n## Include arbitrary CSS/JS {#sec:arbitrary-assets}\n\nYou can include arbitrary CSS frameworks and JS libraries in your documents, not\nlimited to aforementioned ones in the GitHub repository `yihui/lite.js`. Below\nis a quick example of using the\n[simple-datatables](https://github.com/fiduswriter/simple-datatables/) library\nto create interactive tables:\n\n```` md\n---\ntitle: Display Tables with the JS Library DataTables\noutput:\n  html:\n    meta:\n      css: [\"@default\", \"@npm/simple-datatables/dist/style\"]\n      js: [\"@npm/simple-datatables\"]\n---\n\n::: {#mtcars-table}\n```{r}\nI(mtcars)\n```\n:::\n\n```{js, type = \"module\"}\nnew simpleDatatables.DataTable('#mtcars-table > table');\n```\n````\n\nSee @sec:simple-dt for more ways to create this type of table.\n"
  },
  {
    "path": "docs/06-widgets.Rmd",
    "content": "# HTML Widgets {#chp:widgets}\n\n::: epigraph\n> A well-managed factory is boring. Nothing exciting happens in it because the\n> crises have been anticipated and have been converted into routine.\n>\n> ---Peter Drucker, *The Effective Executive*\n>\n> Plain oatmeal is a bowl of sadness.\n>\n> ---Jamie Michalak, *Frank and Bean: Food Truck Fiasco*\n:::\n\n[I was excited](https://youtu.be/dV4gtARPvu8) about HTML widgets when Ramnath\ncame up with the idea [in\n2014](https://github.com/ramnathv/htmlwidgets/graphs/contributors). I often find\nit hard to resist playing with things that are interactive. Although it feels a\nlittle embarassing to confess, I have clicked the buttons in @sec:new-road for\nmore than 20 times, knowing clearly what will happen every time but still...\nThere is certainly a lot more fun interacting with HTML widgets.\n\n## The dilemma\n\nThe existing [HTML widgets](https://www.htmlwidgets.org) framework is very\nfriendly and convenient to R users, since it doesn't require knowledge about\nJavaScript. A lot of boring details about the JS libraries have been hidden\nunder the carpet. Users are not exposed to the JS APIs directly but can simply\ncall high-level R functions, which will call the proper underlying JS APIs.\n\nExisting R packages for HTML widgets also took the \"batteries included\"\napproach. That is, the JS libraries are embedded in the R packages, and usually\nonly one version of a JS library is embedded and supported. JS libraries often\nevolve and are sometimes\n[complicated](https://github.com/user-attachments/assets/e31ec920-0080-43aa-ae71-071ce2cedb87).\nThat means the JS libraries shipped within the R packages could become outdated,\nand users might be tied to the old versions. For example, as of today:\n\n-   [**dygraphs**](https://github.com/rstudio/dygraphs/issues/241) v1.1.1.6 is\n    still using [dygraphs.js\n    v1.1.1](https://github.com/danvk/dygraphs/releases/tag/v1.1.1) from 2015\n\n-   [**leaflet**](https://github.com/rstudio/leaflet) v2.2.2 is using\n    [leaflet.js v1.3.1](https://github.com/Leaflet/Leaflet/releases/tag/v1.3.1)\n    from 2018\n\n-   [**DiagrameR**](https://github.com/rich-iannone/DiagrammeR/issues/475)\n    v1.0.11 is using [mermaid.js\n    v0.5.8](https://github.com/mermaid-js/mermaid/releases/tag/0.5.8) from 2016\n\n-   [**DT**](https://github.com/rstudio/DT/pull/1147) v0.33 is a little better\n    (since I was the maintainer and cared a lot about this problem) and is using\n    [DataTables.js\n    v1.13.6](https://github.com/DataTables/DataTablesSrc/releases/tag/1.13.6)\n    from 2023\n\nAll these JS libraries are still actively developed today, and much more recent\nreleases are available. If you want new features or bug fixes in more recent\nversions, you can't just replace the \"batteries\" (i.e., download new versions of\nJS libraries and override the old embedded versions) and expect the new\nbatteries to work seamlessly.\n\nIn **litedown**, I provide a different approach and let users decide which\nversion they want to use (by default, the latest versions are used), which\nmeans:\n\n1.  Users can enjoy stability if desired, by pinning to a specific version of\n    the library;\n\n2.  Users are not locked to a version chosen by R package authors.\n\nDespite the flexibility, this approach has a major downside that most R users\nmay dislike: you can't avoid JS. In fact, you will primarily write JS to render\nthe widgets. The only help you receive from **litedown** is that you can\ninterpolate your JS code with R code (@sec:option-fill). For example, you can\npass R objects to JS as JSON objects, and then I wish you good luck with your JS\nadventure:\n\n```` md\n```{js, type = 'module', fill = xfun::tojson}\n// R objects serialized to JSON via xfun::tojson\nconst iris = `{ iris }`, euro = `{ euro }`;\n\nconsole.log('The iris data has', Object.keys(iris).length, 'columns.')\nconst euro10 = euro.map(x => Math.log10(x));\n```\n````\n\nThis approach solves one pain point that I have had with HTML widgets for a long\ntime: most of the time, the APIs at the R level look nice and do a great job of\nconverting R objects to JS, but sometimes it's hard or impossible to express\ncertain things with native R code, such as JS events. [The\nsolution](https://github.com/ramnathv/htmlwidgets/pull/32) in HTML widgets was\nto write raw JS code in an R character string, mark the string with a special\nclass, and `eval()` the string on the JS side. Although [it works\nfine](https://rstudio.github.io/DT/002-rowdetails.html), such \"special strings\"\nstill feel like square pegs in round holes, and the scary `eval()` will make\nthem fit.[^06-widgets-1] The **litedown** approach makes you write (nearly) pure\nJS, and you can do whatever JS allows you to do.\n\n[^06-widgets-1]: See\n    [rstudio/DT#1080](https://github.com/rstudio/DT/issues/1080) for a funny\n    story about `eval()`. Speaking of square pegs, perhaps we can rename `eval`\n    to `oval` (`const oval = eval;`)? Oval pegs should fit round holes better.\n\nHowever, I need to warn you that you may have to write a lot more boilerplate\ncode with this approach. This problem could be solved by providing R helper\nfunctions (to generate the boilerplate code for you), but the convenience means\nthat you have to sacrifice some flexibility, e.g., it will be hard for the\nhelper functions to deal with every version of the JS libraries.\n\nThis is essentially problem about trade-offs and I don't think there is a\nperfect solution. It's fine to tie yourself to potentially very old versions of\ndependencies if these old versions work perfectly and you are able to give up\nnew features or bug fixes in newer versions.\n\nThe following sections will give basic examples of some popular JS libraries. If\nyou want to pin their versions, you may specify the version number at the end of\nthe JS package name, e.g.,\n\n``` r\nlitedown::vest(css = '@npm/leaflet@1.9.3/dist/leaflet', js = '@npm/leaflet@1.9.3')\n```\n\nFor the `js` code chunks, you may want to use the chunk option `echo = FALSE`\n(@sec:option-echo) to hide the JS source in the output. The chunk option\n`type = 'module'` (@sec:engine-css) means the JS code is generated to\n`<script type=\"module\">`, which has two nice benefits:\n\n1.  JS modules are deferred, i.e., they will not execute until all elements on\n    the page have been loaded;\n\n2.  Variables in the modules are local and will not pollute the global\n    namespace, so you don't need to worry about one widget overriding another\n    widget's variables by accident.\n\nWith that in mind, let's start looking at the boring way to generate HTML\nwidgets. Plain oatmeal is a bowl of sadness.[^06-widgets-2] So is this chapter\n(of plain JS).\n\n[^06-widgets-2]: My younger son kept repeating this sentence from his audio book\n    last year. I asked him why plain oatmeal is a bowl of sadness, but he never\n    told me. As a lazy dad, I'm counting on my 9-month old daughter to tell me\n    someday.\n\n## Simple DataTables {#sec:simple-dt}\n\n`{r} .ex(21)`\n\n## Dygraphs\n\n`{r} .ex(22)`\n\n## Leaflet\n\n`{r} .ex(23)`\n\n## Chart.js\n\n`{r} .ex(24)`\n"
  },
  {
    "path": "docs/07-editor.Rmd",
    "content": "# Authoring\n\n::: epigraph\n> I don’t hide from you that I don’t detest the countryside—having been brought\n> up there, snatches of memories from past times, yearnings for that infinite of\n> which the Sower, the sheaf, are the symbols, still enchant me as before. But\n> when will I do the starry sky, then, that painting that’s always on my mind?\n>\n> ---Vincent van Gogh, [*The\n> Letters*](https://vangoghletters.org/vg/letters/let628/letter.html)\n:::\n\n## Live preview\n\nUnless it has become your muscle memory to click on the `Knit` button in\nRStudio, you may try to switch to `litedown::roam()` to preview your HTML output\nby clicking on the `run` buttons in the user interface. Note that the preview\ntakes place *in memory only*. Although you see an HTML page rendered from a file\nin the preview, the page is not rendered to disk, unless you click on the\n`render` button (@sec:the-buttons), or call `litedown::fuse()` (@chp:fuse) or\n`mark()` (@chp:mark) on the file.\n\n### Live reload\n\nBy default, the preview will automatically refresh the content after you edit\nand save a file. If you prefer building the document only when you want to, you\ncan turn off the live preview via `litedown::roam(live = FALSE)`. In this case,\nthe document will be rebuilt only when you refresh the page by yourself.\n\n### The file listing\n\nAfter launching the preview via `litedown::roam()`, a file listing will be\ndisplayed, which shows the first few lines of `*.R`, `*.Rmd`, and `*.md` files\nin boxes, followed by the list of other files.\n\nBy clicking on plain-text files, you will see their full content. For binary\nfiles, they may be opened in your browser; if they cannot be opened, the browser\nmay prompt to download the file.\n\nEach file will have its size displayed after the filename, with a link attached\nto the file size. The link points to the raw file (the behavior of the link is\nup to the browser—the file may be opened or downloaded), and **litedown** will\nnot process it at all.\n\nThe full path of the file or directory being previewed is displayed at the top\nleft.\n\nFor `.R`, `.Rmd`, and `.md` files, you can click on the \"Run\" button\n([⏵](){.larger}) to render them to HTML in memory and preview the output. This\nbutton is displayed at the top right of each file box on the listing page, and\nat the top right of the preview page of an individual file.\n\n::: callout-caution\nRendering `.R` and `.Rmd` files via the `Run` button means *the full R code* in\nthem will be executed. If the R code involves intensive computing, it may not be\na good idea to run the whole file, unless you have taken measures to speed up\nthe computing (e.g., via caching).\n:::\n\n### The buttons\n\nThere is a button group at the top right of the preview page.\n\n-   The `render` button (with the lightning or voltage icon, [↯](){.larger}):\n    Render a document or project in a new R session and save the output file(s)\n    to disk, which is similar to what the `Knit` button does in RStudio.\n\n    You can also use the keyboard shortcut `Ctrl + K` (or `Command + K` on\n    macOS).\n\n-   The `edit` buttons (with a pencil icon, [✎](){.larger}): Open the source\n    file of the current page in your default editor if possible.\n\n-   The `back`/`forward` buttons (with arrow icons, [←](){.larger} and\n    [→](){.larger}): Go back/forward through the browsing history, similar to\n    the back/forward buttons on web browser toolbars.\n\n    You can also use the keyboard shortcuts `Alt + Left` / `Alt + Right`.\n\n-   The `add` button (with the plus icon, [+](){.larger}): Create a new `.Rmd` /\n    `.md` / `.R` file with selected HTML features.\n\n-   The `refresh` button (with the refresh icon, [⟳](){.larger}): Refresh the\n    page. If you are previewing an `.Rmd` file, refreshing the page will rebuild\n    it.\n\n    You can also use the keyboard shortcut `Ctrl + R` (or `Command + R` on\n    macOS).\n\n-   The `printer` button (with a printer icon, [⎙](){.larger}): Bring up the\n    printer dialog, e.g., to print the page to PDF.\n\nCode blocks in the preview mode will have line numbers automatically added to\ntheir left. If you click on a line number, it will bring you to that line in the\nsource document.\n\nNote that the keyboard shortcuts require the page to be currently on focus\nbefore they can take effect. This is important when you are viewing a page\ninside RStudio or other IDEs, because the viewer may not gain focus\nautomatically, and you will have to explicitly click on it.\n\n### Cleaning up\n\nPreviewing `.Rmd` and `.R` files that generate plots will leave `*__files/`\ndirectories (containing plot files) on disk by default. If you want to clean up\nsuch directories when closing or navigating away from the preview page, you may\nset the option\n\n``` r\noptions(litedown.roam.cleanup = TRUE)\n```\n\nbefore you run `litedown::roam()` or in your `.Rprofile`. Note that it will not\nclean up a `*__files/` directory if it has existed before you preview a file.\nThis is to make sure `roam()` will not delete existing files by accident. If you\nare certain that a `*__files/` directory can be safely deleted, you can always\ndelete it by hand. After that, `roam()` will automatically clean it up when you\npreview the file again.\n\nPlease also note that when caching is enabled (via the chunk option\n`cache = TRUE`) in the file being previewed, the `*__files/` directory will not\nbe cleaned up, because when a code chunk is cached, it will not be re-evaluated\nor re-generate plots next time (unless the cache is invalidated).\n\n## Visual editor\n\nSince the Markdown syntax of **litedown** can be viewed as a subset of Pandoc's\nMarkdown, you can use RStudio's visual Markdown editor to author documents.\nPlease bear in mind that most common, but not all, Markdown features are\nsupported.\n\n## The `Knit` button {#sec:knit-button}\n\nIf you use the RStudio IDE, the `Knit` button can render R Markdown to a\n**litedown** output format specified in YAML (e.g., `html` or `latex`). Please\nalso remember to add a top-level setting `knit: litedown:::knit` in YAML,\notherwise RStudio will either throw an error if you use the output format `html`\nor `latex`, or use `rmarkdown::render()` instead of `litedown::fuse()` to render\nthe document.\n\n``` yaml\n---\noutput: html\nknit: litedown:::knit\n---\n```\n"
  },
  {
    "path": "docs/08-site.Rmd",
    "content": "# Books and Websites {#chp:sites}\n\n::: epigraph\n> I can at least listen without indignation to the critic who is of the opinion\n> that when one surveys the aims of cultural endeavour and the means it employs,\n> one is bound to come to the conclusion that the whole effort is not worth the\n> trouble, and that the outcome of it can only be a state of affairs which the\n> individual will be unable to tolerate.\n>\n> ---Sigmund Freud, *Civilization and Its Discontents*\n:::\n\nBooks and websites are usually based on multiple input files under a directory.\nFor a directory to be recognized as a book or website project, it needs to\ncontain a configuration file named `_litedown.yml`.\n\nIf you want to customize the output formats for books or websites, you should do\nit in `_litedown.yml`, e.g.,\n\n``` yaml\noutput:\n  html:\n    options:\n      toc:\n        depth: 4\n  latex:\n    meta:\n      documentclass: \"book\"\n```\n\n## Books\n\nThe `_litedown.yml` file should contain a top-level field named `book`, which\ncurrently supports these options:\n\n``` yaml\nbook:\n  input: null\n  new_session: false\n  subdir: false\n  pattern: \"[.]R?md$\"\n  chapter_before: \"Information before a chapter.\"\n  chapter_after: \"This chapter was generated from `$input$`.\"\n```\n\nYou can choose whether to render each input file in a new R session, whether to\nsearch subdirectories for input files, the types of input files (e.g., you can\nuse `.md` or `.R` files if you want), and additional information to be included\nbefore/after each chapter, in which you can use some variables such as\n`$input$`, which is the path of each input file.\n\n### Previewing a single chapter\n\nRendering a whole book may be time-consuming[^08-site-1] and unnecessary when\nyou work on a book. It may be easier to only preview the single chapter that you\ncurrently work on. The preview can be done with `litedown::roam()`:\n\n[^08-site-1]: Remember that you can cache time-consuming code chunks to make the\n    rendering faster.\n\n-   If you run the index file (e.g., `index.Rmd`), the whole book will be\n    previewed.\n\n-   If you run other chapter files, only the specific chapter is previewed.\n\nWhen a chapter contains dependencies on certain elements in other chapters\n(e.g., a chapter includes cross-references to other chapters), we recommend that\nyou run the index file to preview the whole book at least once before you\npreview individual chapters, to make **litedown** know the book elements fully.\n\nSimilarly, if you use the configuration `new_session: false` to render all\nchapters in the same R session, and a later chapter uses computed results from a\nprevious chapter, you will need to preview the full book at least once to make\nsure the results are computed and exist in the R session.\n\nYou may also choose to run the index file to preview the whole book but then\nwork on an individual chapter. In this case, `roam()` will try to detect changes\nin chapter files. When a certain chapter file has been updated, its output on\nthe full book page will be updated. That is, the full book page is *partially*\nupdated without being reloaded. This method lets you preview changes in one\nchapter while presenting the whole book. However, please note that certain JS\nlibraries may not work well in this preview mode. When in doubt, refresh the\npage.\n\n## Websites\n\nThe `_litedown.yml` file should contain a top-level field named `site`, and you\nare likely to customize the `meta` variables `css`, `include_before`, and\n`include_after` for the `html` format, e.g.,\n\n``` yaml\nsite:\n  rebuild: \"outdated\"\n  pattern: \"[.]R?md$\"\n\noutput:\n  html:\n    meta:\n      css: [\"@default\"]\n      include_before: \"[Home](/) [About](/about.html)\"\n      include_after: \"&copy; 2024 | [Edit]($input$)\"\n```\n\nBasically, `include_before` can take a file or text input that will be used as\nthe header of each web page, and `include_after` will be the footer.\n\n## R package documentation {#sec:pkg-site}\n\nR package developers can build the full package documentation as either a book\nor a website.\n\n### The `pkg_*()` helper functions\n\nA series of helper functions have been provided in **litedown** to get various\ninformation about the package, such as the package description (`pkg_desc()`),\nnews (`pkg_news()`), citation (`pkg_citation()`), source code (`pkg_code()`),\nand all manual pages (`pkg_manual()`). You can call these functions in code\nchunks to print out the desired information.\n\nFor example, you may call them in the appendix (@sec:appendices) of a book:\n\n```` md\n# Appendix {.appendix}\n\n# Package Metadata\n\n```{r, echo = FALSE}\nlitedown::pkg_desc()\n```\n\nTo cite the package:\n\n```{r, echo = FALSE}\nlitedown::pkg_citation()\n```\n\n# News\n\n```{r, echo = FALSE}\nlitedown::pkg_news(recent = 0)  # show full news\n```\n\n# Manual pages\n\n```{r, echo = FALSE}\nlitedown::pkg_manual()\n```\n\n# Source code\n\n```{r, echo = FALSE}\nlitedown::pkg_code()\n```\n````\n\nAlternatively, you can build a package website by calling these functions in\nseparate `.Rmd` files, e.g., `pkg_desc()` in `index.Rmd`, `pkg_news()` in\n`news.Rmd`, and so on.\n\n### Package sites via GitHub Action\n\nYou may use GitHub Actions to automatically build and deploy package websites.\nThe key is to call `litedown::fuse_site()`. I have provided [a simple GitHub\naction](https://github.com/yihui/litedown/blob/main/site/action.yml) in the\n**litedown** repository, and you can add a step `uses: yihui/litedown/site@HEAD`\nto your GitHub workflow to use it.\n\n::: callout-note\nRemember to enable the deployment of GitHub Pages via GitHub Actions in your\nrepository settings (`Settings` → `Pages` → `Build and deployment` → `Source` →\n`GitHub Actions`).\n:::\n\nBelow is [the full\nworkflow](https://github.com/yihui/litedown/blob/main/.github/workflows/github-pages.yml)\nfor building and publishing **litedown**'s site to GitHub Pages:\n\n```{embed, attr.output = '.yaml data-file=\"github-pages.yml\" data-source=\"1\"'}\n\"../.github/workflows/github-pages.yml\"\n```\n\nYou can copy it to the `.github/workflows/` directory of the R package's Git\nrepository and commit/push it via Git. Within a few minutes, the package site\nshould be ready at `https://user.github.io/pkg/`, where `user` is your GitHub\nusername, and `pkg` is the repository name, e.g.,\n<https://yihui.github.io/litedown/>.\n\nThe action `yihui/litedown/site` supports a few options that you can configure\nunder the `with` field. In the above example workflow, we used the option\n`site-dir`. Below are the options currently supported:\n\n```{r, echo = FALSE, results = 'asis'}\ninputs = xfun::yaml_load(readLines('../site/action.yml'), use_yaml = FALSE)$inputs\ncat(sprintf(\n  '- `%s`: %s (default: `%s`).', names(inputs),\n  unlist(lapply(inputs, `[[`, 'description')),\n  unlist(lapply(inputs, `[[`, 'default'))\n), sep = '\\n\\n')\n```\n\nBy default, the site is built under the `site/` directory of the package. If you\nchange this path, please remember to adjust the `path` option for the\n`actions/upload-pages-artifact` action accordingly.\n\nIf the site source directory in your package does not exist or is empty, the\naction will copy [the site\ntemplate](https://github.com/yihui/litedown/tree/main/site) from **litedown**,\nwhich contains the following `.Rmd` files (explained in @tab:pkg-site):\n\n```{r}\nlist.files('../site/', '[.]Rmd$')\n```\n\n:::: table\n::: caption\n[ ](#@tab:pkg-site) Explanations of site source files.\n:::\n\n| source | description | content |\n|----|----|----|\n| `index` | home page | `pkg_desc()`, `pkg_citation()`, and `README.md` |\n| `articles` | package vignettes | the `doc/` folder of the installed package |\n| `code` | source code (R, C, C++, etc.) | `pkg_code()` |\n| `examples` | examples | the `examples/` folder in the package root directory |\n| `manual` | help pages | `pkg_manual()` |\n| `news` | news | `pkg_news()` |\n| `_footer` | page footer | package authors |\n::::\n\nYou can freely customize this template according to your own needs (e.g.,\nmodify, add, or delete files), and add the folder to your repository. Then the\naction will use your custom template to build the site.\n\nThe `exclude` option allows you to exclude certain pages from the site. By\ndefault, `code.Rmd` is excluded, and\n\n-   `articles.Rmd` is excluded if the package does not have the `vignettes/`\n    directory;\n\n-   `examples.Rmd` is excluded if the package does not have the `examples/`\n    directory;\n\n-   `news.Rmd` is excluded if neither `NEWS.md` nor `inst/NEWS.Rd` is found in\n    the package.\n\nYou can exclude any page via the `exclude` option. For example, if don't want to\ninclude the news, you can specify `exclude: 'code.Rmd news.Rmd'` (if you do not\nwant `code.Rmd`, either).\n\nThe `cleanup` option allows you to run a command before publishing the site,\ne.g., you can delete the `.Rmd` source and `.yml` config files.\n\n#### Package playground\n\nThe site template also includes a `playground/` subpage, which provides an\ninteractive R playground powered by [webR](https://r-wasm.org). It installs\n**litedown** and its dependencies in the browser and lets users write and run R\ncode or render R Markdown documents without a server or a local R installation\n(yes, it works on your mobile device, too!).\n\nThe playground can be customized by adding a `playground/` directory to the\npackage root, which the site action copies into `site/playground/`. The\nfollowing files are recognized:\n\n-   `_default.Rmd` (or `_default.R`): the content pre-loaded in the editor when\n    the playground opens. The `.Rmd` version is tried first; if it does not\n    exist, the `.R` version is tried.\n\n-   `setup.R`: an R script executed once in the webR session after packages are\n    installed. It is a good place to install/load the package under\n    documentation[^08-site-2] and define any helpers that users might need.\n\n[^08-site-2]: To install the development version of a package,\n    [r-universe.dev](https://docs.r-universe.dev/install/binaries.html) can be\n    helpful.\n\nIf the package has an `examples/` directory, the `.Rmd`/`.R` files under it will\nshow up in the \"Load example...\" dropdown of the playground. Selecting an\nexample loads its source into the editor.\n"
  },
  {
    "path": "docs/A-misc.Rmd",
    "content": "# Appendix {.appendix}\n\n# Miscellaneous Topics {#apd:misc}\n\n## For rmarkdown users {#sec:rmarkdown}\n\nThe **litedown** package can partially recognize the output format names\n`html_document`, `html_vignette`, and `pdf_document` from **rmarkdown**: Some\noptions of these output formats are mapped to **litedown**'s options internally.\n\nFor example, for an R Markdown document with the following output format:\n\n``` yaml\noutput:\n  html_document:\n    toc: true\n    number_sections: true\n    anchor_sections: true\n    self_contained: false\n```\n\nIt will be transformed to:\n\n``` yaml\noutput:\n  html:\n    options:\n      toc: true\n      number_sections: true\n      embed_resources: false\n    meta:\n      css: [\"default\", \"@heading-anchor\"]\n      js: [\"@heading-anchor\"]\n```\n\nNote that not all **rmarkdown** options are supported, and not even all\nsupported options have exactly the same effects in **litedown**. The supported\noptions include:\n`` {r} xfun::join_words(setdiff(names(formals(litedown:::map_args)), '...'), before='`') ``.\n\n## File or text output {#sec:file-text}\n\nBy default, `mark()` and `fuse()` follow the rule of \"file in, file out; text\nin, text out\". That is, if you pass an input file, you get an output file. If\nyou pass text input, you get text output. For example:\n\n``` r\nfuse('foo.Rmd')  # returns a file 'foo.html' or 'foo.tex', etc.\nmark('Hello _world_')  # returns text\nmark(text = 'Hello _world_')  # text\n```\n\nYou always get a file output if the argument `output` is a file path. If the\n`output` argument is not a character string (e.g., `NA`), or it is a format name\n(one of\n`` {r} xfun::join_words(names(litedown:::md_formats), before = '`', and = 'or ') ``),\nthe output will be text. For example:\n\n``` r\nfuse('$\\\\pi$ = `{r} pi`', output = 'foo.pdf')  # file\nmark('foo.md', output = NA)  # text\nmark('foo.md', output = 'latex')  # text (LaTeX code)\n```\n\nIf the input is a file and the `output` argument takes a file extension (one of\n`` {r} xfun::join_words(litedown:::md_formats, before = '`', and = 'or ') ``),[^a-misc-1]\nyou will get a file output with the same base name as the input file. The `.pdf`\nextension can also be used if you want to convert Markdown to LaTeX, and then\nrender LaTeX to PDF. For example:\n\n[^a-misc-1]: Note that the format name `commonmark` and the extension\n    `.markdown` are for CommonMark output. It should be rare that you need to\n    convert Markdown input to CommonMark output, but it is possible anyway.\n\n``` r\nmark('foo/bar.md', output = '.html')  # file 'foo/bar.html'\nmark('foo/bar.md', output = '.pdf')   # file 'foo/bar.pdf'\n```\n\nFor `fuse()`, the `output` argument can take a special value of the form\n`markdown:format`, where `format` is a format name. This value means to generate\nthe intermediate Markdown text for the target output format, e.g.,\n\n``` r\nfuse('foo.Rmd', output = 'markdown:latex')\n```\n\nDepending on the target output format, the intermediate Markdown may be\ndifferent, e.g., for `html` output, the default graphical device is PNG, whereas\nfor `latex` output, the default device is PDF.\n\n@tab:input-output provides a summary of the above rules.\n\n:::: {#tab:input-output .table}\n::: caption\n[ ](#@tab:input-output) The returned value for different combinations of `input`\nand `output` arguments of `mark()` / `fuse()`.\n:::\n\n| function | `input` | `output` | returned value |\n|----|----|----|----|\n| `mark()` / `fuse()` | file | `NULL` | file |\n|  | text | `NULL` | text |\n|  | \\* | file | file |\n|  | \\* | `NA` / `format` | text |\n|  | `file.xxx` | `.ext` | `file.ext` |\n|  | `file.xxx` | `.pdf` | `file.pdf` |\n| `fuse()` | \\* | `markdown` | intermediate Markdown text |\n|  | \\* | `.md` | intermediate Markdown file |\n|  | \\* | `markdown:format` | intermediate Markdown text for `format` |\n::::\n\n## Package vignettes {#sec:vignettes}\n\nTo build package vignettes with **litedown**, first add this to the package\n`DESCRIPTION` file:\n\n``` yaml\nVignetteBuilder: litedown\n```\n\nIf your package does not require **litedown** as a hard dependency (specified in\n`Depends` or `Imports`), you will need to add it to `Suggests` in `DESCRIPTION`:\n\n``` yaml\nSuggests: litedown\n```\n\nThen use the vignette engine `litedown::vignette` in the YAML metadata of a\n`.Rmd` or `.md` vignette file:\n\n``` yaml\nvignette: >\n  %\\VignetteEngine{litedown::vignette}\n  %\\VignetteIndexEntry{Your vignette title}\n  %\\VignetteEncoding{UTF-8}\n```\n\nThe output format of a vignette can be specified in the `output` field of the\nYAML metadata, e.g., `html` (for HTML vignettes) or `latex` (for PDF vignettes).\nIf no output format is specified, the default is HTML.\n\nThe vignette file can be either `.Rmd` or `.md`. The former is processed by\n`litedown::fuse()`, and the latter is converted by `litedown::mark()`. Please\navoid using the same base filename for two `.Rmd` and `.md` files, otherwise\ntheir output files will overwrite each other.\n"
  },
  {
    "path": "docs/_litedown.yml",
    "content": "book:\n  chapter_before: \"[Edit this chapter](https://github.com/yihui/litedown/edit/main/$input$) on GitHub\"\n  chapter_after: \"\"\n\noutput:\n  html:\n    options:\n      toc:\n        depth: 4\n    meta:\n      css: [\"@book-litedown\", \"@key-buttons\", \"@callout\", \"@tabsets\"]\n      js: [\"@chapter-toc\", \"@external-link\", \"@right-quote\", \"@key-buttons\", \"@callout\", \"@tabsets\"]\n"
  },
  {
    "path": "docs/index.Rmd",
    "content": "---\ntitle: \"litedown: R Markdown Reimagined\"\nauthor: \"Yihui Xie\"\ndate: \"`{r} Sys.Date()`\"\nbibliography: [\"packages.bib\"]\n---\n\n```{r, include = FALSE}\noptions(width = 80)\n\n# return an example file in fenced Div\n.ex = function(n, ext = '.Rmd', last = NA, verbatim = TRUE) {\n  f = Sys.glob(sprintf('../examples/%03d*', n))\n  f = f[endsWith(f, ext)]\n  if (length(f) != 1) stop('found ', length(f), ' example(s) with number ', n)\n  x = xfun::read_utf8(f)\n  i = which(x == '<!-- ... -->')[1]\n  if (ext == '.Rmd') {\n    if (!is.na(i)) x = x[-(i + 0:1)]\n  } else {\n    partial = TRUE\n    if (!is.na(last)) x = tail(x, last) else {\n      if (!is.na(i)) x = x[-(1 + 0:i)] else partial = FALSE\n    }\n    if (partial) x = c('... ... ...', '', x)\n  }\n  if (!verbatim) return(x)\n  x = litedown:::trim_blank(paste(x, collapse = '\\n'))\n  xfun::fenced_block(x, c(\n    sub('^([.])R(.+)', '\\\\1\\\\2', ext), sprintf('data-file=\"%03d%s\"', n, ext),\n    sprintf('data-output=\"%s\"', xfun::with_ext(basename(f), '.html'))\n  ))\n}\n# source and output in the same div\n.ex2 = function(n, ext = c('.Rmd', '.md'), ...) {\n  x1 = .ex(n, ext[1])\n  x2 = .ex(n, ext[2], ...)\n  xfun::fenced_div(c(x1, x2), '.flex-col')\n}\n\nxfun::pkg_bib(c('litedown', 'commonmark', 'xfun', 'rbibutils'), 'packages.bib')\n```\n\n```{js, echo = FALSE, type = 'module'}\ndocument.querySelectorAll('pre > code[data-output]').forEach(el => {\n  const a = document.createElement('a');\n  a.className = 'ex-out';\n  a.href = `https://git.yihui.org/litedown/examples/${el.dataset.output}`;\n  el.after(a);\n});\n```\n\n# Preface {.unnumbered}\n\n::: callout-important\nThe **litedown** package is still new and experimental. The documentation is\nonly about 50% complete at the moment. Besides, **litedown** was designed for\nminimalists with a limited scope. Average users should perhaps consider using\n**rmarkdown** or Quarto instead.\n\nIf you do wish to try **litedown**, please install from r-universe:\n\n``` r\ninstall.packages(c('litedown', 'xfun'), repos = 'https://yihui.r-universe.dev')\n```\n:::\n\n::: epigraph\n> You may say I'm a dreamer\\\n> But I'm not the only one\\\n> I hope someday you'll join us\\\n> And the world will live as one\n>\n> ---John Lennon, *Imagine*\n:::\n\nImagine there's no PDF. It's easy if you try. No Word below us. Above us, only\nHTML.\n\nI do not mean PDF and Word are bad. I only lament the time that human beings\nspend on various document formats, given how much a static web page can do.\n\n## The past journey\n\nHaving worked on the development of R Markdown for nearly 12 years, I have to\nconfess that I have become a little perplexed about my work, although I clearly\nremember the exciting moments. For example:\n\n-   The moment when I added syntax highlighting to R code in `.Rnw` documents.\n    Beautiful!\n\n-   The moment when I managed to generate a base R plot and a ggplot in the same\n    code chunk and place them side by side in LaTeX. Magic! Not to mention that\n    ggplot does not require an explicit `print()` call in a code chunk---you\n    probably do not even know what that means now.\n\n-   The moment when I saw the impossible-to-read diagram that shows all possible\n    conversions among numerous document formats on the Pandoc homepage.\n    Incredible!\n\n-   The moment when I discovered DZSlides in Pandoc. Wow! So PowerPoint and\n    LaTeX beamer were not the only choices for slides.\n\n-   The moment when some rstudio::conf attendees started applauding after I told\n    them that the PowerPoint support had been added to the development version\n    of **rmarkdown** when one person asked about it.\n\n-   The moment when I came across remark.js. And tufte.css. And GitBook. And\n    Hugo. And distill.pub. And **htmlwidgets**, **reticulate**, and so on.\n\n-   ...\n\nI think the work was largely meaningful. The only problem is I do not see an\nend. The list of exciting things to do goes on and on.\n\n## A question\n\n\"At forty, I had no more doubts.\" Confucius said. Apparently, I'm not Confucius.\nOn the contrary, as I'm turning forty, I'm having more doubts. I have been\nreflecting on the things that have kept me busy.\n\nIf I were to summarize the 700+ episodes of Naruto in one sentence, I would use\nthe question that Gaara (the Fifth Kazekage) asked Onoki (the Third Tsuchikage):\n\n> When did you all forsake yourselves?\n\nIn my opinion, this question was the most critical turning point in the 700+\nepisodes, reminding the 79-year-old Onoki and the rest of people of their\noriginal dreams. The shinobi world existed to put an end to wars, but it turned\nout to bring even more and larger-scale wars.\n\nWhen did we forsake simplicity? We create software to simplify things, but\nsoftware often ends up in feature wars.\n\nMarkdown was originally invented for simplicity. That is, to make it easier to\nwrite HTML. I used to take out my wallet and tell people that if they are unable\nto learn the basics of Markdown in 5 minutes, I'd award them 20 dollars. I also\nused to say \"In HTML I Trust\", which sounds like a joke, but I really love HTML\nand web technologies. Again, I do not mean other document formats are bad at\nall. It is just that I feel HTML has the greatest potential, and I hope to take\nfull advantage of its power.[^1]\n\n[^1]: At one night, as I was thinking about Pandoc Lua filters, an obvious fact\n    suddenly came to my mind: suppose all I care about is HTML output, then the\n    good old JavaScript can actually play a perfect role of Lua filters, because\n    you can manipulate the DOM arbitrarily with JavaScript.\n\nIn other words, I do not expect that \"the (software) world will live as one\". I\njust want to make the HTML world a little bit better.\n\n## Overview\n\nThe **litedown** package [@R-litedown] is an attempt to reimagine R Markdown\nwith one primary goal—do HTML, and do it well, with the minimalism principle.\nBefore LaTeX fans walk away in disappointment, let me quickly clarify that LaTeX\noutput is also supported, but please do not expect anything fancy before you\nlearn to customize LaTeX templates. Most other output formats are not supported.\nNo Word, RTF, PowerPoint, or EPUB.\n\nR Markdown is rendered via `litedown::fuse()`, which is similar to\n`rmarkdown::render()` and `knitr::knit()`. Markdown is rendered via\n`litedown::mark()`, which uses **commonmark** [@R-commonmark] instead of Pandoc.\n\nThe **commonmark** package follows the GFM (GitHub Flavored Markdown) spec,\nwhich can be seen as a subset of Pandoc's Markdown. Therefore the **litedown**\npackage can be viewed as a small subset of the existing R Markdown ecosystem\n(the latter is based on Pandoc). It aims at simplicity, lightweight, and speed,\nat the cost of giving up some advanced features. This package is intended for\nminimalists. Most users may prefer using tools based on Pandoc instead, such as\n**rmarkdown** or Quarto, which offer richer features.\n\nIs **litedown** really simple? From the developer's perspective, yes, it is,\nlargely due to the limited scope of the package. From the user's perspective,\nsome features are definitely not that simple. However, the point is that the\ncore is simple and small, and you can enable or disable most features. What's\nmore, you can implement features by yourself if you know CSS/JS.\n\n## Scope\n\nYou can view **litedown** as a minimal re-implementation (@fig:scope-litedown)\nof some core packages in the existing R Markdown ecosystem\n(@fig:scope-rmarkdown), such as **rmarkdown** for reports, **xaringan** for\nslides, **bookdown** for books, **blogdown** for websites, **pkgdown** for R\npackage sites, and **pagedown** for paged HTML documents.\n\n$$\\mathrm{litedown} = \\min{\\{R\\}} + \\{D_i\\} - \\{D_e\\} + \\{J\\}$$\n\n-   $R$ = **knitr** + **evaluate** + **rmarkdown** + **bookdown** +\n    **blogdown** + **pagedown** + **pkgdown** + **xaringan** + **tufte** +\n    **distill** + **htmlwidgets**\n\n-   $D_i$ = (internal dependencies) **commonmark** + **xfun**\n\n-   $D_e$ = (external dependencies) Pandoc + Bootstrap + jQuery + GitBook +\n    Hugo + paged.js + remark.js + tufte.css + distill.js/.css + ...\n\n-   $J$ = Lightweight [vanilla JS/CSS](https://github.com/yihui/lite.js)\n\n```{mermaid}\n#| scope-litedown, echo = FALSE,\n#| fig.cap = 'A minimal partial re-implementation of the R Markdown ecosystem as **litedown**, via [Chibaku Tensei](https://naruto.fandom.com/wiki/Chibaku_Tensei) (to [seal the nine tailed beasts](https://youtu.be/IqAD4np1g1s)).'\nclassDiagram\n  class litedown {\n    +HTML/LaTeX\n    +min [ * ]\n    R-dep (xfun, commonmark)\n    web-dep (lite.js)\n  }\n  class rmarkdown {\n    +[ * ]\n  }\n  class pkgdown {\n    +[ * ]\n  }\n  class xaringan {\n    +[ * ]\n  }\n  class tufte {\n    +[ * ]\n  }\n  class blogdown {\n    +[ * ]\n  }\n  class pagedown {\n    +[ * ]\n  }\n  class distill {\n    +[ * ]\n  }\n  class htmlwidgets {\n    +[ * ]\n  }\n  class bookdown {\n    +[ * ]\n  }\n  litedown *-- rmarkdown\n  litedown *-- bookdown\n  litedown *-- pkgdown\n  litedown *-- xaringan\n  litedown *-- tufte\n  litedown *-- htmlwidgets\n  litedown *-- blogdown\n  litedown *-- pagedown\n  litedown *-- distill\n  rmarkdown <|-- bookdown\n  rmarkdown <|-- pkgdown\n  rmarkdown <|-- xaringan\n  rmarkdown <|-- tufte\n  rmarkdown <|-- htmlwidgets\n  bookdown <|-- blogdown\n  bookdown <|-- pagedown\n  bookdown <|-- distill\n  classDef default fill:none\n  style litedown fill:lightcyan\n```\n\n```{mermaid}\n#| scope-rmarkdown, echo = FALSE,\n#| fig.cap = 'A set of core packages in the R Markdown ecosystem. `R-dep()`\n#|  stands for recursive R package dependencies (with their numbers), `sys-dep`\n#|  stands for system dependencies, and `web-dep` stands for CSS/JS dependencies.'\n\nclassDiagram\n  direction BT\n  class htmlwidgets {\n    +JS apps\n    R-dep (26)\n    web-dep (*.js/.css)\n  }\n  class rmarkdown {\n    +HTML/LaTeX\n    +RTF/Word/PowerPoint/EPub/...\n    R-dep (knitr, evaluate, ... 25)\n    sys-dep (Pandoc)\n    web-dep (Bootstrap/jQuery/...)\n  }\n  class blogdown {\n    +Websites\n    R-dep (33)\n    sys-dep (Hugo)\n  }\n  blogdown --|> bookdown\n  class pagedown {\n    +Paged HTML\n    R-dep (38)\n    web-dep (paged.js)\n  }\n  pagedown --|> bookdown\n  class distill {\n    +Grid layout\n    R-dep (48)\n    web-dep (distill)\n  }\n  distill --|> bookdown\n  class pkgdown {\n    +Package sites\n    R-dep (52)\n    web-dep (Bootstrap...)\n  }\n  pkgdown --|> rmarkdown\n  class xaringan {\n    +HTML slides\n    R-dep (32)\n    web-dep (remark.js)\n  }\n  xaringan --|> rmarkdown\n  class tufte {\n    +Two-column layout\n    R-dep (26)\n    web-dep (tufte.css)\n  }\n  tufte --|> rmarkdown\n  class bookdown {\n    +Books\n    R-dep (26)\n    web-dep (GitBook...)\n  }\n  bookdown --|> rmarkdown\n  classDef default fill:none\n  style rmarkdown fill:lightskyblue\n```\n\nIt is absolutely not the goal for **litedown** to become a substitute of tools\nbased on **knitr** and Pandoc, such as **rmarkdown** and Quarto. If you are not\nsure if you should choose **litedown** or **rmarkdown**/Quarto, you may want to\nchoose the latter (especially Quarto).\n\n### Features out of scope\n\nOutput formats besides HTML and LaTeX are unlikely to be supported.[^2] If other\noutput formats are desired, you may use Pandoc to do the conversion.\n\n[^2]: In fact, `xml,` `man`, `text`, and `commonmark` output formats are\n    supported (thanks to the **commonmark** package), but perhaps they are not\n    very useful to average users.\n\nFor tables, only pipe tables are supported. Other table formats are not\nrecognized.\n\nAt the moment, **litedown** mainly supports R as the computing language. Other\nlanguages might be added in the future, but please keep your expectation low,\nbecause the support is unlikely to be as good as R. Back in 2013, I experimented\nwith a simple idea to run program code in other languages, and the outcome was\nthe [**runr**](https://github.com/yihui/runr) package. However, I forsook\nsimplicity after other sophisticated packages emerged, such as **reticulate**\nand **JuliaCall**. I may revisit the idea in the future.\n\nThe support for HTML widgets is minimal. The method requires basic knowledge of\nJavaScript, yet it is quite flexible (@chp:widgets).\n\n## Highlights\n\n### Small footprint\n\nAlmost everything in **litedown** was written from scratch. The package is very\nlightweight, with only two R package dependencies: **commonmark** and **xfun**\n[@R-xfun].\n\nIt is a deliberate design choice to keep this package lightweight, to make it\nrelatively easy to use and simple to maintain. The functions `mark()` and\n`fuse()` can be viewed as significantly trimmed-down versions of Pandoc and\n**knitr**, respectively.\n\nJust to give you a better idea about the \"lightweight\" (the numbers below refer\nto *uncompressed* software and source code as of today):\n\n-   As the Markdown converter, Pandoc 3.5 is about 180Mb, and **commonmark** is\n    about 1Mb on my macOS.\n\n-   Bootstrap's CSS is about 12,000 lines (plus 1500 lines of JS), and\n    **litedown**'s base CSS is about 100 lines (no JS).\n\n-   For paginating HTML documents,\n    [paged.js](https://cdn.jsdelivr.net/npm/pagedjs/) has 33,000+ lines of code,\n    and my naive implementation,\n    [pages.js](https://github.com/yihui/lite.js/blob/main/js/pages.js), has\n    about 600 lines (400 lines of JS and 200 lines of CSS).\n\nAdmittedly, pursuing lightweight requires sacrifice in features and quality\n(e.g., my pages.js is far less sophisticated than paged.js), but overall I feel\nthe trade-off should be reasonable to those who prefer minimalism. With a 2Mb\nfootprint (the total file size of **litedown**, **commonmark**, and **xfun**),\nyou can:\n\n-   Write basic components of Pandoc's Markdown (@chp:syntax), including\n    headings, table of contents (TOC), lists, figures, pipe tables, code blocks,\n    quotes, fenced Divs, LaTeX math, footnotes, and citations, etc.\n\n-   Automatically number and cross-reference arbitrary elements\n    (@sec:cross-references), including but not limited to headings, figures,\n    tables, and equations.\n\n-   Perform computing with code chunks and inline code expressions (@chp:fuse).\n\n-   Create single-file reports/slides, or multiple-file books/websites\n    (@chp:sites).\n\nIf you load additional JS/CSS assets, you can create more types of elements,\nsuch as folded code blocks, tabsets, side elements, sticky TOC, and callout\nblocks (@chp:assets).\n\n### Live preview everything\n\nTo get started, run `litedown::roam()` and it will launch a file browser to let\nyou preview everything that can be rendered to HTML, such as `.md`, `.Rmd`, and\n`.R` files (@sec:live-preview).\n\nThe rendering takes place in memory, which means it will not render `.html`\nfiles on your disk, unless you request so. The page will be automatically\nupdated as you edit and save a file. This update-on-save feature can be turned\noff, and then you can manually refresh the page to re-render the file whenever\nyou want.\n\nYou can open any file in your editor or the default system application by\nclicking a button in the browser. You can also render a file or a project in a\nnew R session by clicking a button in the browser.\n\n### Precise parser\n\nThe R Markdown parser stores the precise line and column numbers of code\nelements. The location information is used in various places. For example, when\nan error occurs, you will get a message that tells you the precise location in\nthe source. In editors that support ANSI links (such as the RStudio IDE), you\ncan even click on the message to go to a specific line/column in the source\ndocument, so you can quickly and easily know where the error occurred exactly.\nWhen previewing `.Rmd` documents with `roam()`, you will see line numbers on the\nleft side of code blocks. Clicking on these numbers will bring you to the lines\nin the source.\n\nDue to the fact that the parser is based on **commonmark** (instead of solely on\nregular expressions like **knitr**'s parser), it can precisely recognize code\nchunks and inline code, which means code within other verbatim code blocks or\ncomments will be untouched. For example:\n\n````` md\n````md\nBelow is not a code chunk but verbatim content\ninside a fenced code block (with four backticks).\n\n```{r}\n1 + 1\n```\n\nInline code expressions like `{r} 1+1` are not\nparsed, either.\n````\n`````\n\n```` html\n<!--\nFeel free to comment out anything, such as a code chunk:\n\n```{r}\n1 + 1\n```\n-->\n````\n\n### Versioned CSS/JS assets\n\nBy default, **litedown** produces bare minimal HTML output, but many features\ncan be enabled by adding CSS/JS assets (@chp:assets). You can freely choose\nwhatever versions that work for you to avoid potential future breakage when the\nassets are upgraded. The assets are not bundled in the **litedown** package, but\nhosted on CDN, so updating **litedown** will not update your assets.\n\nThe CSS and JS code for commonly used features do not depend on any frameworks\nsuch as Bootstrap or jQuery. They are simply vanilla code written from scratch.\nNo generator was used (such as SCSS). The code is often relatively short, so you\ncould just fork the code, modify it, publish your own version, and use it if you\nare not satisfied with the original version.\n\n### Chunk options management\n\nChunk options are managed by an environment, i.e., `litedown::reactor()`\n(@sec:reactor). Using an environment (as compared to a list like **knitr**'s\n`opts_chunk`) means you can access the up-to-date chunk options anywhere,\nbecause environments in R are mutable. I cannot explain how awkward **knitr**'s\n`opts_current` has been. It is basically a lie—chunk options that you get from\n`opts_current` are not necessarily \"current\".\n\n### Non-linear order of execution\n\nA document does not have to be compiled in the linear order. With the chunk\noption `order`, you can specify code chunks and inline code expressions to be\nexecuted in an arbitrary order (@sec:option-order). For example, if you have an\nabstract in the beginning of a document, and the abstract needs to use a number\ncalculated at the end of the document, you can let the abstract be compiled in\nthe end, although it appears earlier in the document.\n\nI guess some users may want to kill me upon knowing this feature, and some may\nsend me flowers (although I'm not sure if they want to thank me or prepare the\nflowers for my funeral). For those who want to kill me, please note that this\nfeature does not mean **litedown** is as awful as Jupyter notebooks. It means\nyou can specify a *fixed* order to execute code in the document. The order does\nnot have to be from beginning to end, but it is *deterministic*. In other words,\nit does not mean you run code chunks in an arbitrary or random order that can\ndetriment reproducibility.\n\n### Time code chunks\n\nIf you want to figure out which code chunks are time-consuming, simply set the\nchunk option `litedown::reactor(time = TRUE)` (@sec:option-time) in the\nbeginning, and put `litedown::timing_data()` in the last code chunk. It will\ntell you detailed information. In the `roam()` preview, the data will also\ncontain links to specific lines of code chunks, so you click to jump to a\nspecific code chunk.\n\n### Table output for data frames by default\n\nRectangular objects such as data frames (including tibbles) and matrices are\nprinted as tables by default (@sec:option-tab), and the number of rows is\nlimited to 10 by default to avoid generating huge tables by accident.\n\n### Relieved pain of paths\n\nFile paths (such as image paths) have been a mess in **knitr**. My deepest\napologies for that. I have worked much harder in **litedown** in this regard.\n\n### Output in memory or to disk\n\nFunctions such as `mark()`, `fuse()`, and `fuse_book()` can operate in memory\nwithout writing to disk. By default, if you pass a file input, you get a file\noutput; if you pass text input, you get text output (@sec:file-text).\n\n### A new cache system\n\nYou can feel more confident with using the chunk option `cache = TRUE` in\n**litedown** than in **knitr**. The new cache system is more robust and\nintelligent (@sec:option-cache).\n\n### A book is a single HTML file\n\nUnlike **bookdown** and Quarto, `litedown::fuse_book()` renders multiple input\nfiles to a single output file. Yes, your whole precious book is in a single\n(HTML or PDF) file. Grab and go.\n\nThe assumption of single-file output for books has made several things a lot\neasier. For example, if you want to search within the book, just press\n`Ctrl + F` or `Command + F` in your browser as you usually do on any other web\npage. You do not need a client-side search library. It is also quicker to jump\nbetween chapters because they are on the same page. If you want to print the\nHTML version of the book to PDF, just press `Ctrl + P` or `Command + P`.\n\nI know you have a concern: wouldn't the single HTML file be too heavy for the\nbrowser? The answer is: you should be fine if you do not have too many images.\nIf you do, do not base64 encode them (which is the default), and you can also\nlazy-load images to make the book load faster.\n\n### R scripts as first-class citizens\n\nAn R script has the same status as an R Markdown document (@sec:r-scripts). You\ncan write Markdown in `#'` comments, and chunk options in `#|` comments (both\nare optional). These are the only two rules to remember. Compared to\n`knitr::spin()`, the rules are much simpler. Besides, R scripts are also\ncarefully parsed into \"code chunks\", so their line numbers work as well as R\nMarkdown's line numbers.\n\nAny application that you can create with R Markdown can be created with R\nscripts, such as books and websites.\n\n### Clean HTML output\n\nThe HTML output generated from **litedown** is very clean. For example, code\nblocks in HTML output contain plain code, instead of full of `<span>` tags with\nrandom attributes. Clean HTML output means the output file size is smaller, and\nmore importantly, it is easier to inspect the differences between two versions\nof output (e.g., in Git). Every time when you update the source document, you\ncan know more clearly what has changed in the output, which can help you avoid\nunexpected changes before publishing the output.\n"
  },
  {
    "path": "examples/001-minimal.Rmd",
    "content": "---\ntitle: Area of a circle\n---\n\nDefine the radius as `x`:\n\n```{r}\nx = 1 + 1\n```\n\nWhen the radius is `{r} x`, the area will be `{r} pi * x^2`.\n"
  },
  {
    "path": "examples/001-minimal.md",
    "content": "---\ntitle: Area of a circle\n---\n\nDefine the radius as `x`:\n\n``` {.r}\nx = 1 + 1\n```\n\nWhen the radius is 2, the area will be 12.6.\n"
  },
  {
    "path": "examples/002-attr-options.Rmd",
    "content": "---\ntitle: The `attr.*` chunk options\n---\n\n1. Add an ID `#example-a` to the whole chunk.\n\n2. Add line numbers to source blocks via the `.line-numbers` class.\n\n3. Add the class `.round` to the first plot and set its width to 400px.\n\n4. Add two classes `.dark` and `.img-center` to the second plot.\n\n```{r}\n#| example-a,\n#| attr.chunk = '#example-a',\n#| attr.source = '.r .line-numbers',\n#| attr.plot = c('.round width=\"400\"', '.dark .img-center'),\n#| fig.alt = c('A scatterplot of rnorm(100) numbers.',\n#|   'A sunflower plot of iris.')\n\nplot(rnorm(100), rnorm(100))\ni34 = iris[, 3:4]\nsmoothScatter(i34)\nsunflowerplot(i34, add = TRUE)\n```\n\nDefine CSS rules for the classes in the `#example-a` chunk:\n\n```{css}\n#example-a {\n  .round { border: solid 1px; border-radius: 50%; }\n  .dark  { filter: invert(1); }\n  .img-center { display: block; margin: auto; }\n}\n```\n"
  },
  {
    "path": "examples/002-attr-options.md",
    "content": "---\ntitle: The `attr.*` chunk options\n---\n\n1. Add an ID `#example-a` to the whole chunk.\n\n2. Add line numbers to source blocks via the `.line-numbers` class.\n\n3. Add the class `.round` to the first plot and set its width to 400px.\n\n4. Add two classes `.dark` and `.img-center` to the second plot.\n\n::: {#example-a}\n\n``` {.r .line-numbers}\nplot(rnorm(100), rnorm(100))\n```\n![A scatterplot of rnorm(100) numbers.](<002-attr-options__files/example-a-1.png>){.round width=\"400\"}\n\n``` {.r .line-numbers}\ni34 = iris[, 3:4]\nsmoothScatter(i34)\nsunflowerplot(i34, add = TRUE)\n```\n![A sunflower plot of iris.](<002-attr-options__files/example-a-2.png>){.dark .img-center}\n:::\n\nDefine CSS rules for the classes in the `#example-a` chunk:\n\n```` {.css}\n#example-a {\n  .round { border: solid 1px; border-radius: 50%; }\n  .dark  { filter: invert(1); }\n  .img-center { display: block; margin: auto; }\n}\n````\n\n``` {=html}\n<style type=\"text/css\">\n#example-a {\n  .round { border: solid 1px; border-radius: 50%; }\n  .dark  { filter: invert(1); }\n  .img-center { display: block; margin: auto; }\n}\n</style>\n```\n"
  },
  {
    "path": "examples/003-attr-callout.Rmd",
    "content": "---\ntitle: Create a callout via the option `attr.chunk`\noutput:\n  html:\n    meta:\n      css: [\"@default\", \"@callout\"]\n      js: [\"@callout\"]\n---\n\nIf you use the class name `.callout-*` on a chunk, you can turn it into a callout, e.g.,\n\n```{r}\n#| attr.chunk = '.callout-example'\n\n1 + 1\n```\n\nRemember to load the `callout` CSS/JS assets in YAML.\n"
  },
  {
    "path": "examples/003-attr-callout.md",
    "content": "---\ntitle: Create a callout via the option `attr.chunk`\noutput:\n  html:\n    meta:\n      css: [\"@default\", \"@callout\"]\n      js: [\"@callout\"]\n---\n\nIf you use the class name `.callout-*` on a chunk, you can turn it into a callout, e.g.,\n\n::: {.callout-example}\n\n``` {.r}\n1 + 1\n```\n\n```\n#> [1] 2\n```\n:::\n\nRemember to load the `callout` CSS/JS assets in YAML.\n"
  },
  {
    "path": "examples/004-caption-position.Rmd",
    "content": "---\ntitle: Caption position\n---\n\n## Default caption positions\n\n```{r, fig-bottom, fig.cap = 'Bottom figure caption.'}\nplot(cars)\n```\n\n```{r, tab-top, tab.cap = 'Top table caption.'}\ncars\n```\n\n## Change the positions\n\n```{r, fig-top, fig.cap = 'Top figure caption.', cap.pos = 'top'}\nplot(cars)\n```\n\n```{r, tab-bottom, tab.cap = 'Bottom table caption.', cap.pos = 'bottom'}\ncars\n```\n"
  },
  {
    "path": "examples/004-caption-position.md",
    "content": "---\ntitle: Caption position\n---\n\n## Default caption positions\n\n``` {.r}\nplot(cars)\n```\n\n:::: {.figure #fig:fig-bottom}\n![Bottom figure caption.](<004-caption-position__files/fig-bottom-1.png>)\n\n\n::: {.caption}\n[](#@fig:fig-bottom)\nBottom figure caption.\n:::\n::::\n\n``` {.r}\ncars\n```\n\n:::: {.table #tab:tab-top}\n\n::: {.caption}\n[](#@tab:tab-top)\nTop table caption.\n:::\n\n|speed|dist|\n|--:|--:|\n| 4|  2|\n| 4| 10|\n| 7|  4|\n| 7| 22|\n| 8| 16|\n|&vellip;|&vellip;|\n|24| 70|\n|24| 92|\n|24| 93|\n|24|120|\n|25| 85|\n::::\n\n\n## Change the positions\n\n``` {.r}\nplot(cars)\n```\n\n:::: {.figure #fig:fig-top}\n\n::: {.caption}\n[](#@fig:fig-top)\nTop figure caption.\n:::\n\n![Top figure caption.](<004-caption-position__files/fig-top-1.png>)\n::::\n\n``` {.r}\ncars\n```\n\n:::: {.table #tab:tab-bottom}\n|speed|dist|\n|--:|--:|\n| 4|  2|\n| 4| 10|\n| 7|  4|\n| 7| 22|\n| 8| 16|\n|&vellip;|&vellip;|\n|24| 70|\n|24| 92|\n|24| 93|\n|24|120|\n|25| 85|\n\n\n::: {.caption}\n[](#@tab:tab-bottom)\nBottom table caption.\n:::\n::::\n\n"
  },
  {
    "path": "examples/005-option-code.Rmd",
    "content": "---\ntitle: The `code` option\n---\n\nDefine a code template `tpl`:\n\n```{r}\ntpl    = 'lm(mpg ~ %s, data = mtcars) |> summary() |> coef()'\nx_vars = names(mtcars)[2:4]\n```\n\nWe run regressions on three variables one by one:\n\n<!-- ... -->\n\n```{r, code = sprintf(tpl, x_vars)}\n```\n"
  },
  {
    "path": "examples/005-option-code.md",
    "content": "---\ntitle: The `code` option\n---\n\nDefine a code template `tpl`:\n\n``` {.r}\ntpl    = 'lm(mpg ~ %s, data = mtcars) |> summary() |> coef()'\nx_vars = names(mtcars)[2:4]\n```\n\nWe run regressions on three variables one by one:\n\n<!-- ... -->\n\n``` {.r}\nlm(mpg ~ cyl, data = mtcars) |> summary() |> coef()\n```\n| |Estimate|Std. Error|t value|Pr(>\\|t\\|)|\n|---|--:|--:|--:|--:|\n|(Intercept)|37.885| 2.074|18.268| 0.000|\n|cyl|-2.876| 0.322|-8.920| 0.000|\n\n\n``` {.r}\nlm(mpg ~ disp, data = mtcars) |> summary() |> coef()\n```\n| |Estimate|Std. Error|t value|Pr(>\\|t\\|)|\n|---|--:|--:|--:|--:|\n|(Intercept)|29.600| 1.230|24.070| 0.000|\n|disp|-0.041| 0.005|-8.747| 0.000|\n\n\n``` {.r}\nlm(mpg ~ hp, data = mtcars) |> summary() |> coef()\n```\n| |Estimate|Std. Error|t value|Pr(>\\|t\\|)|\n|---|--:|--:|--:|--:|\n|(Intercept)|30.099| 1.634|18.421| 0.000|\n|hp|-0.068| 0.010|-6.742| 0.000|\n\n"
  },
  {
    "path": "examples/006-option-collapse.Rmd",
    "content": "---\ntitle: Collapse source code and text output\n---\n\nBy default, the source and output are in separate blocks:\n\n```{r}\nx = 1 + 1\nx\nx + 2\n```\n\nSet `collapse = TRUE` to collapse them:\n\n```{r, collapse = TRUE}\nx = 1 + 1\nx\nx + 100\n```\n"
  },
  {
    "path": "examples/006-option-collapse.md",
    "content": "---\ntitle: Collapse source code and text output\n---\n\nBy default, the source and output are in separate blocks:\n\n``` {.r}\nx = 1 + 1\nx\n```\n\n```\n#> [1] 2\n```\n\n``` {.r}\nx + 2\n```\n\n```\n#> [1] 4\n```\n\nSet `collapse = TRUE` to collapse them:\n\n``` {.r}\nx = 1 + 1\nx\n#> [1] 2\nx + 100\n#> [1] 102\n```\n"
  },
  {
    "path": "examples/007-option-comment.Rmd",
    "content": "---\ntitle: The `comment` option\n---\n\nUse `#-> ` to comment out text output:\n\n```{r, comment = '#-> ', print = NA}\nmatrix(1:12, 3)\n```\n\nDo not comment out text output:\n\n```{r, comment = '', print = NA}\nmatrix(1:12, 3)\n```\n"
  },
  {
    "path": "examples/007-option-comment.md",
    "content": "---\ntitle: The `comment` option\n---\n\nUse `#-> ` to comment out text output:\n\n``` {.r}\nmatrix(1:12, 3)\n```\n\n```\n#->      [,1] [,2] [,3] [,4]\n#-> [1,]    1    4    7   10\n#-> [2,]    2    5    8   11\n#-> [3,]    3    6    9   12\n```\n\nDo not comment out text output:\n\n``` {.r}\nmatrix(1:12, 3)\n```\n\n```\n     [,1] [,2] [,3] [,4]\n[1,]    1    4    7   10\n[2,]    2    5    8   11\n[3,]    3    6    9   12\n```\n"
  },
  {
    "path": "examples/008-option-device.Rmd",
    "content": "---\ntitle: The graphics device\n---\n\nThe default (png) device with a higher resolution:\n\n```{r}\n#| chunk-a, dev.args = list(res = 96),\n#| fig.alt = 'png with a resolution of 96 ppi'\n\nplot(cars)\n```\n\nThe `svg` device with a background color:\n\n```{r}\n#| chunk-b, dev = 'svg', dev.args = list(bg = 'lightyellow'),\n#| fig.alt = 'svg with a lightyellow background'\n\nplot(cars)\n```\n"
  },
  {
    "path": "examples/008-option-device.md",
    "content": "---\ntitle: The graphics device\n---\n\nThe default (png) device with a higher resolution:\n\n``` {.r}\nplot(cars)\n```\n![png with a resolution of 96 ppi](<008-option-device__files/chunk-a-1.png>)\n\nThe `svg` device with a background color:\n\n``` {.r}\nplot(cars)\n```\n![svg with a lightyellow background](<008-option-device__files/chunk-b-1.svg>)\n"
  },
  {
    "path": "examples/009-option-figure-decoration.Rmd",
    "content": "---\ntitle: Decorating figures\noutput:\n  html:\n    meta:\n      css: [\"@default\", \"@article\"]\n      js: [\"@sidenotes\"]\n---\n\nPlace two plots side by side via the `width` attribute:\n\n```{r}\n#| chunk-a, attr.plot = 'width=\"45%\"',\n#| fig.alt = c('a histogram', 'a sunflower plot'),\n#| fig.cap = 'Exploring the faithful dataset.',\n\nhist(faithful$eruptions, main = '', border = 'white')\nsunflowerplot(faithful)\n```\n\nA full-width figure (requires the `@article` CSS):\n\n```{r}\n#| chunk-b, fig.dim = c(14, 4), fig.env = '.figure .fullwidth',\n#| fig.cap = 'Monthly mean relative sunspot numbers from 1749 to 1983.'\npar(mar = c(4, 4, .1, .1), bg = 'lightyellow', fg = 'red', las = 1)\nplot(sunspots, col = 'red')\ngrid()\n```\n\nFeel free to experiment with other class names provided by the `@article` CSS, such as `.side .side-right` or `.embed-right` in addition to `.fullwidth`.\n"
  },
  {
    "path": "examples/009-option-figure-decoration.md",
    "content": "---\ntitle: Decorating figures\noutput:\n  html:\n    meta:\n      css: [\"@default\", \"@article\"]\n      js: [\"@sidenotes\"]\n---\n\nPlace two plots side by side via the `width` attribute:\n\n``` {.r}\nhist(faithful$eruptions, main = '', border = 'white')\nsunflowerplot(faithful)\n```\n\n:::: {.figure #fig:chunk-a}\n![a histogram](<009-option-figure-decoration__files/chunk-a-1.png>){width=\"45%\"}\n![a sunflower plot](<009-option-figure-decoration__files/chunk-a-2.png>){width=\"45%\"}\n\n\n::: {.caption}\n[](#@fig:chunk-a)\nExploring the faithful dataset.\n:::\n::::\n\nA full-width figure (requires the `@article` CSS):\n\n``` {.r}\npar(mar = c(4, 4, .1, .1), bg = 'lightyellow', fg = 'red', las = 1)\nplot(sunspots, col = 'red')\ngrid()\n```\n\n:::: {.figure .fullwidth #fig:chunk-b}\n![Monthly mean relative sunspot numbers from 1749 to 1983.](<009-option-figure-decoration__files/chunk-b-1.png>)\n\n\n::: {.caption}\n[](#@fig:chunk-b)\nMonthly mean relative sunspot numbers from 1749 to 1983.\n:::\n::::\n\nFeel free to experiment with other class names provided by the `@article` CSS, such as `.side .side-right` or `.embed-right` in addition to `.fullwidth`.\n"
  },
  {
    "path": "examples/010-option-plot-files.Rmd",
    "content": "---\ntitle: Plot files\n---\n\nThe default extension for the `jpeg()` device is `jpeg`, and you can change it to `.jpg` if desired:\n\n```{r, chunk-a, dev = 'jpeg', fig.ext = '.jpg'}\nplot(cars)\n```\n\nSet the plot size via `fig.dim`:\n\n```{r, chunk-b, fig.dim = c(5, 4)}\nplot(cars)\n```\n\nWrite plot files to a different folder:\n\n```{r, chunk-c, fig.path = 'figures/'}\nplot(cars)\n```\n"
  },
  {
    "path": "examples/010-option-plot-files.md",
    "content": "---\ntitle: Plot files\n---\n\nThe default extension for the `jpeg()` device is `jpeg`, and you can change it to `.jpg` if desired:\n\n``` {.r}\nplot(cars)\n```\n![](<010-option-plot-files__files/chunk-a-1.jpg>)\n\nSet the plot size via `fig.dim`:\n\n``` {.r}\nplot(cars)\n```\n![](<010-option-plot-files__files/chunk-b-1.png>)\n\nWrite plot files to a different folder:\n\n``` {.r}\nplot(cars)\n```\n![](<figures/chunk-c-1.png>)\n"
  },
  {
    "path": "examples/011-option-label.Rmd",
    "content": "---\ntitle: Shared chunk labels\n---\n\n```{r, chunk-a}\nmessage(\"This chunk's label is chunk-a\")\n```\n\nRepeat `chunk-a` but suppress the message:\n\n```{r, chunk-a, message=FALSE}\n```\n"
  },
  {
    "path": "examples/011-option-label.md",
    "content": "---\ntitle: Shared chunk labels\n---\n\n``` {.r}\nmessage(\"This chunk's label is chunk-a\")\n```\n\n``` {.plain .message}\n#> This chunk's label is chunk-a\n```\n\nRepeat `chunk-a` but suppress the message:\n\n``` {.r}\nmessage(\"This chunk's label is chunk-a\")\n```\n"
  },
  {
    "path": "examples/012-option-order.Rmd",
    "content": "---\ntitle: Custom execution order\nabstract: \"We analyzed `{r, order = N + 1} nrow(x)` `{r} n_cyl`-cylinder cars, with an average MPG of `{r} m`.\"\n---\n\nSubset the data:\n\n```{r}\nn_cyl = 8\nx = subset(mtcars, cyl == n_cyl)\n```\n\nThe average MPG `{r} m` is calculated from:\n\n```{r, order = i - 1.5}\nm = mean(x$mpg)\n```\n"
  },
  {
    "path": "examples/012-option-order.md",
    "content": "---\ntitle: Custom execution order\nabstract: \"We analyzed 14 8-cylinder cars, with an average MPG of 15.1.\"\n---\n\nSubset the data:\n\n``` {.r}\nn_cyl = 8\nx = subset(mtcars, cyl == n_cyl)\n```\n\nThe average MPG 15.1 is calculated from:\n\n``` {.r}\nm = mean(x$mpg)\n```\n"
  },
  {
    "path": "examples/013-option-print.Rmd",
    "content": "---\ntitle: Print objects\n---\n\nPrint objects with `base::print()`, and use different arguments for different objects.\n\n```{r}\n#| print = NA,\n#| print.args = list(table = list(zero.print = '.'),\n#|   factor = list(quote = TRUE, max.levels = 3))\n\nX = c('a', 'b', 'c', 'c', 'c', 'a')\nY = factor(c('A', 'B', 'C', 'C', 'D', 'E'))\nY  # factor\n\ntable(X, Y)  # table\n```\n"
  },
  {
    "path": "examples/013-option-print.md",
    "content": "---\ntitle: Print objects\n---\n\nPrint objects with `base::print()`, and use different arguments for different objects.\n\n``` {.r}\nX = c('a', 'b', 'c', 'c', 'c', 'a')\nY = factor(c('A', 'B', 'C', 'C', 'D', 'E'))\nY  # factor\n```\n\n```\n#> [1] \"A\" \"B\" \"C\" \"C\" \"D\" \"E\"\n#> 5 Levels: \"A\" \"B\" ... \"E\"\n```\n\n``` {.r}\ntable(X, Y)  # table\n```\n\n```\n#>    Y\n#> X   A B C D E\n#>   a 1 . . . 1\n#>   b . 1 . . .\n#>   c . . 2 1 .\n```\n"
  },
  {
    "path": "examples/014-option-purl.R",
    "content": "if (TRUE) {\n  x = 1 + 1\n}\n\n"
  },
  {
    "path": "examples/014-option-purl.Rmd",
    "content": "This code chunk is not important for the `fiss()` output.\n\n```{r, setup, purl = FALSE}\nlitedown::reactor(fig.height = 5)\n```\n\nThis code chunk will be included in the script.\n\n```{r}\nif (TRUE) {\n  x = 1 + 1\n}\n```\n"
  },
  {
    "path": "examples/014-option-purl.md",
    "content": "This code chunk is not important for the `fiss()` output.\n\n``` {.r}\nlitedown::reactor(fig.height = 5)\n```\n\nThis code chunk will be included in the script.\n\n``` {.r}\nif (TRUE) {\n  x = 1 + 1\n}\n```\n"
  },
  {
    "path": "examples/015-fill-chunk.Rmd",
    "content": "---\ntitle: \"Fill out references in code chunks\"\n---\n\nDefine `chunk-a` that includes a reference to `chunk-b`:\n\n```{r, chunk-a, eval = FALSE, echo = FALSE}\nx = runif(1); y = runif(1)\n`<chunk-b>`\n```\n\n```{r, chunk-b, eval = FALSE, echo = FALSE}\nif (x^2 + y^2 <= 1) {\n  n = n + 1\n}\n```\n\n```{r, echo = FALSE}\nN = 10000\n```\n\n<!-- ... -->\n\nEmbed `chunk-a` in a function body:\n\n```{r}\nf = function() {\n  n = 0\n  for (i in 1:`{N}`) {\n    `<chunk-a>`\n  }\n  n/`{N}` * 4\n}\nabs(f() - pi) <= 0.1  # is it close to pi?\n```\n\nWe can generate code dynamically in any code chunk, not limited to R chunks, e.g.,\n\n```{js, eval = FALSE, fill = xfun::tojson}\nconst data = `{ iris[1:5, 1:2] }`;\nObject.keys(data);  // column names\n```\n"
  },
  {
    "path": "examples/015-fill-chunk.md",
    "content": "---\ntitle: \"Fill out references in code chunks\"\n---\n\nDefine `chunk-a` that includes a reference to `chunk-b`:\n\n\n\n\n\n\n\n<!-- ... -->\n\nEmbed `chunk-a` in a function body:\n\n``` {.r}\nf = function() {\n  n = 0\n  for (i in 1:10000) {\n    x = runif(1); y = runif(1)\n    if (x^2 + y^2 <= 1) {\n      n = n + 1\n    }\n  }\n  n/10000 * 4\n}\nabs(f() - pi) <= 0.1  # is it close to pi?\n```\n\n```\n#> [1] TRUE\n```\n\nWe can generate code dynamically in any code chunk, not limited to R chunks, e.g.,\n\n``` {.js}\nconst data = {\n  \"Sepal.Length\": [5.1, 4.9, 4.7, 4.6, 5],\n  \"Sepal.Width\": [3.5, 3, 3.2, 3.1, 3.6]\n};\nObject.keys(data);  // column names\n```\n"
  },
  {
    "path": "examples/016-option-results.Rmd",
    "content": "---\ntitle: Text output\n---\n\nDefault verbatim output:\n\n```{r, test-out}\ncat('Hello _world_!\\n')\n```\n\nHide output:\n\n```{r, test-out, results = FALSE}\n```\n\nOutput as is:\n\n```{r, test-out, results = 'asis'}\n```\n"
  },
  {
    "path": "examples/016-option-results.md",
    "content": "---\ntitle: Text output\n---\n\nDefault verbatim output:\n\n``` {.r}\ncat('Hello _world_!\\n')\n```\n\n```\n#> Hello _world_!\n```\n\nHide output:\n\n``` {.r}\ncat('Hello _world_!\\n')\n```\n\nOutput as is:\n\n``` {.r}\ncat('Hello _world_!\\n')\n```\nHello _world_!\n"
  },
  {
    "path": "examples/017-option-strip-white.Rmd",
    "content": "---\ntitle: Blank lines in source code\n---\n\nKeep blank lines at the beginning/end:\n\n<!-- ... -->\n\n```{r, strip.white = FALSE}\n\n# a blank line before this expression\n1 + 1\n\n# blank lines before and after this expression\nfor (i in 1:10) {\n}\n\n```\n"
  },
  {
    "path": "examples/017-option-strip-white.md",
    "content": "---\ntitle: Blank lines in source code\n---\n\nKeep blank lines at the beginning/end:\n\n<!-- ... -->\n\n``` {.r}\n\n# a blank line before this expression\n1 + 1\n```\n\n```\n#> [1] 2\n```\n\n``` {.r}\n\n# blank lines before and after this expression\nfor (i in 1:10) {\n}\n\n```\n"
  },
  {
    "path": "examples/018-option-table.Rmd",
    "content": "---\ntitle: A simple table\n---\n\nSee @tab:simple.\n\n<!-- ... -->\n\n```{r}\n#| simple, tab.cap = 'First 3 rows of the `cars` data.',\nhead(cars, 3)\n```\n"
  },
  {
    "path": "examples/018-option-table.md",
    "content": "---\ntitle: A simple table\n---\n\nSee @tab:simple.\n\n<!-- ... -->\n\n``` {.r}\nhead(cars, 3)\n```\n\n:::: {.table #tab:simple}\n\n::: {.caption}\n[](#@tab:simple)\nFirst 3 rows of the `cars` data.\n:::\n\n|speed|dist|\n|--:|--:|\n|4| 2|\n|4|10|\n|7| 4|\n::::\n\n"
  },
  {
    "path": "examples/019-option-verbose.Rmd",
    "content": "---\ntitle: Printing verbosity\n---\n\nDefault `verbose = 0`:\n\n```{r, test}\n1:5  # a visible value\nx = 1 + 1  # invisible\nfor (i in 1:10) i^2  # for loop returns invisible NULL\ny = x^2  # invisible\n```\n\n`verbose = 1` always prints the last value:\n\n```{r, test, verbose = 1}\n```\n\n`verbose = 2` prints all invisible values (except for `NULL`):\n\n```{r, test, verbose = 2}\n```\n"
  },
  {
    "path": "examples/019-option-verbose.md",
    "content": "---\ntitle: Printing verbosity\n---\n\nDefault `verbose = 0`:\n\n``` {.r}\n1:5  # a visible value\n```\n\n```\n#> [1] 1 2 3 4 5\n```\n\n``` {.r}\nx = 1 + 1  # invisible\nfor (i in 1:10) i^2  # for loop returns invisible NULL\ny = x^2  # invisible\n```\n\n`verbose = 1` always prints the last value:\n\n``` {.r}\n1:5  # a visible value\n```\n\n```\n#> [1] 1 2 3 4 5\n```\n\n``` {.r}\nx = 1 + 1  # invisible\nfor (i in 1:10) i^2  # for loop returns invisible NULL\ny = x^2  # invisible\n```\n\n```\n#> [1] 4\n```\n\n`verbose = 2` prints all invisible values (except for `NULL`):\n\n``` {.r}\n1:5  # a visible value\n```\n\n```\n#> [1] 1 2 3 4 5\n```\n\n``` {.r}\nx = 1 + 1  # invisible\n```\n\n```\n#> [1] 2\n```\n\n``` {.r}\nfor (i in 1:10) i^2  # for loop returns invisible NULL\ny = x^2  # invisible\n```\n\n```\n#> [1] 4\n```\n"
  },
  {
    "path": "examples/020-inline.Rmd",
    "content": "---\ntitle: Inline output\n---\n\nWe know that $\\pi = `{r} pi`$, and the first 3 letters are `{r} LETTERS[1:3]`. The first 3 variables of `mtcars` are `{r} xfun::join_words(names(mtcars)[1:3])`. You will not see this value `{r, eval = FALSE} 2 * pi`.\n\nSome numbers:\n\n```{r}\nx = 1.23456789 * 10^c(0, 5, 6, 9, -6) * c(1, 1, -1, 1, 1)\nsprintf('%f', x)\n```\n\n| n = signif/p = power | n = 3, p = 6 | n = 5/2/4/1/7 | p = 0/4/Inf/9/5 |\n|-----------|-----------:|---------------------:|----------------------:|\n| x_1       | `{r} x[1]` | `{r, signif=5} x[1]` | `{r, power=0}   x[1]` | \n| x_2 10^5  | `{r} x[2]` | `{r, signif=2} x[2]` | `{r, power=4}   x[2]` |\n| x_3 10^6  | `{r} x[3]` | `{r, signif=4} x[3]` | `{r, power=Inf} x[3]` |\n| x_4 10^9  | `{r} x[4]` | `{r, signif=1} x[4]` | `{r, power=9}   x[4]` |\n| x_5 10^-6 | `{r} x[5]` | `{r, signif=7} x[5]` | `{r, power=5}   x[5]` |\n\nAn equation: $Y = 300 `{r, dollar = FALSE, signif = 1} x[3]`x$.\n"
  },
  {
    "path": "examples/020-inline.md",
    "content": "---\ntitle: Inline output\n---\n\nWe know that $\\pi = 3.14$, and the first 3 letters are A\nB\nC. The first 3 variables of `mtcars` are mpg, cyl, and disp. You will not see this value .\n\nSome numbers:\n\n``` {.r}\nx = 1.23456789 * 10^c(0, 5, 6, 9, -6) * c(1, 1, -1, 1, 1)\nsprintf('%f', x)\n```\n\n```\n#> [1] \"1.234568\"          \"123456.789000\"     \"-1234567.890000\"  \n#> [4] \"1234567890.000000\" \"0.000001\"         \n```\n\n| n = signif/p = power | n = 3, p = 6 | n = 5/2/4/1/7 | p = 0/4/Inf/9/5 |\n|-----------|-----------:|---------------------:|----------------------:|\n| x_1       | 1.23 | 1.2346 | $1.23 \\times 10^{0}$ | \n| x_2 10^5  | 123000 | 120000 | $1.23 \\times 10^{5}$ |\n| x_3 10^6  | $-1.23 \\times 10^{6}$ | $-1.235 \\times 10^{6}$ | -1230000 |\n| x_4 10^9  | $1.23 \\times 10^{9}$ | $10^{9}$ | $1.23 \\times 10^{9}$ |\n| x_5 10^-6 | 0.00000123 | 0.000001234568 | $1.23 \\times 10^{-6}$ |\n\nAn equation: $Y = 300 -10^{6}x$.\n"
  },
  {
    "path": "examples/021-simple-datatables.Rmd",
    "content": "---\ntitle: \"Simple DataTables\"\n---\n\nAdd the CSS/JS assets from the [simple-datatables](https://github.com/fiduswriter/simple-datatables/) library:\n\n```{r}\nlitedown::vest(css = '@npm/simple-datatables/dist/style', js = '@npm/simple-datatables')\n```\n\nThere are two ways to render a table using the library. You can either pass the data as a JSON object:\n\n```{js, type = 'module', fill = xfun::tojson}\nnew simpleDatatables.DataTable('#table-iris', {\n  data: {\n    headings: `{ names(iris) }`,\n    data: `{ unname(iris)[1:20, ] }`\n  },\n  perPage: 5\n});\n```\n\n<table id=\"table-iris\"></table>\n\nor generate the data to an HTML table first:\n\n::: {#mtcars}\n```{r, echo = FALSE}\nI(mtcars[1:10, 1:5])\n```\n:::\n\nand then initialize a simple data table.\n\n```{js, type = 'module'}\nnew simpleDatatables.DataTable('#mtcars > table', {\n  perPage: 1,\n  perPageSelect: [1, 2, 4, 8, ['All', 0]]\n});\n```\n"
  },
  {
    "path": "examples/021-simple-datatables.md",
    "content": "---\ntitle: \"Simple DataTables\"\n---\n\nAdd the CSS/JS assets from the [simple-datatables](https://github.com/fiduswriter/simple-datatables/) library:\n\n``` {.r}\nlitedown::vest(css = '@npm/simple-datatables/dist/style', js = '@npm/simple-datatables')\n```\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/simple-datatables/dist/style.min.css\">\n<script src=\"https://cdn.jsdelivr.net/npm/simple-datatables\" defer></script>\n\nThere are two ways to render a table using the library. You can either pass the data as a JSON object:\n\n```` {.js}\nnew simpleDatatables.DataTable('#table-iris', {\n  data: {\n    headings: [\"Sepal.Length\", \"Sepal.Width\", \"Petal.Length\", \"Petal.Width\", \"Species\"],\n    data: [\n  [5.1, 3.5, 1.4, 0.2, \"setosa\"],\n  [4.9, 3, 1.4, 0.2, \"setosa\"],\n  [4.7, 3.2, 1.3, 0.2, \"setosa\"],\n  [4.6, 3.1, 1.5, 0.2, \"setosa\"],\n  [5, 3.6, 1.4, 0.2, \"setosa\"],\n  [5.4, 3.9, 1.7, 0.4, \"setosa\"],\n  [4.6, 3.4, 1.4, 0.3, \"setosa\"],\n  [5, 3.4, 1.5, 0.2, \"setosa\"],\n  [4.4, 2.9, 1.4, 0.2, \"setosa\"],\n  [4.9, 3.1, 1.5, 0.1, \"setosa\"],\n  [5.4, 3.7, 1.5, 0.2, \"setosa\"],\n  [4.8, 3.4, 1.6, 0.2, \"setosa\"],\n  [4.8, 3, 1.4, 0.1, \"setosa\"],\n  [4.3, 3, 1.1, 0.1, \"setosa\"],\n  [5.8, 4, 1.2, 0.2, \"setosa\"],\n  [5.7, 4.4, 1.5, 0.4, \"setosa\"],\n  [5.4, 3.9, 1.3, 0.4, \"setosa\"],\n  [5.1, 3.5, 1.4, 0.3, \"setosa\"],\n  [5.7, 3.8, 1.7, 0.3, \"setosa\"],\n  [5.1, 3.8, 1.5, 0.3, \"setosa\"]\n]\n  },\n  perPage: 5\n});\n````\n\n``` {=html}\n<script type=\"module\">new simpleDatatables.DataTable('#table-iris', {\n  data: {\n    headings: [\"Sepal.Length\", \"Sepal.Width\", \"Petal.Length\", \"Petal.Width\", \"Species\"],\n    data: [\n  [5.1, 3.5, 1.4, 0.2, \"setosa\"],\n  [4.9, 3, 1.4, 0.2, \"setosa\"],\n  [4.7, 3.2, 1.3, 0.2, \"setosa\"],\n  [4.6, 3.1, 1.5, 0.2, \"setosa\"],\n  [5, 3.6, 1.4, 0.2, \"setosa\"],\n  [5.4, 3.9, 1.7, 0.4, \"setosa\"],\n  [4.6, 3.4, 1.4, 0.3, \"setosa\"],\n  [5, 3.4, 1.5, 0.2, \"setosa\"],\n  [4.4, 2.9, 1.4, 0.2, \"setosa\"],\n  [4.9, 3.1, 1.5, 0.1, \"setosa\"],\n  [5.4, 3.7, 1.5, 0.2, \"setosa\"],\n  [4.8, 3.4, 1.6, 0.2, \"setosa\"],\n  [4.8, 3, 1.4, 0.1, \"setosa\"],\n  [4.3, 3, 1.1, 0.1, \"setosa\"],\n  [5.8, 4, 1.2, 0.2, \"setosa\"],\n  [5.7, 4.4, 1.5, 0.4, \"setosa\"],\n  [5.4, 3.9, 1.3, 0.4, \"setosa\"],\n  [5.1, 3.5, 1.4, 0.3, \"setosa\"],\n  [5.7, 3.8, 1.7, 0.3, \"setosa\"],\n  [5.1, 3.8, 1.5, 0.3, \"setosa\"]\n]\n  },\n  perPage: 5\n});</script>\n```\n\n<table id=\"table-iris\"></table>\n\nor generate the data to an HTML table first:\n\n::: {#mtcars}\n| |mpg|cyl|disp|hp|drat|\n|---|--:|--:|--:|--:|--:|\n|Mazda RX4|21.0|6|160.0|110|3.90|\n|Mazda RX4 Wag|21.0|6|160.0|110|3.90|\n|Datsun 710|22.8|4|108.0| 93|3.85|\n|Hornet 4 Drive|21.4|6|258.0|110|3.08|\n|Hornet Sportabout|18.7|8|360.0|175|3.15|\n|Valiant|18.1|6|225.0|105|2.76|\n|Duster 360|14.3|8|360.0|245|3.21|\n|Merc 240D|24.4|4|146.7| 62|3.69|\n|Merc 230|22.8|4|140.8| 95|3.92|\n|Merc 280|19.2|6|167.6|123|3.92|\n\n:::\n\nand then initialize a simple data table.\n\n```` {.js}\nnew simpleDatatables.DataTable('#mtcars > table', {\n  perPage: 1,\n  perPageSelect: [1, 2, 4, 8, ['All', 0]]\n});\n````\n\n``` {=html}\n<script type=\"module\">new simpleDatatables.DataTable('#mtcars > table', {\n  perPage: 1,\n  perPageSelect: [1, 2, 4, 8, ['All', 0]]\n});</script>\n```\n"
  },
  {
    "path": "examples/022-dygraphs.Rmd",
    "content": "---\ntitle: \"Dygraphs\"\n---\n\nAdd the CSS/JS assets from the [dygraphs](https://github.com/danvk/dygraphs) library:\n\n```{r}\nlitedown::vest(css = '@npm/dygraphs/dist/dygraph', js = '@npm/dygraphs/dist/dygraph')\n```\n\nShow the yearly sunspots data (with a range selector):\n\n```{r}\nsunspot = cbind(Year = time(sunspot.year), Sunspots = sunspot.year)\n```\n\n```{js, type = 'module', fill = xfun::tojson}\nconst g = new Dygraph(\n  document.getElementById('graph-sunspot'),\n  `{ unname(sunspot) }`,\n  {\n    labels: `{ colnames(sunspot) }`,\n    showRangeSelector: true\n  }\n);\n// annotate the maximum\ng.ready(() => g.setAnnotations([\n  {\n    series: `{ colnames(sunspot)[2] }`,\n    x: `{ sunspot[which.max(sunspot[, 2]), 1] }`,\n    shortText: \"M\",\n    text: \"Maximum number\"\n  }\n]));\n```\n\n::: {#graph-sunspot style=\"width: 100%;\"}\n:::\n\nShow two time series:\n\n```{r}\ndeaths = cbind(Time = round(time(mdeaths), 2), Male = mdeaths, Female = fdeaths)\n```\n\n```{js, type = 'module', fill = xfun::tojson}\nnew Dygraph(\n  document.getElementById('graph-deaths'),\n  `{ unname(deaths) }`,\n  {\n    labels: `{ colnames(deaths) }`\n  }\n);\n```\n\n::: {#graph-deaths style=\"width: 100%;\"}\n:::\n"
  },
  {
    "path": "examples/022-dygraphs.md",
    "content": "---\ntitle: \"Dygraphs\"\n---\n\nAdd the CSS/JS assets from the [dygraphs](https://github.com/danvk/dygraphs) library:\n\n``` {.r}\nlitedown::vest(css = '@npm/dygraphs/dist/dygraph', js = '@npm/dygraphs/dist/dygraph')\n```\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/dygraphs/dist/dygraph.min.css\">\n<script src=\"https://cdn.jsdelivr.net/npm/dygraphs/dist/dygraph.min.js\" defer></script>\n\nShow the yearly sunspots data (with a range selector):\n\n``` {.r}\nsunspot = cbind(Year = time(sunspot.year), Sunspots = sunspot.year)\n```\n\n```` {.js}\nconst g = new Dygraph(\n  document.getElementById('graph-sunspot'),\n  [\n  [1700, 5],\n  [1701, 11],\n  [1702, 16],\n  [1703, 23],\n  [1704, 36],\n  [1705, 58],\n  [1706, 29],\n  [1707, 20],\n  [1708, 10],\n  [1709, 8],\n  [1710, 3],\n  [1711, 0],\n  [1712, 0],\n  [1713, 2],\n  [1714, 11],\n  [1715, 27],\n  [1716, 47],\n  [1717, 63],\n  [1718, 60],\n  [1719, 39],\n  [1720, 28],\n  [1721, 26],\n  [1722, 22],\n  [1723, 11],\n  [1724, 21],\n  [1725, 40],\n  [1726, 78],\n  [1727, 122],\n  [1728, 103],\n  [1729, 73],\n  [1730, 47],\n  [1731, 35],\n  [1732, 11],\n  [1733, 5],\n  [1734, 16],\n  [1735, 34],\n  [1736, 70],\n  [1737, 81],\n  [1738, 111],\n  [1739, 101],\n  [1740, 73],\n  [1741, 40],\n  [1742, 20],\n  [1743, 16],\n  [1744, 5],\n  [1745, 11],\n  [1746, 22],\n  [1747, 40],\n  [1748, 60],\n  [1749, 80.9],\n  [1750, 83.4],\n  [1751, 47.7],\n  [1752, 47.8],\n  [1753, 30.7],\n  [1754, 12.2],\n  [1755, 9.6],\n  [1756, 10.2],\n  [1757, 32.4],\n  [1758, 47.6],\n  [1759, 54],\n  [1760, 62.9],\n  [1761, 85.9],\n  [1762, 61.2],\n  [1763, 45.1],\n  [1764, 36.4],\n  [1765, 20.9],\n  [1766, 11.4],\n  [1767, 37.8],\n  [1768, 69.8],\n  [1769, 106.1],\n  [1770, 100.8],\n  [1771, 81.6],\n  [1772, 66.5],\n  [1773, 34.8],\n  [1774, 30.6],\n  [1775, 7],\n  [1776, 19.8],\n  [1777, 92.5],\n  [1778, 154.4],\n  [1779, 125.9],\n  [1780, 84.8],\n  [1781, 68.1],\n  [1782, 38.5],\n  [1783, 22.8],\n  [1784, 10.2],\n  [1785, 24.1],\n  [1786, 82.9],\n  [1787, 132],\n  [1788, 130.9],\n  [1789, 118.1],\n  [1790, 89.9],\n  [1791, 66.6],\n  [1792, 60],\n  [1793, 46.9],\n  [1794, 41],\n  [1795, 21.3],\n  [1796, 16],\n  [1797, 6.4],\n  [1798, 4.1],\n  [1799, 6.8],\n  [1800, 14.5],\n  [1801, 34],\n  [1802, 45],\n  [1803, 43.1],\n  [1804, 47.5],\n  [1805, 42.2],\n  [1806, 28.1],\n  [1807, 10.1],\n  [1808, 8.1],\n  [1809, 2.5],\n  [1810, 0],\n  [1811, 1.4],\n  [1812, 5],\n  [1813, 12.2],\n  [1814, 13.9],\n  [1815, 35.4],\n  [1816, 45.8],\n  [1817, 41.1],\n  [1818, 30.1],\n  [1819, 23.9],\n  [1820, 15.6],\n  [1821, 6.6],\n  [1822, 4],\n  [1823, 1.8],\n  [1824, 8.5],\n  [1825, 16.6],\n  [1826, 36.3],\n  [1827, 49.6],\n  [1828, 64.2],\n  [1829, 67],\n  [1830, 70.9],\n  [1831, 47.8],\n  [1832, 27.5],\n  [1833, 8.5],\n  [1834, 13.2],\n  [1835, 56.9],\n  [1836, 121.5],\n  [1837, 138.3],\n  [1838, 103.2],\n  [1839, 85.7],\n  [1840, 64.6],\n  [1841, 36.7],\n  [1842, 24.2],\n  [1843, 10.7],\n  [1844, 15],\n  [1845, 40.1],\n  [1846, 61.5],\n  [1847, 98.5],\n  [1848, 124.7],\n  [1849, 96.3],\n  [1850, 66.6],\n  [1851, 64.5],\n  [1852, 54.1],\n  [1853, 39],\n  [1854, 20.6],\n  [1855, 6.7],\n  [1856, 4.3],\n  [1857, 22.7],\n  [1858, 54.8],\n  [1859, 93.8],\n  [1860, 95.8],\n  [1861, 77.2],\n  [1862, 59.1],\n  [1863, 44],\n  [1864, 47],\n  [1865, 30.5],\n  [1866, 16.3],\n  [1867, 7.3],\n  [1868, 37.6],\n  [1869, 74],\n  [1870, 139],\n  [1871, 111.2],\n  [1872, 101.6],\n  [1873, 66.2],\n  [1874, 44.7],\n  [1875, 17],\n  [1876, 11.3],\n  [1877, 12.4],\n  [1878, 3.4],\n  [1879, 6],\n  [1880, 32.3],\n  [1881, 54.3],\n  [1882, 59.7],\n  [1883, 63.7],\n  [1884, 63.5],\n  [1885, 52.2],\n  [1886, 25.4],\n  [1887, 13.1],\n  [1888, 6.8],\n  [1889, 6.3],\n  [1890, 7.1],\n  [1891, 35.6],\n  [1892, 73],\n  [1893, 85.1],\n  [1894, 78],\n  [1895, 64],\n  [1896, 41.8],\n  [1897, 26.2],\n  [1898, 26.7],\n  [1899, 12.1],\n  [1900, 9.5],\n  [1901, 2.7],\n  [1902, 5],\n  [1903, 24.4],\n  [1904, 42],\n  [1905, 63.5],\n  [1906, 53.8],\n  [1907, 62],\n  [1908, 48.5],\n  [1909, 43.9],\n  [1910, 18.6],\n  [1911, 5.7],\n  [1912, 3.6],\n  [1913, 1.4],\n  [1914, 9.6],\n  [1915, 47.4],\n  [1916, 57.1],\n  [1917, 103.9],\n  [1918, 80.6],\n  [1919, 63.6],\n  [1920, 37.6],\n  [1921, 26.1],\n  [1922, 14.2],\n  [1923, 5.8],\n  [1924, 16.7],\n  [1925, 44.3],\n  [1926, 63.9],\n  [1927, 69],\n  [1928, 77.8],\n  [1929, 64.9],\n  [1930, 35.7],\n  [1931, 21.2],\n  [1932, 11.1],\n  [1933, 5.7],\n  [1934, 8.7],\n  [1935, 36.1],\n  [1936, 79.7],\n  [1937, 114.4],\n  [1938, 109.6],\n  [1939, 88.8],\n  [1940, 67.8],\n  [1941, 47.5],\n  [1942, 30.6],\n  [1943, 16.3],\n  [1944, 9.6],\n  [1945, 33.2],\n  [1946, 92.6],\n  [1947, 151.6],\n  [1948, 136.3],\n  [1949, 134.7],\n  [1950, 83.9],\n  [1951, 69.4],\n  [1952, 31.5],\n  [1953, 13.9],\n  [1954, 4.4],\n  [1955, 38],\n  [1956, 141.7],\n  [1957, 190.2],\n  [1958, 184.8],\n  [1959, 159],\n  [1960, 112.3],\n  [1961, 53.9],\n  [1962, 37.5],\n  [1963, 27.9],\n  [1964, 10.2],\n  [1965, 15.1],\n  [1966, 47],\n  [1967, 93.8],\n  [1968, 105.9],\n  [1969, 105.5],\n  [1970, 104.5],\n  [1971, 66.6],\n  [1972, 68.9],\n  [1973, 38],\n  [1974, 34.5],\n  [1975, 15.5],\n  [1976, 12.6],\n  [1977, 27.5],\n  [1978, 92.5],\n  [1979, 155.4],\n  [1980, 154.7],\n  [1981, 140.5],\n  [1982, 115.9],\n  [1983, 66.6],\n  [1984, 45.9],\n  [1985, 17.9],\n  [1986, 13.4],\n  [1987, 29.2],\n  [1988, 100.2]\n],\n  {\n    labels: [\"Year\", \"Sunspots\"],\n    showRangeSelector: true\n  }\n);\n// annotate the maximum\ng.ready(() => g.setAnnotations([\n  {\n    series: \"Sunspots\",\n    x: 1957,\n    shortText: \"M\",\n    text: \"Maximum number\"\n  }\n]));\n````\n\n``` {=html}\n<script type=\"module\">const g = new Dygraph(\n  document.getElementById('graph-sunspot'),\n  [\n  [1700, 5],\n  [1701, 11],\n  [1702, 16],\n  [1703, 23],\n  [1704, 36],\n  [1705, 58],\n  [1706, 29],\n  [1707, 20],\n  [1708, 10],\n  [1709, 8],\n  [1710, 3],\n  [1711, 0],\n  [1712, 0],\n  [1713, 2],\n  [1714, 11],\n  [1715, 27],\n  [1716, 47],\n  [1717, 63],\n  [1718, 60],\n  [1719, 39],\n  [1720, 28],\n  [1721, 26],\n  [1722, 22],\n  [1723, 11],\n  [1724, 21],\n  [1725, 40],\n  [1726, 78],\n  [1727, 122],\n  [1728, 103],\n  [1729, 73],\n  [1730, 47],\n  [1731, 35],\n  [1732, 11],\n  [1733, 5],\n  [1734, 16],\n  [1735, 34],\n  [1736, 70],\n  [1737, 81],\n  [1738, 111],\n  [1739, 101],\n  [1740, 73],\n  [1741, 40],\n  [1742, 20],\n  [1743, 16],\n  [1744, 5],\n  [1745, 11],\n  [1746, 22],\n  [1747, 40],\n  [1748, 60],\n  [1749, 80.9],\n  [1750, 83.4],\n  [1751, 47.7],\n  [1752, 47.8],\n  [1753, 30.7],\n  [1754, 12.2],\n  [1755, 9.6],\n  [1756, 10.2],\n  [1757, 32.4],\n  [1758, 47.6],\n  [1759, 54],\n  [1760, 62.9],\n  [1761, 85.9],\n  [1762, 61.2],\n  [1763, 45.1],\n  [1764, 36.4],\n  [1765, 20.9],\n  [1766, 11.4],\n  [1767, 37.8],\n  [1768, 69.8],\n  [1769, 106.1],\n  [1770, 100.8],\n  [1771, 81.6],\n  [1772, 66.5],\n  [1773, 34.8],\n  [1774, 30.6],\n  [1775, 7],\n  [1776, 19.8],\n  [1777, 92.5],\n  [1778, 154.4],\n  [1779, 125.9],\n  [1780, 84.8],\n  [1781, 68.1],\n  [1782, 38.5],\n  [1783, 22.8],\n  [1784, 10.2],\n  [1785, 24.1],\n  [1786, 82.9],\n  [1787, 132],\n  [1788, 130.9],\n  [1789, 118.1],\n  [1790, 89.9],\n  [1791, 66.6],\n  [1792, 60],\n  [1793, 46.9],\n  [1794, 41],\n  [1795, 21.3],\n  [1796, 16],\n  [1797, 6.4],\n  [1798, 4.1],\n  [1799, 6.8],\n  [1800, 14.5],\n  [1801, 34],\n  [1802, 45],\n  [1803, 43.1],\n  [1804, 47.5],\n  [1805, 42.2],\n  [1806, 28.1],\n  [1807, 10.1],\n  [1808, 8.1],\n  [1809, 2.5],\n  [1810, 0],\n  [1811, 1.4],\n  [1812, 5],\n  [1813, 12.2],\n  [1814, 13.9],\n  [1815, 35.4],\n  [1816, 45.8],\n  [1817, 41.1],\n  [1818, 30.1],\n  [1819, 23.9],\n  [1820, 15.6],\n  [1821, 6.6],\n  [1822, 4],\n  [1823, 1.8],\n  [1824, 8.5],\n  [1825, 16.6],\n  [1826, 36.3],\n  [1827, 49.6],\n  [1828, 64.2],\n  [1829, 67],\n  [1830, 70.9],\n  [1831, 47.8],\n  [1832, 27.5],\n  [1833, 8.5],\n  [1834, 13.2],\n  [1835, 56.9],\n  [1836, 121.5],\n  [1837, 138.3],\n  [1838, 103.2],\n  [1839, 85.7],\n  [1840, 64.6],\n  [1841, 36.7],\n  [1842, 24.2],\n  [1843, 10.7],\n  [1844, 15],\n  [1845, 40.1],\n  [1846, 61.5],\n  [1847, 98.5],\n  [1848, 124.7],\n  [1849, 96.3],\n  [1850, 66.6],\n  [1851, 64.5],\n  [1852, 54.1],\n  [1853, 39],\n  [1854, 20.6],\n  [1855, 6.7],\n  [1856, 4.3],\n  [1857, 22.7],\n  [1858, 54.8],\n  [1859, 93.8],\n  [1860, 95.8],\n  [1861, 77.2],\n  [1862, 59.1],\n  [1863, 44],\n  [1864, 47],\n  [1865, 30.5],\n  [1866, 16.3],\n  [1867, 7.3],\n  [1868, 37.6],\n  [1869, 74],\n  [1870, 139],\n  [1871, 111.2],\n  [1872, 101.6],\n  [1873, 66.2],\n  [1874, 44.7],\n  [1875, 17],\n  [1876, 11.3],\n  [1877, 12.4],\n  [1878, 3.4],\n  [1879, 6],\n  [1880, 32.3],\n  [1881, 54.3],\n  [1882, 59.7],\n  [1883, 63.7],\n  [1884, 63.5],\n  [1885, 52.2],\n  [1886, 25.4],\n  [1887, 13.1],\n  [1888, 6.8],\n  [1889, 6.3],\n  [1890, 7.1],\n  [1891, 35.6],\n  [1892, 73],\n  [1893, 85.1],\n  [1894, 78],\n  [1895, 64],\n  [1896, 41.8],\n  [1897, 26.2],\n  [1898, 26.7],\n  [1899, 12.1],\n  [1900, 9.5],\n  [1901, 2.7],\n  [1902, 5],\n  [1903, 24.4],\n  [1904, 42],\n  [1905, 63.5],\n  [1906, 53.8],\n  [1907, 62],\n  [1908, 48.5],\n  [1909, 43.9],\n  [1910, 18.6],\n  [1911, 5.7],\n  [1912, 3.6],\n  [1913, 1.4],\n  [1914, 9.6],\n  [1915, 47.4],\n  [1916, 57.1],\n  [1917, 103.9],\n  [1918, 80.6],\n  [1919, 63.6],\n  [1920, 37.6],\n  [1921, 26.1],\n  [1922, 14.2],\n  [1923, 5.8],\n  [1924, 16.7],\n  [1925, 44.3],\n  [1926, 63.9],\n  [1927, 69],\n  [1928, 77.8],\n  [1929, 64.9],\n  [1930, 35.7],\n  [1931, 21.2],\n  [1932, 11.1],\n  [1933, 5.7],\n  [1934, 8.7],\n  [1935, 36.1],\n  [1936, 79.7],\n  [1937, 114.4],\n  [1938, 109.6],\n  [1939, 88.8],\n  [1940, 67.8],\n  [1941, 47.5],\n  [1942, 30.6],\n  [1943, 16.3],\n  [1944, 9.6],\n  [1945, 33.2],\n  [1946, 92.6],\n  [1947, 151.6],\n  [1948, 136.3],\n  [1949, 134.7],\n  [1950, 83.9],\n  [1951, 69.4],\n  [1952, 31.5],\n  [1953, 13.9],\n  [1954, 4.4],\n  [1955, 38],\n  [1956, 141.7],\n  [1957, 190.2],\n  [1958, 184.8],\n  [1959, 159],\n  [1960, 112.3],\n  [1961, 53.9],\n  [1962, 37.5],\n  [1963, 27.9],\n  [1964, 10.2],\n  [1965, 15.1],\n  [1966, 47],\n  [1967, 93.8],\n  [1968, 105.9],\n  [1969, 105.5],\n  [1970, 104.5],\n  [1971, 66.6],\n  [1972, 68.9],\n  [1973, 38],\n  [1974, 34.5],\n  [1975, 15.5],\n  [1976, 12.6],\n  [1977, 27.5],\n  [1978, 92.5],\n  [1979, 155.4],\n  [1980, 154.7],\n  [1981, 140.5],\n  [1982, 115.9],\n  [1983, 66.6],\n  [1984, 45.9],\n  [1985, 17.9],\n  [1986, 13.4],\n  [1987, 29.2],\n  [1988, 100.2]\n],\n  {\n    labels: [\"Year\", \"Sunspots\"],\n    showRangeSelector: true\n  }\n);\n// annotate the maximum\ng.ready(() => g.setAnnotations([\n  {\n    series: \"Sunspots\",\n    x: 1957,\n    shortText: \"M\",\n    text: \"Maximum number\"\n  }\n]));</script>\n```\n\n::: {#graph-sunspot style=\"width: 100%;\"}\n:::\n\nShow two time series:\n\n``` {.r}\ndeaths = cbind(Time = round(time(mdeaths), 2), Male = mdeaths, Female = fdeaths)\n```\n\n```` {.js}\nnew Dygraph(\n  document.getElementById('graph-deaths'),\n  [\n  [1974, 2134, 901],\n  [1974.08, 1863, 689],\n  [1974.17, 1877, 827],\n  [1974.25, 1877, 677],\n  [1974.33, 1492, 522],\n  [1974.42, 1249, 406],\n  [1974.5, 1280, 441],\n  [1974.58, 1131, 393],\n  [1974.67, 1209, 387],\n  [1974.75, 1492, 582],\n  [1974.83, 1621, 578],\n  [1974.92, 1846, 666],\n  [1975, 2103, 830],\n  [1975.08, 2137, 752],\n  [1975.17, 2153, 785],\n  [1975.25, 1833, 664],\n  [1975.33, 1403, 467],\n  [1975.42, 1288, 438],\n  [1975.5, 1186, 421],\n  [1975.58, 1133, 412],\n  [1975.67, 1053, 343],\n  [1975.75, 1347, 440],\n  [1975.83, 1545, 531],\n  [1975.92, 2066, 771],\n  [1976, 2020, 767],\n  [1976.08, 2750, 1141],\n  [1976.17, 2283, 896],\n  [1976.25, 1479, 532],\n  [1976.33, 1189, 447],\n  [1976.42, 1160, 420],\n  [1976.5, 1113, 376],\n  [1976.58, 970, 330],\n  [1976.67, 999, 357],\n  [1976.75, 1208, 445],\n  [1976.83, 1467, 546],\n  [1976.92, 2059, 764],\n  [1977, 2240, 862],\n  [1977.08, 1634, 660],\n  [1977.17, 1722, 663],\n  [1977.25, 1801, 643],\n  [1977.33, 1246, 502],\n  [1977.42, 1162, 392],\n  [1977.5, 1087, 411],\n  [1977.58, 1013, 348],\n  [1977.67, 959, 387],\n  [1977.75, 1179, 385],\n  [1977.83, 1229, 411],\n  [1977.92, 1655, 638],\n  [1978, 2019, 796],\n  [1978.08, 2284, 853],\n  [1978.17, 1942, 737],\n  [1978.25, 1423, 546],\n  [1978.33, 1340, 530],\n  [1978.42, 1187, 446],\n  [1978.5, 1098, 431],\n  [1978.58, 1004, 362],\n  [1978.67, 970, 387],\n  [1978.75, 1140, 430],\n  [1978.83, 1110, 425],\n  [1978.92, 1812, 679],\n  [1979, 2263, 821],\n  [1979.08, 1820, 785],\n  [1979.17, 1846, 727],\n  [1979.25, 1531, 612],\n  [1979.33, 1215, 478],\n  [1979.42, 1075, 429],\n  [1979.5, 1056, 405],\n  [1979.58, 975, 379],\n  [1979.67, 940, 393],\n  [1979.75, 1081, 411],\n  [1979.83, 1294, 487],\n  [1979.92, 1341, 574]\n],\n  {\n    labels: [\"Time\", \"Male\", \"Female\"]\n  }\n);\n````\n\n``` {=html}\n<script type=\"module\">new Dygraph(\n  document.getElementById('graph-deaths'),\n  [\n  [1974, 2134, 901],\n  [1974.08, 1863, 689],\n  [1974.17, 1877, 827],\n  [1974.25, 1877, 677],\n  [1974.33, 1492, 522],\n  [1974.42, 1249, 406],\n  [1974.5, 1280, 441],\n  [1974.58, 1131, 393],\n  [1974.67, 1209, 387],\n  [1974.75, 1492, 582],\n  [1974.83, 1621, 578],\n  [1974.92, 1846, 666],\n  [1975, 2103, 830],\n  [1975.08, 2137, 752],\n  [1975.17, 2153, 785],\n  [1975.25, 1833, 664],\n  [1975.33, 1403, 467],\n  [1975.42, 1288, 438],\n  [1975.5, 1186, 421],\n  [1975.58, 1133, 412],\n  [1975.67, 1053, 343],\n  [1975.75, 1347, 440],\n  [1975.83, 1545, 531],\n  [1975.92, 2066, 771],\n  [1976, 2020, 767],\n  [1976.08, 2750, 1141],\n  [1976.17, 2283, 896],\n  [1976.25, 1479, 532],\n  [1976.33, 1189, 447],\n  [1976.42, 1160, 420],\n  [1976.5, 1113, 376],\n  [1976.58, 970, 330],\n  [1976.67, 999, 357],\n  [1976.75, 1208, 445],\n  [1976.83, 1467, 546],\n  [1976.92, 2059, 764],\n  [1977, 2240, 862],\n  [1977.08, 1634, 660],\n  [1977.17, 1722, 663],\n  [1977.25, 1801, 643],\n  [1977.33, 1246, 502],\n  [1977.42, 1162, 392],\n  [1977.5, 1087, 411],\n  [1977.58, 1013, 348],\n  [1977.67, 959, 387],\n  [1977.75, 1179, 385],\n  [1977.83, 1229, 411],\n  [1977.92, 1655, 638],\n  [1978, 2019, 796],\n  [1978.08, 2284, 853],\n  [1978.17, 1942, 737],\n  [1978.25, 1423, 546],\n  [1978.33, 1340, 530],\n  [1978.42, 1187, 446],\n  [1978.5, 1098, 431],\n  [1978.58, 1004, 362],\n  [1978.67, 970, 387],\n  [1978.75, 1140, 430],\n  [1978.83, 1110, 425],\n  [1978.92, 1812, 679],\n  [1979, 2263, 821],\n  [1979.08, 1820, 785],\n  [1979.17, 1846, 727],\n  [1979.25, 1531, 612],\n  [1979.33, 1215, 478],\n  [1979.42, 1075, 429],\n  [1979.5, 1056, 405],\n  [1979.58, 975, 379],\n  [1979.67, 940, 393],\n  [1979.75, 1081, 411],\n  [1979.83, 1294, 487],\n  [1979.92, 1341, 574]\n],\n  {\n    labels: [\"Time\", \"Male\", \"Female\"]\n  }\n);</script>\n```\n\n::: {#graph-deaths style=\"width: 100%;\"}\n:::\n"
  },
  {
    "path": "examples/023-leaflet.Rmd",
    "content": "---\ntitle: \"Leaflet\"\n---\n\nAdd the CSS/JS assets from the [leaflet](https://leafletjs.com) library:\n\n```{r}\nlitedown::vest(css = '@npm/leaflet/dist/leaflet', js = '@npm/leaflet')\n```\n\n```{r}\nloc = c(41.2542491, -95.9728748)\ntheta = (1:12)/6 * pi\ncircles = data.frame(Lat = loc[1] + sin(theta)/1000, Lng = loc[2] + cos(theta)/1000)\n```\n\nProvide a fenced Div with the ID `unmc` as the map container, and create the map:\n\n::: {#unmc style=\"height: 500px;\"}\n:::\n\n```{js, type = 'module', fill = xfun::tojson}\nconst map = L.map('unmc').setView(`{ loc }`, 17);\n\n// add a tile layer\nL.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {\n    maxZoom: 21,\n    attribution: '&copy; <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>'\n}).addTo(map);\n\n// add a marker and bind a popup to it\nconst marker = L.marker(`{ loc }`).addTo(map);\nmarker.bindPopup(\"<b>UNMC</b><p>You can play badminton here.</p>\");\n\n// draw a circle of circles (lat/long from the data frame `circles`)\n`{ unname(circles) }`.forEach(row => L.circle(row, {\n  color: 'orangered', radius: 30,\n  fillColor: 'lightskyblue', fillOpacity: 0.5\n}).addTo(map));\n```\n"
  },
  {
    "path": "examples/023-leaflet.md",
    "content": "---\ntitle: \"Leaflet\"\n---\n\nAdd the CSS/JS assets from the [leaflet](https://leafletjs.com) library:\n\n``` {.r}\nlitedown::vest(css = '@npm/leaflet/dist/leaflet', js = '@npm/leaflet')\n```\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/leaflet/dist/leaflet.min.css\">\n<script src=\"https://cdn.jsdelivr.net/npm/leaflet\" defer></script>\n\n``` {.r}\nloc = c(41.2542491, -95.9728748)\ntheta = (1:12)/6 * pi\ncircles = data.frame(Lat = loc[1] + sin(theta)/1000, Lng = loc[2] + cos(theta)/1000)\n```\n\nProvide a fenced Div with the ID `unmc` as the map container, and create the map:\n\n::: {#unmc style=\"height: 500px;\"}\n:::\n\n```` {.js}\nconst map = L.map('unmc').setView([41.2542491, -95.9728748], 17);\n\n// add a tile layer\nL.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {\n    maxZoom: 21,\n    attribution: '&copy; <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>'\n}).addTo(map);\n\n// add a marker and bind a popup to it\nconst marker = L.marker([41.2542491, -95.9728748]).addTo(map);\nmarker.bindPopup(\"<b>UNMC</b><p>You can play badminton here.</p>\");\n\n// draw a circle of circles (lat/long from the data frame `circles`)\n[\n  [41.2547491, -95.9720087745962],\n  [41.2551151254038, -95.9723748],\n  [41.2552491, -95.9728748],\n  [41.2551151254038, -95.9733748],\n  [41.2547491, -95.9737408254038],\n  [41.2542491, -95.9738748],\n  [41.2537491, -95.9737408254038],\n  [41.2533830745962, -95.9733748],\n  [41.2532491, -95.9728748],\n  [41.2533830745962, -95.9723748],\n  [41.2537491, -95.9720087745962],\n  [41.2542491, -95.9718748]\n].forEach(row => L.circle(row, {\n  color: 'orangered', radius: 30,\n  fillColor: 'lightskyblue', fillOpacity: 0.5\n}).addTo(map));\n````\n\n``` {=html}\n<script type=\"module\">const map = L.map('unmc').setView([41.2542491, -95.9728748], 17);\n\n// add a tile layer\nL.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {\n    maxZoom: 21,\n    attribution: '&copy; <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>'\n}).addTo(map);\n\n// add a marker and bind a popup to it\nconst marker = L.marker([41.2542491, -95.9728748]).addTo(map);\nmarker.bindPopup(\"<b>UNMC</b><p>You can play badminton here.</p>\");\n\n// draw a circle of circles (lat/long from the data frame `circles`)\n[\n  [41.2547491, -95.9720087745962],\n  [41.2551151254038, -95.9723748],\n  [41.2552491, -95.9728748],\n  [41.2551151254038, -95.9733748],\n  [41.2547491, -95.9737408254038],\n  [41.2542491, -95.9738748],\n  [41.2537491, -95.9737408254038],\n  [41.2533830745962, -95.9733748],\n  [41.2532491, -95.9728748],\n  [41.2533830745962, -95.9723748],\n  [41.2537491, -95.9720087745962],\n  [41.2542491, -95.9718748]\n].forEach(row => L.circle(row, {\n  color: 'orangered', radius: 30,\n  fillColor: 'lightskyblue', fillOpacity: 0.5\n}).addTo(map));</script>\n```\n"
  },
  {
    "path": "examples/024-chart-js.Rmd",
    "content": "---\ntitle: \"Chart.js\"\n---\n\nImport the [chart.js](https://www.chartjs.org) library:\n\n```{r}\nlitedown::vest(js = '@npm/chart.js')\n```\n\nDraw the chart with the `uspop` dataset:\n\n```{js, type = 'module', fill = xfun::tojson}\nconst ctx = document.getElementById('uspop-polar');\nnew Chart(ctx, {\n  type: 'polarArea',\n  data: {\n    labels: `{ time(uspop) }`,\n    datasets: [{\n      data: `{ uspop }`\n    }]\n  },\n  options: {\n    responsive: true,\n    scales: {\n      r: {\n        pointLabels: {\n          display: true,\n          centerPointLabels: true\n        }\n      }\n    },\n    plugins: {\n      legend: {\n        display: false\n      }\n    }\n  }\n});\n```\n\n<canvas id=\"uspop-polar\"></canvas>\n"
  },
  {
    "path": "examples/024-chart-js.md",
    "content": "---\ntitle: \"Chart.js\"\n---\n\nImport the [chart.js](https://www.chartjs.org) library:\n\n``` {.r}\nlitedown::vest(js = '@npm/chart.js')\n```\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\" defer></script>\n\nDraw the chart with the `uspop` dataset:\n\n```` {.js}\nconst ctx = document.getElementById('uspop-polar');\nnew Chart(ctx, {\n  type: 'polarArea',\n  data: {\n    labels: [1790, 1800, 1810, 1820, 1830, 1840, 1850, 1860, 1870, 1880, 1890, 1900, 1910, 1920, 1930, 1940, 1950, 1960, 1970],\n    datasets: [{\n      data: [3.93, 5.31, 7.24, 9.64, 12.9, 17.1, 23.2, 31.4, 39.8, 50.2, 62.9, 76, 92, 105.7, 122.8, 131.7, 151.3, 179.3, 203.2]\n    }]\n  },\n  options: {\n    responsive: true,\n    scales: {\n      r: {\n        pointLabels: {\n          display: true,\n          centerPointLabels: true\n        }\n      }\n    },\n    plugins: {\n      legend: {\n        display: false\n      }\n    }\n  }\n});\n````\n\n``` {=html}\n<script type=\"module\">const ctx = document.getElementById('uspop-polar');\nnew Chart(ctx, {\n  type: 'polarArea',\n  data: {\n    labels: [1790, 1800, 1810, 1820, 1830, 1840, 1850, 1860, 1870, 1880, 1890, 1900, 1910, 1920, 1930, 1940, 1950, 1960, 1970],\n    datasets: [{\n      data: [3.93, 5.31, 7.24, 9.64, 12.9, 17.1, 23.2, 31.4, 39.8, 50.2, 62.9, 76, 92, 105.7, 122.8, 131.7, 151.3, 179.3, 203.2]\n    }]\n  },\n  options: {\n    responsive: true,\n    scales: {\n      r: {\n        pointLabels: {\n          display: true,\n          centerPointLabels: true\n        }\n      }\n    },\n    plugins: {\n      legend: {\n        display: false\n      }\n    }\n  }\n});</script>\n```\n\n<canvas id=\"uspop-polar\"></canvas>\n"
  },
  {
    "path": "examples/025-option-filter.Rmd",
    "content": "---\ntitle: Filter results\n---\n\nInterleave plots with text output:\n\n```{r, results = 'asis', filter = 'interleave'}\nfor(i in 1:3) {\n  cat('\\n## ', LETTERS[i], '\\n\\n')\n  plot(0, 0, pch = LETTERS[i])\n}\n```\n"
  },
  {
    "path": "examples/025-option-filter.md",
    "content": "---\ntitle: Filter results\n---\n\nInterleave plots with text output:\n\n``` {.r}\nfor(i in 1:3) {\n  cat('\\n## ', LETTERS[i], '\\n\\n')\n  plot(0, 0, pch = LETTERS[i])\n}\n```\n\n##  A \n\n![](<025-option-filter__files/chunk-1-1.png>)\n\n##  B \n\n![](<025-option-filter__files/chunk-1-2.png>)\n\n##  C \n\n![](<025-option-filter__files/chunk-1-3.png>)\n"
  },
  {
    "path": "examples/_run.R",
    "content": "# rebuild all examples on CI and check if their output changes\nci = tolower(Sys.getenv('CI')) == 'true'\n\nfor (f in litedown:::find_input('.')) {\n  # re-render locally only if output file is newer\n  if (!ci) {\n    m = file.mtime(c(f, f2 <- xfun::with_ext(f, '.md')))\n    if (!is.na(m[2]) && m[1] < m[2]) next\n  }\n  # example 005 uses |>, which is available only in R >= 4.1.0\n  if (grepl('^005-', f) && getRversion() < '4.1.0') next\n  xfun::Rscript_call(function(.) {\n    options(width = 80, litedown.jsd_resolve = FALSE)\n    if (xfun::file_ext(.) == 'md') litedown::mark(.) else litedown::fuse(., '.md')\n    if (grepl('^014-.+[.]Rmd$', .)) litedown::fiss(.)\n  }, list(f))\n}\n"
  },
  {
    "path": "examples/test-collapse.Rmd",
    "content": "`collapse = TRUE` will collapse messages into source blocks, too.\n\n```{r, test-a, collapse = TRUE, error = TRUE, strip.white = FALSE}\nmessage(\"this is a message\")\n\nwarning(\"this is a warning\")\n\nstop(\"this is an error\")\n```\n\nNot collapsing or striping blank lines:\n\n```{r, test-a, error = TRUE}\n```\n\nCollapsing without striping blank lines:\n\n```{r, test-a, collapse = TRUE, error = TRUE}\n```\n"
  },
  {
    "path": "examples/test-collapse.md",
    "content": "`collapse = TRUE` will collapse messages into source blocks, too.\n\n``` {.r}\nmessage(\"this is a message\")\n#> this is a message\n\nwarning(\"this is a warning\")\n#> this is a warning\n\nstop(\"this is an error\")\n#> Error: this is an error\n```\n\nNot collapsing or striping blank lines:\n\n``` {.r}\nmessage(\"this is a message\")\n```\n\n``` {.plain .message}\n#> this is a message\n```\n\n``` {.r}\nwarning(\"this is a warning\")\n```\n\n``` {.plain .warning}\n#> this is a warning\n```\n\n``` {.r}\nstop(\"this is an error\")\n```\n\n``` {.plain .error}\n#> Error: this is an error\n```\n\nCollapsing without striping blank lines:\n\n``` {.r}\nmessage(\"this is a message\")\n#> this is a message\nwarning(\"this is a warning\")\n#> this is a warning\nstop(\"this is an error\")\n#> Error: this is an error\n```\n"
  },
  {
    "path": "examples/test-inline.Rmd",
    "content": "---\ntitle: \"Test inline expressions\"\n---\n\nA normal inline expression: `{r} pi`.\n\nThe knitr-style inline expressions are ignored by default: `r pi`.\n\nAn expression that spans multiple lines: `{r} pi +\n1 -\n1`.\n\nWhen a line has leading spaces that are not meaningful,\n it should still work: `{r} pi`.\n\n<!-- doesn't work when the next line has leading spaces, e.g., `{r} pi +\n 1` -->\n\n- An item.\n\n  `{r} pi` (leading spaces are meaningful here)\n  \n  hi `{r} pi > 1 & FALSE`\n   hi `{r} pi`\n  \n  <!-- doesn't work: hi `{r} pi +\n  1` -->\n\n      hi `{r} pi` (four spaces: this is a plain code block)\n"
  },
  {
    "path": "examples/test-inline.md",
    "content": "---\ntitle: \"Test inline expressions\"\n---\n\nA normal inline expression: 3.14.\n\nThe knitr-style inline expressions are ignored by default: `r pi`.\n\nAn expression that spans multiple lines: 3.14.\n\nWhen a line has leading spaces that are not meaningful,\n it should still work: 3.14.\n\n<!-- doesn't work when the next line has leading spaces, e.g., `{r} pi +\n 1` -->\n\n- An item.\n\n  3.14 (leading spaces are meaningful here)\n  \n  hi FALSE\n   hi 3.14\n  \n  <!-- doesn't work: hi `{r} pi +\n  1` -->\n\n      hi `{r} pi` (four spaces: this is a plain code block)\n"
  },
  {
    "path": "examples/test-options.Rmd",
    "content": "## Test Markdown options\n\n```{r}\nlibrary(litedown)\n```\n\n```{r, test-a}\n# toc example\nmkd <- c('# Header 1', 'p1', '## Header 2', 'p2')\n\nmark(mkd, options = '+number_sections')\nmark(mkd, options = '+number_sections+toc')\n\n# hard_wrap example\nmark('foo\\nbar\\n')\nmark('foo\\nbar\\n', options = '+hardbreaks')\n\n# latex math example\nmkd <- c(\n  '`$x$` is inline math $x$!', '', 'Display style:', '', '$$x + y$$', '',\n  '\\\\begin{align}\na^{2}+b^{2} & = c^{2}\\\\\\\\\n\\\\sin^{2}(x)+\\\\cos^{2}(x) & = 1\n\\\\end{align}'\n)\n\nmark(mkd)\nmark(mkd, options = '-latex_math')\n\n# table example\nmark('\nFirst Header  | Second Header\n------------- | -------------\nContent Cell  | Content Cell\nContent Cell  | Content Cell\n')\n\n# caption\nmark('\n| a | b |\n|---|--:|\n| A | 9 |\n\nTable: A table _caption_.\n')\n\n# no table\nmark('\nFirst Header  | Second Header\n------------- | -------------\nContent Cell  | Content Cell\nContent Cell  | Content Cell\n', options = '-table')\n\n# autolink example\nmark('https://www.r-project.org/')\nmark('https://www.r-project.org/', options = '-autolink')\n\n# links and spans\nmark('[a b](#){.red}')\nmark('[a\\nb](){.red}')\n\n# strikethrough example\nmark('~~awesome~~')\nmark('~~awesome~~', options = '-strikethrough')\n\n# superscript and subscript examples\nmark('2^10^')\nmark('2^10^', options = '-superscript')\nmark('H~2~O')\nmark('H~2~O', options = '-subscript')\n\n# code blocks\nmark('```r\\n1 + 1;\\n```')\nmark('```{.r}\\n1 + 1;\\n```')\nmark('```{.r .js}\\n1 + 1;\\n```')\nmark('```{.r .js #foo}\\n1 + 1;\\n```')\nmark('```{.r .js #foo style=\"background:lime;\"}\\n1 + 1;\\n```')\nmark('````\\nA _code chunk_:\\n\\n```{r, echo=TRUE}\\n1 + 1;\\n```\\n````')\n\n# raw blocks\nmark('```{=html}\\n<p>raw HTML</p>\\n```')\nmark('```{=latex}\\n\\\\textbf{raw LaTeX}\\n```')\n\n# fenced Divs\nmark('::: foo\\nasdf\\n:::')\nmark('::: {.foo .bar #baz style=\"color: red;\"}\\nasdf\\n:::')\n\n# footnotes\nmark('Hello[^foo] world!\\n\\n[^foo]: A _footnote_.')\nmark('Hello[^2] world!\\n\\n[^2]: A footnote with _multiple_ elements.\\n\\n    - list')\n\n# smartypants example\nmark('1/2 (c)')\nmark('1/2 (c)', options = '+smartypants')\n\nmkd <- paste(names(litedown:::pants), collapse = ' ')\nmark(mkd, options = '+smartypants')\n\n# filter HTML tags\nmkd = '<style>a {}</style><script type=\"text/javascript\">console.log(\"No!\");</script>\\n[Hello](#)'\nmark(mkd)\nmark(mkd, options = 'tagfilter')\n```\n\n## HTML output of above examples\n\n```{r, test-b, results = 'asis'}\n`<test-a>`\n```\n\n## LaTeX output of above examples\n\n```{r, test-c}\n# temporarily override mark()\nmark = function(...) litedown::mark(..., output = 'latex')\n`<test-a>`\nrm(mark)\n```\n"
  },
  {
    "path": "examples/test-options.md",
    "content": "## Test Markdown options\n\n``` {.r}\nlibrary(litedown)\n```\n\n````` {.r}\n# toc example\nmkd <- c('# Header 1', 'p1', '## Header 2', 'p2')\n\nmark(mkd, options = '+number_sections')\n`````\n\n````` {.html .plain}\n<h1 id=\"chp:header-1\"><span class=\"section-number main-number\">1</span> Header 1</h1>\n<p>p1</p>\n<h2 id=\"sec:header-2\"><span class=\"section-number\">1.1</span> Header 2</h2>\n<p>p2</p>\n`````\n\n````` {.r}\nmark(mkd, options = '+number_sections+toc')\n`````\n\n````` {.html .plain}\n<div id=\"TOC\">\n<ul class=\"numbered\">\n<li><a href=\"#chp:header-1\"><span class=\"section-number main-number\">1</span> Header 1</a>\n<ul>\n<li><a href=\"#sec:header-2\"><span class=\"section-number\">1.1</span> Header 2</a></li>\n</ul>\n</li>\n</ul>\n</div>\n<h1 id=\"chp:header-1\"><span class=\"section-number main-number\">1</span> Header 1</h1>\n<p>p1</p>\n<h2 id=\"sec:header-2\"><span class=\"section-number\">1.1</span> Header 2</h2>\n<p>p2</p>\n`````\n\n````` {.r}\n# hard_wrap example\nmark('foo\\nbar\\n')\n`````\n\n````` {.html .plain}\n<p>foo\nbar</p>\n`````\n\n````` {.r}\nmark('foo\\nbar\\n', options = '+hardbreaks')\n`````\n\n````` {.html .plain}\n<p>foo<br />\nbar</p>\n`````\n\n````` {.r}\n# latex math example\nmkd <- c(\n  '`$x$` is inline math $x$!', '', 'Display style:', '', '$$x + y$$', '',\n  '\\\\begin{align}\na^{2}+b^{2} & = c^{2}\\\\\\\\\n\\\\sin^{2}(x)+\\\\cos^{2}(x) & = 1\n\\\\end{align}'\n)\n\nmark(mkd)\n`````\n\n````` {.html .plain}\n<p><code>$x$</code> is inline math \\(x\\)!</p>\n<p>Display style:</p>\n<p>$$x + y$$</p>\n<p>\\begin{align}\na^{2}+b^{2} &amp; = c^{2}\\\\\n\\sin^{2}(x)+\\cos^{2}(x) &amp; = 1\n\\end{align}</p>\n`````\n\n````` {.r}\nmark(mkd, options = '-latex_math')\n`````\n\n````` {.html .plain}\n<p><code>$x$</code> is inline math $x$!</p>\n<p>Display style:</p>\n<p>$$x + y$$</p>\n<p>\\begin{align}\na^{2}+b^{2} &amp; = c^{2}\\\n\\sin^{2}(x)+\\cos^{2}(x) &amp; = 1\n\\end{align}</p>\n`````\n\n````` {.r}\n# table example\nmark('\nFirst Header  | Second Header\n------------- | -------------\nContent Cell  | Content Cell\nContent Cell  | Content Cell\n')\n`````\n\n````` {.html .plain}\n<table>\n<thead>\n<tr>\n<th>First Header</th>\n<th>Second Header</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Content Cell</td>\n<td>Content Cell</td>\n</tr>\n<tr>\n<td>Content Cell</td>\n<td>Content Cell</td>\n</tr>\n</tbody>\n</table>\n`````\n\n````` {.r}\n# caption\nmark('\n| a | b |\n|---|--:|\n| A | 9 |\n\nTable: A table _caption_.\n')\n`````\n\n````` {.html .plain}\n<table>\n<caption>A table <em>caption</em>.</caption>\n<thead>\n<tr>\n<th>a</th>\n<th align=\"right\">b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>A</td>\n<td align=\"right\">9</td>\n</tr>\n</tbody>\n</table>\n`````\n\n````` {.r}\n# no table\nmark('\nFirst Header  | Second Header\n------------- | -------------\nContent Cell  | Content Cell\nContent Cell  | Content Cell\n', options = '-table')\n`````\n\n````` {.html .plain}\n<p>First Header  | Second Header\n———–– | ———––\nContent Cell  | Content Cell\nContent Cell  | Content Cell</p>\n`````\n\n````` {.r}\n# autolink example\nmark('https://www.r-project.org/')\n`````\n\n````` {.html .plain}\n<p><a href=\"https://www.r-project.org/\">https://www.r-project.org/</a></p>\n`````\n\n````` {.r}\nmark('https://www.r-project.org/', options = '-autolink')\n`````\n\n````` {.html .plain}\n<p>https://www.r-project.org/</p>\n`````\n\n````` {.r}\n# links and spans\nmark('[a b](#){.red}')\n`````\n\n````` {.html .plain}\n<p><a href=\"#\" class=\"red\">a b</a></p>\n`````\n\n````` {.r}\nmark('[a\\nb](){.red}')\n`````\n\n````` {.html .plain}\n<p><span class=\"red\">a\nb</span></p>\n`````\n\n````` {.r}\n# strikethrough example\nmark('~~awesome~~')\n`````\n\n````` {.html .plain}\n<p><del>awesome</del></p>\n`````\n\n````` {.r}\nmark('~~awesome~~', options = '-strikethrough')\n`````\n\n````` {.html .plain}\n<p>~~awesome~~</p>\n`````\n\n````` {.r}\n# superscript and subscript examples\nmark('2^10^')\n`````\n\n````` {.html .plain}\n<p>2<sup>10</sup></p>\n`````\n\n````` {.r}\nmark('2^10^', options = '-superscript')\n`````\n\n````` {.html .plain}\n<p>2^10^</p>\n`````\n\n````` {.r}\nmark('H~2~O')\n`````\n\n````` {.html .plain}\n<p>H<sub>2</sub>O</p>\n`````\n\n````` {.r}\nmark('H~2~O', options = '-subscript')\n`````\n\n````` {.html .plain}\n<p>H~2~O</p>\n`````\n\n````` {.r}\n# code blocks\nmark('```r\\n1 + 1;\\n```')\n`````\n\n````` {.html .plain}\n<pre><code class=\"language-r\">1 + 1;\n</code></pre>\n`````\n\n````` {.r}\nmark('```{.r}\\n1 + 1;\\n```')\n`````\n\n````` {.html .plain}\n<pre><code class=\"language-r\">1 + 1;\n</code></pre>\n`````\n\n````` {.r}\nmark('```{.r .js}\\n1 + 1;\\n```')\n`````\n\n````` {.html .plain}\n<pre><code class=\"language-r js\">1 + 1;\n</code></pre>\n`````\n\n````` {.r}\nmark('```{.r .js #foo}\\n1 + 1;\\n```')\n`````\n\n````` {.html .plain}\n<pre><code class=\"language-r js\" id=\"foo\">1 + 1;\n</code></pre>\n`````\n\n````` {.r}\nmark('```{.r .js #foo style=\"background:lime;\"}\\n1 + 1;\\n```')\n`````\n\n````` {.html .plain}\n<pre><code class=\"language-r js\" id=\"foo\" style=\"background:lime;\">1 + 1;\n</code></pre>\n`````\n\n````` {.r}\nmark('````\\nA _code chunk_:\\n\\n```{r, echo=TRUE}\\n1 + 1;\\n```\\n````')\n`````\n\n````` {.html .plain}\n<pre><code>A _code chunk_:\n\n```{r, echo=TRUE}\n1 + 1;\n```\n</code></pre>\n`````\n\n````` {.r}\n# raw blocks\nmark('```{=html}\\n<p>raw HTML</p>\\n```')\n`````\n\n````` {.html .plain}\n<p>raw HTML</p>\n`````\n\n````` {.r}\nmark('```{=latex}\\n\\\\textbf{raw LaTeX}\\n```')\n`````\n\n````` {.html .plain}\n\n`````\n\n````` {.r}\n# fenced Divs\nmark('::: foo\\nasdf\\n:::')\n`````\n\n````` {.html .plain}\n<div class=\"foo\">\n<p>asdf</p>\n</div>\n`````\n\n````` {.r}\nmark('::: {.foo .bar #baz style=\"color: red;\"}\\nasdf\\n:::')\n`````\n\n````` {.html .plain}\n<div id=\"baz\" class=\"foo bar\" style=\"color: red;\">\n<p>asdf</p>\n</div>\n`````\n\n````` {.r}\n# footnotes\nmark('Hello[^foo] world!\\n\\n[^foo]: A _footnote_.')\n`````\n\n````` {.html .plain}\n<p>Hello<sup class=\"footnote-ref\"><a href=\"#fn-foo\" id=\"fnref-foo\" data-footnote-ref>1</a></sup> world!</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-foo\">\n<p>A <em>footnote</em>. <a href=\"#fnref-foo\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n</ol>\n</section>\n`````\n\n````` {.r}\nmark('Hello[^2] world!\\n\\n[^2]: A footnote with _multiple_ elements.\\n\\n    - list')\n`````\n\n````` {.html .plain}\n<p>Hello<sup class=\"footnote-ref\"><a href=\"#fn-2\" id=\"fnref-2\" data-footnote-ref>1</a></sup> world!</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-2\">\n<p>A footnote with <em>multiple</em> elements.</p>\n<ul>\n<li>list</li>\n</ul>\n<a href=\"#fnref-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a>\n</li>\n</ol>\n</section>\n`````\n\n````` {.r}\n# smartypants example\nmark('1/2 (c)')\n`````\n\n````` {.html .plain}\n<p>1/2 (c)</p>\n`````\n\n````` {.r}\nmark('1/2 (c)', options = '+smartypants')\n`````\n\n````` {.html .plain}\n<p>½ ©</p>\n`````\n\n````` {.r}\nmkd <- paste(names(litedown:::pants), collapse = ' ')\nmark(mkd, options = '+smartypants')\n`````\n\n````` {.html .plain}\n<p>½ ⅓ ⅔ ¼ ¾ ⅕ ⅖ ⅗ ⅘ ⅙ ⅚ ⅛ ⅜ ⅝ ⅞ ⅐ ⅑ ⅒ © ® ™</p>\n`````\n\n````` {.r}\n# filter HTML tags\nmkd = '<style>a {}</style><script type=\"text/javascript\">console.log(\"No!\");</script>\\n[Hello](#)'\nmark(mkd)\n`````\n\n````` {.html .plain}\n<style>a {}</style><script type=\"text/javascript\">console.log(\"No!\");</script>\n<p><a href=\"#\">Hello</a></p>\n`````\n\n````` {.r}\nmark(mkd, options = 'tagfilter')\n`````\n\n````` {.html .plain}\n&lt;style>a {}&lt;/style>&lt;script type=\"text/javascript\">console.log(\"No!\");&lt;/script>\n<p><a href=\"#\">Hello</a></p>\n`````\n\n## HTML output of above examples\n\n````` {.r}\n# toc example\nmkd <- c('# Header 1', 'p1', '## Header 2', 'p2')\n\nmark(mkd, options = '+number_sections')\n`````\n<h1 id=\"chp:header-1\"><span class=\"section-number main-number\">1</span> Header 1</h1>\n<p>p1</p>\n<h2 id=\"sec:header-2\"><span class=\"section-number\">1.1</span> Header 2</h2>\n<p>p2</p>\n\n````` {.r}\nmark(mkd, options = '+number_sections+toc')\n`````\n<div id=\"TOC\">\n<ul class=\"numbered\">\n<li><a href=\"#chp:header-1\"><span class=\"section-number main-number\">1</span> Header 1</a>\n<ul>\n<li><a href=\"#sec:header-2\"><span class=\"section-number\">1.1</span> Header 2</a></li>\n</ul>\n</li>\n</ul>\n</div>\n<h1 id=\"chp:header-1\"><span class=\"section-number main-number\">1</span> Header 1</h1>\n<p>p1</p>\n<h2 id=\"sec:header-2\"><span class=\"section-number\">1.1</span> Header 2</h2>\n<p>p2</p>\n\n````` {.r}\n# hard_wrap example\nmark('foo\\nbar\\n')\n`````\n<p>foo\nbar</p>\n\n````` {.r}\nmark('foo\\nbar\\n', options = '+hardbreaks')\n`````\n<p>foo<br />\nbar</p>\n\n````` {.r}\n# latex math example\nmkd <- c(\n  '`$x$` is inline math $x$!', '', 'Display style:', '', '$$x + y$$', '',\n  '\\\\begin{align}\na^{2}+b^{2} & = c^{2}\\\\\\\\\n\\\\sin^{2}(x)+\\\\cos^{2}(x) & = 1\n\\\\end{align}'\n)\n\nmark(mkd)\n`````\n<p><code>$x$</code> is inline math \\(x\\)!</p>\n<p>Display style:</p>\n<p>$$x + y$$</p>\n<p>\\begin{align}\na^{2}+b^{2} &amp; = c^{2}\\\\\n\\sin^{2}(x)+\\cos^{2}(x) &amp; = 1\n\\end{align}</p>\n\n````` {.r}\nmark(mkd, options = '-latex_math')\n`````\n<p><code>$x$</code> is inline math $x$!</p>\n<p>Display style:</p>\n<p>$$x + y$$</p>\n<p>\\begin{align}\na^{2}+b^{2} &amp; = c^{2}\\\n\\sin^{2}(x)+\\cos^{2}(x) &amp; = 1\n\\end{align}</p>\n\n````` {.r}\n# table example\nmark('\nFirst Header  | Second Header\n------------- | -------------\nContent Cell  | Content Cell\nContent Cell  | Content Cell\n')\n`````\n<table>\n<thead>\n<tr>\n<th>First Header</th>\n<th>Second Header</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Content Cell</td>\n<td>Content Cell</td>\n</tr>\n<tr>\n<td>Content Cell</td>\n<td>Content Cell</td>\n</tr>\n</tbody>\n</table>\n\n````` {.r}\n# caption\nmark('\n| a | b |\n|---|--:|\n| A | 9 |\n\nTable: A table _caption_.\n')\n`````\n<table>\n<caption>A table <em>caption</em>.</caption>\n<thead>\n<tr>\n<th>a</th>\n<th align=\"right\">b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>A</td>\n<td align=\"right\">9</td>\n</tr>\n</tbody>\n</table>\n\n````` {.r}\n# no table\nmark('\nFirst Header  | Second Header\n------------- | -------------\nContent Cell  | Content Cell\nContent Cell  | Content Cell\n', options = '-table')\n`````\n<p>First Header  | Second Header\n———–– | ———––\nContent Cell  | Content Cell\nContent Cell  | Content Cell</p>\n\n````` {.r}\n# autolink example\nmark('https://www.r-project.org/')\n`````\n<p><a href=\"https://www.r-project.org/\">https://www.r-project.org/</a></p>\n\n````` {.r}\nmark('https://www.r-project.org/', options = '-autolink')\n`````\n<p>https://www.r-project.org/</p>\n\n````` {.r}\n# links and spans\nmark('[a b](#){.red}')\n`````\n<p><a href=\"#\" class=\"red\">a b</a></p>\n\n````` {.r}\nmark('[a\\nb](){.red}')\n`````\n<p><span class=\"red\">a\nb</span></p>\n\n````` {.r}\n# strikethrough example\nmark('~~awesome~~')\n`````\n<p><del>awesome</del></p>\n\n````` {.r}\nmark('~~awesome~~', options = '-strikethrough')\n`````\n<p>~~awesome~~</p>\n\n````` {.r}\n# superscript and subscript examples\nmark('2^10^')\n`````\n<p>2<sup>10</sup></p>\n\n````` {.r}\nmark('2^10^', options = '-superscript')\n`````\n<p>2^10^</p>\n\n````` {.r}\nmark('H~2~O')\n`````\n<p>H<sub>2</sub>O</p>\n\n````` {.r}\nmark('H~2~O', options = '-subscript')\n`````\n<p>H~2~O</p>\n\n````` {.r}\n# code blocks\nmark('```r\\n1 + 1;\\n```')\n`````\n<pre><code class=\"language-r\">1 + 1;\n</code></pre>\n\n````` {.r}\nmark('```{.r}\\n1 + 1;\\n```')\n`````\n<pre><code class=\"language-r\">1 + 1;\n</code></pre>\n\n````` {.r}\nmark('```{.r .js}\\n1 + 1;\\n```')\n`````\n<pre><code class=\"language-r js\">1 + 1;\n</code></pre>\n\n````` {.r}\nmark('```{.r .js #foo}\\n1 + 1;\\n```')\n`````\n<pre><code class=\"language-r js\" id=\"foo\">1 + 1;\n</code></pre>\n\n````` {.r}\nmark('```{.r .js #foo style=\"background:lime;\"}\\n1 + 1;\\n```')\n`````\n<pre><code class=\"language-r js\" id=\"foo\" style=\"background:lime;\">1 + 1;\n</code></pre>\n\n````` {.r}\nmark('````\\nA _code chunk_:\\n\\n```{r, echo=TRUE}\\n1 + 1;\\n```\\n````')\n`````\n<pre><code>A _code chunk_:\n\n```{r, echo=TRUE}\n1 + 1;\n```\n</code></pre>\n\n````` {.r}\n# raw blocks\nmark('```{=html}\\n<p>raw HTML</p>\\n```')\n`````\n<p>raw HTML</p>\n\n````` {.r}\nmark('```{=latex}\\n\\\\textbf{raw LaTeX}\\n```')\n`````\n\n\n````` {.r}\n# fenced Divs\nmark('::: foo\\nasdf\\n:::')\n`````\n<div class=\"foo\">\n<p>asdf</p>\n</div>\n\n````` {.r}\nmark('::: {.foo .bar #baz style=\"color: red;\"}\\nasdf\\n:::')\n`````\n<div id=\"baz\" class=\"foo bar\" style=\"color: red;\">\n<p>asdf</p>\n</div>\n\n````` {.r}\n# footnotes\nmark('Hello[^foo] world!\\n\\n[^foo]: A _footnote_.')\n`````\n<p>Hello<sup class=\"footnote-ref\"><a href=\"#fn-foo\" id=\"fnref-foo\" data-footnote-ref>1</a></sup> world!</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-foo\">\n<p>A <em>footnote</em>. <a href=\"#fnref-foo\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n</ol>\n</section>\n\n````` {.r}\nmark('Hello[^2] world!\\n\\n[^2]: A footnote with _multiple_ elements.\\n\\n    - list')\n`````\n<p>Hello<sup class=\"footnote-ref\"><a href=\"#fn-2\" id=\"fnref-2\" data-footnote-ref>1</a></sup> world!</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-2\">\n<p>A footnote with <em>multiple</em> elements.</p>\n<ul>\n<li>list</li>\n</ul>\n<a href=\"#fnref-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a>\n</li>\n</ol>\n</section>\n\n````` {.r}\n# smartypants example\nmark('1/2 (c)')\n`````\n<p>1/2 (c)</p>\n\n````` {.r}\nmark('1/2 (c)', options = '+smartypants')\n`````\n<p>½ ©</p>\n\n````` {.r}\nmkd <- paste(names(litedown:::pants), collapse = ' ')\nmark(mkd, options = '+smartypants')\n`````\n<p>½ ⅓ ⅔ ¼ ¾ ⅕ ⅖ ⅗ ⅘ ⅙ ⅚ ⅛ ⅜ ⅝ ⅞ ⅐ ⅑ ⅒ © ® ™</p>\n\n````` {.r}\n# filter HTML tags\nmkd = '<style>a {}</style><script type=\"text/javascript\">console.log(\"No!\");</script>\\n[Hello](#)'\nmark(mkd)\n`````\n<style>a {}</style><script type=\"text/javascript\">console.log(\"No!\");</script>\n<p><a href=\"#\">Hello</a></p>\n\n````` {.r}\nmark(mkd, options = 'tagfilter')\n`````\n&lt;style>a {}&lt;/style>&lt;script type=\"text/javascript\">console.log(\"No!\");&lt;/script>\n<p><a href=\"#\">Hello</a></p>\n\n## LaTeX output of above examples\n\n````` {.r}\n# temporarily override mark()\nmark = function(...) litedown::mark(..., output = 'latex')\n# toc example\nmkd <- c('# Header 1', 'p1', '## Header 2', 'p2')\n\nmark(mkd, options = '+number_sections')\n`````\n\n````` {.latex .plain}\n\\section{Header 1}\n\np1\n\n\\subsection{Header 2}\n\np2\n`````\n\n````` {.r}\nmark(mkd, options = '+number_sections+toc')\n`````\n\n````` {.latex .plain}\n\\tableofcontents\n\\section{Header 1}\n\np1\n\n\\subsection{Header 2}\n\np2\n`````\n\n````` {.r}\n# hard_wrap example\nmark('foo\\nbar\\n')\n`````\n\n````` {.latex .plain}\nfoo\nbar\n`````\n\n````` {.r}\nmark('foo\\nbar\\n', options = '+hardbreaks')\n`````\n\n````` {.latex .plain}\nfoo\\\\\nbar\n`````\n\n````` {.r}\n# latex math example\nmkd <- c(\n  '`$x$` is inline math $x$!', '', 'Display style:', '', '$$x + y$$', '',\n  '\\\\begin{align}\na^{2}+b^{2} & = c^{2}\\\\\\\\\n\\\\sin^{2}(x)+\\\\cos^{2}(x) & = 1\n\\\\end{align}'\n)\n\nmark(mkd)\n`````\n\n````` {.latex .plain}\n\\texttt{\\$x\\$} is inline math \\(x\\)!\n\nDisplay style:\n\n$$x + y$$\n\n\\begin{align}\na^{2}+b^{2} & = c^{2}\\\\\n\\sin^{2}(x)+\\cos^{2}(x) & = 1\n\\end{align}\n`````\n\n````` {.r}\nmark(mkd, options = '-latex_math')\n`````\n\n````` {.latex .plain}\n\\texttt{\\$x\\$} is inline math \\$x\\$!\n\nDisplay style:\n\n\\$\\$x + y\\$\\$\n\n\\textbackslash{}begin\\{align\\}\na\\^{}\\{2\\}+b\\^{}\\{2\\} \\& = c\\^{}\\{2\\}\\textbackslash{}\n\\textbackslash{}sin\\^{}\\{2\\}(x)+\\textbackslash{}cos\\^{}\\{2\\}(x) \\& = 1\n\\textbackslash{}end\\{align\\}\n`````\n\n````` {.r}\n# table example\nmark('\nFirst Header  | Second Header\n------------- | -------------\nContent Cell  | Content Cell\nContent Cell  | Content Cell\n')\n`````\n\n````` {.latex .plain}\n\\begin{tabular}{ll}\nFirst Header & Second Header \\\\\nContent Cell & Content Cell \\\\\nContent Cell & Content Cell \\\\\n\\end{tabular}\n\n`````\n\n````` {.r}\n# caption\nmark('\n| a | b |\n|---|--:|\n| A | 9 |\n\nTable: A table _caption_.\n')\n`````\n\n````` {.latex .plain}\n\\begin{tabular}{lr}\na & b \\\\\nA & 9 \\\\\n\\end{tabular}\n\nTable: A table \\emph{caption}.\n`````\n\n````` {.r}\n# no table\nmark('\nFirst Header  | Second Header\n------------- | -------------\nContent Cell  | Content Cell\nContent Cell  | Content Cell\n', options = '-table')\n`````\n\n````` {.latex .plain}\nFirst Header  \\textbar{} Second Header\n------------- \\textbar{} -------------\nContent Cell  \\textbar{} Content Cell\nContent Cell  \\textbar{} Content Cell\n`````\n\n````` {.r}\n# autolink example\nmark('https://www.r-project.org/')\n`````\n\n````` {.latex .plain}\n\\url{https://www.r-project.org/}\n`````\n\n````` {.r}\nmark('https://www.r-project.org/', options = '-autolink')\n`````\n\n````` {.latex .plain}\nhttps://www.r-project.org/\n`````\n\n````` {.r}\n# links and spans\nmark('[a b](#){.red}')\n`````\n\n````` {.latex .plain}\n\\protect\\hyperlink{}{a b}\\{.red\\}\n`````\n\n````` {.r}\nmark('[a\\nb](){.red}')\n`````\n\n````` {.latex .plain}\n{a\nb}\\{.red\\}\n`````\n\n````` {.r}\n# strikethrough example\nmark('~~awesome~~')\n`````\n\n````` {.latex .plain}\n\\sout{awesome}\n`````\n\n````` {.r}\nmark('~~awesome~~', options = '-strikethrough')\n`````\n\n````` {.latex .plain}\n\\textasciitilde{}\\textasciitilde{}awesome\\textasciitilde{}\\textasciitilde{}\n`````\n\n````` {.r}\n# superscript and subscript examples\nmark('2^10^')\n`````\n\n````` {.latex .plain}\n2\\textsuperscript{10}\n`````\n\n````` {.r}\nmark('2^10^', options = '-superscript')\n`````\n\n````` {.latex .plain}\n2\\^{}10\\^{}\n`````\n\n````` {.r}\nmark('H~2~O')\n`````\n\n````` {.latex .plain}\nH\\textsubscript{2}O\n`````\n\n````` {.r}\nmark('H~2~O', options = '-subscript')\n`````\n\n````` {.latex .plain}\nH\\textasciitilde{}2\\textasciitilde{}O\n`````\n\n````` {.r}\n# code blocks\nmark('```r\\n1 + 1;\\n```')\n`````\n\n````` {.latex .plain}\n\\begin{verbatim}\n1 + 1;\n\\end{verbatim}\n`````\n\n````` {.r}\nmark('```{.r}\\n1 + 1;\\n```')\n`````\n\n````` {.latex .plain}\n\\begin{verbatim}\n1 + 1;\n\\end{verbatim}\n`````\n\n````` {.r}\nmark('```{.r .js}\\n1 + 1;\\n```')\n`````\n\n````` {.latex .plain}\n\\begin{verbatim}\n1 + 1;\n\\end{verbatim}\n`````\n\n````` {.r}\nmark('```{.r .js #foo}\\n1 + 1;\\n```')\n`````\n\n````` {.latex .plain}\n\\begin{verbatim}\n1 + 1;\n\\end{verbatim}\n`````\n\n````` {.r}\nmark('```{.r .js #foo style=\"background:lime;\"}\\n1 + 1;\\n```')\n`````\n\n````` {.latex .plain}\n\\begin{verbatim}\n1 + 1;\n\\end{verbatim}\n`````\n\n````` {.r}\nmark('````\\nA _code chunk_:\\n\\n```{r, echo=TRUE}\\n1 + 1;\\n```\\n````')\n`````\n\n````` {.latex .plain}\n\\begin{verbatim}\nA _code chunk_:\n\n```{r, echo=TRUE}\n1 + 1;\n```\n\\end{verbatim}\n`````\n\n````` {.r}\n# raw blocks\nmark('```{=html}\\n<p>raw HTML</p>\\n```')\n`````\n\n````` {.latex .plain}\n\n`````\n\n````` {.r}\nmark('```{=latex}\\n\\\\textbf{raw LaTeX}\\n```')\n`````\n\n````` {.latex .plain}\n\\textbf{raw LaTeX}\n`````\n\n````` {.r}\n# fenced Divs\nmark('::: foo\\nasdf\\n:::')\n`````\n\n````` {.latex .plain}\n\\begin{verbatim}\n::: {.foo} :::\n\\end{verbatim}\n\nasdf\n\\end\n`````\n\n````` {.r}\nmark('::: {.foo .bar #baz style=\"color: red;\"}\\nasdf\\n:::')\n`````\n\n````` {.latex .plain}\n\\begin{verbatim}\n::: {.foo .bar #baz style=\"color: red;\"} :::\n\\end{verbatim}\n\nasdf\n\\end\n`````\n\n````` {.r}\n# footnotes\nmark('Hello[^foo] world!\\n\\n[^foo]: A _footnote_.')\n`````\n\n````` {.latex .plain}\nHello\\footnote{A \\emph{footnote}.} world!\n`````\n\n````` {.r}\nmark('Hello[^2] world!\\n\\n[^2]: A footnote with _multiple_ elements.\\n\\n    - list')\n`````\n\n````` {.latex .plain}\nHello\\footnote{A footnote with \\emph{multiple} elements.\n\n\\begin{itemize}\n\\item list\n\n\\end{itemize}} world!\n`````\n\n````` {.r}\n# smartypants example\nmark('1/2 (c)')\n`````\n\n````` {.latex .plain}\n1/2 (c)\n`````\n\n````` {.r}\nmark('1/2 (c)', options = '+smartypants')\n`````\n\n````` {.latex .plain}\n½ ©\n`````\n\n````` {.r}\nmkd <- paste(names(litedown:::pants), collapse = ' ')\nmark(mkd, options = '+smartypants')\n`````\n\n````` {.latex .plain}\n½ ⅓ ⅔ ¼ ¾ ⅕ ⅖ ⅗ ⅘ ⅙ ⅚ ⅛ ⅜ ⅝ ⅞ ⅐ ⅑ ⅒ © ® ™\n`````\n\n````` {.r}\n# filter HTML tags\nmkd = '<style>a {}</style><script type=\"text/javascript\">console.log(\"No!\");</script>\\n[Hello](#)'\nmark(mkd)\n`````\n\n````` {.latex .plain}\n\\protect\\hyperlink{}{Hello}\n`````\n\n````` {.r}\nmark(mkd, options = 'tagfilter')\n`````\n\n````` {.latex .plain}\n\\protect\\hyperlink{}{Hello}\n`````\n\n````` {.r}\nrm(mark)\n`````\n"
  },
  {
    "path": "examples/test-results-hide.Rmd",
    "content": "`results = 'hide'` should try to collapse output, e.g., merge the source blocks below:\n\n```{r, chunk-a, results = 'hide'}\nnrow(iris)\nncol(iris)\n\niris\n```\n\nKeep the blank line:\n\n```{r, chunk-a, results = 'hide', strip.white = FALSE}\n```\n\nMessage blocks will be collapsed, too:\n\n```{r, results = 'hide'}\nnrow(iris)\nmessage(ncol(iris))\niris\n```\n"
  },
  {
    "path": "examples/test-results-hide.md",
    "content": "`results = 'hide'` should try to collapse output, e.g., merge the source blocks below:\n\n``` {.r}\nnrow(iris)\nncol(iris)\niris\n```\n\nKeep the blank line:\n\n``` {.r}\nnrow(iris)\nncol(iris)\n\niris\n```\n\nMessage blocks will be collapsed, too:\n\n``` {.r}\nnrow(iris)\nmessage(ncol(iris))\n#> 5\niris\n```\n"
  },
  {
    "path": "inst/resources/default.css",
    "content": "body {\n  font-family: sans-serif;\n  max-width: 800px;\n  margin: auto;\n  padding: 1em;\n  line-height: 1.5;\n  print-color-adjust: exact;\n  -webkit-print-color-adjust: exact;\n}\nbody, .abstract, code, .footnotes, footer, #refs, .caption { font-size: .9em; }\nli li { font-size: .95em; }\nul:has(li > input[type=\"checkbox\"]) { list-style: none; padding-left: 1em; }\n*, :before, :after { box-sizing: border-box; }\na { color: steelblue; }\npre, img { max-width: 100%; }\npre { white-space: pre-wrap; word-break: break-word; }\npre code { display: block; padding: 1em; overflow-x: auto; }\ncode { font-family: 'DejaVu Sans Mono', 'Droid Sans Mono', 'Lucida Console', Consolas, Monaco, monospace; }\n:not(pre, th) > code, code[class], div > .caption { background: #f8f8f8; }\npre > code:is(:not([class]), .language-plain, .language-none, .plain), .box, .figure, .table { background: inherit; border: 1px solid #eee; }\npre > code {\n  &.message { border-color: #9eeaf9; }\n  &.warning { background: #fff3cd; border-color: #fff3cd; }\n  &.error { background: #f8d7da; border-color: #f8d7da; }\n}\n.fenced-chunk { border-left: 1px solid #666; }\n.code-fence {\n  opacity: .4;\n  border: 1px dashed #666;\n  border-left: 2px solid;\n  &:hover { opacity: inherit; }\n}\n.box, .figure, .table, table { margin: 1em auto; }\ndiv > .caption { padding: 1px 1em; }\n.figure { p:has(img, svg), pre:has(svg) { text-align: center; } }\n.flex-col { display: flex; justify-content: space-between; }\ntable {\n  &:only-child:not(.table > *) { margin: auto; }\n  th, td { padding: 5px; font-variant-numeric: tabular-nums; }\n  thead, tfoot, tr:nth-child(even) { background: whitesmoke; }\n  thead th { border-bottom: 1px solid #ddd; }\n  &:not(.datatable-table) {\n    border-top: 1px solid #666;\n    border-bottom: 1px solid #666;\n  }\n}\nblockquote {\n  color: #666;\n  margin: 0;\n  padding: 1px 1em;\n  border-left: .5em solid #eee;\n}\nhr, .footnotes::before { border: 1px dashed #ddd; }\n.frontmatter { text-align: center; }\n#TOC {\n  a { text-decoration: none; }\n  ul { list-style: none; padding-left: 1em; }\n  & > ul { padding: 0; }\n  ul ul { border-left: 1px solid lightsteelblue; }\n}\n.body h2 { border-bottom: 1px solid #666; }\n.body .appendix, .appendix ~ h2 { border-bottom-style: dashed; }\n.main-number::after { content: \".\"; }\nspan[class^=\"ref-number-\"] { font-weight: bold; }\n.ref-number-fig::after, .ref-number-tab::after { content: \":\"; }\n.cross-ref-chp::before { content: \"Chapter \"; }\n.cross-ref-sec::before { content: \"Section \"; }\n.cross-ref-apd::before { content: \"Appendix \"; }\n.cross-ref-fig::before, .ref-number-fig::before { content: \"Figure \"; }\n.cross-ref-tab::before, .ref-number-tab::before { content: \"Table \"; }\n.cross-ref-eqn::before, .MathJax_ref:has(mjx-mtext > mjx-c + mjx-c)::before { content: \"Equation \"; }\n.abstract, #refs {\n  &::before { display: block; margin: 1em auto; font-weight: bold; }\n}\n.abstract::before { content: \"Abstract\"; text-align: center; }\n#refs::before { content: \"Bibliography\"; font-size: 1.5em; }\n.ref-paren-open::before { content: \"(\"; }\n.ref-paren-close::after { content: \")\"; }\n.ref-semicolon::after { content: \"; \"; }\n.ref-and::after { content: \" and \"; }\n.ref-et-al::after { content: \" et al.\"; font-style: italic; }\n.footnote-ref a {\n  &::before { content: \"[\"; }\n  &::after { content: \"]\"; }\n}\nsection.footnotes {\n  margin-top: 2em;\n  &::before { content: \"\"; display: block; max-width: 20em; }\n}\n.fade {\n  background: repeating-linear-gradient(135deg, white, white 30px, #ddd 32px, #ddd 32px);\n  opacity: 0.6;\n}\n@media print {\n  body { max-width: 100%; }\n  tr, img { break-inside: avoid; }\n}\n@media only screen and (min-width: 992px) {\n  body:not(.pagesjs) pre:has(.line-numbers):not(:hover) { white-space: pre; }\n}\n"
  },
  {
    "path": "inst/resources/listing.css",
    "content": "body {\n  max-width: none;\n}\n.body, ul {\n  display: flex;\n  flex-wrap: wrap;\n}\nh1 {\n  text-align: left;\n  font-size: 1em;\n}\n.box {\n  width: 30em;\n  flex-grow: 1;\n  margin: .5em;\n  & > .name {\n    font-size: 1em;\n    padding: 1px .5em;\n    p {\n      margin: .5em 0;\n    }\n  }\n  .run {\n    float: right;\n  }\n}\npre > code:not([class]) {\n  border: none;\n  padding: 0 1em 1em;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\npre, pre:hover {\n  white-space: pre;\n}\nul {\n  width: 100%;\n  padding-left: .5em;\n}\nli {\n  display: inline-block;\n  margin-right: 2em;\n}\nem, pre code {\n  font-size: .8em;\n}\nem:not(:hover), pre code:not(:hover) {\n  opacity: .7;\n}\n"
  },
  {
    "path": "inst/resources/litedown.html",
    "content": "<!DOCTYPE html>\n<html lang=\"$lang$\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=yes\">\n<meta name=\"generator\" content=\"$generator$\">\n\n<title>$plain-title$</title>\n\n$css$\n\n$header-includes$\n</head>\n\n<body>\n$include-before$\n\n<div class=\"frontmatter\">\n$title_$\n$subtitle_$\n$author_$\n$date_$\n</div>\n\n$abstract_$\n\n<div class=\"$body-class$\">\n$body$\n</div>\n\n$include-after$\n\n$js$\n</body>\n\n</html>\n"
  },
  {
    "path": "inst/resources/litedown.latex",
    "content": "\\documentclass[$classoption$]{$documentclass$}\n\\usepackage[T1]{fontenc}\n\\usepackage{graphicx,hyperref}\n$bib-preamble$\n$header-includes$\n$title_$\n$author_$\n$date_$\n\\begin{document}\n\\maketitle\n$include-before$\n\n$abstract_$\n\n$body$\n$bib-end$\n$include-after$\n\\end{document}\n"
  },
  {
    "path": "inst/resources/server.css",
    "content": ".nav-path {\n  font-size: .8em;\n  width: 100%;\n  order: -1;\n}\n.buttons {\n  float: right;\n  a {\n    margin-left: .5em;\n  }\n}\n.btn-lite {\n  font-style: normal;\n  text-decoration: none;\n  font-size: 1.3em;\n}\ntd .pencil {\n  font-size: inherit;\n}\n.auto-numbers span[data-line-number] {\n  cursor: pointer;\n}\n.litedown-dialog {\n  label { display: inline-block; }\n  #filename-input { min-width: 20em; }\n}\n::backdrop {\n  cursor: wait;\n  opacity: .5;\n}\n@media screen {\n  .pagesjs .nav-path {\n    display: block;\n  }\n}\n@media print {\n  .nav-path, .auto-numbers span[data-line-number] {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "inst/resources/server.js",
    "content": "(d => {\n  function new_req(url, data, callback) {\n    const req = new XMLHttpRequest();\n    req.open('POST', url);\n    // let R know the type of the request\n    req.setRequestHeader('litedown-data', data);\n    req.send();\n    req.onload = callback;\n  }\n  function new_dialog(text, error, html, action = el => el.close(), callback = (el, b) => {}) {\n    let el = d.querySelector('dialog.litedown-dialog');\n    if (!el) {\n      el = d.createElement('dialog');\n      el.className = 'litedown-dialog';\n      el.innerHTML = '<div></div><p><button>OK</button></p>';\n      d.body.append(el);\n    }\n    const b = el.querySelector('button');\n    b.onclick = e => action(el, b);\n    const p = el.firstElementChild;\n    p[(html && !error) ? 'innerHTML' : 'innerText'] = text.toLowerCase() === 'connection refused' ?\n      (text + '; please re-run litedown::roam()') : text;\n    if (error) p.className = 'error';\n    callback(el, b);\n    el.showModal();\n  }\n  function show_dialog(resp) {\n    new_dialog(resp.responseText, resp.status !== 200);\n  }\n  // create a new file\n  function new_file() {\n    new_req(location.href, 'new', e => {\n      new_dialog(e.target.responseText, e.target.status !== 200, true, (el, b) => {\n        const file = el.querySelector('#filename-input').value;\n        if (!file || b.innerText === 'Cancel') return el.close();\n        const o = location.origin, p = location.pathname,\n          u = o + p + (/\\/$/.test(p) ? '' : '/../') + file, features = [];\n        el.querySelectorAll('input[type=\"checkbox\"]').forEach(input => {\n          if (input.checked) features.push(input.name);\n        });\n        new_req(u, 'new:' + features.join(','), e => {\n          const text = e.target.responseText;\n          switch (text) {\n            case 'view': location.href = u + '?preview=2'; break;\n            case 'true': break;\n            case 'false': new_dialog('Failed to create the file', true); break;\n            default: new_dialog(text, true);\n          }\n        });\n      }, (el, b) => {\n        b.innerText = 'Cancel';\n        const input = el.querySelector('#filename-input'),\n          options = [...el.querySelector('#file-list').options].map(o => o.value);\n        input.onchange = e => {\n          const v = input.value.trim();\n          if (!/.+\\.[a-zA-Z0-9]+$/.test(v)) {\n            input.value = v + '.Rmd'; input.oninput();\n          }\n        };\n        input.oninput = e => {\n          const v = input.value.trim(), X = '❌';\n          b.innerText = (v === '' || v.startsWith(X) || options.includes(v) || options.includes(X + ' ' + v)) ? 'Cancel' : 'OK';\n        };\n      });\n    });\n  }\n  // remove empty frontmatter\n  const fm = d.querySelector('.frontmatter');\n  fm && !fm.innerText.trim() && fm.remove();\n  const chapters = d.querySelectorAll('div[data-source]');\n  // add edit buttons for book chapters\n  chapters.length > 1 && chapters.forEach((el, i) => {\n    const u = el.dataset.source;\n    if (!u || el.querySelector('.buttons')) return;\n    const u2 = Array((u.match(/\\/+/g) || []).length).fill('../').join(''),\n      a2 = i === 0 ? '' : `<a href=\"${u2}${u}?preview=2\" class=\"btn-lite run\" title=\"Preview single chapter\">⏵</a>`;\n    el.insertAdjacentHTML('afterbegin', `<span class=\"buttons\">${a2}<a href=\"?path=${u}\" class=\"btn-lite open pencil\" title=\"Open ${u}\">✎</a></span>`);\n  });\n  const nav = d.querySelector('.nav-path, .title h1');\n  const btn = nav.querySelector('.buttons') || d.createElement('span');\n  btn.className = 'buttons';\n  ['back', 'forward', 'new', 'refresh', 'print'].forEach((action, i) => {\n    if (btn.querySelector(`.${action}`)) return;\n    const a = d.createElement('a');\n    a.href = '#'; a.title = action[0].toUpperCase() + action.slice(1);\n    const k = ({\n      back: 'Alt + LeftArrow', forward: 'Alt + RightArrow',\n      refresh: 'Ctrl + R / Command + R'\n    })[action];\n    if (k) a.title += ` (${k})`;\n    a.className = action + ' btn-lite';\n    a.innerText = ['←', '→', '+', '⟳', '⎙'][i];\n    a.onclick = e => btnAction(e, action);\n    btn.append(a);\n  });\n  if (nav) {\n    nav.querySelectorAll('a.btn-lite').forEach(a => btn.append(a));\n    nav.append(btn);\n  }\n  function btnAction(e, action) {\n    if (!action) return;\n    e.preventDefault();\n    switch (action) {\n      case 'back': history.back(); break;\n      case 'forward': history.forward(); break;\n      case 'new': new_file(); break;\n      case 'refresh': location.reload(); break;\n      case 'print': window.print();\n    }\n  }\n  // add classes and events to edit buttons\n  d.querySelectorAll('a[href]').forEach(a => {\n    if (a.innerText !== '✎' || a.onclick) return;\n    a.classList.add('pencil'); if (!a.title) a.title = 'Open';\n    a.onclick = e => {\n      e.preventDefault();\n      new_req(a.href, 'open');\n    };\n  });\n  function btnSave(e, a) {\n    e.preventDefault();\n    const cls = d.body.classList;\n    if (cls.contains('waiting')) return;\n    cls.add('waiting');\n    new_req(a ? a.href : location.href, 'save', e => {\n      show_dialog(e.target);\n      cls.remove('waiting');\n    });\n  };\n  // add classes and events to save buttons\n  d.querySelectorAll('a.save[href]').forEach(a => {\n    if (a.innerText !== '↯') return;\n    a.title = 'Render to disk';\n    a.getAttribute('href') === '#' && (a.title += '(Ctrl + K / Command + K)');\n    a.onclick = e => btnSave(e, a);\n  });\n  function check_one(q, a, s) {\n    (q ? d.querySelectorAll(q) : [d.body]).forEach(el => {\n      let u = a ? el[a] : location.href;\n      // only check assets served by local server\n      if (a && u && !(u.startsWith(location.origin) && u.includes('/custom/litedown/')))\n        return;\n      // ignore plots under *__files/\n      if (u && u.includes('__files/')) return;\n      if (el.dataset.wait) return;\n      el.dataset.wait = 1;  // don't send another request while waiting\n      new_req(u, q ? (a ? 'asset' : `book:${el.dataset[s]}`) : 'page', e => {\n        const res = e.target.responseText;\n        if (e.target.status !== 200) {\n          if (res.toLowerCase() != 'connection reset by peer') show_dialog(e.target);\n        } else if (res !== '') {\n          if (a) {\n            el[a] = `${u.replace(/[?].*/, '')}?timestamp=${+new Date()}`;\n            el.tagName ==='SCRIPT' && update_script(el);\n          } else if (s) {\n            // update a book chapter (response is an HTML fragment)\n            update_chapter(el, res);\n            window.mermaid && mermaid?.init();\n            // also reload js\n            d.querySelectorAll('script[src]').forEach(update_script);\n          } else {\n            // the current page source file has changed; refresh page\n            res === '1' && location.reload();\n          }\n        }\n        el.dataset.wait = '';\n      });\n    });\n  }\n  function update_chapter(el, html) {\n    const w = d.createElement('div'); w.innerHTML = html;\n    w.querySelector('#TOC')?.remove();\n    w.firstElementChild.className = el.className;\n    // TODO: update fig/tab numbers\n    let q = '[class^=\"ref-number-\"]';\n    w.querySelector(q) && d.querySelectorAll(q).forEach(el => {\n    });\n    // update cross-references\n    w.querySelectorAll('a[class^=\"cross-ref-\"][href^=\"#\"]').forEach(el => {\n      const n = d.getElementById(el.getAttribute('href').replace(/^#/, ''))\n        ?.querySelector('.section-number,[class^=\"ref-number-\"]')\n        ?.innerText;\n      if (n) el.innerText = n;\n    });\n    el.outerHTML = w.innerHTML;\n    // update footnote numbers\n    q = 'a[id^=\"fnref-\"]';\n    w.querySelector(q) && d.querySelectorAll(q).forEach((el, i) => {\n      el.innerText = i + 1;\n    });\n    w.remove();\n  }\n  // to reload <script src>, it has to be destroyed and re-created\n  function update_script(el) {\n    const s = d.createElement('script');\n    el.after(s);\n    for (const a of el.attributes) {\n      s.setAttribute(a.name, a.value);\n    }\n    el.remove();\n  }\n  function check_all() {\n    // don't refresh whole page when checking a book page of multiple source files\n    chapters.length <= 1 ? check_one() : check_one('div[data-source]', false, 'source');\n    check_one('[src]', 'src');  // <img> and <script>\n    check_one('link[href]', 'href');  // css\n  }\n  // do live preview only when requested\n  const live = d.querySelector('meta[name=\"live-previewer\"]')?.content === 'litedown::roam';\n  function new_interval() {\n    if (live) d.body.dataset.timerId = setInterval(check_all, 2000);\n    // also support opening files by clicking on line numbers in code blocks\n    chapters.length <= 1 ? open_line(d) :\n      chapters.forEach(el => open_line(el, el.dataset.source));\n  }\n  function open_line(container, path) {\n    container.querySelectorAll('.auto-numbers span[data-line-number]').forEach(el => {\n      const n = +el.dataset.lineNumber;\n      if (n) el.onclick = e => {\n        const u = `${location.href.replace(/[?#].*/, '')}?line=${n}`;\n        new_req(path ? `${u}&path=${path}` : u, 'open');\n      };\n    });\n  }\n  window.addEventListener('load', e => {\n    d.onkeydown = e => {\n      const k = e.key, ctrl = e.metaKey || e.ctrlKey; let a;\n      k === 'r' && ctrl && (a = 'refresh');\n      e.altKey && (a = ({'ArrowLeft': 'back', 'ArrowRight': 'forward'})[k]);\n      btnAction(e, a);\n      k == 'k' && ctrl && btnSave(e);\n    };\n    new_interval();\n  });\n  // send a request to clean up __files/ before page is unloaded\n  window.addEventListener('beforeunload', e => new_req(location.href, 'cleanup'));\n  // when updating a book chapter, this script will be reloaded, and we need to\n  // clear the old interval and create a new loop\n  if (d.body.dataset.timerId) {\n    clearInterval(d.body.dataset.timerId);\n    new_interval();\n  }\n})(document);\n"
  },
  {
    "path": "inst/resources/snap.css",
    "content": ":root { --slide-width: 100%; }\nhtml { scroll-snap-type: y mandatory; }\nth, td { padding: .2em .5em; }\n.slide { padding: 0 1em; background-color: #fff; }\n.slide, :is(.frontmatter, .middle) .main {\n  display: flex;\n  flex-direction: column;\n}\n.slide {\n  & > .main { flex-grow: 1; }\n  & > .header { margin-bottom: 1em; }\n  h2, h3 { margin-top: unset; }\n}\nbody {\n  max-width: fit-content;\n  padding: 0;\n}\na { color: #eb4a47; }\n:not(pre) > code { background: #fdfded; }\n#TOC {\n  columns: 2;\n  &::before {\n    font-size: 1.3em;\n    font-weight: bold;\n    display: block;\n    border-bottom: 1px solid #666;\n  }\n}\n:is(.frontmatter, .middle) .main { justify-content: center; }\n.footer {\n  display: flex;\n  justify-content: space-between;\n  opacity: .5;\n  font: .7em monospace;\n}\n.inverse {\n  background: #eee;\n  filter: invert(1);\n}\n.center { text-align: center; }\n.slide-container h2 .section-number {\n  display: inline-block;\n  background: #666;\n  color: white;\n  padding: 0 .1em;\n  margin-right: .3em;\n}\n.overview {\n  font-size: .8em;\n  .slide-container {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-evenly;\n    .slide {\n      width: var(--slide-width);\n      border: 1px dotted #ccc;\n      margin-bottom: 0.5em;\n    }\n  }\n}\n.mirrored { transform: scale(-1, 1); }\n.timer { opacity: 0; }\nhtml:fullscreen::-webkit-scrollbar, .spacer { display: none; }\nhtml:fullscreen {\n  -ms-overflow-style: none;\n  scrollbar-width: none;\n}\n@media screen and (min-width: 992px) {\n  :root {\n    --slide-width: 49%;\n    --slide-scale: 1;\n    --slide-ratio: 0.75;\n    --slide-top: auto;\n  }\n  .slide-mode {\n    font-size: 2em;\n    background: #d7d8d2;\n    scale: var(--slide-scale);\n    margin-top: var(--slide-top);\n    .slide {\n      min-height: calc(100vh / var(--slide-scale));\n      width: calc(100vh / var(--slide-ratio) / var(--slide-scale));\n      box-shadow: 0 0 2em #888;\n      clip-path: inset(0 -2em 0 -2em);\n      scroll-snap-align: start;\n    }\n    .spacer { display: block; }\n    .timer { opacity: initial; }\n  }\n  li li { font-size: .9em; }\n}\n@media (min-width: 1400px) {\n  :root { --slide-width: 33%; }\n}\n@media (min-width: 1800px) {\n  :root { --slide-width: 24.67%; }\n}\n"
  },
  {
    "path": "inst/resources/snap.js",
    "content": "(function(d) {\n  let p = d.body;  // container of slides; assume <body> for now\n  const s1 = ':scope > hr:not([class])', s2 = ':scope > h2';\n  // find a container that has at least n \"slides\"\n  function findContainer(s, n = 1) {\n    if (p.querySelectorAll(s).length >= n) return true;\n    // if body doesn't contain headings or <hr>s, look into children\n    for (let i = 0; i < p.children.length; i++) {\n      if (p.children[i].querySelectorAll(s).length >= n) {\n        p = p.children[i]; break;\n      }\n    }\n    return false;\n  }\n  function newEl(tag, cls) {\n    const el = d.createElement(tag);\n    if (cls) el.className = cls;\n    return el;\n  }\n  if (!findContainer(s1, 3)) {\n    // if not enough <hr>s found in children; look for <h2> instead\n    if (p.tagName === 'BODY') {\n      // not enough h2 found, this page is not appropriate for slides\n      if (!findContainer(s2) && p.tagName === 'BODY') return;\n      p.querySelectorAll(s2).forEach(h2 => h2.before(newEl('hr')));\n    }\n  }\n  p.classList.add('slide-container');\n  // add 'slide' class to the frontmatter/abstract divs and toc\n  ['.frontmatter', '.abstract', '#TOC'].forEach(sel => {\n    const el = d.body.querySelector(sel);\n    if (!el) return;\n    if (sel === '.frontmatter') {\n      el.classList.add('slide');\n    } else {\n      const s = newSlide(); el.before(s); s.append(el);\n    }\n  });\n\n  function newSlide(s) {\n    return (s?.innerHTML === '') ? s : newEl('div', 'slide');\n  }\n  function isSep(el) {\n    return el.tagName === 'HR' && el.attributes.length === 0;\n  }\n  let el = p.firstElementChild; isSep(el) && el.remove();\n  el = p.firstElementChild; if (!el) return;\n  let s = newSlide(); el.before(s);\n  while (true) {\n    let el = s.nextSibling;\n    if (!el) break;\n    // remove slide separators (<hr>) and create new slide\n    if (isSep(el)) {\n      s = newSlide(s);\n      el.before(s); el.remove();\n    } else if (el.classList?.contains('slide')) {\n      s = newSlide(s);\n      el.after(s);\n    } else {\n      s.append(el);\n    }\n  }\n  function setAttr(el, attr) {\n    const m = newEl('div');\n    m.innerHTML = `<div ${attr}></div>`;\n    const attrs = m.firstElementChild.attributes;\n    for (const a of attrs) {\n      el.setAttribute(a.name, a.value);\n    }\n    m.remove();\n  }\n  function reveal(el) {\n    setTimeout(() => el?.scrollIntoView(), 100);\n  }\n  const dE = d.documentElement, dC = d.body.classList;\n  const slides = d.querySelectorAll('div.slide'), N = slides.length,\n        tm = d.querySelector('span.timer'), fn = d.querySelector('.footnotes');\n  slides.forEach((s, i) => {\n    // slide header, main body, and footer\n    const header = newEl('div', 'header'), main = newEl('div', 'main'), footer = newEl('div', 'footer');\n    main.append(...s.childNodes);\n    s.append(main);\n    s.insertAdjacentElement('afterbegin', header);\n    s.insertAdjacentElement('beforeend', footer);\n    // append footnotes\n    if (fn) s.querySelectorAll('.footnote-ref > a[href^=\"#fn\"]').forEach(a => {\n      const li = fn.querySelector('li' + a.getAttribute('href'));\n      if (!li) return;\n      let f = s.querySelector('section.footnotes');\n      if (!f || f.contains(li)) {\n        f = newEl('section', 'footnotes'); footer.before(f);\n      }\n      f.append(li);\n      li.firstElementChild?.insertAdjacentHTML('afterbegin', `[${a.innerHTML}] `);\n      li.outerHTML = li.innerHTML;\n    });\n    // add a timer\n    footer.append(tm ? tm.cloneNode() : newEl('span', 'timer'));\n    // add page numbers\n    const n = newEl('span', 'page-number');\n    n.innerText = i + 1 + '/' + N;\n    n.onclick = e => location.hash = i + 1;\n    footer.append(n);\n    // apply slide attributes in <!--# -->\n    for (const node of main.childNodes) {\n      if (node.nodeType !== Node.COMMENT_NODE) continue;\n      let t = node.textContent;\n      if (!/^#/.test(t)) continue;\n      t = t.replace(/^#/, '');\n      const r = /[\\s\\n]class=\"([^\"]+)\"/, m = t.match(r);\n      if (m) {\n        t = t.replace(r, '').trim();\n        s.className += ' ' + m[1];\n      }\n      if (t) setAttr(s, t);\n      break;\n    }\n    s.addEventListener('click', e => {\n      (e.altKey || e.ctrlKey) && (toggleView(e), reveal(e.target));\n    });\n  });\n  [...d.querySelectorAll('a.footnote-backref'), fn, tm].forEach(el => el?.remove());\n  const tms = d.querySelectorAll('span.timer'), t1 = 1000 * tms[0].dataset.total;\n  let t0;\n  function startTimers() {\n    t0 = new Date();\n    setInterval(setTimers, 1000);\n  }\n  function setTimers() {\n    if (!dC.contains('slide-mode')) return;  // set timer only in slide mode\n    let t = (new Date() - t0);\n    if (t1) t = t1 - t;\n    const t2 = new Date(Math.abs(t)).toISOString().substr(11, 8).replace(/^00:/, '');\n    tms.forEach(el => {\n      el.innerText = t2;\n      if (t < 0) el.style.opacity = Math.ceiling(t/1000) % 2;\n    });\n  }\n  function toggleView(e) {\n    (dC.toggle('overview') ? dC.remove('slide-mode') : setScale(e));\n  }\n  // press f for fullscreen mode\n  d.addEventListener('keyup', e => {\n    if (e.target !== d.body) return;\n    e.key === 'f' && dE.requestFullscreen();\n    e.key === 'o' && toggleView(e);\n    e.key === 'm' && dC.toggle('mirrored');\n    sessionStorage.setItem('body-class', d.body.className);\n  });\n  // start timer and set scale on fullscreen\n  d.onfullscreenchange = e => {\n    d.fullscreenElement && (!t0 && startTimers(), setScale(e));\n  }\n  tms.forEach(el => el.addEventListener('click', e => startTimers()));\n  // measure the height of an empty slide\n  let H = -1;\n  function slideHeight() {\n    if (H >= 0) return H;\n    const s = newEl('div', 'slide');\n    p.querySelector('.slide:last-of-type').after(s);\n    H = s.offsetHeight;\n    s.remove()\n    return H;\n  }\n  // scale slides according to window height (baseline: 900px)\n  const sty = newEl('style'); sty.setAttribute('type', 'text/css');\n  d.head.append(sty);\n  // default height/width ratio from screen size\n  sty.innerHTML = `:root{--slide-ratio:${screen.height / screen.width}}`;\n  // read --slide-ratio in case users have set it in their CSS\n  const ratio = +getComputedStyle(dE).getPropertyValue('--slide-ratio');\n  function setScale(e) {\n    // navigate to a slide indicated by the hash if provided\n    e.type === 'load' && reveal(slides[location.hash.replace(/^#/, '') - 1]);\n    if (dC.contains('overview')) return;\n    let h = window.innerHeight, p = h / 900, r2 = h / window.innerWidth, full = d.fullscreenElement;\n    // add slide mode if there's enough window width, and remove it in case of scrollbar\n    dC.toggle('slide-mode', full || r2 <= ratio) &&\n      (!full && (dE.scrollWidth > dE.offsetWidth)) && dC.remove('slide-mode');\n    sty.innerHTML = `:root{--slide-ratio:${ratio};--slide-scale:${p};--slide-top:${(p - 1)/2 * d.body.scrollHeight + 'px'};}`;\n    // add spacers with enough height on load\n    !d.querySelector('.spacer.fade') && dC.contains('slide-mode') && slides.forEach(s => {\n      const sp = newEl('div', 'spacer fade'), h = s.offsetHeight, h2 = slideHeight();\n      s.append(sp);\n      if (h <= h2) return;\n      sp.style.height = (h2 - h % h2) * p + 'px';\n    });\n  }\n  ['load', 'resize'].forEach(evt => window.addEventListener(evt, setScale));\n  // restore previously saved body class\n  const bc = sessionStorage.getItem('body-class');\n  if (bc) d.body.className += ' ' + bc;\n})(document);\n"
  },
  {
    "path": "litedown.Rproj",
    "content": "Version: 1.0\nProjectId: 06407e40-3cb5-4312-b9b7-2532a448baf3\n\nRestoreWorkspace: Default\nSaveWorkspace: Default\nAlwaysSaveHistory: Default\n\nEnableCodeIndexing: Yes\nUseSpacesForTab: Yes\nNumSpacesForTab: 2\nEncoding: UTF-8\n\nRnwWeave: knitr\nLaTeX: pdfLaTeX\n\nAutoAppendNewline: Yes\nStripTrailingWhitespace: Yes\n\nBuildType: Package\nPackageInstallArgs: -v && make -B -C\nPackageBuildArgs: -v && Rscript -e \"Rd2roxygen::rab()\"\nPackageCheckArgs: --as-cran\n"
  },
  {
    "path": "man/crack.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/fuse.R\n\\name{crack}\n\\alias{crack}\n\\alias{sieve}\n\\title{Parse R Markdown or R scripts}\n\\usage{\ncrack(input, text = NULL)\n\nsieve(input, text = NULL)\n}\n\\arguments{\n\\item{input}{A character vector to provide the input file path or text. If\nnot provided, the \\code{text} argument must be provided instead. The \\code{input}\nvector will be treated as a file path if it is a single string, and points\nto an existing file or has a filename extension. In other cases, the vector\nwill be treated as the \\code{text} argument input. To avoid ambiguity, if a\nstring should be treated as \\code{text} input when it happens to be an existing\nfile path or has an extension, wrap it in \\code{\\link[=I]{I()}}, or simply use the \\code{text}\nargument instead.}\n\n\\item{text}{A character vector as the text input. By default, it is read from\nthe \\code{input} file if provided.}\n}\n\\value{\nA list of code chunks and text blocks:\n\\itemize{\n\\item Code chunks are of the form \\code{list(source, type = \"code_chunk\", options,   comments, ...)}: \\code{source} is a character vector of the source code of a\ncode chunk, \\code{options} is a list of chunk options, and \\code{comments} is a\nvector of pipe comments.\n\\item Text blocks are of the form \\code{list(source, type = \"text_block\", ...)}. If\nthe text block does not contain any inline code, \\code{source} will be a\ncharacter string (lines of text concatenated by line breaks), otherwise it\nwill be a list with members that are either character strings (normal text\nfragments) or lists of the form \\code{list(source, options, ...)} (\\code{source} is\nthe inline code, and \\code{options} contains its options specified inside \\code{  `{lang, ...}`}).\n}\n\nBoth code chunks and text blocks have a list member named \\code{lines} that\nstores their starting and ending line numbers in the input.\n}\n\\description{\nParse input into code chunks, inline code expressions, and text fragments:\n\\code{\\link[=crack]{crack()}} is for parsing R Markdown, and \\code{\\link[=sieve]{sieve()}} is for R scripts.\n}\n\\details{\nFor R Markdown, a code chunk must start with a fence of the form \\verb{```\\{lang\\}}, where \\code{lang} is the language name, e.g., \\code{r} or \\code{python}. The\nbody of a code chunk can start with chunk options written in \"pipe comments\",\ne.g., \\verb{#| eval = TRUE, echo = FALSE} (the CSV syntax) or \\verb{#| eval: true} (the\nYAML syntax). An inline code fragment is of the form \\code{`{lang} source`}\nembedded in Markdown text.\n\nFor R scripts, text blocks are extracted by removing the leading\n\\verb{#'} tokens. All other lines are treated as R code, which can optionally be\nseparated into chunks by consecutive lines of \\verb{#|} comments (chunk options\nare written in these comments). If no \\verb{#'} or \\verb{#|} tokens are found in the\nscript, the script will be divided into chunks that contain smallest\npossible complete R expressions.\n}\n\\note{\nFor simplicity, \\code{\\link[=sieve]{sieve()}} does not support inline code expressions.\nText after \\verb{#'} is treated as pure Markdown.\n\nIt is a pure coincidence that the function names \\code{crack()} and \\code{sieve()}\nweakly resemble Carson Sievert's name, but I will consider adding a class\nname \\code{sievert} to the returned value of \\code{sieve()} if Carson becomes the\npresident of the United States someday, which may make the value\nradioactive and introduce a new programming paradigm named \\emph{Radioactive\nProgramming} (in case \\emph{Reactive Programming} is no longer fun or cool).\n}\n\\examples{\nlibrary(litedown)\n# parse R Markdown\nres = crack(c(\"```{r}\\n1+1\\n```\", \"Hello, `pi` = `{r} pi` and `e` = `{r} exp(1)`!\"))\nstr(res)\n# evaluate inline code and combine results with text fragments\ntxt = lapply(res[[2]]$source, function(x) {\n    if (is.character(x))\n        x else eval(parse(text = x$source))\n})\npaste(unlist(txt), collapse = \"\")\n\n# parse R code\nres = sieve(c(\"#' This is _doc_.\", \"\", \"#| eval=TRUE\", \"# this is code\", \"1 + 1\"))\nstr(res)\n}\n"
  },
  {
    "path": "man/engines.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/fuse.R\n\\name{engines}\n\\alias{engines}\n\\title{Language engines}\n\\usage{\nengines(...)\n}\n\\arguments{\n\\item{...}{Named values (for setting) or unnamed values (for getting).}\n}\n\\value{\nThe usage is similar to \\code{\\link[=reactor]{reactor()}}: \\code{engines('LANG')} returns an\nengine function for the language \\code{LANG}, and \\code{engines(LANG = function(x, inline = FALSE, ...) {})} sets the engine for a language.\n}\n\\description{\nGet or set language engines for evaluating code chunks and inline code.\n}\n\\details{\nAn engine function should have three arguments:\n\\itemize{\n\\item \\code{x}: An element in the \\code{\\link[=crack]{crack()}} list (a code chunk or a text block).\n\\item \\code{inline}: It indicates if \\code{x} is from a code chunk or inline code.\n\\item \\code{...}: Currently unused but recommended for future compatibility (more\narguments might be passed to the function).\n}\n\nThe function should return a character value.\n}\n\\examples{\nlitedown::engines()  # built-in engines\n}\n"
  },
  {
    "path": "man/fuse_book.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/site.R\n\\name{fuse_book}\n\\alias{fuse_book}\n\\title{Fuse multiple R Markdown documents to a single output file}\n\\usage{\nfuse_book(input = \".\", output = NULL, envir = parent.frame())\n}\n\\arguments{\n\\item{input}{A directory or a vector of file paths. By default, all\n\\code{.Rmd}/\\code{.md} files under the current working directory are used as the\ninput, except for filenames that start with \\code{.} or \\verb{_} (e.g., \\verb{_foo.Rmd}),\nor \\code{.md} files with the same base names as \\code{.Rmd} files (e.g., \\code{bar.md}\nwill not be used if \\code{bar.Rmd} exists). For a directory \\code{input}, the file\nsearch will be recursive if \\code{input} ends with a slash (i.e.,\nsub-directories will also be searched). If a file named \\code{index.Rmd} or\n\\code{index.md} exists, it will always be treated as the first input file. Input\nfiles can also be specified in the config file \\verb{_litedown.yml} (in the\n\\code{input} field under \\code{book}).}\n\n\\item{output}{An output file path or a filename extension (e.g., \\code{.html},\n\\code{.tex}, \\code{.xml}, \\code{.man}, \\code{.markdown}, or \\code{.txt}). In the latter case, the\noutput file path will use the extension on the same base filename as the\ninput file if the \\code{input} is a file. If \\code{output} is not character (e.g.,\n\\code{NA}), the results will be returned as a character vector instead of being\nwritten to a file. If \\code{output} is \\code{NULL} or an extension, and the input is\na file path, the output file path will have the same base name as the input\nfile, with an extension corresponding to the output format. The output\nformat is retrieved from the first value in the \\code{output} field of the YAML\nmetadata of the \\code{input} (e.g., \\code{html} will generate HTML output). The\n\\code{output} argument can also take an output format name (possible values are\n\\code{html}, \\code{latex}, \\code{xml}, \\code{man}, \\code{commonmark}, and \\code{text}). If no output\nformat is detected or provided, the default is HTML.}\n\n\\item{envir}{An environment in which the code is to be evaluated. It can be\naccessed via \\code{\\link[=fuse_env]{fuse_env()}} inside \\code{\\link[=fuse]{fuse()}}.}\n}\n\\value{\nAn output file path or the output content, depending on the \\code{output}\nargument.\n}\n\\description{\nThis is a helper function to \\code{\\link[=fuse]{fuse()}} \\code{.Rmd} files and convert all their\nMarkdown output to a single output file, which is similar to\n\\code{bookdown::render_book()}, but one major differences is that all HTML output\nis written to one file, instead of one HTML file per chapter.\n}\n\\details{\nIf the output format needs to be customized, the settings should be written\nin the config file \\verb{_litedown.yml}, e.g.,\n\n\\if{html}{\\out{<div class=\"sourceCode yaml\">}}\\preformatted{---\noutput:\n  html:\n    options:\n      toc:\n        depth: 4\n  latex:\n    meta:\n      documentclass: \"book\"\n}\\if{html}{\\out{</div>}}\n\nIn addition, you can configure the book via the \\code{book} field, e.g.,\n\n\\if{html}{\\out{<div class=\"sourceCode yaml\">}}\\preformatted{---\nbook:\n  new_session: true\n  subdir: false\n  pattern: \"[.]R?md$\"\n  chapter_before: \"Information before a chapter.\"\n  chapter_after: \"This chapter was generated from `$input$`.\"\n---\n}\\if{html}{\\out{</div>}}\n\nThe option \\code{new_session} specifies whether to render each input file in the\ncurrent R session or a separate new R session; \\code{chapter_before} and\n\\code{chapter_after} specify text to be added to the beginning and end of each\nfile, respectively, which accepts some variables (e.g., \\verb{$input$} is the\ncurrent input file path).\n}\n"
  },
  {
    "path": "man/fuse_env.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/package.R\n\\name{fuse_env}\n\\alias{fuse_env}\n\\title{The \\code{fuse()} environment}\n\\usage{\nfuse_env()\n}\n\\value{\nWhen called during \\code{fuse()}, it returns the \\code{envir} argument value of\n\\code{fuse()}. When called outside \\code{fuse()}, it returns the global environment.\n}\n\\description{\nGet the environment passed to the \\code{envir} argument of \\code{\\link[=fuse]{fuse()}}, i.e., the\nenvironment in which code chunks and inline code are evaluated.\n}\n"
  },
  {
    "path": "man/fuse_site.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/site.R\n\\name{fuse_site}\n\\alias{fuse_site}\n\\title{Fuse R Markdown documents individually under a directory}\n\\usage{\nfuse_site(input = \".\")\n}\n\\arguments{\n\\item{input}{The root directory of the site, or a vector of input file paths.}\n}\n\\value{\nOutput file paths (invisibly).\n}\n\\description{\nRun \\code{\\link[=fuse]{fuse()}} on R Markdown documents individually to generate a website.\n}\n\\details{\nIf a directory contains a config file \\verb{_litedown.yml}, which has a YAML field\n\\code{site}, the directory will be recognized as a site root directory. The YAML\nfield \\code{output} will be applied to all R Markdown files (an individual R\nMarkdown file can provide its own \\code{output} field in YAML to override the\nglobal config). For example:\n\n\\if{html}{\\out{<div class=\"sourceCode yaml\">}}\\preformatted{---\nsite:\n  rebuild: \"outdated\"\n  pattern: \"[.]R?md$\"\noutput:\n  html:\n    meta:\n      css: [\"@default\"]\n      include_before: \"[Home](/) [About](/about.html)\"\n      include_after: \"&copy; 2024 | [Edit]($input$)\"\n---\n}\\if{html}{\\out{</div>}}\n\nThe option \\code{rebuild} determines whether to rebuild \\code{.Rmd} files. Possible\nvalues are:\n\\itemize{\n\\item \\code{newfile}: Build an input file if it does not have a \\code{.html} output file.\n\\item \\code{outdated}: Rebuild an input file if the modification time of its \\code{.html}\noutput file is older than the input.\n}\n}\n"
  },
  {
    "path": "man/get_context.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/fuse.R\n\\name{get_context}\n\\alias{get_context}\n\\title{Get the \\code{fuse()} context}\n\\usage{\nget_context(item = NULL)\n}\n\\arguments{\n\\item{item}{The name of the context item.}\n}\n\\value{\nIf the \\code{item} is provided, return its value in the context. If\n\\code{NULL}, the whole context (an environment) is returned.\n}\n\\description{\nA helper function to query the \\code{\\link[=fuse]{fuse()}} context (such as the input file path\nor the output format name) when called inside a code chunk.\n}\n\\examples{\nlitedown::get_context(\"input\")\nlitedown::get_context(\"format\")\nnames(litedown::get_context())  # all available items\n}\n"
  },
  {
    "path": "man/html_format.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/format.R\n\\name{html_format}\n\\alias{html_format}\n\\alias{latex_format}\n\\title{Output formats in YAML metadata}\n\\usage{\nhtml_format(options = NULL, meta = NULL, template = NULL, keep_md = FALSE)\n\nlatex_format(\n  options = NULL,\n  meta = NULL,\n  template = NULL,\n  keep_md = FALSE,\n  keep_tex = FALSE,\n  latex_engine = \"xelatex\",\n  citation_package = \"natbib\"\n)\n}\n\\arguments{\n\\item{meta, options}{Arguments to be passed to \\code{\\link[=mark]{mark()}}.}\n\n\\item{template}{A template file path.}\n\n\\item{keep_md, keep_tex}{Whether to keep the intermediate \\file{.md} and\n\\file{.tex} files generated from \\file{.Rmd}.}\n\n\\item{latex_engine}{The LaTeX engine to compile \\file{.tex} to \\file{.pdf}.}\n\n\\item{citation_package}{The LaTeX package for processing citations. Possible\nvalues are \\code{none}, \\code{natbib}, and \\code{biblatex}.}\n}\n\\description{\nThese functions exist only for historical reasons, and should never be called\ndirectly. They can be used to configure output formats in YAML, but you are\nrecommended to use the file format names instead of these function names.\n}\n\\details{\nTo configure output formats in the YAML metadata of the Markdown document,\nsimply use the output format names such as \\code{html} or \\code{latex} in the \\code{output}\nfield in YAML, e.g.,\n\n\\if{html}{\\out{<div class=\"sourceCode yaml\">}}\\preformatted{---\noutput:\n  html:\n    options:\n      toc: true\n    keep_md: true\n  latex:\n    latex_engine: pdflatex\n---\n}\\if{html}{\\out{</div>}}\n\nYou can also use \\code{litedown::html_format} instead of \\code{html} (or\n\\code{litedown::latex_format} instead of \\code{latex}) if you like.\n}\n\\note{\nIf you want to use the \\code{Knit} button in RStudio, you must add a\ntop-level field \\code{knit: litedown:::knit} to the YAML metadata. See\n\\url{https://yihui.org/litedown/#sec:knit-button} for more information.\n}\n"
  },
  {
    "path": "man/litedown-package.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/package.R\n\\docType{package}\n\\name{litedown-package}\n\\alias{litedown}\n\\alias{litedown-package}\n\\title{A lightweight version of R Markdown}\n\\description{\nMarkdown is a plain-text format that can be converted to HTML and other\nformats. This package can render R Markdown to Markdown, and then to an\noutput document format. The main differences between this package and\n\\pkg{rmarkdown} are that it does not use Pandoc or \\pkg{knitr} (i.e., fewer\ndependencies), and it also has fewer Markdown features.\n}\n\\seealso{\nUseful links:\n\\itemize{\n  \\item \\url{https://github.com/yihui/litedown}\n  \\item Report bugs at \\url{https://github.com/yihui/litedown/issues}\n}\n\n}\n\\author{\n\\strong{Maintainer}: Yihui Xie \\email{xie@yihui.name} (\\href{https://orcid.org/0000-0003-0645-5666}{ORCID}) (https://yihui.org)\n\nOther contributors:\n\\itemize{\n  \\item Tim Taylor (\\href{https://orcid.org/0000-0002-8587-7113}{ORCID}) [contributor]\n}\n\n}\n"
  },
  {
    "path": "man/mark.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/fuse.R, R/mark.R\n\\name{fuse}\n\\alias{fuse}\n\\alias{fiss}\n\\alias{mark}\n\\title{Render Markdown, R Markdown, and R scripts}\n\\usage{\nfuse(input, output = NULL, text = NULL, envir = parent.frame(), quiet = FALSE)\n\nfiss(input, output = \".R\", text = NULL)\n\nmark(input, output = NULL, text = NULL, options = NULL, meta = list())\n}\n\\arguments{\n\\item{input}{A character vector to provide the input file path or text. If\nnot provided, the \\code{text} argument must be provided instead. The \\code{input}\nvector will be treated as a file path if it is a single string, and points\nto an existing file or has a filename extension. In other cases, the vector\nwill be treated as the \\code{text} argument input. To avoid ambiguity, if a\nstring should be treated as \\code{text} input when it happens to be an existing\nfile path or has an extension, wrap it in \\code{\\link[=I]{I()}}, or simply use the \\code{text}\nargument instead.}\n\n\\item{output}{An output file path or a filename extension (e.g., \\code{.html},\n\\code{.tex}, \\code{.xml}, \\code{.man}, \\code{.markdown}, or \\code{.txt}). In the latter case, the\noutput file path will use the extension on the same base filename as the\ninput file if the \\code{input} is a file. If \\code{output} is not character (e.g.,\n\\code{NA}), the results will be returned as a character vector instead of being\nwritten to a file. If \\code{output} is \\code{NULL} or an extension, and the input is\na file path, the output file path will have the same base name as the input\nfile, with an extension corresponding to the output format. The output\nformat is retrieved from the first value in the \\code{output} field of the YAML\nmetadata of the \\code{input} (e.g., \\code{html} will generate HTML output). The\n\\code{output} argument can also take an output format name (possible values are\n\\code{html}, \\code{latex}, \\code{xml}, \\code{man}, \\code{commonmark}, and \\code{text}). If no output\nformat is detected or provided, the default is HTML.}\n\n\\item{text}{A character vector as the text input. By default, it is read from\nthe \\code{input} file if provided.}\n\n\\item{envir}{An environment in which the code is to be evaluated. It can be\naccessed via \\code{\\link[=fuse_env]{fuse_env()}} inside \\code{\\link[=fuse]{fuse()}}.}\n\n\\item{quiet}{If \\code{TRUE}, do not show the progress bar. If \\code{FALSE}, the\nprogress bar will be shown after a number of seconds, which can be set via\na global \\link[=options]{option} \\code{litedown.progress.delay} (the default is \\code{2}).\nTHe progress bar output can be set via a global option\n\\code{litedown.progress.output} (the default is \\code{\\link[=stderr]{stderr()}}).}\n\n\\item{options}{Options to be passed to the renderer. See \\code{\\link[=markdown_options]{markdown_options()}}\nfor details. This argument can take either a character vector of the form\n\\code{\"+option1 option2-option3\"} (use \\code{+} or a space to enable an option, and\n\\code{-} to disable an option), or a list of the form \\code{list(option1 = value1, option2 = value2, ...)}. A string \\code{\"+option1\"} is equivalent to\n\\code{list(option1 = TRUE)}, and \\code{\"-option2\"} means \\code{list(option2 = FALSE)}.\nOptions that do not take logical values must be specified via a list, e.g.,\n\\code{list(width = 30)}.}\n\n\\item{meta}{A named list of metadata. Elements in the metadata will be used\nto fill out the template by their names and values, e.g., \\code{list(title = ...)} will replace the \\verb{$title$} variable in the template. See the Section\n\\dQuote{YAML metadata} \\href{https://yihui.org/litedown/#sec:yaml-metadata}{in the documentation} for supported\nvariables.}\n}\n\\value{\nThe output file path if output is written to a file, otherwise a\ncharacter vector of the rendered output (wrapped in \\code{\\link[xfun:raw_string]{xfun::raw_string()}}\nfor clearer printing).\n}\n\\description{\nThe function \\code{fuse()} runs code from code chunks and inline code\nexpressions in R Markdown, interweaves the results with the rest of text in\nthe input to intermediate Markdown output (which is similar to what\n\\code{knitr::knit()} does), and renders the Markdown output through \\code{mark()} to\nthe final output format, such as HTML or LaTeX (similar to\n\\code{rmarkdown::render()}). It also works on R scripts in a way similar to\n\\code{knitr::spin()}. The function \\code{fiss()} extracts code from the input, and is\nsimilar to \\code{knitr::purl()}.\n\nThe function \\code{mark()} renders Markdown to an output format via the\n\\pkg{commonmark} package.\n}\n\\note{\nFor \\code{fuse()}, you can generate the intermediate Markdown output via\n\\code{output = '.md'} or \\code{output = 'markdown'} without further calling \\code{mark()}.\n}\n\\examples{\nlibrary(litedown)\ndoc = c(\"```{r}\", \"1 + 1\", \"```\", \"\", \"$\\\\\\\\pi$ = `{r} pi`.\")\nfuse(doc)\nfuse(doc, \".tex\")\nfiss(doc)\n\nmark(c(\"Hello _World_!\", \"\", \"Welcome to **litedown**.\"))\n# if input appears to be a file path but should be treated as text, use I()\nmark(I(\"This is *not* a file.md\"))\n# that's equivalent to\nmark(text = \"This is *not* a file.md\")\n\n# output to a file\n(mark(\"_Hello_, **World**!\", output = tempfile()))\n\n# convert to other formats\nmark(\"Hello _World_!\", \".tex\")\nmark(\"Hello _**`World`**_!\", \"xml\")\nmark(\"Hello _**`World`**_!\", \"text\")\n}\n\\seealso{\n\\code{\\link[=sieve]{sieve()}}, for the syntax of R scripts to be passed to \\code{\\link[=fuse]{fuse()}}.\n\nThe spec of GitHub Flavored Markdown:\n\\url{https://github.github.com/gfm/}\n}\n"
  },
  {
    "path": "man/markdown_options.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/mark.R\n\\name{markdown_options}\n\\alias{markdown_options}\n\\title{Markdown rendering options}\n\\usage{\nmarkdown_options()\n}\n\\value{\nA character vector of all available options.\n}\n\\description{\nA list of all options to control Markdown rendering. Options that are enabled\nby default are marked by a \\code{+} prefix, and those disabled by default are\nmarked by \\code{-}.\n}\n\\details{\nSee \\url{https://yihui.org/litedown/#sec:markdown-options} for the full list of\noptions and their documentation.\n}\n\\examples{\n# all available options\nlitedown::markdown_options()\n}\n"
  },
  {
    "path": "man/pkg_desc.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/package.R\n\\name{pkg_desc}\n\\alias{pkg_desc}\n\\alias{pkg_news}\n\\alias{pkg_code}\n\\alias{pkg_citation}\n\\alias{pkg_manual}\n\\title{Print the package description, news, citation, manual pages, and source code}\n\\usage{\npkg_desc(name = detect_pkg(), type = c(\"table\", \"dl\"))\n\npkg_news(\n  name = detect_pkg(),\n  path = detect_news(name),\n  recent = 1,\n  toc = TRUE,\n  number_sections = TRUE,\n  ...\n)\n\npkg_code(\n  path = attr(detect_pkg(), \"path\"),\n  pattern = \"[.](R|c|h|f|cpp)$\",\n  toc = TRUE,\n  number_sections = TRUE,\n  link = TRUE\n)\n\npkg_citation(name = detect_pkg())\n\npkg_manual(\n  name = detect_pkg(),\n  toc = TRUE,\n  number_sections = TRUE,\n  overview = TRUE,\n  examples = list()\n)\n}\n\\arguments{\n\\item{name}{The package name (by default, it is automatically detected from\nthe \\code{DESCRIPTION} file if it exists in the current working directory or\nupper-level directories).}\n\n\\item{type}{The HTML tag for the description: \\code{table} (\\verb{<table>} with \\verb{<tr>}\nand \\verb{<td>}) or \\code{dl} (definition list \\verb{<dl>} with \\verb{<dt>} and \\verb{<dd>}).}\n\n\\item{path}{For \\code{\\link[=pkg_news]{pkg_news()}}, path to the \\code{NEWS.md} file. If empty, \\code{\\link[=news]{news()}}\nwill be called to retrieve the news entries. For \\code{\\link[=pkg_code]{pkg_code()}}, path to the\npackage root directory that contains \\verb{R/} and/or \\verb{src/} subdirectories.}\n\n\\item{recent}{The number of recent versions to show. By default, only the\nlatest version's news entries are retrieved. To show the full news, set\n\\code{recent = 0}.}\n\n\\item{toc}{Whether to add section headings to the document TOC (when TOC has\nbeen enabled for the document).}\n\n\\item{number_sections}{Whether to number section headings (when sections are\nnumbered in the document).}\n\n\\item{...}{Other arguments to be passed to \\code{\\link[=news]{news()}}.}\n\n\\item{pattern}{A regular expression to match filenames that should be treated\nas source code.}\n\n\\item{link}{Whether to add links on the file paths of source code. By\ndefault, if a GitHub repo link is detected from the \\code{BugReports} field of\nthe package \\code{DESCRIPTION}, GitHub links will be added to file paths. You\ncan also provide a string template containing the placeholder \\verb{\\%s} (which\nwill be filled out with the file paths via \\code{sprintf()}), e.g.,\n\\verb{https://github.com/yihui/litedown/blob/main/\\%s}.}\n\n\\item{overview}{Whether to include the package overview page, i.e., the\n\\code{{name}-package.Rd} page.}\n\n\\item{examples}{A list of arguments to be passed to \\code{\\link[xfun:record]{xfun::record()}} to run\nexamples each help page, e.g., \\code{list(dev = 'svg', dev.args = list(height = 6))}. If not a list (e.g., \\code{FALSE}), examples will not be run.}\n}\n\\value{\nA character vector (HTML or Markdown) that will be printed as is\ninside a code chunk of an R Markdown document.\n\n\\code{pkg_desc()} returns an HTML table containing the package metadata.\n\n\\code{pkg_news()} returns the news entries.\n\n\\code{pkg_code()} returns the package source code under the \\verb{R/} and\n\\verb{src/} directories.\n\n\\code{pkg_citation()} returns the package citation in both the plain-text\nand BibTeX formats.\n\n\\code{pkg_manual()} returns all manual pages of the package in HTML.\n}\n\\description{\nHelper functions to retrieve various types of package information that can be\nput together as the full package documentation like a \\pkg{pkgdown} website.\nThese functions can be called inside any R Markdown document.\n}\n\\examples{\n\\dontrun{\nlitedown::pkg_desc()\nlitedown::pkg_news()\nlitedown::pkg_citation()\n}\n}\n"
  },
  {
    "path": "man/raw_text.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/fuse.R\n\\name{raw_text}\n\\alias{raw_text}\n\\title{Mark a character vector as raw output}\n\\usage{\nraw_text(x, format = NULL)\n}\n\\arguments{\n\\item{x}{A character vector (each element will be treated as a line).}\n\n\\item{format}{An output format name, e.g., \\code{html} or \\code{latex}. If provided,\n\\code{x} will be wrapped in a fenced code block, e.g., \\verb{ ```\\{=html\\}}.}\n}\n\\value{\nA character vector with a special class to indicate that it should be\ntreated as raw output.\n}\n\\description{\nThis function should be called inside a code chunk, and its effect is the\nsame as the chunk option \\code{results = \"asis\"}. The input character vector will\nbe written verbatim to the output (and interpreted as Markdown).\n}\n\\examples{\nlitedown::raw_text(c(\"**This**\", \"_is_\", \"[Markdown](#).\"))\nlitedown::raw_text(\"<b>Bold</b>\", \"html\")\nlitedown::raw_text(\"\\\\\\\\textbf{Bold}\", \"latex\")\n}\n"
  },
  {
    "path": "man/reactor.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/fuse.R\n\\name{reactor}\n\\alias{reactor}\n\\title{Get and set chunk options}\n\\usage{\nreactor(...)\n}\n\\arguments{\n\\item{...}{Named values (for setting) or unnamed values (for getting).}\n}\n\\value{\nWith no arguments, \\code{reactor()} returns an environment that stores the\noptions, which can also be used to get or set options. For example, with\n\\code{opts = reactor()}, \\code{opts$name} returns an option value, and \\code{opts$name = value} sets an option to a value.\n\nWith named arguments, \\code{reactor()} sets options and returns a list of their\nold values (e.g., \\code{reactor(echo = FALSE, fig.width = 8)}). The returned\nlist can be passed to \\code{reactor()} later to restore the options.\n\nWith unnamed arguments, \\code{reactor()} returns option values after received\noption names as input. If one name is received, its value is returned\n(e.g., \\code{reactor('echo')}). If multiple names are received, a named list of\nvalues is returned (e.g., \\code{reactor(c('echo', 'fig.width'))}). A special\ncase is that if only one unnamed argument is received and it takes a list\nof named values, the list will be used to set options, e.g.,\n\\code{reactor(list(echo = FALSE, fig.width = 8))}, which is equivalent to\n\\code{reactor(echo = FALSE, fig.width = 8)}.\n}\n\\description{\nChunk options are stored in an environment returned by \\code{reactor()}. Option\nvalues can be queried by passing their names to \\code{reactor()}, and set by\npassing named values.\n}\n\\examples{\n# get options\nlitedown::reactor(\"echo\")\nlitedown::reactor(c(\"echo\", \"fig.width\"))\n\n# set options\nold = litedown::reactor(echo = FALSE, fig.width = 8)\nlitedown::reactor(c(\"echo\", \"fig.width\"))\nlitedown::reactor(old)  # restore options\n\n# use the environment directly\nopts = litedown::reactor()\nopts$echo\nmget(c(\"echo\", \"fig.width\"), opts)\nls(opts)  # built-in options\n}\n"
  },
  {
    "path": "man/roam.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/preview.R\n\\name{roam}\n\\alias{roam}\n\\title{Preview Markdown and R Markdown files}\n\\usage{\nroam(dir = \".\", live = TRUE, ...)\n}\n\\arguments{\n\\item{dir}{A directory path.}\n\n\\item{live}{Whether to enable live preview. If enabled, the browser page will\nbe automatically updated upon modification of local files used by the page\n(e.g., the Markdown file or external CSS/JS/image files). If disabled, you\ncan manually refresh the page to fully re-render it.}\n\n\\item{...}{Other arguments to be passed to \\code{\\link[xfun:new_app]{xfun::new_app()}}.}\n}\n\\value{\nA URL (invisibly) for the preview.\n}\n\\description{\nLaunch a web page to list and preview files under a directory.\n}\n\\details{\nMarkdown files will be converted to HTML and returned to the web browser\ndirectly without writing to HTML files, to keep the directory clean during\nthe preview. Clicking on a filename will bring up an HTML preview. To see its\nraw content, click on the link on its file size instead.\n}\n"
  },
  {
    "path": "man/smartypants.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/utils.R\n\\name{smartypants}\n\\alias{smartypants}\n\\title{Convert some ASCII strings to HTML entities}\n\\usage{\nsmartypants(text)\n}\n\\arguments{\n\\item{text}{A character vector of the Markdown text.}\n}\n\\value{\nA character vector of the transformed text.\n}\n\\description{\nTransform ASCII strings \\code{(c)} (copyright), \\code{(r)} (registered trademark),\n\\code{(tm)} (trademark), and fractions \\code{n/m} into \\emph{smart} typographic HTML\nentities.\n}\n\\keyword{internal}\n"
  },
  {
    "path": "man/timing_data.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/fuse.R\n\\name{timing_data}\n\\alias{timing_data}\n\\title{Get the timing data of code chunks and text blocks in a document}\n\\usage{\ntiming_data(threshold = 0, sort = TRUE, total = TRUE)\n}\n\\arguments{\n\\item{threshold}{A number (time in seconds) to subset data with. Only rows\nwith time above this threshold are returned.}\n\n\\item{sort}{Whether to sort the data by time in the decreasing order.}\n\n\\item{total}{Whether to append the total time to the data.}\n}\n\\value{\nA data frame containing input file paths, line numbers, chunk labels,\nand time. If no timing data is available, \\code{NULL} is returned.\n}\n\\description{\nTiming can be enabled via the chunk option \\code{time = TRUE} (e.g., set\n\\link{reactor}\\code{(time = TRUE)} in the first code chunk). After it is\nenabled, the execution time for code chunks and text blocks will be recorded.\nThis function can be called to retrieve the timing data later in the document\n(e.g., in the last code chunk).\n}\n\\note{\nBy default, the data will be cleared after each call of \\code{\\link[=fuse]{fuse()}} and\nwill not be available outside \\code{\\link[=fuse]{fuse()}}. To store the data persistently, you\ncan set the \\code{time} option to a file path. This is necessary if you want to\nget the timing data for multiple input documents (such as all chapters of a\nbook). Each document needs to point the \\code{time} option to the same path.\nWhen you do not need timing any more, you will need to delete this file by\nyourself.\n}\n"
  },
  {
    "path": "man/vest.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/utils.R\n\\name{vest}\n\\alias{vest}\n\\title{Add CSS/JS assets to HTML output}\n\\usage{\nvest(feature = NULL, css = NULL, js = NULL)\n}\n\\arguments{\n\\item{feature}{A character vector of features supported by CSS/JS, e.g.,\n\\code{c('article', 'callout')}. See the row names of \\code{litedown:::assets} for all\navailable features. Each feature will be mapped to CSS/JS.}\n\n\\item{css, js}{Character vectors of CSS/JS assets.}\n}\n\\value{\nA vector of \\verb{<link>} (CSS) or \\verb{<script>} (JS) tags.\n}\n\\description{\nWhile CSS/JS assets can be set via the \\code{css}/\\code{js} keys under the \\code{meta} field\nof the \\code{html} output format in YAML, this function provides another way to\nadd them, which can be called in a code chunk to dynamically add assets.\n}\n\\examples{\nlitedown:::assets[, -1]\n# add features\nlitedown::vest(c(\"copy-button\", \"tabsets\"))\n# add css/js directly\nlitedown::vest(css = \"@tabsets\", js = c(\"@tabsets\", \"@fold-details\"))\n}\n"
  },
  {
    "path": "playground/_default.Rmd",
    "content": "---\ntitle: Hello litedown\nauthor: Playground User\ndate: '`{r} Sys.Date()`'\n---\n\n# Introduction\n\nThis is a **litedown** playground powered by [webR](https://docs.r-wasm.org/webr/).\nEdit this R Markdown on the left and click **Run** (or press\nCtrl/Cmd + Shift + Enter) to see the HTML output on the right.\n\nLearn more at <https://github.com/yihui/litedown>.\n\n## R Code\n\n```{r}\nx = 1:10\nmean(x)\n```\n\nA simple plot:\n\n```{r}\n#| fig.cap = \"A scatter plot\", dev = \"svg\"\npar(mar = c(4, 4, 1, .1))\nplot(cars, pch = 19, col = \"steelblue\")\n```\n\n## Table\n\nSee @tab:penguins for the `penguins` dataset.\n\n```{r}\n#| penguins, tab.cap = \"First and last 5 rows of the `penguins` dataset.\"\npenguins\n```\n\n## Math\n\nInline math: $\\alpha + \\beta$\n\nDisplay math:\n\n$$E = mc^2$$\n\n## Summary\n\nYou can write any R Markdown supported by litedown here!"
  },
  {
    "path": "playground/setup.R",
    "content": "webr::install('KernSmooth', quiet = TRUE)\n"
  },
  {
    "path": "site/_footer.Rmd",
    "content": "```{r, include = FALSE}\nget_authors = function() {\n  d = packageDescription(litedown:::detect_pkg())\n  a = litedown:::pkg_authors(d, 'aut', FALSE)\n  xfun::join_words(a)\n}\n```\n\n::: flex-col\nDeveloped by `{r} get_authors()`\n\nSite built with [litedown](https://github.com/yihui/litedown) v`{r} packageVersion('litedown')`\n:::\n"
  },
  {
    "path": "site/_litedown.yml",
    "content": "site:\n\noutput:\n  html:\n    meta:\n      include_after: \"_footer.md\"\n    options:\n      number_sections: true\n"
  },
  {
    "path": "site/action.yml",
    "content": "name: 'Build an R package site'\ndescription: 'Build an R package site with litedown that includes the description, news, manuals, vignettes, and citation, etc.'\ninputs:\n  site-dir:\n    description: 'Directory of the site source'\n    default: 'site'\n  pkg-root:\n    description: 'Root directory of the R package in the repository'\n    default: '.'\n  exclude:\n    description: 'Pages to be excluded (filenames separated by spaces)'\n    default: 'code.Rmd'\n  cleanup:\n    description: 'Command to clean up the site directory before publishing, e.g., you can delete the `.Rmd` source files'\n    default: 'rm -f *.Rmd *.yml _*'\nruns:\n  using: \"composite\"\n  steps:\n    - name: Install R\n      uses: r-lib/actions/setup-r@HEAD\n      with:\n        use-public-rspm: true\n    - name: Install R package dependencies\n      uses: yihui/actions/setup-r-dependencies@HEAD\n      with:\n        extra-packages: litedown xfun .\n        r-universe: yihui\n    - name: Build site\n      shell: bash\n      run: |\n        cd \"${{ inputs.pkg-root }}\"\n        if [ ! -f DESCRIPTION ]; then\n          for i in $(seq 2 10); do\n            result=$(find . -mindepth $i -maxdepth $i -name DESCRIPTION | head -1)\n            if [ -n \"$result\" ]; then\n              cd \"$(dirname \"$result\")\"\n              break\n            fi\n          done\n        fi\n        mkdir -p \"${{ inputs.site-dir }}\"\n        cd \"${{ inputs.site-dir }}\"\n        rsync -a --ignore-existing \"${{ github.action_path }}\"/ ./\n        [ -d \"$OLDPWD/vignettes\" ] || rm -f articles.Rmd\n        [ -f \"$OLDPWD/NEWS.md\" ] || [ -f \"$OLDPWD/inst/NEWS.Rd\" ] || rm -f news.Rmd\n        if [ -d \"$OLDPWD/examples\" ]; then\n          cp -r \"$OLDPWD/examples\" ./\n        else\n          rm -f examples.Rmd\n        fi\n        [ -f \"_footer.Rmd\" ] && Rscript -e \"litedown::fuse('_footer.Rmd', '.md')\"\n        rm -rf ${{ inputs.exclude }}\n        Rscript -e \"litedown::fuse_site()\"\n        [ -d \"$OLDPWD/playground\" ] && cp -r \"$OLDPWD/playground/.\" playground\n        if [ -d \"examples\" ]; then\n          ls examples | grep -E '^[^_]' | grep -E '[.](Rmd|R)$' > playground/examples.txt\n        fi\n        ${{ inputs.cleanup }}\n"
  },
  {
    "path": "site/articles.Rmd",
    "content": "---\ntitle: Package vignettes\n---\n\n```{r, echo = FALSE, results = 'asis'}\npkg_name = litedown:::detect_pkg()\nres = tools::getVignetteInfo(pkg_name)\nres = if (nrow(res)) {\n  file.copy(system.file('doc', package = pkg_name), './', recursive = TRUE)\n  vigs = res[, 'PDF']\n  # redirect doc/index.html to article.html\n  if (!'index.html' %in% vigs) writeLines(\n    \"<script>location.href = location.href.replace(/\\\\/[^\\\\/]*$/, '') + '/../articles.html';</script>\", 'doc/index.html'\n  )\n  # clean up .R files\n  file.remove(file.path('doc', setdiff(res[, 'R'], '')))\n  src = res[, 'File']\n  # TODO: show excerpts of vignettes if possible\n  sprintf('- [%s](doc/%s) (source: [%s](doc/%s))\\n', res[, 'Title'], vigs, src, src)\n} else 'This package has no vignettes.'\nxfun::raw_string(res)\n```\n"
  },
  {
    "path": "site/code.Rmd",
    "content": "---\ntitle: Source code\n---\n\n```{r, echo = FALSE}\nlitedown::pkg_code()\n```\n"
  },
  {
    "path": "site/examples.Rmd",
    "content": "---\ntitle: Examples\n---\n\n```{r, echo = FALSE}\nlitedown::vest(css = 'https://cdn.jsdelivr.net/gh/yihui/litedown/inst/resources/listing.css')\n```\n\n```{css, echo = FALSE}\n.box pre {\n  & > code {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    background: none;\n  }\n  &:not(:focus) code {\n    cursor: pointer;\n    display: -webkit-box;\n    -webkit-box-orient: vertical;\n    -webkit-line-clamp: 10;\n  }\n}\npre, pre:hover {\n  white-space: pre-wrap;\n}\n.box a {\n  text-decoration: none;\n}\n```\n\n```{r, echo = FALSE, results = 'asis'}\nres = if (dir.exists('examples')) {\n  lapply(litedown:::find_input('examples'), function(f) {\n    h = xfun::with_ext(f, 'html'); b = basename(f)\n    x = xfun::read_utf8(f)\n    # get title\n    t = xfun::grep_sub('^title: [\"\\']?(.+?)[\"\\']?\\\\s*$', '\\\\1', x)\n    t = if (length(t)) t[1] else basename(h)\n    # get example number\n    i = xfun::grep_sub('^([0-9]+).*', '\\\\1', b)\n    i = if (length(i)) paste0(i, '\\\\. ') else ''\n    # put example source in a box\n    xfun::fenced_div(c(\n      xfun::fenced_div(\n        sprintf('%s[%s](%s) _(source: [%s](%s))_ [&#9205;](%s){.run}', i, t, h, b, f, h),\n        '.caption .name'\n      ),\n      xfun::fenced_block(x, '.md')\n    ), '.box')\n  })\n} else \"The 'examples/' directory does not exist.\"\nxfun::raw_string(unlist(res))\n```\n"
  },
  {
    "path": "site/index.Rmd",
    "content": "---\ntitle: \"`{r} pkg_name`: `{r} packageDescription(pkg_name, fields = 'Title')`\"\ndate: \"`{r} Sys.Date()`\"\n---\n\n```{r, order = 0, include = FALSE}\nlitedown::reactor(echo = FALSE)\n# package description\npkg_name = litedown:::detect_pkg()\npkg_path = attr(pkg_name, 'path')\n# if man/figures exists and used, copy it to current dir\nfig = file.path(pkg_path, 'man/figures')\nif (!xfun::same_path('.', pkg_path) && dir.exists(fig)) {\n  xfun::dir_create('man')\n  file.copy(fig, 'man', recursive = TRUE)\n}\n```\n\n```{r}\nlitedown::pkg_desc(pkg_name)\n```\n\n```{r, results = 'asis'}\n# readme, if exists\nif (file.exists(readme <- file.path(pkg_path, 'README.md'))) {\n  x = xfun::read_utf8(readme)\n  i = which(x == '<!-- badges: start -->')\n  # remove lines before badges since they usually contain duplicated info (e.g., title)\n  if (length(i)) x = x[-seq_len(i[1] - 1)]\n  i = which(x == paste('#', pkg_name))\n  # remove the title line if it still exists\n  if (length(i)) x = x[-i[1]]\n  xfun::raw_string(x)\n}\n```\n\n## Appendix {.appendix}\n\nTo cite the package **`{r} pkg_name`** in publications, please use:\n\n```{r, warning = FALSE}\nlitedown::pkg_citation(pkg_name)\n```\n"
  },
  {
    "path": "site/manual.Rmd",
    "content": "---\ntitle: Help pages\n---\n\n```{css, echo = FALSE}\nh2 {\n  text-align: center;\n  margin-top: 2em;\n}\n```\n\n`{r} litedown::pkg_manual()`\n"
  },
  {
    "path": "site/news.Rmd",
    "content": "---\ntitle: News\n---\n\n```{r, echo = FALSE}\nlitedown::pkg_news(recent = 0, number_sections = FALSE)\n```\n"
  },
  {
    "path": "site/playground/_default.R",
    "content": "#' ---\n#' title: Hello World!\n#' ---\n\n#' Just type your R code here and run[^1]!\n\nsessionInfo()\n\n#' Package developers: you can provide your own default example in the script `playground/_default.R` in your package. For more info, please see https://yihui.org/litedown/#sec:package-playground\n\n#' [^1]: I mean \"execute\" the code via the \"Run\" button, instead of running off as if it were going to explode.\n"
  },
  {
    "path": "site/playground/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Playground</title>\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/codemirror@5.65.18/lib/codemirror.min.css\">\n<style>\n* { margin: 0; padding: 0; box-sizing: border-box; }\n:root {\n  --header-h: 44px;\n  --border: #ccc;\n  --bg: #f7f7f7;\n  --accent: #4a90d9;\n}\nbody { font-family: system-ui, -apple-system, sans-serif; height: 100vh; overflow: hidden; }\nheader {\n  height: var(--header-h); display: flex; align-items: center;\n  padding: 0 12px; background: var(--bg); border-bottom: 1px solid var(--border);\n  gap: 10px;\n}\nheader h1 { font-size: 15px; font-weight: 600; }\n#run-btn {\n  padding: 4px 14px; border: 1px solid var(--accent); border-radius: 4px;\n  background: var(--accent); color: #fff; cursor: pointer; font-size: 13px;\n}\n#run-btn:hover { background: #3a7bc8; }\n#run-btn:disabled { opacity: .5; cursor: not-allowed; }\n#status { font-size: 12px; color: #666; flex: 1; text-align: right; }\n#examples-select {\n  font-size: 12px; padding: 3px 6px;\n  border: 1px solid var(--border); border-radius: 4px; background: #fff; cursor: pointer;\n}\n.container { display: flex; height: calc(100vh - var(--header-h)); }\n.pane { flex: 1; min-width: 0; display: flex; flex-direction: column; }\n.pane-header {\n  height: 28px; display: flex; align-items: center; padding: 0 10px;\n  background: var(--bg); border-bottom: 1px solid var(--border);\n  font-size: 12px; font-weight: 600; color: #555;\n}\n#editor-pane { border-right: 1px solid var(--border); }\n#editor-wrap { flex: 1; overflow: hidden; }\n#editor-wrap .CodeMirror { height: 100%; font-size: 13px; }\n#preview-frame { flex: 1; border: none; width: 100%; background: #fff; transition: opacity .2s; }\n#loading-overlay {\n  position: fixed; inset: 0; background: rgba(255,255,255,.92);\n  display: flex; flex-direction: column; align-items: center; justify-content: center;\n  z-index: 1000; font-family: system-ui, sans-serif;\n}\n#loading-overlay h2 { font-size: 18px; margin-bottom: 12px; }\n#loading-overlay p { font-size: 13px; color: #666; max-width: 400px; text-align: center; }\n.spinner {\n  width: 32px; height: 32px; border: 3px solid #eee;\n  border-top-color: var(--accent); border-radius: 50%;\n  animation: spin .8s linear infinite; margin-bottom: 16px;\n}\n@keyframes spin { to { transform: rotate(360deg); } }\n</style>\n</head>\n<body>\n\n<div id=\"loading-overlay\">\n  <div class=\"spinner\"></div>\n  <h2>Initializing Playground</h2>\n  <p id=\"loading-msg\">Loading webR…</p>\n</div>\n\n<header>\n  <h1>Playground</h1>\n  <button id=\"run-btn\" disabled>&#9654; Run</button>\n  <select id=\"examples-select\"><option value=\"\">Load example…</option></select>\n  <span id=\"status\"></span>\n</header>\n\n<div class=\"container\">\n  <div class=\"pane\" id=\"editor-pane\">\n    <div class=\"pane-header\">Source Editor</div>\n    <div id=\"editor-wrap\"></div>\n  </div>\n  <div class=\"pane\" id=\"preview-pane\">\n    <div class=\"pane-header\">HTML Preview</div>\n    <!-- An iframe is the cleanest way to display litedown's standalone HTML output:\n         its own <head> CSS/JS would conflict with the playground page if injected directly. -->\n    <iframe id=\"preview-frame\"></iframe>\n  </div>\n</div>\n\n<!-- CodeMirror 5 — plain <script> tags from CDN, no ES-module deduplication issues\n     (the problem that plagued CodeMirror 6 when loading its modules from CDN). -->\n<script src=\"https://cdn.jsdelivr.net/npm/codemirror@5.65.18/lib/codemirror.min.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/codemirror@5.65.18/mode/markdown/markdown.min.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/codemirror@5.65.18/mode/r/r.min.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/codemirror@5.65.18/mode/yaml/yaml.min.js\"></script>\n<!-- xml mode is needed by the markdown mode for inline HTML highlighting -->\n<script src=\"https://cdn.jsdelivr.net/npm/codemirror@5.65.18/mode/xml/xml.min.js\"></script>\n\n<script type=\"module\">\n// ---------------------------------------------------------------------------\n// UI references\n// ---------------------------------------------------------------------------\nconst runBtn       = document.getElementById('run-btn');\nconst statusEl     = document.getElementById('status');\nconst overlay      = document.getElementById('loading-overlay');\nconst loadingMsg   = document.getElementById('loading-msg');\nconst previewFrame = document.getElementById('preview-frame');\nconst sel          = document.getElementById('examples-select');\n\nconst setStatus  = (msg) => { statusEl.textContent = msg; };\nconst setLoading = (msg) => { loadingMsg.textContent = msg; };\nconst escapeHtml = (s) =>\n  s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\n// ---------------------------------------------------------------------------\n// CodeMirror 5 editor  (global `CodeMirror` injected by the <script> tags above)\n// ---------------------------------------------------------------------------\n\n// Register {r} as a MIME alias so fenced ```{r} blocks get R syntax highlighting\nCodeMirror.defineMIME('text/{r}', 'text/x-rsrc');\n\nconst rmdMode = { name: 'markdown', fencedCodeBlockHighlighting: true, highlightFormatting: true };\nconst rMode   = 'text/x-rsrc';\n\nconst editor = CodeMirror(document.getElementById('editor-wrap'), {\n  value: '',\n  mode: rmdMode,\n  lineNumbers: true,\n  lineWrapping: true,\n  extraKeys: {\n    'Ctrl-Shift-Enter': () => run(),\n    'Cmd-Shift-Enter':  () => run(),\n  },\n});\n\nfunction setEditorMode(file) {\n  const isR = /\\.R$/i.test(file) && !/\\.Rmd$/i.test(file);\n  editor.setOption('mode', isR ? rMode : rmdMode);\n}\n\n// ---------------------------------------------------------------------------\n// Default content — fetch _default.Rmd or _default.R from playground dir\n// ---------------------------------------------------------------------------\nasync function loadDefault() {\n  for (const file of ['_default.Rmd', '_default.R']) {\n    try {\n      const res = await fetch('./' + file);\n      if (!res.ok) continue;\n      const text = await res.text();\n      sel.options[0].value = file;\n      sel.value = file;\n      setEditorMode(file);\n      editor.setValue(text.trimStart());\n      return;\n    } catch (_) {}\n  }\n  // Fallback: empty editor in Rmd mode\n  editor.setOption('mode', rmdMode);\n  editor.setValue('');\n}\n\n// ---------------------------------------------------------------------------\n// Examples dropdown — numbered examples appended from examples.txt when\n// available (generated by the GitHub Actions workflow).\n// ---------------------------------------------------------------------------\nsel.addEventListener('change', async () => {\n  if (sel.selectedIndex === 0) {\n    await loadDefault();\n    return;\n  }\n  try {\n    const r = await fetch('../examples/' + sel.value);\n    if (!r.ok) throw new Error(r.statusText);\n    setEditorMode(sel.value);\n    editor.setValue(await r.text());\n  } catch (e) {\n    console.error('Failed to load example:', e);\n  }\n});\n\nasync function initExamples() {\n  try {\n    const res = await fetch('./examples.txt');\n    if (!res.ok) return;\n    const files = (await res.text()).trim().split('\\n').filter(Boolean);\n    for (const f of files) {\n      const opt = document.createElement('option');\n      opt.value = f;\n      opt.textContent = f;\n      sel.appendChild(opt);\n    }\n  } catch (_) { /* examples.txt not present */ }\n}\n\n// ---------------------------------------------------------------------------\n// webR setup\n// ---------------------------------------------------------------------------\nlet webR, shelter;\n\nasync function initWebR() {\n  setLoading('Loading webR (this may take a moment)…');\n  const { WebR } = await import('https://webr.r-wasm.org/latest/webr.mjs');\n  webR = new WebR();\n  await webR.init();\n  shelter = await new webR.Shelter();\n\n  // `codetools` is a recommended base-R package not bundled with webR but\n  // required during code evaluation (used internally by the R evaluator).\n  setLoading('Installing R packages…');\n  await webR.evalRVoid(\n    'webr::shim_install(); webr::install(c(\"commonmark\", \"xfun\", \"litedown\", \"codetools\"), repos = c(\"https://yihui.r-universe.dev\", \"https://repo.r-wasm.org\"), quiet = TRUE)'\n  );\n\n  // Run setup.R if present in the playground directory\n  try {\n    const setupRes = await fetch('./setup.R');\n    if (setupRes.ok) {\n      setLoading('Running setup.R…');\n      await webR.evalRVoid(await setupRes.text());\n    }\n  } catch (_) { /* setup.R not present */ }\n\n  setLoading('Ready!');\n}\n\n// ---------------------------------------------------------------------------\n// Run — write source to /tmp, call fuse(), display HTML\n// ---------------------------------------------------------------------------\nlet running = false;\n\nasync function run() {\n  if (running || !webR) return;\n  running = true;\n  runBtn.disabled = true;\n  setStatus('Running…');\n  previewFrame.style.opacity = '.25';\n  document.body.style.cursor = 'wait';\n  const t0 = performance.now();\n\n  const fileName = sel.value || '_playground.Rmd';\n  const tmpFile  = '/tmp/' + fileName;\n\n  try {\n    // Write source to the webR virtual filesystem\n    await shelter.captureR(\n      'writeLines(text, con = file)',\n      { env: { text: editor.getValue(), file: tmpFile } }\n    );\n\n    // fuse() handles both .R and .Rmd inputs.\n    // setwd(\"/tmp\") so plot files land there and litedown base64-encodes them.\n    const result = await shelter.captureR(\n      'setwd(\"/tmp\"); litedown::fuse(file, output = structure(NA, full = TRUE), quiet = TRUE)',\n      { env: { file: tmpFile } }\n    );\n\n    const html = (await result.result.toArray()).join('\\n');\n\n    const doc = previewFrame.contentDocument || previewFrame.contentWindow.document;\n    doc.open();\n    doc.write(html);\n    doc.close();\n    const secs = ((performance.now() - t0) / 1000).toFixed(1);\n    setStatus(`Done in ${secs}s`);\n  } catch (e) {\n    console.error('Run error:', e);\n    setStatus('Error — see preview for details');\n    const errHtml = [\n      '<html><body style=\"font-family:system-ui;padding:20px\">',\n      '<h3 style=\"color:#c00\">Error</h3>',\n      '<pre style=\"white-space:pre-wrap\">' + escapeHtml(String(e)) + '</pre>',\n      '</body></html>'\n    ].join('');\n    const doc = previewFrame.contentDocument || previewFrame.contentWindow.document;\n    doc.open();\n    doc.write(errHtml);\n    doc.close();\n  } finally {\n    running = false;\n    runBtn.disabled = false;\n    previewFrame.style.opacity = '';\n    document.body.style.cursor = '';\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Bootstrap\n// ---------------------------------------------------------------------------\nasync function init() {\n  await loadDefault();  // must complete before auto-run so editor is populated\n  initExamples();       // fire-and-forget; independent of webR\n  try {\n    await initWebR();\n    setLoading('Running default example…');\n    runBtn.disabled = false;\n    await run();        // auto-run renders the default content and warms up fuse()\n    overlay.style.display = 'none';\n    setStatus('Ready — click Run or press Ctrl+Shift+Enter');\n  } catch (e) {\n    overlay.style.display = 'none';\n    setStatus('webR failed to load: ' + e.message);\n    console.error('Init error:', e);\n  }\n}\n\nrunBtn.addEventListener('click', run);\ninit();\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tests/examples.R",
    "content": "if (file.exists(f <- '../examples/_run.R')) sys.source(f, globalenv(), chdir = TRUE)\n"
  },
  {
    "path": "tests/test-cran/test-crack.R",
    "content": "library(testit)\n\n# helper: build a fake code chunk block\nnew_chunk = function(engine, source) {\n  list(\n    source = source, type = 'code_chunk',\n    options = list(engine = engine, label = 'chunk-1'),\n    lines = c(1L, length(source) + 2L), code_start = 2L\n  )\n}\n\nassert('crack() returns an empty list for empty input', {\n  (crack(character(0)) %==% list())\n  # a single empty string becomes a text_block\n  (length(crack('')) %==% 1L)\n  (crack('')[[1]]$type %==% 'text_block')\n})\n\nassert('crack() identifies a code chunk followed by text', {\n  src = c('```{r}', '1 + 1', '```', '', 'Hello world.')\n  res = crack(text=src)\n  (length(res) %==% 2L)\n  (res[[1]]$type %==% 'code_chunk')\n  (res[[2]]$type %==% 'text_block')\n  (res[[1]]$options$engine %==% 'r')\n  (res[[1]]$source %==% '1 + 1')\n})\n\nassert('crack() labels code chunks as chunk-1, chunk-2, ...', {\n  src = c('```{r}', 'x = 1', '```', '', '```{r}', 'x + 1', '```')\n  res = crack(text=src)\n  chunks = Filter(function(b) b$type == 'code_chunk', res)\n  (length(chunks) %==% 2L)\n  (chunks[[1]]$options$label %==% 'chunk-1')\n  (chunks[[2]]$options$label %==% 'chunk-2')\n})\n\nassert('crack() respects explicit chunk labels', {\n  src = c('```{r my-label}', 'x = 1', '```')\n  res = crack(text=src)\n  (res[[1]]$options$label %==% 'my-label')\n})\n\nassert('crack() supports pipe-comment chunk options', {\n  src = c('```{r}', '#| eval: false', 'x = 1', '```')\n  res = crack(text=src)\n  (isFALSE(res[[1]]$options$eval))\n})\n\nassert('crack() records line numbers of blocks', {\n  src = c('```{r}', 'x = 1', '```', 'text')\n  res = crack(text=src)\n  (res[[1]]$lines %==% c(1L, 3L))\n  (res[[2]]$lines %==% c(4L, 4L))\n})\n\nassert('crack() handles text-only input (no code chunks)', {\n  res = crack(text=c('Hello', 'world'))\n  (length(res) %==% 1L)\n  (res[[1]]$type %==% 'text_block')\n})\n\nassert('crack() handles inline code in text blocks', {\n  src = c('Value is `{r} 1 + 1`.')\n  res = crack(text=src)\n  (length(res) %==% 1L)\n  (res[[1]]$type %==% 'text_block')\n  # source is a list (not plain character) when inline code is present\n  (is.list(res[[1]]$source))\n})\n\nassert('crack() supports non-R engines', {\n  src = c('```{python}', 'x = 1', '```')\n  res = crack(text=src)\n  (res[[1]]$options$engine %==% 'python')\n})\n\nassert('sieve() returns list for R scripts', {\n  src = c(\"x = 1\", \"x + 1\")\n  res = sieve(text=src)\n  (is.list(res))\n  (length(res) >= 1L)\n  (res[[1]]$type %==% 'code_chunk')\n  (res[[1]]$options$engine %==% 'r')\n})\n\nassert(\"sieve() extracts text blocks from lines starting with #'\", {\n  src = c(\"#' This is *doc*.\", '', 'x = 1')\n  res = sieve(text=src)\n  types = vapply(res, `[[`, '', 'type')\n  ('text_block' %in% types)\n  ('code_chunk' %in% types)\n  # the text block source contains the doc text\n  tb = res[[which(types == 'text_block')]]\n  (grepl('doc', tb$source))\n})\n\nassert('sieve() splits code by #| comments into separate chunks', {\n  src = c('#| eval: false', 'x = 1', '#| echo: false', 'y = 2')\n  res = sieve(text=src)\n  (length(res) == 2L)\n  (isFALSE(res[[1]]$options$eval))\n  (isFALSE(res[[2]]$options$echo))\n})\n\nassert(\"sieve() handles pure code with no #| or #' markers\", {\n  src = c('x = 1', 'y = 2', 'x + y')\n  res = sieve(text=src)\n  # splits by expressions\n  (length(res) >= 1L)\n  all_code = all(vapply(res, function(b) b$type == 'code_chunk', TRUE))\n  (all_code)\n})\n"
  },
  {
    "path": "tests/test-cran/test-fuse.R",
    "content": "library(testit)\n\nassert('fuse() evaluates inline code in text blocks', {\n  src = 'Value is `{r} 1 + 1`.'\n  out = fuse(text = src, output = 'markdown')\n  ('Value is 2.' %==% as.character(out))\n})\n\nassert('fuse() handles text input without code chunks', {\n  src = '# My Document\\n\\nJust some text.'\n  out = fuse(text = src, output = 'markdown')\n  (src %==% as.character(out))\n})\n\nassert('code blocks after asis HTML output are rendered correctly (regression)', {\n  # regression: asis output containing HTML tags must not cause following code\n  # blocks to be treated as HTML content (the resulting HTML should have no\n  # triple backticks)\n  out = fuse(text = '#| results=\"asis\"\\ncat(\"<p>hi</p>\\\\n\")\\n#| foo\\n1:2')\n  (!grepl('```', out))\n  (as.character(gsub('.*<pre><code class=\"language-r\">1:2.*', '', out)) %==% '')\n})\n\nassert('fuse() fig.path option controls plot file location', {\n  src = c(\n    '---', 'output:', '  html:', '    options:', '      embed_resources: false',\n    '---', '', '```{r}', 'plot(1)', '```'\n  )\n  old = reactor(fig.path = 'foo')\n  out = fuse(text = src, output = 'markdown')\n  reactor(old)\n  (any(grepl('foochunk-1-1.png', out, fixed = TRUE)))\n  (file_exists('foochunk-1-1.png'))\n  unlink('foochunk-1-1.png')\n\n  old = reactor(fig.path = 'foo/bar')\n  out = fuse(text = src, output = 'markdown')\n  reactor(old)\n  (any(grepl('foo/barchunk-1-1.png', out, fixed = TRUE)))\n  (file_exists(file.path('foo', 'barchunk-1-1.png')))\n  unlink('foo', recursive = TRUE)\n})\n\nassert('fuse() does not let nested fuse() override outer plot files (#127)', {\n  b = tempfile(fileext = '.Rmd')\n  a = tempfile(fileext = '.Rmd')\n  writeLines(c('```{r}', 'plot(1)', '```'), b)\n  writeLines(c(\n    '```{r}', 'plot(1)', '```', '',\n    '```{r}',\n    paste0('fuse(b, output = \"markdown\")'),\n    '```'\n  ), a)\n  out = fuse(a, output = 'markdown')\n  a_files = paste0(tools::file_path_sans_ext(a), '__files')\n  b_files = paste0(tools::file_path_sans_ext(b), '__files')\n  (any(grepl(basename(a_files), out, fixed = TRUE)))\n  (any(grepl(basename(b_files), out, fixed = TRUE)))\n  # outer and inner plots must be in separate directories\n  (dir.exists(a_files))\n  (dir.exists(b_files))\n  (length(list.files(a_files, '\\\\.png$')) > 0L)\n  (length(list.files(b_files, '\\\\.png$')) > 0L)\n  unlink(c(a, b, a_files, b_files), recursive = TRUE)\n})\n\n\nassert('fiss() extracts R code from an R Markdown document', {\n  src = c('```{r}', 'x = 1', '```', 'text', '```{r}', 'x + 1', '```')\n  out = fiss(I(src))\n  (c('x = 1', '', 'x + 1', '') %==% as.character(out))\n})\n\nassert('fiss() respects purl = FALSE chunk option', {\n  src = c('```{r, purl=FALSE}', 'secret = 1', '```', '```{r}', 'public = 2', '```')\n  out = fiss(I(src))\n  (c('public = 2', '') %==% as.character(out))\n})\n"
  },
  {
    "path": "tests/test-cran/test-fuse.md",
    "content": "# Snapshot tests for fuse()\n\n## Basic code chunk execution\n\n````r\nlibrary(litedown)\nfuse(text = c('```{r}', '1 + 1', '```'), output = 'markdown')\n````\n````\n``` {.r}\n1 + 1\n```\n\n```\n#> [1] 2\n```\n````\n\n## Inline code evaluation\n\n````r\nfuse(text = 'Value is `{r} 1 + 1`.', output = 'markdown')\n````\n````\nValue is 2.\n````\n\n## echo = FALSE hides source, shows output\n\n````r\nfuse(text = c('```{r, echo=FALSE}', '2 * 3', '```'), output = 'markdown')\n````\n````\n```\n#> [1] 6\n```\n````\n\n## eval = FALSE doesn't run code\n\n````r\nfuse(text = c('```{r, eval=FALSE}', 'stop(\"No\")', '```'), output = 'markdown')\n````\n````\n``` {.r}\nstop(\"No\")\n```\n````\n\n## results = FALSE suppresses output\n\n````r\nfuse(text = c('```{r, results=FALSE}', '1 + 1', '```'), output = 'markdown')\n````\n````\n``` {.r}\n1 + 1\n```\n````\n\n## include = FALSE produces no output at all\n\n````r\nfuse(text = c('```{r, include=FALSE}', '1 + 1', '```'), output = 'markdown')\n````\n````\n\n````\n\n## comment option prefixes each output line\n\n````r\nfuse(text = c('```{r, comment=\"##\"}', '1:3', '```'), output = 'markdown')\n````\n````\n``` {.r}\n1:3\n```\n\n```\n##[1] 1 2 3\n```\n````\n\n## comment = \"\" produces no prefix\n\n````r\nfuse(text = c('```{r, comment=\"\"}', '1:3', '```'), output = 'markdown')\n````\n````\n``` {.r}\n1:3\n```\n\n```\n[1] 1 2 3\n```\n````\n\n## collapse = TRUE merges source and output blocks\n\n````r\nfuse(text = c('```{r, collapse=TRUE}', '1 + 1', '2 + 2', '```'), output = 'markdown')\n````\n````\n``` {.r}\n1 + 1\n#> [1] 2\n2 + 2\n#> [1] 4\n```\n````\n\n## results = \"asis\" output is written verbatim (no code fence)\n\n````r\nfuse(text = c('#| results=\"asis\"', 'cat(\"<p>hi</p>\\\\n\")', '#| foo', '1:2'), output = 'markdown')\n````\n````\n``` {.r}\ncat(\"<p>hi</p>\\n\")\n```\n<p>hi</p>\n``` {.r}\n1:2\n```\n\n```\n#> [1] 1 2\n```\n````\n\n## error = TRUE captures errors instead of stopping\n\n````r\nfuse(text = c('```{r, error=TRUE}', 'stop(\"oops\")', '```'), output = 'markdown')\n````\n````\n``` {.r}\nstop(\"oops\")\n```\n\n``` {.plain .error}\n#> Error: oops\n```\n````\n\n## warning = TRUE includes warnings in output\n\n````r\nfuse(text = c('```{r, warning=TRUE}', 'warning(\"careful!\")', '```'), output = 'markdown')\n````\n````\n``` {.r}\nwarning(\"careful!\")\n```\n\n``` {.plain .warning}\n#> careful!\n```\n````\n\n## warning = FALSE suppresses warnings\n\n````r\nfuse(text = c('```{r, warning=FALSE}', 'warning(\"shh\")', '```'), output = 'markdown')\n````\n````\n``` {.r}\nwarning(\"shh\")\n```\n````\n\n## message = TRUE includes messages in output\n\n````r\nfuse(text = c('```{r, message=TRUE}', 'message(\"hey\")', '```'), output = 'markdown')\n````\n````\n``` {.r}\nmessage(\"hey\")\n```\n\n``` {.plain .message}\n#> hey\n```\n````\n\n## Multiple chunks with independent results\n\n````r\nfuse(text = c('```{r}', 'x = 42', '```', '', '```{r}', 'x', '```'), output = 'markdown')\n````\n````\n``` {.r}\nx = 42\n```\n\n``` {.r}\nx\n```\n\n```\n#> [1] 42\n```\n````\n\n## Text-only input passes through unchanged\n\n````r\nfuse(text = '# Title\\n\\nJust some text.', output = 'markdown')\n````\n````\n# Title\n\nJust some text.\n````\n"
  },
  {
    "path": "tests/test-cran/test-mark.R",
    "content": "library(testit)\n\nassert('mark() with empty or trivial input produces empty output', {\n  # character(0) gives a length-0 raw_string\n  (length(mark(character(0))) %==% 0L)\n  # empty string gives an empty raw_string\n  (nchar(mark('')) %==% 0L)\n  # empty file does not error and writes an empty HTML file\n  f = tempfile()\n  file.create(f)\n  out = mark(f)\n  (file_exists(out))\n  (file.info(out)[, 'size'] %==% 0)\n  unlink(c(f, out))\n})\n\nassert('mark() writes to a file when given a file path and returns it invisibly', {\n  f = tempfile(fileext = '.html')\n  ret = mark('Hello.', output = f)\n  (file_exists(f))\n  (ret %==% f)\n  unlink(f)\n})\n\nassert('mark() treats I() input as text, not a file path', {\n  out = mark(I('foo.md'), output = NA)\n  (grepl('foo', out))\n})\n\nassert('mark() supports LaTeX and plain text output formats', {\n  tex = mark('Hello _world_!', '.tex')\n  (grepl('\\\\\\\\emph', tex))\n  txt = mark('Hello _world_!', 'text')\n  (grepl('Hello world', txt))\n})\n\nassert('mark() processes YAML metadata and applies title to output', {\n  src = c('---', 'title: My Doc', '---', '', '# Hello')\n  out = mark(I(src))\n  (grepl('<title>My Doc</title>', out, fixed = TRUE))\n})\n"
  },
  {
    "path": "tests/test-cran/test-mark.md",
    "content": "# Snapshot tests for mark()\n\nBasic Markdown rendering is verified below. The expected output blocks are generated and\ncompared on each test run.\n\n## Basic inline formatting\n\n```r\nlibrary(litedown)\nmark('**bold** and _italic_')\n```\n```\n<p><strong>bold</strong> and <em>italic</em></p>\n```\n\n## Headings\n\n```r\nmark(c('# H1', '', '## H2', '', '### H3'))\n```\n```\n<h1 id=\"chp:h1\">H1</h1>\n<h2 id=\"sec:h2\">H2</h2>\n<h3 id=\"sec:h3\">H3</h3>\n```\n\n## Unordered list\n\n```r\nmark(c('- item a', '- item b', '- item c'))\n```\n```\n<ul>\n<li>item a</li>\n<li>item b</li>\n<li>item c</li>\n</ul>\n```\n\n## Ordered list\n\n```r\nmark(c('1. first', '2. second', '3. third'))\n```\n```\n<ol>\n<li>first</li>\n<li>second</li>\n<li>third</li>\n</ol>\n```\n\n## Fenced code block with language attribute\n\n```r\nmark(c('```r', '1 + 1', '```'))\n```\n```\n<pre><code class=\"language-r\">1 + 1\n</code></pre>\n```\n\n## Inline code\n\n```r\nmark('Use `x <- 1` to assign.')\n```\n```\n<p>Use <code>x &lt;- 1</code> to assign.</p>\n```\n\n## Hyperlink\n\n```r\nmark('[example](https://example.com)')\n```\n```\n<p><a href=\"https://example.com\">example</a></p>\n```\n\n## Blockquote\n\n```r\nmark('> A wise quote.')\n```\n```\n<blockquote>\n<p>A wise quote.</p>\n</blockquote>\n```\n\n## Horizontal rule\n\n```r\nmark(c('---'))\n```\n```\n<hr />\n```\n\n## Table\n\n```r\nmark(c('| a | b |', '|---|---|', '| 1 | 2 |', '| 3 | 4 |'))\n```\n```\n<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>1</td>\n<td>2</td>\n</tr>\n<tr>\n<td>3</td>\n<td>4</td>\n</tr>\n</tbody>\n</table>\n```\n\n## Superscript and subscript\n\n```r\nmark('x^2^ and H~2~O')\n```\n```\n<p>x<sup>2</sup> and H<sub>2</sub>O</p>\n```\n\n## Footnote\n\n```r\nmark(c('text[^1]', '', '[^1]: A footnote.'))\n```\n```\n<p>text<sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup></p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>A footnote. <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n</ol>\n</section>\n```\n\n## Raw HTML block\n\n```r\nmark(c('```{=html}', '<hr class=\"special\">', '```'))\n```\n```\n<hr class=\"special\">\n```\n\n## LaTeX output format\n\n```r\nmark('Hello **world** and _litedown_.', '.tex')\n```\n```\nHello \\textbf{world} and \\emph{litedown}.\n```\n\n## Plain text output format\n\n```r\nmark('Hello **world**!', 'text')\n```\n```\nHello world!\n```\n\n## Smart quotes (smart option enabled by default)\n\n```r\nmark('\"hello\" and \\'world\\'')\n```\n```\n<p>“hello” and ‘world’</p>\n```\n"
  },
  {
    "path": "tests/test-cran/test-reactor.R",
    "content": "library(testit)\n\nassert('reactor() can get multiple options at once', {\n  res = reactor(c('echo', 'eval'))\n  (is.list(res))\n  (length(res) %==% 2L)\n  (names(res) %==% c('echo', 'eval'))\n})\n\nassert('reactor() can set options and returns old values', {\n  old = reactor(echo = FALSE, fig.width = 5)\n  (isTRUE(old$echo))\n  (old$fig.width %==% 7)\n  (isFALSE(reactor('echo')))\n  (reactor('fig.width') %==% 5)\n  # restore\n  reactor(old)\n  (isTRUE(reactor('echo')))\n  (reactor('fig.width') %==% 7)\n})\n\nassert('reactor() accepts a list as first unnamed argument to set options', {\n  old = reactor(list(echo = FALSE, fig.width = 3))\n  (isFALSE(reactor('echo')))\n  (reactor('fig.width') %==% 3)\n  reactor(old)\n  (isTRUE(reactor('echo')))\n  (reactor('fig.width') %==% 7)\n})\n\nassert('reactor() allows accessing options via $ on returned environment', {\n  opts = reactor()\n  (isTRUE(opts$echo))\n  (isTRUE(opts$eval))\n})\n\nassert('engines() can register and retrieve a custom engine', {\n  old = engines(mytest = function(x, inline = FALSE, ...) 'custom')\n  (is.function(engines('mytest')))\n  (engines('mytest')(NULL) %==% 'custom')\n  # restore\n  engines(old)\n  (is.null(engines('mytest')))\n})\n\nassert('fuse_env() returns the global environment when called outside fuse()', {\n  (fuse_env() %==% globalenv())\n})\n\nassert('get_context() returns NULL for items outside fuse()', {\n  (is.null(get_context('input')))\n  (is.null(get_context('format')))\n})\n\n"
  },
  {
    "path": "tests/test-cran/test-reactor.md",
    "content": "# Snapshot tests for get_context()\n\n## get_context('format') returns the output format inside fuse()\n\n````r\nlibrary(litedown)\nfuse(text = c('```{r}', 'get_context(\"format\")', '```'), output = 'markdown')\n````\n````\n``` {.r}\nget_context(\"format\")\n```\n\n```\n#> [1] \"markdown\"\n```\n````\n\n## get_context('input') is NULL when fuse() uses text input (no file)\n\n````r\nfuse(text = c('```{r}', 'get_context(\"input\")', '```'), output = 'markdown')\n````\n````\n``` {.r}\nget_context(\"input\")\n```\n\n```\n#> NULL\n```\n````\n"
  },
  {
    "path": "tests/test-cran/test-utils.R",
    "content": "library(testit)\n\nassert('smartypants() transforms ASCII typographic markers', {\n  # all pants names produce their HTML entities\n  mkd = paste(names(pants), collapse = ' ')\n  (smartypants(mkd) %==% paste(\n    '&frac12; &frac13; &frac23; &frac14; &frac34; &frac15; &frac25; &frac35;',\n    '&frac45; &frac16; &frac56; &frac18; &frac38; &frac58; &frac78;',\n    '&#8528; &#8529; &#8530; &copy; &reg; &trade;'\n  ))\n  # fractions are transformed\n  (smartypants('1/2') %==% '&frac12;')\n  (smartypants('1/3') %==% '&frac13;')\n  (smartypants('3/4') %==% '&frac34;')\n  # copyright/trademark symbols\n  (smartypants('(c)') %==% '&copy;')\n  (smartypants('(r)') %==% '&reg;')\n  (smartypants('(tm)') %==% '&trade;')\n  # no transformation in code blocks\n  (smartypants(c('text (c)', '```', '(c)', '```')) %==% c('text &copy;', '```', '(c)', '```'))\n})\n\nassert('merge_list() merges named lists, later values override earlier ones', {\n  (merge_list(list(a = 1), list(b = 2)) %==% list(a = 1, b = 2))\n  (merge_list(list(a = 1), list(a = 2)) %==% list(a = 2))\n  (merge_list(list(a = 1, b = 2), list(a = 3)) %==% list(a = 3, b = 2))\n  # non-list arguments are ignored\n  (merge_list(list(a = 1), NULL) %==% list(a = 1))\n  (merge_list(list(a = 1), 'not a list') %==% list(a = 1))\n  # first list wins when later args are non-list\n  (merge_list(list(a = 1), list(), list(a = 2)) %==% list(a = 2))\n})\n\nassert('is_file() detects whether an input looks like a file path', {\n  # a string with an extension looks like a file\n  (is_file('foo.md'))\n  (is_file('path/to/file.Rmd'))\n  # I() wrapping disables file detection\n  (!is_file(I('foo.md')))\n  # a character vector of length > 1 is not a file\n  (!is_file(c('a.md', 'b.md')))\n  # a string with no extension and non-existent path is not a file\n  (!is_file('justtext'))\n  # a plain string that is a bare word (no ext, not existing) is not a file\n  (!is_file('hello world'))\n})\n\nassert('restore_html() unescapes HTML entities', {\n  (restore_html('&quot;') %==% '\"')\n  (restore_html('&amp;') %==% '&')\n  (restore_html('&lt;') %==% '<')\n  (restore_html('&gt;') %==% '>')\n  (restore_html('a &lt;b&gt; c') %==% 'a <b> c')\n  # multiple entities in one string\n  (restore_html('&lt;div&gt;&amp;&lt;/div&gt;') %==% '<div>&</div>')\n})\n\nassert('locale_lang() returns a BCP 47 language tag string', {\n  lang = locale_lang()\n  (is.character(lang))\n  # either empty or a valid BCP 47 tag (letters, possibly with a hyphen and region)\n  (nchar(lang) == 0L || grepl('^[a-z]{2,3}(-[A-Z]{2,3})?$', lang))\n})\n"
  },
  {
    "path": "tests/test-cran.R",
    "content": "testit::test_pkg('litedown', 'test-cran')\n"
  },
  {
    "path": "vignettes/slides.Rmd",
    "content": "---\ntitle: Making HTML Slides with the **litedown** Package\nauthor: \"[Yihui Xie](https://yihui.org)\"\ndate: \"`{r} Sys.Date()`\"\noutput:\n  html:\n    meta:\n      css: [\"@default\", \"@snap\", \"@key-buttons\", \"@heading-anchor\"]\n      js: [\"@snap\", \"@key-buttons\", \"@external-link\", \"@heading-anchor\"]\n    options:\n      toc: true\n      number_sections: true\nvignette: >\n  %\\VignetteEngine{litedown::vignette}\n  %\\VignetteIndexEntry{Making HTML Slides with the litedown Package}\n---\n\n## Get started\n\nSpecify at least one CSS file (`snap.css`) and one JS script (`snap.js`) in YAML\nmetadata:\n\n``` yaml\n---\noutput:\n  html:\n    meta:\n      css: [default, snap]\n      js: [snap]\n---\n```\n\nYou will learn more about `snap.css` and `snap.js` at the end of this\npresentation.\n\n--------------------------------------------------------------------------------\n\n<!--# class=\"fade\" -->\n\nEquivalently, you can specify them in the arguments of `litedown::mark()`:\n\n``` r\nlitedown::mark(\"test.md\", meta = list(\n  css = c(\"default\", \"snap\"),\n  js = \"snap\"\n))\n```\n\nbut I recommend that you specify these options in YAML metadata instead.\n\n--------------------------------------------------------------------------------\n\n## Create slides\n\nThere are two ways to create a new slide:\n\n1.  Insert a horizontal rule (`---`).\n2.  Or use the level-two section heading (`##`).\n\nYou must choose only one way, i.e., either use horizontal rules to separate all\nslides, or avoid horizontal rules but only use section headings.\n\nThe first way is more flexible---you don't have to start a slide with a section\nheading.\n\n--------------------------------------------------------------------------------\n\n### Example (`---`)\n\n``` markdown\n## First slide\n\n**Content**.\n\n---\n\nMore _content_ on the next page.\n\n---\n\n## Third slide\n```\n\n--------------------------------------------------------------------------------\n\n### Example (`##`)\n\n``` markdown\n## First slide\n\nContent.\n\n## Second slide\n\nContent.\n\n## Third slide\n```\n\n--------------------------------------------------------------------------------\n\n## Keyboard shortcuts\n\n-   <kbd>f</kbd>: Fullscreen mode (press `Esc` to exit). The scroll bar will be\n    hidden in the fullscreen mode. You can also use the keyboard shortcut of\n    your browser, e.g., `fn + F` in Chrome on macOS.\n\n-   <kbd>o</kbd>: Overview mode\n\n    -   Slides become thumbnails, to make it easier to glance over multiple\n        slides at once.\n\n    -   Press <kbd>o</kbd> again to go back to presentation mode.\n\n    -   Alternatively, you can click on a slide while holding the `Alt` key to\n        toggle the overview mode and navigate to the clicked slide. Basically,\n        `Alt + Click` is like zoom in/out.\n\n--------------------------------------------------------------------------------\n\n-   <kbd>m</kbd>: Mirror mode\n\n    -   Slides are mirrored.[^1]\n\n    -   Press <kbd>m</kbd> again to resume.\n\n[^1]: See [this post](https://yihui.org/en/2020/04/xaringan-mirror/) for a\n    possible application of this odd feature.\n\n--------------------------------------------------------------------------------\n\n## CSS and styling\n\nYou can pass more CSS files to the `css` option, e.g., if you have `extra.css`\nunder the same directory as the Markdown input file:\n\n``` yaml\n---\noutput:\n  html:\n    meta:\n      css: [default, snap, extra.css]\n      js: [snap]\n---\n```\n\n--------------------------------------------------------------------------------\n\nIf your input document is `.Rmd`, you can also embed CSS directly in a `css`\ncode chunk:\n\n```` markdown\n```{css}\n#| echo = FALSE\n\n/* your CSS rules */\n```\n````\n\n--------------------------------------------------------------------------------\n\nBelow is a CSS code chunk in which we defined the font families for this\npresentation:\n\n```{css}\nbody {\n  font-family: Georgia, serif;\n}\n.slide h1, .slide h2 {\n  font-family: Baskerville, Garamond, serif;\n}\ncode {\n  font-family: Consolas, \"Andale Mono\", monospace;\n  font-weight: bold;\n}\n```\n\n--------------------------------------------------------------------------------\n\n### Example: section numbers\n\nWhen the Markdown option `number_sections` is enabled, all sections are\nnumbered. You can hide all numbers via CSS:\n\n```{css}\n.section-number { display: none; }\n```\n\nFor this presentation, only section numbers for level-two headings are\ndisplayed:\n\n```{css}\n#TOC > ul > li > .section-number,\nh2 > .section-number {\n  display: inline-block;\n}\n```\n\n--------------------------------------------------------------------------------\n\n### Example: TOC\n\nIf you enable the table of contents (TOC) by setting the option `toc: true`, you\nwill get a TOC slide after the title slide. It uses a two-column layout by\ndefault. You can custom its styles via the CSS selector `#TOC`. For example, you\ncan use three columns:\n\n``` css\n#TOC { columns: 3; }\n```\n\n--------------------------------------------------------------------------------\n\nOr define the TOC title by:\n\n```{css}\n#TOC::before { content: \"Outline\"; }\n```\n\nOr shorten TOC (hide lower-level headings):\n\n``` css\n#TOC li ul { display: none; }\n```\n\n--------------------------------------------------------------------------------\n\nFor this presentation, we don't hide lower-level headings in TOC but just make\nthem more compact (`display: inline;`):\n\n```{css}\n#TOC li ul li {\n  display: inline;\n  border-left: 0.2em dotted #ccc;\n  padding-left: 0.2em;\n}\n#TOC li ul li a {\n  color: #666;\n  text-decoration: none;\n}\n```\n\n--------------------------------------------------------------------------------\n\n### Responsive layout\n\n| Media               | Width    |  Mode   | Font size | Overview columns |\n|---------------------|----------|:-------:|:---------:|:----------------:|\n| Super large devices | ≥ 1800px |    ↓    |     ↓     |        4         |\n| Larger desktops     | ≥ 1400px |    ↓    |     ↓     |        3         |\n| Desktops            | ≥ 992px  | Slides  |   200%    |        2         |\n| Phones and tablets  | \\< 992px | Article |   100%    |       N/A        |\n\nYou can resize your browser window to see the effect (also try to press\n<kbd>o</kbd> and test the overview mode). If you are on a mobile device, you\nshould see a normal continuous page, since you cannot adjust the window size.\n\n--------------------------------------------------------------------------------\n\n### Printing\n\n-   When the slides are printed, the font size will be reduced.\n\n-   To save space, the presentation is printed as a *continuous* document\n    instead of slides.\n\n-   If you want borders on slides, print them from the overview mode.\n\n-   To customize style for printing, you may define CSS rules in:\n\n    ``` css\n    @media print {\n    }\n    ```\n\n--------------------------------------------------------------------------------\n\nIf you need to print each slide on a separate page, you may include the\nfollowing CSS and may need to tweak the paper size and font size. The PDF will\nnot have exactly the same appearance as HTML.\n\n``` css\n@page {\n  size: a4 landscape;\n  margin: 0;\n}\n\n@media print {\n  .slide {\n    font-size: 1.8em;\n    page-break-after: always;\n  }\n}\n```\n\n--------------------------------------------------------------------------------\n\n## Slide attributes\n\nYou can add more attributes to a slide via an HTML comment that starts with `#`\n(`Cmd / Ctrl + Shift + C` in RStudio's visual Markdown editor), e.g.,\n\n``` html\n<!--# class=\"inverse\" style=\"color: red;\"\ncontenteditable -->\n```\n\nThe syntax is just HTML. These attributes will be added to the slide container:\n\n``` html\n<div class=\"slide inverse\" style=\"color: red;\" contenteditable>\n</div>\n```\n\n--------------------------------------------------------------------------------\n\n### Built-in classes\n\n-   `inverse`: Apply a dark background and invert the slide color.\n\n-   `center`: Center all elements horizontally.\n\n-   `middle`: Center all elements vertically.\n\n-   `fade`: Fade a slide (50% opacity) and apply grid lines to the background\n    (can be useful for de-emphasizing a slide).\n\nYou can define your own arbitrary class names (e.g., `<!--# class=\"large\" -->`)\nand corresponding CSS rules (e.g., `.large { font-size: 150%; }`).\n\n--------------------------------------------------------------------------------\n\n### Example: an inverse slide\n\n<!--# class=\"inverse\" style=\"font-size: 130%;\" -->\n\n``` html\n<!--# class=\"inverse\" style=\"font-size: 130%;\" -->\n```\n\n-   The background is (nearly) black and the text is white.\n\n-   The font size is 30% larger.\n\n--------------------------------------------------------------------------------\n\n### Example: center content\n\n<!--# class=\"middle center\" -->\n\nEverything is centered both vertically and horizontally.\n\n``` html\n<!--# class=\"middle center\" -->\n```\n\nOf course, you don't have to use both classes at the same time. Depending on how\nyou want to center content, you can use one of these classes.\n\n--------------------------------------------------------------------------------\n\n### Example: fade a slide\n\n<!--# class=\"fade\" -->\n\n``` html\n<!--# class=\"fade\" -->\n```\n\nThis slide is not important. You do not need to read it carefully. You can even\ntake a nap, since the speaker is boring.\n\n--------------------------------------------------------------------------------\n\n### Example: a background image\n\n<!--# style=\"background-image: url(https://user-images.githubusercontent.com/163582/219277796-6d2dd826-fb11-4970-b07c-dd920a694e3b.jpg); background-size: cover; color: lightgreen;\" -->\n\n``` html\n<!--#\nstyle=\"background-image: url(path/to/image);\"\n-->\n```\n\nWe use the `style` attribute to introduce a background image to this slide. You\ncan learn more about the `background-image` property\n[here](https://www.w3schools.com/cssref/pr_background-image.php).\n\n--------------------------------------------------------------------------------\n\n### Example: an editable slide\n\n<!--# contenteditable style=\"font-family: fantasy, monospace;\" -->\n\n``` html\n<!--# contenteditable -->\n```\n\nBelieve it or not, this slide is editable because we have enabled the\n`contenteditable` attribute. If you find any mistake on your slide during your\npresentation, you can click on it and edit any text.\n\nNote that your edits *will not* be saved, though.\n\n--------------------------------------------------------------------------------\n\n## Miscellaneous elements\n\n### Page numbers\n\nThey are placed in `<span class=\"page-number\"></span>` at the bottom right of\nall slides.\n\nIf you click on a page number, the URL of the presentation will be appended by a\nhash of the form `#N`, where `N` is the page number. You can share this URL with\nother people when you want to point them to a specific page in the slides.\n\n--------------------------------------------------------------------------------\n\n### Timer\n\nA timer is added to the bottom left in `<span class=\"timer\"></span>` by default.\nIf you want a *countdown* timer, you can add a custom `<span>` to your document\n(in Markdown, you can use a raw HTML block ```` ```{=html} ````), and specify\nthe total number of seconds in the attribute `data-total`, e.g.,\n\n``` html\n<span class=\"timer\" data-total=\"1200\"></span>\n```\n\nThe timer will start when the presentation becomes fullscreen. To restart the\ntimer, click on it.\n\nFor the countdown timer, when the time is up, the timer will start to blink.\n\n```{=html}\n<span class=\"timer\" data-total=\"1200\"></span>\n```\n\n--------------------------------------------------------------------------------\n\n## Caveats\n\n### Lengthy slides\n\nWhen the height of a slide exceeds the window height, you need to be careful\nbecause it can be easy to accidentally scroll to the next page as you approach\nthe bottom of the slide. If you move from an oversized slide to the next slide\nby accident, you *will not* be able to move back to the bottom of the previous\nslide directly! Instead, you will always be navigated to the top of the previous\nslide if you want to go back. When you are on a long slide, I recommend that you\nuse your mouse wheel or the `Down` arrow key to scroll at small steps, instead\nof using the `PageDown` key to scroll over to the next screen at once.\n\nBecause of this hassle, you may not really want to make a slide lengthy, but it\nmay be unavoidable when you have lengthy content to show on one slide and it\ncannot be broken. Below is a toy example (the output block cannot be split onto\ntwo slides):\n\n```{r}\n#| comment = ''\n\ncat(1:10, sep = \"\\n\")\n```\n\n--------------------------------------------------------------------------------\n\n### Page mode\n\nThe page will switch between the *slides* and *article* mode depending on the\nratio of the window height to width, $R_w=H_w/W_w$.\n\n-   If $R_w$ is greater than the screen height/width ratio $R_s=H_s/W_s$, the\n    page will be in the article mode.\n\n-   If $R_w \\leq R_s$, the page will be in the slides mode.\n\n-   That means when the window is too narrow, the page will be a continuous\n    article. If the window is wide enough (at least 992px), it will show the\n    slides.\n\n--------------------------------------------------------------------------------\n\n### Aspect ratio\n\nThe aspect ratio of slides is determined by your screen aspect ratio by default.\nThe content will fit all available space on the screen when the slides are\nfull-screen (press <kbd>f</kbd>). If you present the slides directly with your\nown computer, this may not be a problem since you know if all content fits well\non your own screen. However, if you connect your computer to a projector or\npresent the slides on another computer, you'd better know the screen resolution\nbeforehand, because the aspect ratio may be different, and your slides can look\ndifferent on that screen.\n\nYou can fix the aspect ratio by setting the CSS variable `--slide-ratio`, e.g.,\n\n``` css\n:root { --slide-ratio: .75; }\n```\n\n--------------------------------------------------------------------------------\n\n### Zooming\n\nFor best results in the slide mode, set the browser zooming level to 100%.\nZooming out (\\< 100%) is usually okay. Zooming in (\\> 100%) may lead to slides\nbeing cut off at the margin.\n\n--------------------------------------------------------------------------------\n\n### Cross-browser/device issues\n\nUnlike PDF, HTML slides may not look exactly the same in different web browsers\nor on different devices. For example, text may be rendered via system fonts, and\ndifferent systems may have different fonts. To ensure the same fonts are used on\ndifferent devices, you may use web fonts (e.g., Google Fonts).\n\nYou may print the slides to PDF in advance. In case you have trouble with HTML\nslides when presenting from a different device, you may use the PDF copy (of\ncourse, you will lose features that rely on HTML/CSS/JS).\n\n--------------------------------------------------------------------------------\n\n## Technical notes\n\nHow does this HTML presentation work under the hood?\n\n### The original HTML\n\n``` html\n<h2>First slide</h2>\n<p>Content</p>\n<hr>\n<h2>Second slide</h2>\n<p>More content</p>\n<hr>\n```\n\nThis can be generated from Markdown.\n\n--------------------------------------------------------------------------------\n\n### snap.js\n\nThe script [`snap.js`](https://github.com/yihui/lite.js/blob/main/js/snap.js)\nconverts HTML to a structure more convenient to style as slides:\n\n``` html\n<div class=\"slide-container\">\n  <div class=\"slide\">\n    <h2>First slide</h2>\n    <p>Content</p>\n  </div>\n  <div class=\"slide\">\n    <h2>Second slide</h2>\n    <p>More content</p>\n  </div>\n</div>\n```\n\n--------------------------------------------------------------------------------\n\n### snap.css\n\nThe core technique is [CSS Scroll\nSnap](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap/Basic_concepts).\nThe full CSS is\n[`snap.css`](https://github.com/yihui/lite.js/blob/main/css/snap.css), and the\nessential rules are:\n\n``` css\nhtml {\n  scroll-snap-type: y mandatory;\n}\n.slide {\n  min-height: 100vh;\n  scroll-snap-align: start;\n}\n```\n\n--------------------------------------------------------------------------------\n\nThe JS and CSS code can be used outside the R package **litedown**, too. You\njust need to have the correct HTML structure. Then in your HTML document:\n\n``` html\n<script src=\"https://cdn.jsdelivr.net/npm/@xiee/utils@VERSION/js/snap.min.js\" defer></script>\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/combine/npm/@xiee/utils@VERSION/css/default.min.css,npm/@xiee/utils@VERSION/css/snap.min.css\">\n```\n\nRemember to substitute `VERSION` with [an appropriate\nversion](https://github.com/yihui/lite.js/tags) (e.g., `1.11.0`). You can omit\n`@VERSION` to use the latest version but it is not recommended because future\nupdates might break your old slides.\n\nBoth the JS and CSS are quite lightweight (total size is\n`{r} xfun::format_bytes(sum(file.size(pres_files <- litedown:::pkg_file('resources', c('default.css', 'snap.css', 'snap.js')))))`\nwhen uncompressed) and have no dependencies. They were written from scratch.\n\n--------------------------------------------------------------------------------\n\n## Enjoy!\n\n<!--# style=\"background-image: url(https://upload.wikimedia.org/wikipedia/commons/7/7e/Mudra-Naruto-KageBunshin.svg); background-position: bottom right; background-repeat: no-repeat; background-size: 25%;\" -->\n\n-   I developed the [xaringan](https://github.com/yihui/xaringan) in package\n    2016 and have used it since then. It still works perfectly, but now I prefer\n    more lightweight frameworks.\n\n-   Most of presentation features that I need are included in\n    `{r} length(xfun::read_all(pres_files))` lines of JS/CSS code (`default.css`\n    / `snap.js` / `snap.css`).\n\n-   This simple presentation framework is highly customizable. Customizing\n    slides can be addictive if you know HTML/CSS/JS.\n\n-   Learn more about the Markdown syntax at <https://yihui.org/litedown/>.\n\n-   Github repo: <https://github.com/yihui/litedown> (you can find the [Rmd\n    source](https://github.com/yihui/litedown/blob/main/vignettes/slides.Rmd) of\n    this presentation in the repo)\n"
  }
]