From eaa01e6ce7b0aec3aab79c0ac04b6f69395a232f Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:26:08 +0200 Subject: [PATCH 01/22] Initialize the package --- .Rbuildignore | 23 ++ .github/CODEOWNERS | 1 + .github/CODE_OF_CONDUCT.md | 133 ++++++++++ .github/CONTRIBUTING.md | 136 ++++++++++ .github/ISSUE_TEMPLATE/bug.yml | 51 ++++ .github/ISSUE_TEMPLATE/config.yml | 10 + .github/ISSUE_TEMPLATE/feature.yml | 27 ++ .github/ISSUE_TEMPLATE/question.yml | 27 ++ .github/ISSUE_TEMPLATE/release.yaml | 122 +++++++++ .github/PULL_REQUEST_TEMPLATE.md | 3 + .github/workflows/check.yaml | 92 +++++++ .github/workflows/cla.yaml | 18 ++ .github/workflows/docs.yaml | 44 +++ .github/workflows/post-release.yaml | 15 ++ .github/workflows/release.yaml | 42 +++ .github/workflows/scheduled.yaml | 34 +++ .gitignore | 36 +++ .lintr | 3 + .pre-commit-config.yaml | 88 ++++++ DESCRIPTION | 40 +++ LICENSE | 13 + NAMESPACE | 12 + NEWS.md | 3 + R/examplesShinyLive.R | 169 ++++++++++++ R/parse_url.R | 19 ++ README.md | 39 +++ SECURITY.md | 25 ++ _pkgdown.yml | 27 ++ man/create_shinylive_url.Rd | 17 ++ man/tag-examplesShinyLive.Rd | 100 +++++++ roxy.shinylive.Rproj | 17 ++ tests/testthat.R | 3 + tests/testthat/_snaps/examplesShinyLive.md | 14 + tests/testthat/setup-options.R | 20 ++ tests/testthat/test-examplesShinyLive.R | 295 +++++++++++++++++++++ tests/testthat/test-parse_url.R | 7 + 36 files changed, 1725 insertions(+) create mode 100644 .Rbuildignore create mode 100644 .github/CODEOWNERS create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 .github/ISSUE_TEMPLATE/release.yaml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/check.yaml create mode 100644 .github/workflows/cla.yaml create mode 100644 .github/workflows/docs.yaml create mode 100644 .github/workflows/post-release.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/scheduled.yaml create mode 100644 .gitignore create mode 100644 .lintr create mode 100644 .pre-commit-config.yaml create mode 100644 DESCRIPTION create mode 100644 LICENSE create mode 100644 NAMESPACE create mode 100644 NEWS.md create mode 100644 R/examplesShinyLive.R create mode 100644 R/parse_url.R create mode 100644 SECURITY.md create mode 100644 _pkgdown.yml create mode 100644 man/create_shinylive_url.Rd create mode 100644 man/tag-examplesShinyLive.Rd create mode 100644 roxy.shinylive.Rproj create mode 100644 tests/testthat.R create mode 100644 tests/testthat/_snaps/examplesShinyLive.md create mode 100644 tests/testthat/setup-options.R create mode 100644 tests/testthat/test-examplesShinyLive.R create mode 100644 tests/testthat/test-parse_url.R diff --git a/.Rbuildignore b/.Rbuildignore new file mode 100644 index 0000000..0ce030a --- /dev/null +++ b/.Rbuildignore @@ -0,0 +1,23 @@ +^renv$ +^renv\.lock$ +CODE_OF_CONDUCT.md +SECURITY.md +^.*\.Rproj$ +^\.Rproj\.user$ +^_pkgdown\.yml$ +^vignettes/hello\.Rmd$ +^docs$ +^\.github$ +README.* +^\.lintr$ +^staged_dependencies\.yaml$ +coverage.* +^\.pre-commit-config\.yaml$ +^codemeta\.json$ +init.sh +workflows.md +images +^pkgdown$ +^.revdeprefs\.yaml$ +^revdep$ +^\.covrignore$ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..536b996 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @pawelru diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..45d257b --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[INSERT CONTACT METHOD]. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..8cef653 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,136 @@ +# Contribution Guidelines + +πŸ™ Thank you for taking the time to contribute! + +Your input is deeply valued, whether an issue, a pull request, or even feedback, regardless of size, content or scope. + +## Table of contents + +[πŸ‘Ά Getting started](#getting-started) + +[πŸ“” Code of Conduct](#code-of-conduct) + +[πŸ—ƒ License](#license) + +[πŸ“œ Issues](#issues) + +[🚩 Pull requests](#pull-requests) + +[πŸ’» Coding guidelines](#coding-guidelines) + +[πŸ† Recognition model](#recognition-model) + +[❓ Questions](#questions) + +## Getting started + +Please refer the project [documentation][docs] for a brief introduction. Please also see other [articles][articles] within the project documentation for additional information. + +## Code of Conduct + +A [Code of Conduct](CODE_OF_CONDUCT.md) governs this project. Participants and contributors are expected to follow the rules outlined therein. + +## License + +All your contributions will be covered by this project's [license][license]. + +## Issues + +We use GitHub to track issues, feature requests, and bugs. Before submitting a new issue, please check if the issue has already been reported. If the issue already exists, please upvote the existing issue πŸ‘. + +For new feature requests, please elaborate on the context and the benefit the feature will have for users, developers, or other relevant personas. + +## Pull requests + +### GitHub Flow + +This repository uses the [GitHub Flow](https://docs.github.com/en/get-started/quickstart/github-flow) model for collaboration. To submit a pull request: + +1. Create a branch + + Please see the [branch naming convention](#branch-naming-convention) below. If you don't have write access to this repository, please fork it. + +2. Make changes + + Make sure your code + * passes all checks imposed by GitHub Actions + * is well documented + * is well tested with unit tests sufficiently covering the changes introduced + +3. Create a pull request (PR) + + In the pull request description, please link the relevant issue (if any), provide a detailed description of the change, and include any assumptions. + +4. Address review comments, if any + +5. Post approval + + Merge your PR if you have write access. Otherwise, the reviewer will merge the PR on your behalf. + +6. Pat yourself on the back + + Congratulations! πŸŽ‰ + You are now an official contributor to this project! We are grateful for your contribution. + +### Branch naming convention + +Suppose your changes are related to a current issue in the current project; please name your branch as follows: `_`. Please use underscore (`_`) as a delimiter for word separation. For example, `420_fix_ui_bug` would be a suitable branch name if your change is resolving and UI-related bug reported in issue number `420` in the current project. + +If your change affects multiple repositories, please name your branches as follows: `__`. For example, `69_awesomeproject_fix_spelling_error` would reference issue `69` reported in project `awesomeproject` and aims to resolve one or more spelling errors in multiple (likely related) repositories. + +### `monorepo` and `staged.dependencies` + +Sometimes you might need to change upstream dependent package(s) to be able to submit a meaningful change. We are using [`staged.dependencies`](https://github.com/openpharma/staged.dependencies) functionality to simulate a `monorepo` behavior. The dependency configuration is already specified in this project's `staged_dependencies.yaml` file. You need to name the feature branches appropriately. _This is the only exception from the branch naming convention described above_. + +Please refer to the [staged.dependencies package documentation](https://openpharma.github.io/staged.dependencies/) for more details. + +## Coding guidelines + +This repository follows some unified processes and standards adopted by its maintainers to ensure software development is carried out consistently within teams and cohesively across other repositories. + +### Style guide + +This repository follows the standard [`tidyverse` style guide](https://style.tidyverse.org/) and uses [`lintr`](https://github.com/r-lib/lintr) for lint checks. Customized lint configurations are available in this repository's `.lintr` file. + +### Dependency management + +Lightweight is the right weight. This repository follows [tinyverse](https://www.tinyverse.org/) recommedations of limiting dependencies to minimum. + +### Dependency version management + +If the code is not compatible with all (!) historical versions of a given dependenct package, it is required to specify minimal version in the `DESCRIPTION` file. In particular: if the development version requires (imports) the development version of another package - it is required to put `abc (>= 1.2.3.9000)`. + +### Recommended development environment & tools + +#### R & package versions + +We continuously test our packages against the newest R version along with the most recent dependencies from CRAN and BioConductor. We recommend that your working environment is also set up in the same way. You can find the details about the R version and packages used in the `R CMD check` GitHub Action execution log - there is a step that prints out the R `sessionInfo()`. + +If you discover bugs on older R versions or with an older set of dependencies, please create the relevant bug reports. + +#### `pre-commit` + +We highly recommend that you use the [`pre-commit`](https://pre-commit.com/) tool combined with [`R hooks for pre-commit`](https://github.com/lorenzwalthert/precommit) to execute some of the checks before committing and pushing your changes. + +Pre-commit hooks are already available in this repository's `.pre-commit-config.yaml` file. + +## Recognition model + +As mentioned previously, all contributions are deeply valued and appreciated. While all contribution data is available as part of the [repository insights][insights], to recognize a _significant_ contribution and hence add the contributor to the package authors list, the following rules are enforced: + +* Minimum 5% of lines of code authored* (determined by `git blame` query) OR +* Being at the top 5 contributors in terms of number of commits OR lines added OR lines removed* + +*Excluding auto-generated code, including but not limited to `roxygen` comments or `renv.lock` files. + +The package maintainer also reserves the right to adjust the criteria to recognize contributions. + +## Questions + +If you have further questions regarding the contribution guidelines, please contact the package/repository maintainer. + + +[docs]: https://insightsengineering.github.io/r.pkg.template/index.html +[articles]: https://insightsengineering.github.io/r.pkg.template/main/articles/index.html +[license]: https://insightsengineering.github.io/r.pkg.template/main/LICENSE-text.html +[insights]: https://github.com/insightsengineering/r.pkg.template/pulse diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..cf47384 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,51 @@ +--- +name: 🐞 Bug Report +description: File a bug report +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: dropdown + id: r-version + attributes: + label: Which version(s) of R were you using? + multiple: true + options: + - "3.6.x" + - "4.1.x" + - "4.2.x" + - "Other" + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: R + - type: checkboxes + id: code-of-conduct + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://insightsengineering.github.io/r.pkg.template/CODE_OF_CONDUCT.html) + options: + - label: I agree to follow this project's Code of Conduct. + required: true + - type: checkboxes + id: contributor-guidelines + attributes: + label: Contribution Guidelines + description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://insightsengineering.github.io/r.pkg.template/CONTRIBUTING.html) + options: + - label: I agree to follow this project's Contribution Guidelines. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..d17075b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,10 @@ +--- +blank_issues_enabled: true + +contact_links: + - name: We are hiring! + url: https://careers.gene.com/ + about: Genentech and Roche are hiring! + - name: Pharmaverse + url: https://pharmaverse.org/ + about: Related projects @ Pharmaverse.org diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000..5efe065 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,27 @@ +--- +name: ✨ Feature Request +description: Request or propose a new feature +title: "[Feature Request]: <title>" +labels: ["enhancement"] +body: + - type: textarea + attributes: + label: Feature description + validations: + required: true + - type: checkboxes + id: code-of-conduct + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://insightsengineering.github.io/r.pkg.template/CODE_OF_CONDUCT.html) + options: + - label: I agree to follow this project's Code of Conduct. + required: true + - type: checkboxes + id: contributor-guidelines + attributes: + label: Contribution Guidelines + description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://insightsengineering.github.io/r.pkg.template/CONTRIBUTING.html) + options: + - label: I agree to follow this project's Contribution Guidelines. + required: true diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..b3e46c1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,27 @@ +--- +name: ❓ Question +description: Question about usage or documentation +title: "[Question]: <title>" +labels: ["question"] +body: + - type: textarea + attributes: + label: What is your question? + validations: + required: true + - type: checkboxes + id: code-of-conduct + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://insightsengineering.github.io/r.pkg.template/CODE_OF_CONDUCT.html) + options: + - label: I agree to follow this project's Code of Conduct. + required: true + - type: checkboxes + id: contributor-guidelines + attributes: + label: Contribution Guidelines + description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://insightsengineering.github.io/r.pkg.template/CONTRIBUTING.html) + options: + - label: I agree to follow this project's Contribution Guidelines. + required: true diff --git a/.github/ISSUE_TEMPLATE/release.yaml b/.github/ISSUE_TEMPLATE/release.yaml new file mode 100644 index 0000000..73bb11d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release.yaml @@ -0,0 +1,122 @@ +--- +name: πŸš€ Release +description: Template for package release +title: "[Release]: <version>" +labels: ["release"] +assignees: + - KlaudiaBB + - cicdguy +body: + - type: markdown + attributes: + value: | + ⚠️ Please do not link or mention any internal references in this issue. This includes internal URLs, intellectual property and references. + - type: textarea + id: blocked-by + attributes: + label: Blocked by + description: Any PRs or issues that this release is blocked by. + placeholder: Add a list of blocking PRs or issues here. + value: | + ### PRs + + - [ ] PR 1 + + ### Issues + + - [ ] Issue 1 + validations: + required: true + - type: textarea + id: pre-release + attributes: + label: Pre-release + description: Pre-requisites that must be fulfilled before initiating the release process. + placeholder: Add your list of pre-requisites here. + value: | + - [ ] Make sure that high priority bugs (label "priority" + "bug") have been resolved before going into the release. + - [ ] Review old/hanging PRs before going into the release. + - [ ] Revisit R-package's lifecycle badges (Optional). + - [ ] Release Manager: Discuss package dependencies, create a plan to sequentially close release activities and submit groups of packages for internal validation (Applicable only for regulatory release). + - [ ] Check Validation Pipeline dry-run results for the package. + - [ ] Make sure all relevant integration tests are green 2-3 days before the release. Look carefully through logs (check for warnings and notes). + - [ ] Inform about the soft code freeze, decide what gets merged in before starting release activities. + - type: textarea + id: release + attributes: + label: Release + description: The steps to be taken in order to create a release. + placeholder: Steps to create a release. + value: | + ### Prepare the release + + - [ ] Create a new release candidate branch + `git checkout -b release-candidate-vX.Y.Z` + - [ ] Update NEWS.md file: make sure it reflects a holistic summary of what has changed in the package, check README. + - [ ] Remove the additional fields (`Remotes`) from the DESCRIPTION file where applicable. + - [ ] Make sure that the minimum dependency versions are updated in the DESCRIPTION file for the package. + - [ ] Increase versioned dependency on {package name} to >=X.Y.Z. + - [ ] Commit your changes and create the PR on GitHub (add "[skip vbump]" in the PR title). Add all updates, commit, and push changes: + `# Make the necessary modifications to your files + # Stage the changes + git add <files your modified> + # Commit the changes + git commit -m "[skip vbump] <Your commit message>" + git push origin release-candidate-vX.Y.Z` + + ### Test the release + + - [ ] Execute the manual tests on Shiny apps that are deployed on various hosting providers (Posit connect and shinyapps.io) - track the results in GitHub issue (Applicable only for frameworks that use Shiny). + - [ ] Monitor integration tests, if integration fails, create priority issues on the board. + - [ ] Execute UAT tests (Optional). + + ### Validation loop + + Note: This section is applicable only for regulatory packages. + + - [ ] Tag the update(s) as a release candidate vX.Y.Z-rc<iteration-number> (e.g. v0.5.3-rc1) on the release candidate branch (release-candidate-vX.Y.Z). + `# Create rc tag for submission for internal validation + git tag vX.Y.Z-rc<iteration number> + git push origin vX.Y.Z-rc<iteration number>` + - [ ] Submit the package for internal validation. + - [ ] Address any feedback (internal validation/user testing), retag the package as a release candidate vX.Y.Z-rc(n+1). Repeat the submission for internal validation if necessary. + - [ ] Get the package validated. + + ### Tag the release + + - [ ] If the additional fields were removed, add them back in a separate PR, and then merge the PR back to main (add "[skip vbump]" in the PR title). If nothing was removed just merge the PR you created in the "Prepare the release" section to `main`. Note the commit hash of the merged commit. **Note:** additional commits might be added to the `main` branch by a bot or an automation - we do **NOT** want to tag this commit. + + #### Make sure of the following before continuing with the release: + + - [ ] CI checks are passing in GH. + - [ ] Shiny apps are deployable and there are no errors/warnings (Applicable only for frameworks that use Shiny). + + - [ ] Create a git tag with the final version set to vX.Y.Z on the main branch. In order to do this: + 1. Checkout the commit hash. + `git checkout <commit hash>` + 2. Tag the hash with the release version (vX.Y.Z). + `git tag vX.Y.Z` + 3. Push the tag to make the final release. + `git push origin vX.Y.Z` + - [ ] Update downstream package dependencies to (>=X.Y.Z) in {package name}. + Note: Once the release tag is created, the package is automatically published to internal repositories. + - type: textarea + id: post-release + attributes: + label: Post-release + description: The list of activities to be completed after the release. + placeholder: The steps that must be taken after the release. + value: | + - [ ] Make sure that the package is published to internal repositories (Validated and/or Non-Validated repository). + - [ ] Review and update installation instructions for the package if needed. + - [ ] Make sure internal documentation/documentation catalogs are up to date. + - [ ] Notify the IDR team to start post-release/clean-up activities. + - [ ] Announce the release on ________. + - type: textarea + id: decision-tree + attributes: + label: Decision tree + description: Any decision tree(s) that would aid release management + placeholder: Any decision tree(s) that would aid release management. + value: | + Click [here](https://github.com/insightsengineering/.github/blob/main/.github/ISSUE_TEMPLATE/RELEASE_DECISION_TREE.md) to see the release decision tree. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..81c84dc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,3 @@ +# Pull Request + +<!-- Please describe your pull request here --> diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml new file mode 100644 index 0000000..6c25abe --- /dev/null +++ b/.github/workflows/check.yaml @@ -0,0 +1,92 @@ +--- +name: Check πŸ›  + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + branches: + - main + push: + branches: + - main + workflow_dispatch: + +jobs: + audit: + name: Audit Dependencies πŸ•΅οΈβ€β™‚οΈ + uses: insightsengineering/r.pkg.template/.github/workflows/audit.yaml@main + r-cmd: + name: R CMD Check 🧬 + uses: insightsengineering/r.pkg.template/.github/workflows/build-check-install.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + with: + additional-env-vars: | + _R_CHECK_CRAN_INCOMING_REMOTE_=false + additional-r-cmd-check-params: --as-cran + r-cmd-non-cran: + name: R CMD Check (non-CRAN) 🧬 + uses: insightsengineering/r.pkg.template/.github/workflows/build-check-install.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + with: + additional-env-vars: | + NOT_CRAN=true + concurrency-group: non-cran + unit-test-report-directory: unit-test-report-non-cran + coverage: + name: Coverage πŸ“” + uses: insightsengineering/r.pkg.template/.github/workflows/test-coverage.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + linter: + if: github.event_name != 'push' + name: SuperLinter πŸ¦Έβ€β™€οΈ + uses: insightsengineering/r.pkg.template/.github/workflows/linter.yaml@main + roxygen: + name: Roxygen πŸ…Ύ + uses: insightsengineering/r.pkg.template/.github/workflows/roxygen.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + with: + auto-update: true + gitleaks: + name: gitleaks πŸ’§ + uses: insightsengineering/r.pkg.template/.github/workflows/gitleaks.yaml@main + spelling: + name: Spell Check πŸ†Ž + uses: insightsengineering/r.pkg.template/.github/workflows/spelling.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + links: + if: github.event_name != 'push' + name: Check URLs 🌐 + uses: insightsengineering/r.pkg.template/.github/workflows/links.yaml@main + vbump: + name: Version Bump πŸ€œπŸ€› + if: github.event_name == 'push' + uses: insightsengineering/r.pkg.template/.github/workflows/version-bump.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + version: + name: Version Check 🏁 + uses: insightsengineering/r.pkg.template/.github/workflows/version.yaml@main + licenses: + name: License Check πŸƒ + uses: insightsengineering/r.pkg.template/.github/workflows/licenses.yaml@main + style: + if: github.event_name != 'push' + name: Style Check πŸ‘— + uses: insightsengineering/r.pkg.template/.github/workflows/style.yaml@main + with: + auto-update: true + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + grammar: + if: github.event_name != 'push' + name: Grammar Check πŸ”€ + uses: insightsengineering/r.pkg.template/.github/workflows/grammar.yaml@main diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml new file mode 100644 index 0000000..b674b0b --- /dev/null +++ b/.github/workflows/cla.yaml @@ -0,0 +1,18 @@ +name: CLA πŸ” + +on: + issue_comment: + types: + - created + # For PRs that originate from forks + pull_request_target: + types: + - opened + - closed + - synchronize + +jobs: + CLA: + name: CLA πŸ“ + uses: insightsengineering/.github/.github/workflows/cla.yaml@main + secrets: inherit diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000..9dbcfd0 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,44 @@ +--- +name: Docs πŸ“š + +on: + push: + branches: + - main + paths: + - "inst/templates/**" + - "_pkgdown.*" + - DESCRIPTION + - "**.md" + - "**.Rmd" + - "man/**" + - "LICENSE.*" + - NAMESPACE + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + branches: + - main + paths: + - "inst/templates/**" + - "_pkgdown.*" + - DESCRIPTION + - "**.md" + - "**.Rmd" + - "man/**" + - "LICENSE.*" + - NAMESPACE + workflow_dispatch: + +jobs: + docs: + name: Pkgdown Docs πŸ“š + uses: insightsengineering/r.pkg.template/.github/workflows/pkgdown.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + with: + default-landing-page: main + additional-unit-test-report-directories: unit-test-report-non-cran diff --git a/.github/workflows/post-release.yaml b/.github/workflows/post-release.yaml new file mode 100644 index 0000000..c5fb822 --- /dev/null +++ b/.github/workflows/post-release.yaml @@ -0,0 +1,15 @@ +--- +name: Post release ✨ + +on: + release: + types: ["released"] + +jobs: + vbump: + name: Version Bump πŸ€œπŸ€› + uses: insightsengineering/r.pkg.template/.github/workflows/version-bump.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + with: + vbump-after-release: true diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..cf7732a --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,42 @@ +--- +name: Release 🎈 + +on: + push: + tags: + - "v*" + workflow_dispatch: + +jobs: + build: + name: Build package 🎁 + needs: release + uses: insightsengineering/r.pkg.template/.github/workflows/build-check-install.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + with: + skip-r-cmd-check: true + skip-r-cmd-install: true + docs: + name: Pkgdown Docs πŸ“š + needs: release + uses: insightsengineering/r.pkg.template/.github/workflows/pkgdown.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + with: + default-landing-page: latest-tag + validation: + name: R Package Validation report πŸ“ƒ + needs: release + uses: insightsengineering/r.pkg.template/.github/workflows/validation.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + release: + name: Create release πŸŽ‰ + uses: insightsengineering/r.pkg.template/.github/workflows/release.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + wasm: + name: Build WASM packages πŸ§‘β€πŸ­ + needs: release + uses: insightsengineering/r.pkg.template/.github/workflows/wasm.yaml@main diff --git a/.github/workflows/scheduled.yaml b/.github/workflows/scheduled.yaml new file mode 100644 index 0000000..d0cc504 --- /dev/null +++ b/.github/workflows/scheduled.yaml @@ -0,0 +1,34 @@ +--- +name: On-demand πŸ§‘β€πŸ”¬ + +on: + schedule: + - cron: '45 3 * * 0' + workflow_dispatch: + +jobs: + dependency-test: + strategy: + fail-fast: false + matrix: + test-strategy: ["min_cohort", "min_isolated", "release", "max"] + uses: insightsengineering/r.pkg.template/.github/workflows/verdepcheck.yaml@main + name: Dependency Test - ${{ matrix.test-strategy }} πŸ”’ + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + GCHAT_WEBHOOK: ${{ secrets.GCHAT_WEBHOOK }} + with: + strategy: ${{ matrix.test-strategy }} + additional-env-vars: | + PKG_SYSREQS_DRY_RUN=true + branch-cleanup: + name: Branch Cleanup 🧹 + uses: insightsengineering/r.pkg.template/.github/workflows/branch-cleanup.yaml@main + secrets: + REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} + revdepcheck: + name: revdepcheck ↩️ + uses: insightsengineering/r.pkg.template/.github/workflows/revdepcheck.yaml@main + rhub: + name: R-hub 🌐 + uses: insightsengineering/r.pkg.template/.github/workflows/rhub.yaml@main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6526a76 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +.DS_Store +.httr-oauth +.project +.RData +.Rhistory +.Rproj.user +.Ruserdata +.settings/** +*.html +*.Rcheck +*.rprof +*.sas.txt +*~ +/.project +devel/* +doc +docs +inst/outputs/* +logs +Meta +packrat/lib*/ +temp +temp_w +templates/ +tmp.* +vignettes/*.html +vignettes/*.md +vignettes/*.R +coverage.* +.vscode/ +node_modules/ +package-lock.json +package.json +tests/testthat/_snaps/**/*.new.md +tests/testthat/_snaps/**/*.new.svg +inst/doc diff --git a/.lintr b/.lintr new file mode 100644 index 0000000..ec50cf4 --- /dev/null +++ b/.lintr @@ -0,0 +1,3 @@ +linters: linters_with_defaults( + line_length_linter = line_length_linter(120) + ) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..78fcce0 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,88 @@ +--- +# All available hooks: https://pre-commit.com/hooks.html +# R specific hooks: https://github.com/lorenzwalthert/precommit +repos: + - repo: https://github.com/lorenzwalthert/precommit + rev: v0.3.2.9007 + hooks: + - id: style-files + args: [--style_pkg=styler, --style_fun=tidyverse_style] + - id: roxygenize + additional_dependencies: + - glue + - jsonlite + - roxygen2 + - lzstring + - roxygen2 + - stringr + # codemeta must be above use-tidy-description when both are used + # - id: codemeta-description-updated + - id: use-tidy-description + - id: spell-check + exclude: > + (?x)^( + data/.*| + inst/.*| + (.*/|)\.Rprofile| + (.*/|)\.Renviron| + (.*/|)\.gitignore| + (.*/|)NAMESPACE| + (.*/|)DESCRIPTION| + (.*/|)WORDLIST| + (.*/|)LICENSE| + (.*/|)\.Rbuildignore| + (.*/|)\.lintr| + (.*/|)_pkgdown.y[a]?ml| + (.*/|)\.covrignore| + (.*/|)staged_dependencies.y[a]?ml| + (.*/|)\.pre-commit-.*| + \.github/.*| + .*\.[rR]| + .*\.Rproj| + .*\.py| + .*\.png| + .*\.feather| + .*\.rds| + .*\.Rds| + .*\.sh| + .*\.RData + )$ + - id: lintr + - id: readme-rmd-rendered + - id: parsable-R + - id: no-browser-statement + - id: deps-in-desc + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.0.0-alpha.6 + hooks: + - id: prettier + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + args: ["--maxkb=200"] + - id: end-of-file-fixer + exclude: '\.Rd' + - id: trailing-whitespace + exclude: '\.Rd' + - id: check-yaml + - id: no-commit-to-branch + - id: mixed-line-ending + args: ["--fix=lf"] + - id: detect-aws-credentials + args: ["--allow-missing-credentials"] + - id: detect-private-key + - id: forbid-new-submodules + - id: check-symlinks + - repo: local + hooks: + - id: forbid-to-commit + name: Don't commit common R artifacts + entry: Cannot commit .Rhistory, .RData, .Rds or .rds. + language: fail + files: '\.Rhistory|\.RData|\.Rds|\.rds$' + # `exclude: <regex>` to allow committing specific files. + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.33.0 + hooks: + - id: markdownlint diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..f8351df --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,40 @@ +Type: Package +Package: roxy.shinylive +Title: A roxygen2 Extension for Shinylive +Version: 0.0.0.9000 +Authors@R: c( + person("Pawel Rucki", , , "pawel.rucki@roche.com", role = c("aut", "cre")), + person("F. Hoffmann-La Roche AG", role = c("cph", "fnd")) + ) +Description: An extension for roxygen2 that allows to auto-create links to shinylive from examples in the documentation. +License: Apache License 2.0 | file LICENSE +URL: https://github.com/insightsengineering/roxy.shinylive/ +BugReports: https://github.com/insightsengineering/roxy.shinylive/issues +Depends: + R (>= 4.0) +Imports: + glue, + jsonlite (>= 0.9.4), + lzstring, + roxygen2 (>= 7.1.1), + stringr (>= 0.4) +Suggests: + testthat (>= 3.0.4), + withr (>= 2.4.3) +Config/Needs/verdepcheck: + tidyverse/glue, + jeroen/jsonlite, + lzstring=parmsam/lzstring-r, + r-lib/roxygen2, + tidyverse/stringr, + r-lib/testthat +Config/Needs/website: insightsengineering/nesttemplate +Encoding: UTF-8 +Language: en-US +LazyData: true +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.2 +Config/testthat/edition: 3 +Collate: + 'examplesShinyLive.R' + 'parse_url.R' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fb8e419 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2024 F. Hoffmann-La Roche AG + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..c641397 --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,12 @@ +# Generated by roxygen2: do not edit by hand + +S3method(format,rd_section_examplesShinyLive) +S3method(roxygen2::roxy_tag_parse,roxy_tag_examplesShinyLive) +S3method(roxygen2::roxy_tag_rd,roxy_tag_examplesShinyLive) +importFrom(glue,glue_data) +importFrom(jsonlite,toJSON) +importFrom(jsonlite,unbox) +importFrom(lzstring,compressToEncodedURIComponent) +importFrom(roxygen2,rd_section) +importFrom(roxygen2,warn_roxy_tag) +importFrom(stringr,str_trim) diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..538886c --- /dev/null +++ b/NEWS.md @@ -0,0 +1,3 @@ +# roxy.shinylive 0.0.0.9001 + +- Initialize the package. diff --git a/R/examplesShinyLive.R b/R/examplesShinyLive.R new file mode 100644 index 0000000..02cc7db --- /dev/null +++ b/R/examplesShinyLive.R @@ -0,0 +1,169 @@ +#' Custom `examplesShinyLive` tag. +#' +#' This function generates a new section with ShinyLive links to the applications from this tag content. +#' If no code is provided then the code from the following `@examples` tag is used. +#' +#' The application code must be executable inside ShinyLive. If the application code includes functions from your +#' package, you must add `library(<your package>)` beforehand. For more information, refer to the Decoration section +#' on how to use and decorate existing examples. +#' +#' Note: All the packages used in the application code need to be installable in WebR. +#' See [this article](https://docs.r-wasm.org/webr/latest/packages.html) for more details. +#' +#' @section Decoration: +#' +#' To avoid repetition between the `@examplesShinyLive` and `@examples` sections, there are special string literals +#' that provide access to the `@examples` content from within `@examplesShinyLive`. +#' These literals should be used as expressions embraced with `{{ }}`, which are then interpolated using +#' `glue::glue_data(..., .open = "{{", .close = "}}")`. +#' +#' The following keywords are available: +#' * `"{{ tags_examples }}"` - a list of tags with examples +#' * `"{{ examples }}"` - a list of "raw" elements from `tags_examples` +#' * `"{{ next_example }}"` - "raw" element of the next example +#' * `"{{ prev_example }}"` - "raw" element of the previous example +#' +#' This allows you to access and decorate existing example code to create executable application code for ShinyLive. +#' Refer to the examples section for possible use cases. +#' +#' @name tag-examplesShinyLive +#' +#' @usage +#' #' @examplesShinyLive${1:# example code (optional)} +#' +#' @examples +#' # As a part of documentation: +#' +#' # basic example: +#' #' (docs) +#' #' @examplesShinyLive +#' #' @examples +#' #' (example code) +#' +#' # using keywords: +#' #' (docs) +#' #' @examplesShinyLive +#' #' foo <- 1 +#' #' {{ next_example }} +#' #' bar <- 2 +#' #' @examples +#' #' (your example code) +#' +#' # A typical example would be: +#' #' (docs) +#' #' @examplesShinyLive +#' #' library(<your package>) +#' #' interactive <- function() TRUE +#' #' {{ next_example }} +#' #' @examples +#' #' app <- ... +#' #' if (interactive()) { +#' #' shinyApp(app$ui, app$server) +#' #' } +#' +#' # multiple apps: +#' #' (docs) +#' #' @examplesShinyLive +#' #' @examples +#' #' (your example app 1) +#' #' @examplesShinyLive +#' #' @examples +#' #' (your example app 2) +#' +#' # skip parts of example code: +#' #' (docs) +#' #' @examples +#' #' (your example code - skipped) +#' #' @examplesShinyLive +#' #' @examples +#' #' (your example code - included) +#' +#' # multiple apps with keywords: +#' #' (docs) +#' #' @examplesShinyLive +#' #' x <- 1 +#' #' {{ next_example }} +#' #' @examples +#' #' (your example app 1) +#' #' @examplesShinyLive +#' #' y <- 1 +#' #' {{ next_example }} +#' #' @examples +#' #' (your example app 2) +NULL + +#' @noRd +#' @exportS3Method roxygen2::roxy_tag_parse roxy_tag_examplesShinyLive +#' @importFrom glue glue_data +#' @importFrom stringr str_trim +#' @importFrom roxygen2 warn_roxy_tag +roxy_tag_parse.roxy_tag_examplesShinyLive <- function(x) { + if (stringr::str_trim(x$raw) == "") { + x$raw <- "{{ next_example }}" + } + + # not elegant but this is the most efficient way to access sibling tags + tokens <- get("tokens", envir = parent.frame(3L)) + + tags_examples <- Filter(function(x) x$tag == "examples", tokens) + + examples <- lapply(tags_examples, `[[`, "raw") + + next_example <- Reduce( + function(x, y) `if`(x$line < y$line, x, y), + Filter(function(y) y$line > x$line, tags_examples) + )$raw + + prev_example <- Reduce( + function(x, y) `if`(x$line > y$line, x, y), + Filter(function(y) y$line < x$line, tags_examples) + )$raw + + x$raw <- try( + as.character( + glue::glue_data( + .x = list( + tags_examples = tags_examples, + examples = examples, + next_example = next_example, + prev_example = prev_example + ), + x$raw, + .open = "{{", + .close = "}}" + ) + ), + silent = TRUE + ) + + if (inherits(x$raw, "try-error")) { + roxygen2::warn_roxy_tag(x, "failed to interpolate the content") + return(NULL) + } + + if (is.null(x$raw) || length(x$raw) == 0) { + roxygen2::warn_roxy_tag(x, "requires a value") + return(NULL) + } + x$val <- create_shinylive_url(x$raw) + x +} + +#' @noRd +#' @exportS3Method roxygen2::roxy_tag_rd roxy_tag_examplesShinyLive +#' @importFrom roxygen2 rd_section +roxy_tag_rd.roxy_tag_examplesShinyLive <- function(x, base_path, env) { + roxygen2::rd_section("examplesShinyLive", x$val) +} + +#' @noRd +#' @exportS3Method format rd_section_examplesShinyLive +format.rd_section_examplesShinyLive <- function(x, ...) { + paste0( + "\\section{Run examples in Shinylive}{\n", + "\\itemize{\n", + paste0(" \\item", "\\href{", x$value, "}{example-", seq_along(x$value), "}\n", collapse = ""), + "}\n", + "}\n" + ) +} diff --git a/R/parse_url.R b/R/parse_url.R new file mode 100644 index 0000000..ee8a4e0 --- /dev/null +++ b/R/parse_url.R @@ -0,0 +1,19 @@ +#' Creates shinylive url for the app code. +#' +#' @importFrom jsonlite unbox toJSON +#' @importFrom lzstring compressToEncodedURIComponent +#' +#' @param code (`character(1)`) A string with app code. +#' @return (`character(1)`) ShinyLive app url. +create_shinylive_url <- function(code) { + # implementation based on "Create ShinyLive Link" feature of Shiny VSCode extension + # https://github.com/posit-dev/shiny-vscode/blob/80560bf36d516ff89dffe88bd9a28cee9edd4d43/src/shinylive.ts#L499 + files <- list( + name = jsonlite::unbox("app.R"), + content = jsonlite::unbox(code) + ) + files_json <- jsonlite::toJSON(list(files)) + files_lz <- lzstring::compressToEncodedURIComponent(as.character(files_json)) + files_lz <- gsub("/", "-", files_lz) + sprintf("https://shinylive.io/r/app/#code=%s", files_lz) +} diff --git a/README.md b/README.md index c579f1d..a6e6dd9 100644 --- a/README.md +++ b/README.md @@ -1 +1,40 @@ # roxy.shinylive + +## Overview + +This package provides a `roxygen2` extension that automatically takes the example code from `@examples` tag that follows and crate an URL to the shinylive service. That URL is then added to the documentation. + +## Install + +```r +devtools::install_github("insightsengineering/roxy.shinylive") +``` + +## Usage + +In your `DESCRIPTION` file, add the following: +```yaml +Roxygen: list(markdown = TRUE, packages = c("roxy.shinylive")) +``` + +Then in your package documentation: +```r +#' (docs) +#' @examplesShinyLive +#' @examples +#' (example code with shiny App) +``` + +Which would produce a following output in your documentation: + +```Rd +\section{Run examples in Shinylive}{ +\itemize{ + \item\href{https://shinylive.io/r/app/#code=...}{example-1} + \item\href{https://shinylive.io/r/app/#code=...}{example-2} + ... +} +} +``` + +See the pacakge documentation for more details. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..98ab70f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security Policy + +## Reporting Security Issues + +If you believe you have found a security vulnerability in any of the repositories in this organization, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please send an email to vulnerability.management[@]roche.com. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Data Security Standards (DSS) + +Please make sure that while reporting issues in the form a bug, feature, or pull request, *all* sensitive information such as [PII](https://www.dhs.gov/privacy-training/what-personally-identifiable-information), [PHI](https://www.hhs.gov/hipaa/for-professionals/security/laws-regulations/index.html), and [PCI](https://www.pcisecuritystandards.org/pci_security/standards_overview) is completely removed from any text and attachments, including pictures and videos. diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..60ef829 --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,27 @@ +--- +url: https://insightsengineering.github.io/roxy.shinylive/ + +template: + package: nesttemplate + +navbar: + right: + - icon: fa-github + href: https://github.com/insightsengineering/roxy.shinylive + +articles: + - title: Articles + navbar: ~ + contents: + - matches("*") + +reference: + - title: Tags + desc: Functions for working with tags + contents: + - starts_with("tag-") + + - title: Other + desc: Other functions + contents: + - create_shinylive_url diff --git a/man/create_shinylive_url.Rd b/man/create_shinylive_url.Rd new file mode 100644 index 0000000..8886a5d --- /dev/null +++ b/man/create_shinylive_url.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/parse_url.R +\name{create_shinylive_url} +\alias{create_shinylive_url} +\title{Creates shinylive url for the app code.} +\usage{ +create_shinylive_url(code) +} +\arguments{ +\item{code}{(\code{character(1)}) A string with app code.} +} +\value{ +(\code{character(1)}) ShinyLive app url. +} +\description{ +Creates shinylive url for the app code. +} diff --git a/man/tag-examplesShinyLive.Rd b/man/tag-examplesShinyLive.Rd new file mode 100644 index 0000000..9ce7f45 --- /dev/null +++ b/man/tag-examplesShinyLive.Rd @@ -0,0 +1,100 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/examplesShinyLive.R +\name{tag-examplesShinyLive} +\alias{tag-examplesShinyLive} +\title{Custom \code{examplesShinyLive} tag.} +\usage{ +#' @examplesShinyLive${1:# example code (optional)} +} +\description{ +This function generates a new section with ShinyLive links to the applications from this tag content. +If no code is provided then the code from the following \verb{@examples} tag is used. +} +\details{ +The application code must be executable inside ShinyLive. If the application code includes functions from your +package, you must add \verb{library(<your package>)} beforehand. For more information, refer to the Decoration section +on how to use and decorate existing examples. + +Note: All the packages used in the application code need to be installable in WebR. +See \href{https://docs.r-wasm.org/webr/latest/packages.html}{this article} for more details. +} +\section{Decoration}{ + + +To avoid repetition between the \verb{@examplesShinyLive} and \verb{@examples} sections, there are special string literals +that provide access to the \verb{@examples} content from within \verb{@examplesShinyLive}. +These literals should be used as expressions embraced with \code{{{ }}}, which are then interpolated using +\code{glue::glue_data(..., .open = "{{", .close = "}}")}. + +The following keywords are available: +\itemize{ +\item \code{"{{ tags_examples }}"} - a list of tags with examples +\item \code{"{{ examples }}"} - a list of "raw" elements from \code{tags_examples} +\item \code{"{{ next_example }}"} - "raw" element of the next example +\item \code{"{{ prev_example }}"} - "raw" element of the previous example +} + +This allows you to access and decorate existing example code to create executable application code for ShinyLive. +Refer to the examples section for possible use cases. +} + +\examples{ +# As a part of documentation: + +# basic example: +#' (docs) +#' @examplesShinyLive +#' @examples +#' (example code) + +# using keywords: +#' (docs) +#' @examplesShinyLive +#' foo <- 1 +#' {{ next_example }} +#' bar <- 2 +#' @examples +#' (your example code) + +# A typical example would be: +#' (docs) +#' @examplesShinyLive +#' library(<your package>) +#' interactive <- function() TRUE +#' {{ next_example }} +#' @examples +#' app <- ... +#' if (interactive()) { +#' shinyApp(app$ui, app$server) +#' } + +# multiple apps: +#' (docs) +#' @examplesShinyLive +#' @examples +#' (your example app 1) +#' @examplesShinyLive +#' @examples +#' (your example app 2) + +# skip parts of example code: +#' (docs) +#' @examples +#' (your example code - skipped) +#' @examplesShinyLive +#' @examples +#' (your example code - included) + +# multiple apps with keywords: +#' (docs) +#' @examplesShinyLive +#' x <- 1 +#' {{ next_example }} +#' @examples +#' (your example app 1) +#' @examplesShinyLive +#' y <- 1 +#' {{ next_example }} +#' @examples +#' (your example app 2) +} diff --git a/roxy.shinylive.Rproj b/roxy.shinylive.Rproj new file mode 100644 index 0000000..aaa62a5 --- /dev/null +++ b/roxy.shinylive.Rproj @@ -0,0 +1,17 @@ +Version: 1.0 + +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +Encoding: UTF-8 + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes +LineEndingConversion: Posix + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..b31aad1 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,3 @@ +pkg_name <- "roxy.shinylive" +library(pkg_name, character.only = TRUE) +testthat::test_check(pkg_name) diff --git a/tests/testthat/_snaps/examplesShinyLive.md b/tests/testthat/_snaps/examplesShinyLive.md new file mode 100644 index 0000000..3126614 --- /dev/null +++ b/tests/testthat/_snaps/examplesShinyLive.md @@ -0,0 +1,14 @@ +# examplesShinyLive tag - errors - missing @examples + + Code + block <- roxygen2::parse_text(text)[[1]] + Message + x <text>:8: @examplesShinyLive requires a value. + +# examplesShinyLive tag - keywords - error when parsing with glue + + Code + block <- roxygen2::parse_text(text)[[1]] + Message + x <text>:8: @examplesShinyLive failed to interpolate the content. + diff --git a/tests/testthat/setup-options.R b/tests/testthat/setup-options.R new file mode 100644 index 0000000..78be1f9 --- /dev/null +++ b/tests/testthat/setup-options.R @@ -0,0 +1,20 @@ +# `opts_partial_match_old` is left for exclusions due to partial matching in dependent packages (i.e. not fixable here) +# it might happen that it is not used right now, but it is left for possible future use +# use with: `withr::with_options(opts_partial_match_old, { ... })` inside the test +opts_partial_match_old <- list( + warnPartialMatchDollar = getOption("warnPartialMatchDollar"), + warnPartialMatchArgs = getOption("warnPartialMatchArgs"), + warnPartialMatchAttr = getOption("warnPartialMatchAttr") +) +opts_partial_match_new <- list( + warnPartialMatchDollar = TRUE, + warnPartialMatchArgs = TRUE, + warnPartialMatchAttr = TRUE +) + +if (isFALSE(getFromNamespace("on_cran", "testthat")()) && requireNamespace("withr", quietly = TRUE)) { + withr::local_options( + opts_partial_match_new, + .local_envir = testthat::teardown_env() + ) +} diff --git a/tests/testthat/test-examplesShinyLive.R b/tests/testthat/test-examplesShinyLive.R new file mode 100644 index 0000000..9588c45 --- /dev/null +++ b/tests/testthat/test-examplesShinyLive.R @@ -0,0 +1,295 @@ +test_that("examplesShinyLive tag - errors - missing @examples", { + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' @examplesShinyLive + f <- function(x, y) x + y + " + expect_snapshot( + block <- roxygen2::parse_text(text)[[1]] + ) + expect_false(roxygen2::block_has_tags(block, "examplesShinyLive")) +}) + +test_that("examplesShinyLive tag - single occurrence", { + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' @examplesShinyLive + #' @examples + #' f(1, 2) + f <- function(x, y) x + y + " + expect_silent(block <- roxygen2::parse_text(text)[[1]]) + expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_length( + roxygen2::block_get_tags(block, "examplesShinyLive"), + 1 + ) + expect_identical( + roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + "\nf(1, 2)" + ) + expect_identical( + roxygen2::block_get_tag_value(block, "examplesShinyLive"), + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" + ) +}) + +test_that("examplesShinyLive tag - multiple occurrences", { + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @examplesShinyLive + #' @examples + #' f(1, 2) + #' @examplesShinyLive + #' @examples + #' f(1, 3) + f <- function(x, y) x + y + " + expect_silent(block <- roxygen2::parse_text(text)[[1]]) + expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_length( + roxygen2::block_get_tags(block, "examplesShinyLive"), + 2 + ) + expect_identical( + roxygen2::block_get_tags(block, "examplesShinyLive")[[1]]$raw, + "\nf(1, 2)" + ) + expect_identical( + roxygen2::block_get_tags(block, "examplesShinyLive")[[2]]$raw, + "\nf(1, 3)" + ) + expect_identical( + roxygen2::block_get_tags(block, "examplesShinyLive")[[1]]$val, + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" + ) + expect_identical( + roxygen2::block_get_tags(block, "examplesShinyLive")[[2]]$val, + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIBmASjAF8BdIA" + ) +}) + +test_that("examplesShinyLive tag - don't use previous example code", { + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' + #' @examples + #' x <- 'this is excluded' + #' @examplesShinyLive + #' @examples + #' f(1, 2) + f <- function(x, y) x + y + " + expect_silent(block <- roxygen2::parse_text(text)[[1]]) + expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_length( + roxygen2::block_get_tags(block, "examplesShinyLive"), + 1 + ) + expect_identical( + roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + "\nf(1, 2)" + ) + expect_identical( + roxygen2::block_get_tag_value(block, "examplesShinyLive"), + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" + ) +}) + +test_that("examplesShinyLive tag - keywords - {{next_example}}", { + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' + #' @examplesShinyLive + #' {{ next_example }} + #' @examples + #' f(1, 2) + f <- function(x, y) x + y + " + expect_silent(block <- roxygen2::parse_text(text)[[1]]) + expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_length( + roxygen2::block_get_tags(block, "examplesShinyLive"), + 1 + ) + expect_identical( + roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + "\nf(1, 2)" + ) + expect_identical( + roxygen2::block_get_tag_value(block, "examplesShinyLive"), + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" + ) +}) + +test_that("examplesShinyLive tag - keywords - {{prev_example}}", { + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' + #' @examples + #' f(1, 2) + #' @examplesShinyLive + #' {{ prev_example }} + f <- function(x, y) x + y + " + expect_silent(block <- roxygen2::parse_text(text)[[1]]) + expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_length( + roxygen2::block_get_tags(block, "examplesShinyLive"), + 1 + ) + expect_identical( + roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + "\nf(1, 2)" + ) + expect_identical( + roxygen2::block_get_tag_value(block, "examplesShinyLive"), + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" + ) +}) + +test_that("examplesShinyLive tag - keywords - {{examples}}", { + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' + #' @examples + #' f(1, 2) + #' @examplesShinyLive + #' {{ examples[[1]] }} + f <- function(x, y) x + y + " + expect_silent(block <- roxygen2::parse_text(text)[[1]]) + expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_length( + roxygen2::block_get_tags(block, "examplesShinyLive"), + 1 + ) + expect_identical( + roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + "\nf(1, 2)" + ) + expect_identical( + roxygen2::block_get_tag_value(block, "examplesShinyLive"), + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" + ) +}) + +test_that("examplesShinyLive tag - keywords - {{tags_examples}}", { + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' + #' @examples + #' f(1, 2) + #' @examplesShinyLive + #' {{ tags_examples[[1]]$raw }} + f <- function(x, y) x + y + " + expect_silent(block <- roxygen2::parse_text(text)[[1]]) + expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_length( + roxygen2::block_get_tags(block, "examplesShinyLive"), + 1 + ) + expect_identical( + roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + "\nf(1, 2)" + ) + expect_identical( + roxygen2::block_get_tag_value(block, "examplesShinyLive"), + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" + ) +}) + +test_that("examplesShinyLive tag - keywords - error when parsing with glue", { + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' @examplesShinyLive + #' {{ keyword_not_found }} + f <- function(x, y) x + y + " + expect_snapshot( + block <- roxygen2::parse_text(text)[[1]] + ) + expect_false(roxygen2::block_has_tags(block, "examplesShinyLive")) +}) + + + +test_that("examplesShinyLive tag - decorate using {{next_example}} keyword", { + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' + #' @example + #' x <- 'this is excluded' + #' @examplesShinyLive + #' x1 <- 1 # this is included + #' {{ next_example }} + #' x2 <- 2 # this is included + #' @examples + #' f(1, 2) + f <- function(x, y) x + y + " + expect_silent(block <- roxygen2::parse_text(text)[[1]]) + expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_length( + roxygen2::block_get_tags(block, "examplesShinyLive"), + 1 + ) + expect_identical( + roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + "x1 <- 1 # this is included\n\nf(1, 2)\nx2 <- 2 # this is included" + ) + expect_identical( + roxygen2::block_get_tag_value(block, "examplesShinyLive"), + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMADwEYACAHgFp6GBie0gCwEsBnegKEQCAGwCuAEzhSAOhAUAzABS1c9AEwBKBdU1NWBzj2FnRkmVLABfALpA" + ) +}) \ No newline at end of file diff --git a/tests/testthat/test-parse_url.R b/tests/testthat/test-parse_url.R new file mode 100644 index 0000000..febc86e --- /dev/null +++ b/tests/testthat/test-parse_url.R @@ -0,0 +1,7 @@ +test_that("create_shinylive_url works as expected", { + app_code <- "x <- 1" + expect_identical( + create_shinylive_url(app_code), + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMADwAIAeAWloEYwBfAXSA" + ) +}) From f576c902055be0d3980d36f7186a1bac894eddac Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:28:46 +0000 Subject: [PATCH 02/22] [skip style] [skip vbump] Restyle files --- tests/testthat/test-examplesShinyLive.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-examplesShinyLive.R b/tests/testthat/test-examplesShinyLive.R index 9588c45..edbebc1 100644 --- a/tests/testthat/test-examplesShinyLive.R +++ b/tests/testthat/test-examplesShinyLive.R @@ -292,4 +292,4 @@ test_that("examplesShinyLive tag - decorate using {{next_example}} keyword", { roxygen2::block_get_tag_value(block, "examplesShinyLive"), "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMADwEYACAHgFp6GBie0gCwEsBnegKEQCAGwCuAEzhSAOhAUAzABS1c9AEwBKBdU1NWBzj2FnRkmVLABfALpA" ) -}) \ No newline at end of file +}) From 7ec7bf2c17f4ed3fe43c781ea508b41549597bd5 Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:39:30 +0200 Subject: [PATCH 03/22] ShinyLive to Shinylive; spelling --- DESCRIPTION | 4 +- NAMESPACE | 6 +- R/examplesShinyLive.R | 46 ++++---- R/parse_url.R | 4 +- README.md | 8 +- inst/WORDLIST | 6 ++ man/create_shinylive_url.Rd | 6 +- man/tag-examplesShinyLive.Rd | 36 +++---- tests/testthat/_snaps/examplesShinyLive.md | 8 +- tests/testthat/test-examplesShinyLive.R | 116 ++++++++++----------- 10 files changed, 123 insertions(+), 117 deletions(-) create mode 100644 inst/WORDLIST diff --git a/DESCRIPTION b/DESCRIPTION index f8351df..9fae1e4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: roxy.shinylive Title: A roxygen2 Extension for Shinylive -Version: 0.0.0.9000 +Version: 0.0.0.9001 Authors@R: c( person("Pawel Rucki", , , "pawel.rucki@roche.com", role = c("aut", "cre")), person("F. Hoffmann-La Roche AG", role = c("cph", "fnd")) @@ -36,5 +36,5 @@ Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.2 Config/testthat/edition: 3 Collate: - 'examplesShinyLive.R' + 'examplesShinylive.R' 'parse_url.R' diff --git a/NAMESPACE b/NAMESPACE index c641397..dfd57ae 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,8 +1,8 @@ # Generated by roxygen2: do not edit by hand -S3method(format,rd_section_examplesShinyLive) -S3method(roxygen2::roxy_tag_parse,roxy_tag_examplesShinyLive) -S3method(roxygen2::roxy_tag_rd,roxy_tag_examplesShinyLive) +S3method(format,rd_section_examplesShinylive) +S3method(roxygen2::roxy_tag_parse,roxy_tag_examplesShinylive) +S3method(roxygen2::roxy_tag_rd,roxy_tag_examplesShinylive) importFrom(glue,glue_data) importFrom(jsonlite,toJSON) importFrom(jsonlite,unbox) diff --git a/R/examplesShinyLive.R b/R/examplesShinyLive.R index 02cc7db..25208e4 100644 --- a/R/examplesShinyLive.R +++ b/R/examplesShinyLive.R @@ -1,9 +1,9 @@ -#' Custom `examplesShinyLive` tag. +#' Custom `examplesShinylive` tag. #' -#' This function generates a new section with ShinyLive links to the applications from this tag content. +#' This function generates a new section with Shinylive links to the applications from this tag content. #' If no code is provided then the code from the following `@examples` tag is used. #' -#' The application code must be executable inside ShinyLive. If the application code includes functions from your +#' The application code must be executable inside Shinylive. If the application code includes functions from your #' package, you must add `library(<your package>)` beforehand. For more information, refer to the Decoration section #' on how to use and decorate existing examples. #' @@ -12,8 +12,8 @@ #' #' @section Decoration: #' -#' To avoid repetition between the `@examplesShinyLive` and `@examples` sections, there are special string literals -#' that provide access to the `@examples` content from within `@examplesShinyLive`. +#' To avoid repetition between the `@examplesShinylive` and `@examples` sections, there are special string literals +#' that provide access to the `@examples` content from within `@examplesShinylive`. #' These literals should be used as expressions embraced with `{{ }}`, which are then interpolated using #' `glue::glue_data(..., .open = "{{", .close = "}}")`. #' @@ -23,26 +23,26 @@ #' * `"{{ next_example }}"` - "raw" element of the next example #' * `"{{ prev_example }}"` - "raw" element of the previous example #' -#' This allows you to access and decorate existing example code to create executable application code for ShinyLive. +#' This allows you to access and decorate existing example code to create executable application code for Shinylive. #' Refer to the examples section for possible use cases. #' -#' @name tag-examplesShinyLive +#' @name tag-examplesShinylive #' #' @usage -#' #' @examplesShinyLive${1:# example code (optional)} +#' #' @examplesShinylive${1:# example code (optional)} #' #' @examples #' # As a part of documentation: #' #' # basic example: #' #' (docs) -#' #' @examplesShinyLive +#' #' @examplesShinylive #' #' @examples #' #' (example code) #' #' # using keywords: #' #' (docs) -#' #' @examplesShinyLive +#' #' @examplesShinylive #' #' foo <- 1 #' #' {{ next_example }} #' #' bar <- 2 @@ -51,7 +51,7 @@ #' #' # A typical example would be: #' #' (docs) -#' #' @examplesShinyLive +#' #' @examplesShinylive #' #' library(<your package>) #' #' interactive <- function() TRUE #' #' {{ next_example }} @@ -63,10 +63,10 @@ #' #' # multiple apps: #' #' (docs) -#' #' @examplesShinyLive +#' #' @examplesShinylive #' #' @examples #' #' (your example app 1) -#' #' @examplesShinyLive +#' #' @examplesShinylive #' #' @examples #' #' (your example app 2) #' @@ -74,18 +74,18 @@ #' #' (docs) #' #' @examples #' #' (your example code - skipped) -#' #' @examplesShinyLive +#' #' @examplesShinylive #' #' @examples #' #' (your example code - included) #' #' # multiple apps with keywords: #' #' (docs) -#' #' @examplesShinyLive +#' #' @examplesShinylive #' #' x <- 1 #' #' {{ next_example }} #' #' @examples #' #' (your example app 1) -#' #' @examplesShinyLive +#' #' @examplesShinylive #' #' y <- 1 #' #' {{ next_example }} #' #' @examples @@ -93,11 +93,11 @@ NULL #' @noRd -#' @exportS3Method roxygen2::roxy_tag_parse roxy_tag_examplesShinyLive +#' @exportS3Method roxygen2::roxy_tag_parse roxy_tag_examplesShinylive #' @importFrom glue glue_data #' @importFrom stringr str_trim #' @importFrom roxygen2 warn_roxy_tag -roxy_tag_parse.roxy_tag_examplesShinyLive <- function(x) { +roxy_tag_parse.roxy_tag_examplesShinylive <- function(x) { if (stringr::str_trim(x$raw) == "") { x$raw <- "{{ next_example }}" } @@ -150,15 +150,15 @@ roxy_tag_parse.roxy_tag_examplesShinyLive <- function(x) { } #' @noRd -#' @exportS3Method roxygen2::roxy_tag_rd roxy_tag_examplesShinyLive +#' @exportS3Method roxygen2::roxy_tag_rd roxy_tag_examplesShinylive #' @importFrom roxygen2 rd_section -roxy_tag_rd.roxy_tag_examplesShinyLive <- function(x, base_path, env) { - roxygen2::rd_section("examplesShinyLive", x$val) +roxy_tag_rd.roxy_tag_examplesShinylive <- function(x, base_path, env) { + roxygen2::rd_section("examplesShinylive", x$val) } #' @noRd -#' @exportS3Method format rd_section_examplesShinyLive -format.rd_section_examplesShinyLive <- function(x, ...) { +#' @exportS3Method format rd_section_examplesShinylive +format.rd_section_examplesShinylive <- function(x, ...) { paste0( "\\section{Run examples in Shinylive}{\n", "\\itemize{\n", diff --git a/R/parse_url.R b/R/parse_url.R index ee8a4e0..25c3b94 100644 --- a/R/parse_url.R +++ b/R/parse_url.R @@ -1,10 +1,10 @@ -#' Creates shinylive url for the app code. +#' Creates Shinylive url for the app code. #' #' @importFrom jsonlite unbox toJSON #' @importFrom lzstring compressToEncodedURIComponent #' #' @param code (`character(1)`) A string with app code. -#' @return (`character(1)`) ShinyLive app url. +#' @return (`character(1)`) Shinylive app url. create_shinylive_url <- function(code) { # implementation based on "Create ShinyLive Link" feature of Shiny VSCode extension # https://github.com/posit-dev/shiny-vscode/blob/80560bf36d516ff89dffe88bd9a28cee9edd4d43/src/shinylive.ts#L499 diff --git a/README.md b/README.md index a6e6dd9..212fb19 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This package provides a `roxygen2` extension that automatically takes the exampl ## Install ```r -devtools::install_github("insightsengineering/roxy.shinylive") +pak::pak("insightsengineering/roxy.shinylive") ``` ## Usage @@ -20,9 +20,9 @@ Roxygen: list(markdown = TRUE, packages = c("roxy.shinylive")) Then in your package documentation: ```r #' (docs) -#' @examplesShinyLive +#' @examplesShinylive #' @examples -#' (example code with shiny App) +#' (example code with a Shiny app) ``` Which would produce a following output in your documentation: @@ -37,4 +37,4 @@ Which would produce a following output in your documentation: } ``` -See the pacakge documentation for more details. +See the package documentation for more details. diff --git a/inst/WORDLIST b/inst/WORDLIST new file mode 100644 index 0000000..55883b0 --- /dev/null +++ b/inst/WORDLIST @@ -0,0 +1,6 @@ +Shinylive +WebR +installable +roxy +roxygen +shinylive diff --git a/man/create_shinylive_url.Rd b/man/create_shinylive_url.Rd index 8886a5d..6175fcd 100644 --- a/man/create_shinylive_url.Rd +++ b/man/create_shinylive_url.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/parse_url.R \name{create_shinylive_url} \alias{create_shinylive_url} -\title{Creates shinylive url for the app code.} +\title{Creates Shinylive url for the app code.} \usage{ create_shinylive_url(code) } @@ -10,8 +10,8 @@ create_shinylive_url(code) \item{code}{(\code{character(1)}) A string with app code.} } \value{ -(\code{character(1)}) ShinyLive app url. +(\code{character(1)}) Shinylive app url. } \description{ -Creates shinylive url for the app code. +Creates Shinylive url for the app code. } diff --git a/man/tag-examplesShinyLive.Rd b/man/tag-examplesShinyLive.Rd index 9ce7f45..e345dc1 100644 --- a/man/tag-examplesShinyLive.Rd +++ b/man/tag-examplesShinyLive.Rd @@ -1,17 +1,17 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/examplesShinyLive.R -\name{tag-examplesShinyLive} -\alias{tag-examplesShinyLive} -\title{Custom \code{examplesShinyLive} tag.} +% Please edit documentation in R/examplesShinylive.R +\name{tag-examplesShinylive} +\alias{tag-examplesShinylive} +\title{Custom \code{examplesShinylive} tag.} \usage{ -#' @examplesShinyLive${1:# example code (optional)} +#' @examplesShinylive${1:# example code (optional)} } \description{ -This function generates a new section with ShinyLive links to the applications from this tag content. +This function generates a new section with Shinylive links to the applications from this tag content. If no code is provided then the code from the following \verb{@examples} tag is used. } \details{ -The application code must be executable inside ShinyLive. If the application code includes functions from your +The application code must be executable inside Shinylive. If the application code includes functions from your package, you must add \verb{library(<your package>)} beforehand. For more information, refer to the Decoration section on how to use and decorate existing examples. @@ -21,8 +21,8 @@ See \href{https://docs.r-wasm.org/webr/latest/packages.html}{this article} for m \section{Decoration}{ -To avoid repetition between the \verb{@examplesShinyLive} and \verb{@examples} sections, there are special string literals -that provide access to the \verb{@examples} content from within \verb{@examplesShinyLive}. +To avoid repetition between the \verb{@examplesShinylive} and \verb{@examples} sections, there are special string literals +that provide access to the \verb{@examples} content from within \verb{@examplesShinylive}. These literals should be used as expressions embraced with \code{{{ }}}, which are then interpolated using \code{glue::glue_data(..., .open = "{{", .close = "}}")}. @@ -34,7 +34,7 @@ The following keywords are available: \item \code{"{{ prev_example }}"} - "raw" element of the previous example } -This allows you to access and decorate existing example code to create executable application code for ShinyLive. +This allows you to access and decorate existing example code to create executable application code for Shinylive. Refer to the examples section for possible use cases. } @@ -43,13 +43,13 @@ Refer to the examples section for possible use cases. # basic example: #' (docs) -#' @examplesShinyLive +#' @examplesShinylive #' @examples #' (example code) # using keywords: #' (docs) -#' @examplesShinyLive +#' @examplesShinylive #' foo <- 1 #' {{ next_example }} #' bar <- 2 @@ -58,7 +58,7 @@ Refer to the examples section for possible use cases. # A typical example would be: #' (docs) -#' @examplesShinyLive +#' @examplesShinylive #' library(<your package>) #' interactive <- function() TRUE #' {{ next_example }} @@ -70,10 +70,10 @@ Refer to the examples section for possible use cases. # multiple apps: #' (docs) -#' @examplesShinyLive +#' @examplesShinylive #' @examples #' (your example app 1) -#' @examplesShinyLive +#' @examplesShinylive #' @examples #' (your example app 2) @@ -81,18 +81,18 @@ Refer to the examples section for possible use cases. #' (docs) #' @examples #' (your example code - skipped) -#' @examplesShinyLive +#' @examplesShinylive #' @examples #' (your example code - included) # multiple apps with keywords: #' (docs) -#' @examplesShinyLive +#' @examplesShinylive #' x <- 1 #' {{ next_example }} #' @examples #' (your example app 1) -#' @examplesShinyLive +#' @examplesShinylive #' y <- 1 #' {{ next_example }} #' @examples diff --git a/tests/testthat/_snaps/examplesShinyLive.md b/tests/testthat/_snaps/examplesShinyLive.md index 3126614..21a9616 100644 --- a/tests/testthat/_snaps/examplesShinyLive.md +++ b/tests/testthat/_snaps/examplesShinyLive.md @@ -1,14 +1,14 @@ -# examplesShinyLive tag - errors - missing @examples +# examplesShinylive tag - errors - missing @examples Code block <- roxygen2::parse_text(text)[[1]] Message - x <text>:8: @examplesShinyLive requires a value. + x <text>:8: @examplesShinylive requires a value. -# examplesShinyLive tag - keywords - error when parsing with glue +# examplesShinylive tag - keywords - error when parsing with glue Code block <- roxygen2::parse_text(text)[[1]] Message - x <text>:8: @examplesShinyLive failed to interpolate the content. + x <text>:8: @examplesShinylive failed to interpolate the content. diff --git a/tests/testthat/test-examplesShinyLive.R b/tests/testthat/test-examplesShinyLive.R index edbebc1..85b3b93 100644 --- a/tests/testthat/test-examplesShinyLive.R +++ b/tests/testthat/test-examplesShinyLive.R @@ -1,4 +1,4 @@ -test_that("examplesShinyLive tag - errors - missing @examples", { +test_that("examplesShinylive tag - errors - missing @examples", { text <- " #' This is a title #' @@ -6,16 +6,16 @@ test_that("examplesShinyLive tag - errors - missing @examples", { #' #' @param x,y A number #' @export - #' @examplesShinyLive + #' @examplesShinylive f <- function(x, y) x + y " expect_snapshot( block <- roxygen2::parse_text(text)[[1]] ) - expect_false(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_false(roxygen2::block_has_tags(block, "examplesShinylive")) }) -test_that("examplesShinyLive tag - single occurrence", { +test_that("examplesShinylive tag - single occurrence", { text <- " #' This is a title #' @@ -23,67 +23,67 @@ test_that("examplesShinyLive tag - single occurrence", { #' #' @param x,y A number #' @export - #' @examplesShinyLive + #' @examplesShinylive #' @examples #' f(1, 2) f <- function(x, y) x + y " expect_silent(block <- roxygen2::parse_text(text)[[1]]) - expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_true(roxygen2::block_has_tags(block, "examplesShinylive")) expect_length( - roxygen2::block_get_tags(block, "examplesShinyLive"), + roxygen2::block_get_tags(block, "examplesShinylive"), 1 ) expect_identical( - roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + roxygen2::block_get_tag(block, "examplesShinylive")$raw, "\nf(1, 2)" ) expect_identical( - roxygen2::block_get_tag_value(block, "examplesShinyLive"), + roxygen2::block_get_tag_value(block, "examplesShinylive"), "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" ) }) -test_that("examplesShinyLive tag - multiple occurrences", { +test_that("examplesShinylive tag - multiple occurrences", { text <- " #' This is a title #' #' This is the description. #' #' @param x,y A number - #' @examplesShinyLive + #' @examplesShinylive #' @examples #' f(1, 2) - #' @examplesShinyLive + #' @examplesShinylive #' @examples #' f(1, 3) f <- function(x, y) x + y " expect_silent(block <- roxygen2::parse_text(text)[[1]]) - expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_true(roxygen2::block_has_tags(block, "examplesShinylive")) expect_length( - roxygen2::block_get_tags(block, "examplesShinyLive"), + roxygen2::block_get_tags(block, "examplesShinylive"), 2 ) expect_identical( - roxygen2::block_get_tags(block, "examplesShinyLive")[[1]]$raw, + roxygen2::block_get_tags(block, "examplesShinylive")[[1]]$raw, "\nf(1, 2)" ) expect_identical( - roxygen2::block_get_tags(block, "examplesShinyLive")[[2]]$raw, + roxygen2::block_get_tags(block, "examplesShinylive")[[2]]$raw, "\nf(1, 3)" ) expect_identical( - roxygen2::block_get_tags(block, "examplesShinyLive")[[1]]$val, + roxygen2::block_get_tags(block, "examplesShinylive")[[1]]$val, "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" ) expect_identical( - roxygen2::block_get_tags(block, "examplesShinyLive")[[2]]$val, + roxygen2::block_get_tags(block, "examplesShinylive")[[2]]$val, "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIBmASjAF8BdIA" ) }) -test_that("examplesShinyLive tag - don't use previous example code", { +test_that("examplesShinylive tag - don't use previous example code", { text <- " #' This is a title #' @@ -94,28 +94,28 @@ test_that("examplesShinyLive tag - don't use previous example code", { #' #' @examples #' x <- 'this is excluded' - #' @examplesShinyLive + #' @examplesShinylive #' @examples #' f(1, 2) f <- function(x, y) x + y " expect_silent(block <- roxygen2::parse_text(text)[[1]]) - expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_true(roxygen2::block_has_tags(block, "examplesShinylive")) expect_length( - roxygen2::block_get_tags(block, "examplesShinyLive"), + roxygen2::block_get_tags(block, "examplesShinylive"), 1 ) expect_identical( - roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + roxygen2::block_get_tag(block, "examplesShinylive")$raw, "\nf(1, 2)" ) expect_identical( - roxygen2::block_get_tag_value(block, "examplesShinyLive"), + roxygen2::block_get_tag_value(block, "examplesShinylive"), "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" ) }) -test_that("examplesShinyLive tag - keywords - {{next_example}}", { +test_that("examplesShinylive tag - keywords - {{next_example}}", { text <- " #' This is a title #' @@ -124,29 +124,29 @@ test_that("examplesShinyLive tag - keywords - {{next_example}}", { #' @param x,y A number #' @export #' - #' @examplesShinyLive + #' @examplesShinylive #' {{ next_example }} #' @examples #' f(1, 2) f <- function(x, y) x + y " expect_silent(block <- roxygen2::parse_text(text)[[1]]) - expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_true(roxygen2::block_has_tags(block, "examplesShinylive")) expect_length( - roxygen2::block_get_tags(block, "examplesShinyLive"), + roxygen2::block_get_tags(block, "examplesShinylive"), 1 ) expect_identical( - roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + roxygen2::block_get_tag(block, "examplesShinylive")$raw, "\nf(1, 2)" ) expect_identical( - roxygen2::block_get_tag_value(block, "examplesShinyLive"), + roxygen2::block_get_tag_value(block, "examplesShinylive"), "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" ) }) -test_that("examplesShinyLive tag - keywords - {{prev_example}}", { +test_that("examplesShinylive tag - keywords - {{prev_example}}", { text <- " #' This is a title #' @@ -157,27 +157,27 @@ test_that("examplesShinyLive tag - keywords - {{prev_example}}", { #' #' @examples #' f(1, 2) - #' @examplesShinyLive + #' @examplesShinylive #' {{ prev_example }} f <- function(x, y) x + y " expect_silent(block <- roxygen2::parse_text(text)[[1]]) - expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_true(roxygen2::block_has_tags(block, "examplesShinylive")) expect_length( - roxygen2::block_get_tags(block, "examplesShinyLive"), + roxygen2::block_get_tags(block, "examplesShinylive"), 1 ) expect_identical( - roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + roxygen2::block_get_tag(block, "examplesShinylive")$raw, "\nf(1, 2)" ) expect_identical( - roxygen2::block_get_tag_value(block, "examplesShinyLive"), + roxygen2::block_get_tag_value(block, "examplesShinylive"), "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" ) }) -test_that("examplesShinyLive tag - keywords - {{examples}}", { +test_that("examplesShinylive tag - keywords - {{examples}}", { text <- " #' This is a title #' @@ -188,27 +188,27 @@ test_that("examplesShinyLive tag - keywords - {{examples}}", { #' #' @examples #' f(1, 2) - #' @examplesShinyLive + #' @examplesShinylive #' {{ examples[[1]] }} f <- function(x, y) x + y " expect_silent(block <- roxygen2::parse_text(text)[[1]]) - expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_true(roxygen2::block_has_tags(block, "examplesShinylive")) expect_length( - roxygen2::block_get_tags(block, "examplesShinyLive"), + roxygen2::block_get_tags(block, "examplesShinylive"), 1 ) expect_identical( - roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + roxygen2::block_get_tag(block, "examplesShinylive")$raw, "\nf(1, 2)" ) expect_identical( - roxygen2::block_get_tag_value(block, "examplesShinyLive"), + roxygen2::block_get_tag_value(block, "examplesShinylive"), "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" ) }) -test_that("examplesShinyLive tag - keywords - {{tags_examples}}", { +test_that("examplesShinylive tag - keywords - {{tags_examples}}", { text <- " #' This is a title #' @@ -219,27 +219,27 @@ test_that("examplesShinyLive tag - keywords - {{tags_examples}}", { #' #' @examples #' f(1, 2) - #' @examplesShinyLive + #' @examplesShinylive #' {{ tags_examples[[1]]$raw }} f <- function(x, y) x + y " expect_silent(block <- roxygen2::parse_text(text)[[1]]) - expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_true(roxygen2::block_has_tags(block, "examplesShinylive")) expect_length( - roxygen2::block_get_tags(block, "examplesShinyLive"), + roxygen2::block_get_tags(block, "examplesShinylive"), 1 ) expect_identical( - roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + roxygen2::block_get_tag(block, "examplesShinylive")$raw, "\nf(1, 2)" ) expect_identical( - roxygen2::block_get_tag_value(block, "examplesShinyLive"), + roxygen2::block_get_tag_value(block, "examplesShinylive"), "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAHQgDMAKARlwAIAmASjAF8BdIA" ) }) -test_that("examplesShinyLive tag - keywords - error when parsing with glue", { +test_that("examplesShinylive tag - keywords - error when parsing with glue", { text <- " #' This is a title #' @@ -247,19 +247,19 @@ test_that("examplesShinyLive tag - keywords - error when parsing with glue", { #' #' @param x,y A number #' @export - #' @examplesShinyLive + #' @examplesShinylive #' {{ keyword_not_found }} f <- function(x, y) x + y " expect_snapshot( block <- roxygen2::parse_text(text)[[1]] ) - expect_false(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_false(roxygen2::block_has_tags(block, "examplesShinylive")) }) -test_that("examplesShinyLive tag - decorate using {{next_example}} keyword", { +test_that("examplesShinylive tag - decorate using {{next_example}} keyword", { text <- " #' This is a title #' @@ -270,7 +270,7 @@ test_that("examplesShinyLive tag - decorate using {{next_example}} keyword", { #' #' @example #' x <- 'this is excluded' - #' @examplesShinyLive + #' @examplesShinylive #' x1 <- 1 # this is included #' {{ next_example }} #' x2 <- 2 # this is included @@ -279,17 +279,17 @@ test_that("examplesShinyLive tag - decorate using {{next_example}} keyword", { f <- function(x, y) x + y " expect_silent(block <- roxygen2::parse_text(text)[[1]]) - expect_true(roxygen2::block_has_tags(block, "examplesShinyLive")) + expect_true(roxygen2::block_has_tags(block, "examplesShinylive")) expect_length( - roxygen2::block_get_tags(block, "examplesShinyLive"), + roxygen2::block_get_tags(block, "examplesShinylive"), 1 ) expect_identical( - roxygen2::block_get_tag(block, "examplesShinyLive")$raw, + roxygen2::block_get_tag(block, "examplesShinylive")$raw, "x1 <- 1 # this is included\n\nf(1, 2)\nx2 <- 2 # this is included" ) expect_identical( - roxygen2::block_get_tag_value(block, "examplesShinyLive"), - "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMADwEYACAHgFp6GBie0gCwEsBnegKEQCAGwCuAEzhSAOhAUAzABS1c9AEwBKBdU1NWBzj2FnRkmVLABfALpA" + roxygen2::block_get_tag_value(block, "examplesShinylive"), + "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMADwEYACAHgFp6GBie0gCwEsBnegKEQCAGwCuAEzhSAOhAUAzABS1c9AEwBKBdU1NWBzj2FnRkmVLABfALpA" # nolint: line_length_linter. ) }) From 185238afb6d62fed5dfd61c7e1123a198eee7cd1 Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:49:43 +0200 Subject: [PATCH 04/22] rename file --- DESCRIPTION | 4 ++-- R/{examplesShinyLive.R => tag_examplesShinylive.R} | 0 README.md | 2 ++ man/tag-examplesShinyLive.Rd | 2 +- ...{test-examplesShinyLive.R => test-tag_examplesShinylive.R} | 0 5 files changed, 5 insertions(+), 3 deletions(-) rename R/{examplesShinyLive.R => tag_examplesShinylive.R} (100%) rename tests/testthat/{test-examplesShinyLive.R => test-tag_examplesShinylive.R} (100%) diff --git a/DESCRIPTION b/DESCRIPTION index 9fae1e4..317ea0a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Type: Package Package: roxy.shinylive -Title: A roxygen2 Extension for Shinylive +Title: A Roxygen2 Extension for Shinylive Version: 0.0.0.9001 Authors@R: c( person("Pawel Rucki", , , "pawel.rucki@roche.com", role = c("aut", "cre")), @@ -36,5 +36,5 @@ Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.2 Config/testthat/edition: 3 Collate: - 'examplesShinylive.R' + 'tag_examplesShinylive.R' 'parse_url.R' diff --git a/R/examplesShinyLive.R b/R/tag_examplesShinylive.R similarity index 100% rename from R/examplesShinyLive.R rename to R/tag_examplesShinylive.R diff --git a/README.md b/README.md index 212fb19..e6ed676 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,13 @@ pak::pak("insightsengineering/roxy.shinylive") ## Usage In your `DESCRIPTION` file, add the following: + ```yaml Roxygen: list(markdown = TRUE, packages = c("roxy.shinylive")) ``` Then in your package documentation: + ```r #' (docs) #' @examplesShinylive diff --git a/man/tag-examplesShinyLive.Rd b/man/tag-examplesShinyLive.Rd index e345dc1..7a77abf 100644 --- a/man/tag-examplesShinyLive.Rd +++ b/man/tag-examplesShinyLive.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/examplesShinylive.R +% Please edit documentation in R/tag_examplesShinylive.R \name{tag-examplesShinylive} \alias{tag-examplesShinylive} \title{Custom \code{examplesShinylive} tag.} diff --git a/tests/testthat/test-examplesShinyLive.R b/tests/testthat/test-tag_examplesShinylive.R similarity index 100% rename from tests/testthat/test-examplesShinyLive.R rename to tests/testthat/test-tag_examplesShinylive.R From d1eb09259c18be6db9f104bcb17da581646c6f77 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:53:01 +0000 Subject: [PATCH 05/22] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/{tag-examplesShinyLive.Rd => tag-examplesShinylive.Rd} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename man/{tag-examplesShinyLive.Rd => tag-examplesShinylive.Rd} (100%) diff --git a/man/tag-examplesShinyLive.Rd b/man/tag-examplesShinylive.Rd similarity index 100% rename from man/tag-examplesShinyLive.Rd rename to man/tag-examplesShinylive.Rd From 8ca36f9e81de9fc9f39ce190d62bea7c4e6cda29 Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:53:58 +0200 Subject: [PATCH 06/22] empty From 98bf9b82cb9070a9f839460bb2ae5a81229fbb5c Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:19:24 +0200 Subject: [PATCH 07/22] nolint --- R/tag_examplesShinylive.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index 25208e4..ff413e5 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -145,7 +145,7 @@ roxy_tag_parse.roxy_tag_examplesShinylive <- function(x) { roxygen2::warn_roxy_tag(x, "requires a value") return(NULL) } - x$val <- create_shinylive_url(x$raw) + x$val <- create_shinylive_url(x$raw) # nolint: object_usage_linter. x } From 6f0bdf091fb28603f5a1d7438a48922670521d3b Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:05:19 +0200 Subject: [PATCH 08/22] new feat: add iframe to docs; includes examplesIf tag --- R/tag_examplesShinylive.R | 35 ++++++++++++++----- README.md | 12 ++++--- man/tag-examplesShinylive.Rd | 11 +++--- ...sShinyLive.md => tag_examplesShinylive.md} | 0 4 files changed, 41 insertions(+), 17 deletions(-) rename tests/testthat/_snaps/{examplesShinyLive.md => tag_examplesShinylive.md} (100%) diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index ff413e5..8142f17 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -1,7 +1,8 @@ #' Custom `examplesShinylive` tag. #' -#' This function generates a new section with Shinylive links to the applications from this tag content. -#' If no code is provided then the code from the following `@examples` tag is used. +#' This function generates a new "Examples in Shinylive" section in the documentation. This section contains URL to +#' the application in Shinylive as well as an iframe with the application. +#' If no code is provided then the code is taken from the following `@examples` or `@examplesIf` tag. #' #' The application code must be executable inside Shinylive. If the application code includes functions from your #' package, you must add `library(<your package>)` beforehand. For more information, refer to the Decoration section @@ -13,13 +14,13 @@ #' @section Decoration: #' #' To avoid repetition between the `@examplesShinylive` and `@examples` sections, there are special string literals -#' that provide access to the `@examples` content from within `@examplesShinylive`. +#' that provide access to the other tags from within the content of `@examplesShinylive`. #' These literals should be used as expressions embraced with `{{ }}`, which are then interpolated using #' `glue::glue_data(..., .open = "{{", .close = "}}")`. #' #' The following keywords are available: -#' * `"{{ tags_examples }}"` - a list of tags with examples -#' * `"{{ examples }}"` - a list of "raw" elements from `tags_examples` +#' * `"{{ tags_examples }}"` - a list of `@examples` or `@examplesIf` tags +#' * `"{{ examples }}"` - a list of "raw" elements from `tags_examples` list elements #' * `"{{ next_example }}"` - "raw" element of the next example #' * `"{{ prev_example }}"` - "raw" element of the previous example #' @@ -105,7 +106,7 @@ roxy_tag_parse.roxy_tag_examplesShinylive <- function(x) { # not elegant but this is the most efficient way to access sibling tags tokens <- get("tokens", envir = parent.frame(3L)) - tags_examples <- Filter(function(x) x$tag == "examples", tokens) + tags_examples <- Filter(function(x) x$tag %in% c("examples", "examplesIf"), tokens) examples <- lapply(tags_examples, `[[`, "raw") @@ -159,10 +160,28 @@ roxy_tag_rd.roxy_tag_examplesShinylive <- function(x, base_path, env) { #' @noRd #' @exportS3Method format rd_section_examplesShinylive format.rd_section_examplesShinylive <- function(x, ...) { + iframe_attrs <- paste( + "height=\"800\"", + "width=\"150\\%\"", # @TODO: find a better way to set the width + "allow=\"fullscreen\"", + "scrolling=\"auto\"", + sep = " " + ) + iframe_style <- paste( + "border: 1px solid rgba(0,0,0,0.175);", + "border-radius: .375rem;", + sep = " " + ) + iframe_style <- paste0("style=\"", iframe_style, "\"") paste0( - "\\section{Run examples in Shinylive}{\n", + "\\section{Examples in Shinylive}{\n", "\\itemize{\n", - paste0(" \\item", "\\href{", x$value, "}{example-", seq_along(x$value), "}\n", collapse = ""), + paste0( + " \\item example-", seq_along(x$value), "\\cr\n", + " \\href{", x$value, "}{Open in Shinylive}\\cr\n", + " \\if{html}{\\out{<iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>}}\n", + collapse = "" + ), "}\n", "}\n" ) diff --git a/README.md b/README.md index e6ed676..25769f9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Overview -This package provides a `roxygen2` extension that automatically takes the example code from `@examples` tag that follows and crate an URL to the shinylive service. That URL is then added to the documentation. +This package provides a `roxygen2` extension that automatically takes the code from the `@examples` tag that follows and crate an URL to the shinylive service. During the documentation build, a new section is added to the function manual that contains aforementioned link as well as iframe to the application itself. ## Install @@ -30,10 +30,14 @@ Then in your package documentation: Which would produce a following output in your documentation: ```Rd -\section{Run examples in Shinylive}{ +\section{Examples in Shinylive}{ \itemize{ - \item\href{https://shinylive.io/r/app/#code=...}{example-1} - \item\href{https://shinylive.io/r/app/#code=...}{example-2} + \item example-1\cr + \href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr + \if{html}{\out{<iframe src="https://shinylive.io/r/app/#code=..." ..."></iframe>}} + \item example-2\cr + \href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr + \if{html}{\out{<iframe src="https://shinylive.io/r/app/#code=..." ..."></iframe>}} ... } } diff --git a/man/tag-examplesShinylive.Rd b/man/tag-examplesShinylive.Rd index 7a77abf..142c0a6 100644 --- a/man/tag-examplesShinylive.Rd +++ b/man/tag-examplesShinylive.Rd @@ -7,8 +7,9 @@ #' @examplesShinylive${1:# example code (optional)} } \description{ -This function generates a new section with Shinylive links to the applications from this tag content. -If no code is provided then the code from the following \verb{@examples} tag is used. +This function generates a new "Examples in Shinylive" section in the documentation. This section contains URL to +the application in Shinylive as well as an iframe with the application. +If no code is provided then the code is taken from the following \verb{@examples} or \verb{@examplesIf} tag. } \details{ The application code must be executable inside Shinylive. If the application code includes functions from your @@ -22,14 +23,14 @@ See \href{https://docs.r-wasm.org/webr/latest/packages.html}{this article} for m To avoid repetition between the \verb{@examplesShinylive} and \verb{@examples} sections, there are special string literals -that provide access to the \verb{@examples} content from within \verb{@examplesShinylive}. +that provide access to the other tags from within the content of \verb{@examplesShinylive}. These literals should be used as expressions embraced with \code{{{ }}}, which are then interpolated using \code{glue::glue_data(..., .open = "{{", .close = "}}")}. The following keywords are available: \itemize{ -\item \code{"{{ tags_examples }}"} - a list of tags with examples -\item \code{"{{ examples }}"} - a list of "raw" elements from \code{tags_examples} +\item \code{"{{ tags_examples }}"} - a list of \verb{@examples} or \verb{@examplesIf} tags +\item \code{"{{ examples }}"} - a list of "raw" elements from \code{tags_examples} list elements \item \code{"{{ next_example }}"} - "raw" element of the next example \item \code{"{{ prev_example }}"} - "raw" element of the previous example } diff --git a/tests/testthat/_snaps/examplesShinyLive.md b/tests/testthat/_snaps/tag_examplesShinylive.md similarity index 100% rename from tests/testthat/_snaps/examplesShinyLive.md rename to tests/testthat/_snaps/tag_examplesShinylive.md From 5812742ecd935f78c52dbceb757111a6cf10d16a Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:13:44 +0200 Subject: [PATCH 09/22] add to wordlist --- inst/WORDLIST | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inst/WORDLIST b/inst/WORDLIST index 55883b0..3e45b60 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,5 +1,7 @@ +Roxygen Shinylive WebR +iframe installable roxy roxygen From e1fac2a5951dc872e9818dadc599c8aaf8cef56b Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:48:30 +0200 Subject: [PATCH 10/22] export create_shinylive_url --- NAMESPACE | 1 + R/parse_url.R | 8 ++++++++ man/create_shinylive_url.Rd | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index dfd57ae..e9e45e9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,7 @@ S3method(format,rd_section_examplesShinylive) S3method(roxygen2::roxy_tag_parse,roxy_tag_examplesShinylive) S3method(roxygen2::roxy_tag_rd,roxy_tag_examplesShinylive) +export(create_shinylive_url) importFrom(glue,glue_data) importFrom(jsonlite,toJSON) importFrom(jsonlite,unbox) diff --git a/R/parse_url.R b/R/parse_url.R index 25c3b94..dfe3904 100644 --- a/R/parse_url.R +++ b/R/parse_url.R @@ -5,7 +5,15 @@ #' #' @param code (`character(1)`) A string with app code. #' @return (`character(1)`) Shinylive app url. +#' +#' @export +#' +#' @examples +#' code <- "this is your app code as a string" +#' create_shinylive_url(code) create_shinylive_url <- function(code) { + stopifnot(is.character(code) && length(code) == 1) + # implementation based on "Create ShinyLive Link" feature of Shiny VSCode extension # https://github.com/posit-dev/shiny-vscode/blob/80560bf36d516ff89dffe88bd9a28cee9edd4d43/src/shinylive.ts#L499 files <- list( diff --git a/man/create_shinylive_url.Rd b/man/create_shinylive_url.Rd index 6175fcd..8b312cb 100644 --- a/man/create_shinylive_url.Rd +++ b/man/create_shinylive_url.Rd @@ -15,3 +15,7 @@ create_shinylive_url(code) \description{ Creates Shinylive url for the app code. } +\examples{ +code <- "this is your app code as a string" +create_shinylive_url(code) +} From 3e5ede9dd15b05152a8d47d2882982a4eb94d50a Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:29:21 +0200 Subject: [PATCH 11/22] fit to 100% width plus scale down --- R/tag_examplesShinylive.R | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index 8142f17..d6433f4 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -160,26 +160,39 @@ roxy_tag_rd.roxy_tag_examplesShinylive <- function(x, base_path, env) { #' @noRd #' @exportS3Method format rd_section_examplesShinylive format.rd_section_examplesShinylive <- function(x, ...) { + wrap_style <- paste0( + "style=\"", + paste( + "height: 800px", + sep = "; " + ), + "\"" + ) iframe_attrs <- paste( - "height=\"800\"", - "width=\"150\\%\"", # @TODO: find a better way to set the width + "height=\"125\\%\"", + "width=\"125\\%\"", "allow=\"fullscreen\"", "scrolling=\"auto\"", sep = " " ) - iframe_style <- paste( - "border: 1px solid rgba(0,0,0,0.175);", - "border-radius: .375rem;", - sep = " " + iframe_style <- paste0( + "style=\"", + paste( + "border: 1px solid rgba(0,0,0,0.175)", + "border-radius: .375rem", + "-webkit-transform: scale(0.8)", + "-webkit-transform-origin: 0 0", + sep = "; " + ), + "\"" ) - iframe_style <- paste0("style=\"", iframe_style, "\"") paste0( "\\section{Examples in Shinylive}{\n", "\\itemize{\n", paste0( " \\item example-", seq_along(x$value), "\\cr\n", " \\href{", x$value, "}{Open in Shinylive}\\cr\n", - " \\if{html}{\\out{<iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>}}\n", + " \\if{html}{\\out{<div ", wrap_style, "><iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe></div>}}\n", # nolint: line_length_linter. collapse = "" ), "}\n", From ed71fff91a30fda71db0a79050d651134c3978b7 Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:58:04 +0200 Subject: [PATCH 12/22] styling; detect if in pkgdown --- R/tag_examplesShinylive.R | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index d6433f4..f5ff781 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -164,13 +164,12 @@ format.rd_section_examplesShinylive <- function(x, ...) { "style=\"", paste( "height: 800px", + "width: 100\\%", sep = "; " ), "\"" ) iframe_attrs <- paste( - "height=\"125\\%\"", - "width=\"125\\%\"", "allow=\"fullscreen\"", "scrolling=\"auto\"", sep = " " @@ -178,21 +177,36 @@ format.rd_section_examplesShinylive <- function(x, ...) { iframe_style <- paste0( "style=\"", paste( + "height: 100\\%", + "width: 100\\%", "border: 1px solid rgba(0,0,0,0.175)", "border-radius: .375rem", - "-webkit-transform: scale(0.8)", - "-webkit-transform-origin: 0 0", sep = "; " ), "\"" ) + jscode <- " +$(function() { + var if_pkgdown = [...document.scripts].filter(x => x.src.includes('pkgdown.js')).length > 0; + if (if_pkgdown) { + $('.iframe-wrapper').css('width', '140%'); + } +});" paste0( "\\section{Examples in Shinylive}{\n", "\\itemize{\n", paste0( " \\item example-", seq_along(x$value), "\\cr\n", " \\href{", x$value, "}{Open in Shinylive}\\cr\n", - " \\if{html}{\\out{<div ", wrap_style, "><iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe></div>}}\n", # nolint: line_length_linter. + " \\if{html}{\n", + " \\out{\n", + " <div class = \"iframe-wrapper\" ", wrap_style, ">\n", + " <script type=\"text/javascript\">", jscode, "\n", + " </script>\n", + " <iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>\n", + " </div>\n", + " }\n", + " }\n", collapse = "" ), "}\n", From 187ddfe2649581d826d15a193d15d3959f43d19e Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:05:01 +0200 Subject: [PATCH 13/22] update --- R/tag_examplesShinylive.R | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index f5ff781..5ee49f4 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -185,11 +185,12 @@ format.rd_section_examplesShinylive <- function(x, ...) { ), "\"" ) + # If in pkgdown website - increase the width jscode <- " $(function() { - var if_pkgdown = [...document.scripts].filter(x => x.src.includes('pkgdown.js')).length > 0; + var if_pkgdown = [...document.scripts].filter(x => x.src.includes(\"pkgdown.js\")).length > 0; if (if_pkgdown) { - $('.iframe-wrapper').css('width', '140%'); + $(\".iframe-wrapper\").css(\"width\", \"140\\%\"); } });" paste0( @@ -199,12 +200,13 @@ $(function() { " \\item example-", seq_along(x$value), "\\cr\n", " \\href{", x$value, "}{Open in Shinylive}\\cr\n", " \\if{html}{\n", - " \\out{\n", - " <div class = \"iframe-wrapper\" ", wrap_style, ">\n", - " <script type=\"text/javascript\">", jscode, "\n", - " </script>\n", - " <iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>\n", - " </div>\n", + # there must be no break lines inside `out{}` - otherwise there is a R CMD CHECK warning + " \\out{", + " <div class = \"iframe-wrapper\" ", wrap_style, ">", + " <script type=\"text/javascript\">", gsub("\n", "", jscode), + " </script>", + " <iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>", + " </div>", " }\n", " }\n", collapse = "" From 88ca7a983cdb93472809f7b8a1e929394530cefd Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:30:21 +0200 Subject: [PATCH 14/22] update --- R/tag_examplesShinylive.R | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index 5ee49f4..6a7a276 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -195,23 +195,24 @@ $(function() { });" paste0( "\\section{Examples in Shinylive}{\n", - "\\itemize{\n", + " \\itemize{\n", paste0( - " \\item example-", seq_along(x$value), "\\cr\n", - " \\href{", x$value, "}{Open in Shinylive}\\cr\n", - " \\if{html}{\n", + " \\item{example-", seq_along(x$value), "}{\\cr\n", + " \\href{", x$value, "}{Open in Shinylive}\\cr\n", + " \\if{html}{\n", # there must be no break lines inside `out{}` - otherwise there is a R CMD CHECK warning - " \\out{", - " <div class = \"iframe-wrapper\" ", wrap_style, ">", - " <script type=\"text/javascript\">", gsub("\n", "", jscode), - " </script>", - " <iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>", - " </div>", + " \\out{", + " <div class = \"iframe-wrapper\" ", wrap_style, ">", + " <script type=\"text/javascript\">", gsub("\n", "", jscode), + " </script>", + " <iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>", + " </div>", + " }\n", " }\n", " }\n", collapse = "" ), - "}\n", + " }\n", "}\n" ) } From e22c2b03668879ffc4fc09a02b0eaf3bf4953ffe Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:33:33 +0200 Subject: [PATCH 15/22] update --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 25769f9..ea9541e 100644 --- a/README.md +++ b/README.md @@ -31,15 +31,17 @@ Which would produce a following output in your documentation: ```Rd \section{Examples in Shinylive}{ -\itemize{ - \item example-1\cr - \href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr - \if{html}{\out{<iframe src="https://shinylive.io/r/app/#code=..." ..."></iframe>}} - \item example-2\cr - \href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr - \if{html}{\out{<iframe src="https://shinylive.io/r/app/#code=..." ..."></iframe>}} - ... -} + \itemize{ + \item{example-1}{\cr + \href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr + \if{html}{\out{ ... (HTML code including <iframe> to Shinylive) }} + } + \item{example-2}{\cr + \href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr + \if{html}{\out{ ... (HTML code including <iframe> to Shinylive) }} + } + ... + } } ``` From f1c123bd292cbe794fff709879516afb64c5f9ba Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:14:16 +0200 Subject: [PATCH 16/22] update --- R/tag_examplesShinylive.R | 25 +++++++++++-------------- README.md | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index 6a7a276..256fc23 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -170,7 +170,6 @@ format.rd_section_examplesShinylive <- function(x, ...) { "\"" ) iframe_attrs <- paste( - "allow=\"fullscreen\"", "scrolling=\"auto\"", sep = " " ) @@ -195,21 +194,19 @@ $(function() { });" paste0( "\\section{Examples in Shinylive}{\n", - " \\itemize{\n", + "\\itemize{\n", paste0( - " \\item{example-", seq_along(x$value), "}{\\cr\n", - " \\href{", x$value, "}{Open in Shinylive}\\cr\n", - " \\if{html}{\n", + "\\item example-", seq_along(x$value), "\\cr\n", + "\\href{", x$value, "}{Open in Shinylive}\\cr\n", + "\\if{html}{\n", # there must be no break lines inside `out{}` - otherwise there is a R CMD CHECK warning - " \\out{", - " <div class = \"iframe-wrapper\" ", wrap_style, ">", - " <script type=\"text/javascript\">", gsub("\n", "", jscode), - " </script>", - " <iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>", - " </div>", - " }\n", - " }\n", - " }\n", + "\\out{", + "<div class = \"iframe-wrapper\" ", wrap_style, ">", + "<script type=\"text/javascript\">", gsub("\n", "", jscode), "</script>", + "<iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>", + "</div>", + "}\n", + "}\\cr\n", collapse = "" ), " }\n", diff --git a/README.md b/README.md index ea9541e..4e1b784 100644 --- a/README.md +++ b/README.md @@ -31,17 +31,17 @@ Which would produce a following output in your documentation: ```Rd \section{Examples in Shinylive}{ - \itemize{ - \item{example-1}{\cr - \href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr - \if{html}{\out{ ... (HTML code including <iframe> to Shinylive) }} - } - \item{example-2}{\cr - \href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr - \if{html}{\out{ ... (HTML code including <iframe> to Shinylive) }} - } - ... - } +\itemize{ +\item example-1 \cr +\href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr +\if{html}{\out{ ... (HTML code including <iframe> to Shinylive) }}\cr +} +\item{example-2}{\cr +\href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr +\if{html}{\out{ ... (HTML code including <iframe> to Shinylive) }}\cr +} +... +} } ``` From 036b3e7609d7f513412d6f4dd6011a6559f6c263 Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:27:48 +0200 Subject: [PATCH 17/22] tidy HTML --- DESCRIPTION | 5 +- R/tag_examplesShinylive.R | 35 ++++-------- README.md | 22 +++---- tests/testthat/test-tag_examplesShinylive.R | 63 ++++++++++++++++++++- 4 files changed, 87 insertions(+), 38 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 317ea0a..be734ad 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -19,6 +19,7 @@ Imports: roxygen2 (>= 7.1.1), stringr (>= 0.4) Suggests: + pkgdown (>= 1.2.0), testthat (>= 3.0.4), withr (>= 2.4.3) Config/Needs/verdepcheck: @@ -27,7 +28,9 @@ Config/Needs/verdepcheck: lzstring=parmsam/lzstring-r, r-lib/roxygen2, tidyverse/stringr, - r-lib/testthat + r-lib/pkgdown, + r-lib/testthat, + r-lib/withr Config/Needs/website: insightsengineering/nesttemplate Encoding: UTF-8 Language: en-US diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index 256fc23..8aeed5c 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -160,23 +160,14 @@ roxy_tag_rd.roxy_tag_examplesShinylive <- function(x, base_path, env) { #' @noRd #' @exportS3Method format rd_section_examplesShinylive format.rd_section_examplesShinylive <- function(x, ...) { - wrap_style <- paste0( - "style=\"", - paste( - "height: 800px", - "width: 100\\%", - sep = "; " - ), - "\"" - ) iframe_attrs <- paste( - "scrolling=\"auto\"", + "allowfullscreen", sep = " " ) iframe_style <- paste0( "style=\"", paste( - "height: 100\\%", + "height: 800px", "width: 100\\%", "border: 1px solid rgba(0,0,0,0.175)", "border-radius: .375rem", @@ -189,27 +180,21 @@ format.rd_section_examplesShinylive <- function(x, ...) { $(function() { var if_pkgdown = [...document.scripts].filter(x => x.src.includes(\"pkgdown.js\")).length > 0; if (if_pkgdown) { - $(\".iframe-wrapper\").css(\"width\", \"140\\%\"); + $(\"iframe.iframe_shinylive\").css(\"width\", \"140\\%\"); } });" paste0( "\\section{Examples in Shinylive}{\n", - "\\itemize{\n", + "\\describe{\n", paste0( - "\\item example-", seq_along(x$value), "\\cr\n", - "\\href{", x$value, "}{Open in Shinylive}\\cr\n", - "\\if{html}{\n", - # there must be no break lines inside `out{}` - otherwise there is a R CMD CHECK warning - "\\out{", - "<div class = \"iframe-wrapper\" ", wrap_style, ">", - "<script type=\"text/javascript\">", gsub("\n", "", jscode), "</script>", - "<iframe src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>", - "</div>", - "}\n", - "}\\cr\n", + " \\item{example-", seq_along(x$value), "}{\n", + " \\href{", x$value, "}{Open in Shinylive}\n", + " \\if{html}{\\out{<script type=\"text/javascript\">", gsub("\n", "", jscode), "</script>}}\n", + " \\if{html}{\\out{<iframe class=\"iframe_shinylive\" src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>}}\n", + " }\n", collapse = "" ), - " }\n", + "}\n", "}\n" ) } diff --git a/README.md b/README.md index 4e1b784..f829102 100644 --- a/README.md +++ b/README.md @@ -31,16 +31,18 @@ Which would produce a following output in your documentation: ```Rd \section{Examples in Shinylive}{ -\itemize{ -\item example-1 \cr -\href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr -\if{html}{\out{ ... (HTML code including <iframe> to Shinylive) }}\cr -} -\item{example-2}{\cr -\href{https://shinylive.io/r/app/#code=...}{Open in Shinylive}\cr -\if{html}{\out{ ... (HTML code including <iframe> to Shinylive) }}\cr -} -... +\describe{ + \item{example-1}{ + \href{https://shinylive.io/r/app/#code=...}{Open in Shinylive} + \if{html}{\out{<script type="text/javascript">(custom JS)</script>}} + \if{html}{\out{<iframe src="https://shinylive.io/r/app/#code=..."></iframe>}} + } + \item{example-2}{ + \href{https://shinylive.io/r/app/#code=...}{Open in Shinylive} + \if{html}{\out{<script type="text/javascript">(custom JS)</script>}} + \if{html}{\out{<iframe src="https://shinylive.io/r/app/#code=..."></iframe>}} + } + ... } } ``` diff --git a/tests/testthat/test-tag_examplesShinylive.R b/tests/testthat/test-tag_examplesShinylive.R index 85b3b93..1d0a6a5 100644 --- a/tests/testthat/test-tag_examplesShinylive.R +++ b/tests/testthat/test-tag_examplesShinylive.R @@ -257,8 +257,6 @@ test_that("examplesShinylive tag - keywords - error when parsing with glue", { expect_false(roxygen2::block_has_tags(block, "examplesShinylive")) }) - - test_that("examplesShinylive tag - decorate using {{next_example}} keyword", { text <- " #' This is a title @@ -293,3 +291,64 @@ test_that("examplesShinylive tag - decorate using {{next_example}} keyword", { "https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMADwEYACAHgFp6GBie0gCwEsBnegKEQCAGwCuAEzhSAOhAUAzABS1c9AEwBKBdU1NWBzj2FnRkmVLABfALpA" # nolint: line_length_linter. ) }) + + + +test_that("format returns Rd parsable to HTML", { + testthat::skip_if_not_installed("pkgdown") + + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' @examplesShinylive + #' @examples + #' f(1, 2) + f <- function(x, y) x + y + " + + topic <- roxygen2::roc_proc_text(roxygen2::rd_roclet(), text)[[1]] + rd_code <- capture.output(topic$get_section("examplesShinylive")) + suppressWarnings(expect_no_warning(html_code <- pkgdown::rd2html(rd_code), message = ".*unexpected END_OF_INPUT.*")) + expect_gt(length(html_code), 0) +}) + +test_that("format returns Rd parsable to tidy HTML", { + testthat::skip_if_not_installed("pkgdown") + testthat::skip_if_not_installed("withr") + testthat::skip_if_not( + nzchar(Sys.which("tidy")), + "tidy is not installed" + ) + + text <- " + #' This is a title + #' + #' This is the description. + #' + #' @param x,y A number + #' @export + #' @examplesShinylive + #' @examples + #' f(1, 2) + f <- function(x, y) x + y + " + + topic <- roxygen2::roc_proc_text(roxygen2::rd_roclet(), text)[[1]] + rd_code <- capture.output(topic$get_section("examplesShinylive")) + html_code <- paste0(suppressWarnings(pkgdown::rd2html(rd_code)), collapse = "\n") + withr::with_tempfile("x", { + writeLines(html_code, x) + # https://github.com/wch/r-source/blob/7450caaef0076c8b43dfdcc0deab1dbe646b8fc4/src/library/tools/R/htmltools.R#L19 + tidy_res <- suppressWarnings(system2( + "tidy", + c("-language en", "-qe", x), + stdout = TRUE, stderr = TRUE) + ) + tidy_res <- grep("line 1 column 1", tidy_res, value = TRUE, invert = TRUE) + testthat::expect_setequal(tidy_res, character(0)) + }) +}) From 0154369d71b9fe26f7cd0dcb5688501c9ebcaa46 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:29:49 +0000 Subject: [PATCH 18/22] [skip style] [skip vbump] Restyle files --- tests/testthat/test-tag_examplesShinylive.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-tag_examplesShinylive.R b/tests/testthat/test-tag_examplesShinylive.R index 1d0a6a5..ace91b5 100644 --- a/tests/testthat/test-tag_examplesShinylive.R +++ b/tests/testthat/test-tag_examplesShinylive.R @@ -346,8 +346,8 @@ test_that("format returns Rd parsable to tidy HTML", { tidy_res <- suppressWarnings(system2( "tidy", c("-language en", "-qe", x), - stdout = TRUE, stderr = TRUE) - ) + stdout = TRUE, stderr = TRUE + )) tidy_res <- grep("line 1 column 1", tidy_res, value = TRUE, invert = TRUE) testthat::expect_setequal(tidy_res, character(0)) }) From 61d8673b9c2856a10ed75bfba2a43ddc7fafaf62 Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:31:04 +0200 Subject: [PATCH 19/22] empty From f6f162a7c34b13b83212098da4784d2c8214e9e0 Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:36:43 +0200 Subject: [PATCH 20/22] nolint --- R/tag_examplesShinylive.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index 8aeed5c..0216f4f 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -190,7 +190,7 @@ $(function() { " \\item{example-", seq_along(x$value), "}{\n", " \\href{", x$value, "}{Open in Shinylive}\n", " \\if{html}{\\out{<script type=\"text/javascript\">", gsub("\n", "", jscode), "</script>}}\n", - " \\if{html}{\\out{<iframe class=\"iframe_shinylive\" src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>}}\n", + " \\if{html}{\\out{<iframe class=\"iframe_shinylive\" src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>}}\n", # nolint: line_length_linter. " }\n", collapse = "" ), From 1181b1efa580bef28745abbd12ad475340b53d28 Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:28:10 +0200 Subject: [PATCH 21/22] z-index --- R/tag_examplesShinylive.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index 0216f4f..c088768 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -171,6 +171,8 @@ format.rd_section_examplesShinylive <- function(x, ...) { "width: 100\\%", "border: 1px solid rgba(0,0,0,0.175)", "border-radius: .375rem", + "position: relative", + "z-index: 1", sep = "; " ), "\"" From 0b2a818a25bac8333bf52082b29e96f626196155 Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:09:03 +0200 Subject: [PATCH 22/22] docs; rm allowfullscreen --- R/tag_examplesShinylive.R | 62 +++++++++++++++++++++++++----------- man/tag-examplesShinylive.Rd | 56 ++++++++++++++++++++++++-------- 2 files changed, 85 insertions(+), 33 deletions(-) diff --git a/R/tag_examplesShinylive.R b/R/tag_examplesShinylive.R index c088768..1ffe962 100644 --- a/R/tag_examplesShinylive.R +++ b/R/tag_examplesShinylive.R @@ -5,7 +5,7 @@ #' If no code is provided then the code is taken from the following `@examples` or `@examplesIf` tag. #' #' The application code must be executable inside Shinylive. If the application code includes functions from your -#' package, you must add `library(<your package>)` beforehand. For more information, refer to the Decoration section +#' package, you must add `library(<package>)` beforehand. For more information, refer to the Decoration section #' on how to use and decorate existing examples. #' #' Note: All the packages used in the application code need to be installable in WebR. @@ -13,16 +13,17 @@ #' #' @section Decoration: #' -#' To avoid repetition between the `@examplesShinylive` and `@examples` sections, there are special string literals -#' that provide access to the other tags from within the content of `@examplesShinylive`. +#' To avoid repetition between the `@examplesShinylive` and `@examples` sections contents, +#' there are special string literals to be used inside `@examplesShinylive` tag content +#' that allow you to access the content(s) of the `@examples` or `@examplesIf` tags. #' These literals should be used as expressions embraced with `{{ }}`, which are then interpolated using #' `glue::glue_data(..., .open = "{{", .close = "}}")`. #' #' The following keywords are available: +#' * `"{{ next_example }}"` - (the default if empty) "raw" element of the next example +#' * `"{{ prev_example }}"` - "raw" element of the previous example #' * `"{{ tags_examples }}"` - a list of `@examples` or `@examplesIf` tags #' * `"{{ examples }}"` - a list of "raw" elements from `tags_examples` list elements -#' * `"{{ next_example }}"` - "raw" element of the next example -#' * `"{{ prev_example }}"` - "raw" element of the previous example #' #' This allows you to access and decorate existing example code to create executable application code for Shinylive. #' Refer to the examples section for possible use cases. @@ -41,19 +42,28 @@ #' #' @examples #' #' (example code) #' -#' # using keywords: +#' # using keywords - `{{ next_example }}`: #' #' (docs) #' #' @examplesShinylive #' #' foo <- 1 #' #' {{ next_example }} #' #' bar <- 2 #' #' @examples -#' #' (your example code) +#' #' (example code) +#' +#' # using keywords - `{{ prev_example }}`: +#' #' (docs) +#' #' bar <- 2 +#' #' @examples +#' #' (example code) +#' #' @examplesShinylive +#' #' foo <- 1 +#' #' {{ prev_example }} #' #' # A typical example would be: #' #' (docs) #' #' @examplesShinylive -#' #' library(<your package>) +#' #' library(<package>) #' #' interactive <- function() TRUE #' #' {{ next_example }} #' #' @examples @@ -66,18 +76,18 @@ #' #' (docs) #' #' @examplesShinylive #' #' @examples -#' #' (your example app 1) +#' #' (example app 1) #' #' @examplesShinylive #' #' @examples -#' #' (your example app 2) +#' #' (example app 2) #' #' # skip parts of example code: #' #' (docs) #' #' @examples -#' #' (your example code - skipped) +#' #' (example code - skipped) #' #' @examplesShinylive #' #' @examples -#' #' (your example code - included) +#' #' (example code - included) #' #' # multiple apps with keywords: #' #' (docs) @@ -85,12 +95,30 @@ #' #' x <- 1 #' #' {{ next_example }} #' #' @examples -#' #' (your example app 1) +#' #' (example app 1) #' #' @examplesShinylive #' #' y <- 1 #' #' {{ next_example }} #' #' @examples -#' #' (your example app 2) +#' #' (example app 2) +#' +#' # combining multiple examples: +#' #' (docs) +#' #' @examples +#' #' (app pre-requisites) +#' #' @examples +#' #' (example app) +#' #' @examplesShinylive +#' #' {{ paste0(examples, collapse = ", ") }} +#' +#' # identical to the above example but with a different approach: +#' #' (docs) +#' #' @examples +#' #' (app pre-requisites) +#' #' @examples +#' #' (example app) +#' #' @examplesShinylive +#' #' {{ paste0(lapply(tags_examples, `[[`, "raw"), collapse = ", ") }} NULL #' @noRd @@ -160,10 +188,6 @@ roxy_tag_rd.roxy_tag_examplesShinylive <- function(x, base_path, env) { #' @noRd #' @exportS3Method format rd_section_examplesShinylive format.rd_section_examplesShinylive <- function(x, ...) { - iframe_attrs <- paste( - "allowfullscreen", - sep = " " - ) iframe_style <- paste0( "style=\"", paste( @@ -192,7 +216,7 @@ $(function() { " \\item{example-", seq_along(x$value), "}{\n", " \\href{", x$value, "}{Open in Shinylive}\n", " \\if{html}{\\out{<script type=\"text/javascript\">", gsub("\n", "", jscode), "</script>}}\n", - " \\if{html}{\\out{<iframe class=\"iframe_shinylive\" src=\"", x$value, "\" ", iframe_attrs, " ", iframe_style, "></iframe>}}\n", # nolint: line_length_linter. + " \\if{html}{\\out{<iframe class=\"iframe_shinylive\" src=\"", x$value, "\" ", iframe_style, "></iframe>}}\n", # nolint: line_length_linter. " }\n", collapse = "" ), diff --git a/man/tag-examplesShinylive.Rd b/man/tag-examplesShinylive.Rd index 142c0a6..aa14bdd 100644 --- a/man/tag-examplesShinylive.Rd +++ b/man/tag-examplesShinylive.Rd @@ -13,7 +13,7 @@ If no code is provided then the code is taken from the following \verb{@examples } \details{ The application code must be executable inside Shinylive. If the application code includes functions from your -package, you must add \verb{library(<your package>)} beforehand. For more information, refer to the Decoration section +package, you must add \verb{library(<package>)} beforehand. For more information, refer to the Decoration section on how to use and decorate existing examples. Note: All the packages used in the application code need to be installable in WebR. @@ -22,17 +22,18 @@ See \href{https://docs.r-wasm.org/webr/latest/packages.html}{this article} for m \section{Decoration}{ -To avoid repetition between the \verb{@examplesShinylive} and \verb{@examples} sections, there are special string literals -that provide access to the other tags from within the content of \verb{@examplesShinylive}. +To avoid repetition between the \verb{@examplesShinylive} and \verb{@examples} sections contents, +there are special string literals to be used inside \verb{@examplesShinylive} tag content +that allow you to access the content(s) of the \verb{@examples} or \verb{@examplesIf} tags. These literals should be used as expressions embraced with \code{{{ }}}, which are then interpolated using \code{glue::glue_data(..., .open = "{{", .close = "}}")}. The following keywords are available: \itemize{ +\item \code{"{{ next_example }}"} - (the default if empty) "raw" element of the next example +\item \code{"{{ prev_example }}"} - "raw" element of the previous example \item \code{"{{ tags_examples }}"} - a list of \verb{@examples} or \verb{@examplesIf} tags \item \code{"{{ examples }}"} - a list of "raw" elements from \code{tags_examples} list elements -\item \code{"{{ next_example }}"} - "raw" element of the next example -\item \code{"{{ prev_example }}"} - "raw" element of the previous example } This allows you to access and decorate existing example code to create executable application code for Shinylive. @@ -48,19 +49,28 @@ Refer to the examples section for possible use cases. #' @examples #' (example code) -# using keywords: +# using keywords - `{{ next_example }}`: #' (docs) #' @examplesShinylive #' foo <- 1 #' {{ next_example }} #' bar <- 2 #' @examples -#' (your example code) +#' (example code) + +# using keywords - `{{ prev_example }}`: +#' (docs) +#' bar <- 2 +#' @examples +#' (example code) +#' @examplesShinylive +#' foo <- 1 +#' {{ prev_example }} # A typical example would be: #' (docs) #' @examplesShinylive -#' library(<your package>) +#' library(<package>) #' interactive <- function() TRUE #' {{ next_example }} #' @examples @@ -73,18 +83,18 @@ Refer to the examples section for possible use cases. #' (docs) #' @examplesShinylive #' @examples -#' (your example app 1) +#' (example app 1) #' @examplesShinylive #' @examples -#' (your example app 2) +#' (example app 2) # skip parts of example code: #' (docs) #' @examples -#' (your example code - skipped) +#' (example code - skipped) #' @examplesShinylive #' @examples -#' (your example code - included) +#' (example code - included) # multiple apps with keywords: #' (docs) @@ -92,10 +102,28 @@ Refer to the examples section for possible use cases. #' x <- 1 #' {{ next_example }} #' @examples -#' (your example app 1) +#' (example app 1) #' @examplesShinylive #' y <- 1 #' {{ next_example }} #' @examples -#' (your example app 2) +#' (example app 2) + +# combining multiple examples: +#' (docs) +#' @examples +#' (app pre-requisites) +#' @examples +#' (example app) +#' @examplesShinylive +#' {{ paste0(examples, collapse = ", ") }} + +# identical to the above example but with a different approach: +#' (docs) +#' @examples +#' (app pre-requisites) +#' @examples +#' (example app) +#' @examplesShinylive +#' {{ paste0(lapply(tags_examples, `[[`, "raw"), collapse = ", ") }} }