├── .Rbuildignore ├── .github ├── .gitignore ├── CODE_OF_CONDUCT.md └── workflows │ ├── R-CMD-check.yaml │ ├── freebsd.yaml │ ├── pkgdown.yaml │ ├── pr-commands.yaml │ └── test-coverage.yaml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── DESCRIPTION ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── clipboard.R ├── compare.R ├── dependent-packages.R ├── external-info.R ├── github-actions.R ├── loaded-packages.R ├── mocks.r ├── osname.R ├── package-info.R ├── platform-info.R ├── printing.R ├── python-info.R ├── rematch2.R ├── session-info.R ├── sessioninfo-package.R ├── sysdata.rda └── utils.R ├── README.md ├── _pkgdown.yml ├── air.toml ├── codecov.yml ├── man ├── external_info.Rd ├── figures │ └── session-info2.svg ├── os_name.Rd ├── package_info.Rd ├── platform_info.Rd ├── python_info.Rd ├── session_diff.Rd ├── session_info.Rd └── sessioninfo-package.Rd ├── sessioninfo.Rproj ├── tests ├── testthat.R └── testthat │ ├── _snaps │ ├── diff.md │ ├── platform-info.md │ └── utils.md │ ├── fixtures │ ├── MD5 │ ├── biobase.rda │ ├── descs.rda │ ├── devtools-deps.rda │ ├── devtools-info-unix.rda │ ├── devtools-info-windows.rda │ ├── devtools.rda │ ├── fsdfgwetdhsdfhq4yqh_0.0.0.9000.tar.gz │ ├── gh.html.gz │ ├── installed.rda │ ├── lines1.txt │ ├── lines2.txt │ ├── lines3.txt │ ├── lines4.txt │ ├── memoise.rda │ ├── no-remote-repo.rda │ └── no-sha.rda │ ├── test-dependent-packages.R │ ├── test-diff.R │ ├── test-loaded-packages.R │ ├── test-os-name.R │ ├── test-package-info.R │ ├── test-platform-info.R │ ├── test-printing.R │ ├── test-session-info.R │ ├── test-utils.R │ └── test-warnings.R └── tools └── bad-emoji.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^Makefile$ 4 | ^README.markdown$ 5 | ^.travis.yml$ 6 | ^appveyor.yml$ 7 | ^revdep$ 8 | ^\.github$ 9 | ^dev[-]lib$ 10 | ^_pkgdown\.yml$ 11 | ^docs$ 12 | ^pkgdown$ 13 | ^vignettes$ 14 | ^codecov\.yml$ 15 | ^LICENSE\.md$ 16 | ^[\.]?air\.toml$ 17 | ^\.vscode$ 18 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at codeofconduct@posit.co. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.1, available at 118 | . 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][https://github.com/mozilla/inclusion]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | . Translations are available at . 125 | 126 | [homepage]: https://www.contributor-covenant.org 127 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | 12 | name: R-CMD-check.yaml 13 | 14 | permissions: read-all 15 | 16 | jobs: 17 | R-CMD-check: 18 | runs-on: ${{ matrix.config.os }} 19 | 20 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | config: 26 | - {os: macos-latest, r: 'release'} 27 | 28 | - {os: windows-latest, r: 'release'} 29 | # use 4.0 or 4.1 to check with rtools40's older compiler 30 | - {os: windows-latest, r: 'oldrel-4'} 31 | 32 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 33 | - {os: ubuntu-latest, r: 'release'} 34 | - {os: ubuntu-latest, r: 'oldrel-1'} 35 | - {os: ubuntu-latest, r: 'oldrel-2'} 36 | - {os: ubuntu-latest, r: 'oldrel-3'} 37 | - {os: ubuntu-latest, r: 'oldrel-4'} 38 | 39 | env: 40 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 41 | R_KEEP_PKG_SOURCE: yes 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - uses: r-lib/actions/setup-pandoc@v2 47 | 48 | - uses: r-lib/actions/setup-r@v2 49 | with: 50 | r-version: ${{ matrix.config.r }} 51 | http-user-agent: ${{ matrix.config.http-user-agent }} 52 | use-public-rspm: true 53 | 54 | - uses: r-lib/actions/setup-r-dependencies@v2 55 | with: 56 | extra-packages: any::rcmdcheck 57 | needs: check 58 | 59 | - uses: r-lib/actions/check-r-package@v2 60 | with: 61 | upload-snapshots: true 62 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 63 | -------------------------------------------------------------------------------- /.github/workflows/freebsd.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main, master] 4 | pull_request: 5 | workflow_dispatch: 6 | inputs: 7 | release: 8 | description: 'FreeBSD release' 9 | required: true 10 | type: choice 11 | options: 12 | - '15.0' 13 | - '14.2-pre' 14 | - '14.1' 15 | - '14.0' 16 | - '13.4' 17 | - '13.3' 18 | - '13.2' 19 | - '12.4' 20 | default: '14.1' 21 | 22 | name: freebsd.yaml 23 | 24 | jobs: 25 | freebsd: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: r-hub/actions/setup-r-freebsd@main 30 | with: 31 | release: ${{ github.event.inputs.release || '14.1' }} 32 | - uses: r-hub/actions/platform-info@v1 33 | 34 | - uses: r-lib/actions/setup-r-dependencies@v2 35 | with: 36 | pak-version: none 37 | install-pandoc: false 38 | install-quarto: false 39 | extra-packages: any::rcmdcheck 40 | needs: check 41 | 42 | - uses: r-lib/actions/check-r-package@v2 43 | with: 44 | upload-snapshots: true 45 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 46 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown.yaml 12 | 13 | permissions: read-all 14 | 15 | jobs: 16 | pkgdown: 17 | runs-on: ubuntu-latest 18 | # Only restrict concurrency for non-PR jobs 19 | concurrency: 20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | permissions: 24 | contents: write 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | use-public-rspm: true 33 | 34 | - uses: r-lib/actions/setup-r-dependencies@v2 35 | with: 36 | extra-packages: any::pkgdown, local::. 37 | needs: website 38 | 39 | - name: Build site 40 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 41 | shell: Rscript {0} 42 | 43 | - name: Deploy to GitHub pages 🚀 44 | if: github.event_name != 'pull_request' 45 | uses: JamesIves/github-pages-deploy-action@v4.5.0 46 | with: 47 | clean: false 48 | branch: gh-pages 49 | folder: docs 50 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | name: pr-commands.yaml 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | document: 13 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} 14 | name: document 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | permissions: 19 | contents: write 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: r-lib/actions/pr-fetch@v2 24 | with: 25 | repo-token: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::roxygen2 34 | needs: pr-document 35 | 36 | - name: Document 37 | run: roxygen2::roxygenise() 38 | shell: Rscript {0} 39 | 40 | - name: commit 41 | run: | 42 | git config --local user.name "$GITHUB_ACTOR" 43 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 44 | git add man/\* NAMESPACE 45 | git commit -m 'Document' 46 | 47 | - uses: r-lib/actions/pr-push@v2 48 | with: 49 | repo-token: ${{ secrets.GITHUB_TOKEN }} 50 | 51 | style: 52 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} 53 | name: style 54 | runs-on: ubuntu-latest 55 | env: 56 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 57 | permissions: 58 | contents: write 59 | steps: 60 | - uses: actions/checkout@v4 61 | 62 | - uses: r-lib/actions/pr-fetch@v2 63 | with: 64 | repo-token: ${{ secrets.GITHUB_TOKEN }} 65 | 66 | - uses: r-lib/actions/setup-r@v2 67 | 68 | - name: Install dependencies 69 | run: install.packages("styler") 70 | shell: Rscript {0} 71 | 72 | - name: Style 73 | run: styler::style_pkg() 74 | shell: Rscript {0} 75 | 76 | - name: commit 77 | run: | 78 | git config --local user.name "$GITHUB_ACTOR" 79 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 80 | git add \*.R 81 | git commit -m 'Style' 82 | 83 | - uses: r-lib/actions/pr-push@v2 84 | with: 85 | repo-token: ${{ secrets.GITHUB_TOKEN }} 86 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: test-coverage.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | test-coverage: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::covr, any::xml2 28 | needs: coverage 29 | 30 | - name: Test coverage 31 | run: | 32 | cov <- covr::package_coverage( 33 | quiet = FALSE, 34 | clean = FALSE, 35 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 36 | ) 37 | print(cov) 38 | covr::to_cobertura(cov) 39 | shell: Rscript {0} 40 | 41 | - uses: codecov/codecov-action@v5 42 | with: 43 | # Fail if error if not on PR, or if on PR and token is given 44 | fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} 45 | files: ./cobertura.xml 46 | plugins: noop 47 | disable_search: true 48 | token: ${{ secrets.CODECOV_TOKEN }} 49 | 50 | - name: Show testthat output 51 | if: always() 52 | run: | 53 | ## -------------------------------------------------------------------- 54 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 55 | shell: bash 56 | 57 | - name: Upload test results 58 | if: failure() 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: coverage-test-failures 62 | path: ${{ runner.temp }}/package 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | /revdep 5 | /dev-lib 6 | docs 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Posit.air-vscode" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[r]": { 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "Posit.air-vscode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: sessioninfo 2 | Title: R Session Information 3 | Version: 1.2.3.9000 4 | Authors@R: c( 5 | person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", role = "cre"), 6 | person("Hadley", "Wickham", role = "aut"), 7 | person("Winston", "Chang", role = "aut"), 8 | person("Robert", "Flight", role = "aut"), 9 | person("Kirill", "Müller", role = "aut"), 10 | person("Jim", "Hester", role = "aut"), 11 | person("R Core team", role = "ctb"), 12 | person("Posit Software, PBC", role = c("cph", "fnd"), 13 | comment = c(ROR = "03wc8by49")) 14 | ) 15 | Maintainer: Gábor Csárdi 16 | Description: Query and print information about the current R session. It 17 | is similar to 'utils::sessionInfo()', but includes more information 18 | about packages, and where they were installed from. 19 | License: GPL-2 20 | URL: https://github.com/r-lib/sessioninfo#readme, 21 | https://sessioninfo.r-lib.org 22 | BugReports: https://github.com/r-lib/sessioninfo/issues 23 | Depends: 24 | R (>= 3.4) 25 | Imports: 26 | cli (>= 3.1.0), 27 | tools, 28 | utils 29 | Suggests: 30 | callr, 31 | covr, 32 | gh, 33 | reticulate, 34 | rmarkdown, 35 | testthat (>= 3.2.0), 36 | withr 37 | Config/Needs/website: 38 | pkgdown, 39 | tidyverse/tidytemplate 40 | Config/testthat/edition: 3 41 | Config/testthat/parallel: true 42 | Config/usethis/last-upkeep: 2025-05-07 43 | Encoding: UTF-8 44 | Roxygen: list(markdown = TRUE) 45 | RoxygenNote: 7.3.2 46 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU General Public License 2 | ========================== 3 | 4 | _Version 2, June 1991_ 5 | _Copyright © 1989, 1991 Free Software Foundation, Inc.,_ 6 | _51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA_ 7 | 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | ### Preamble 12 | 13 | The licenses for most software are designed to take away your 14 | freedom to share and change it. By contrast, the GNU General Public 15 | License is intended to guarantee your freedom to share and change free 16 | software--to make sure the software is free for all its users. This 17 | General Public License applies to most of the Free Software 18 | Foundation's software and to any other program whose authors commit to 19 | using it. (Some other Free Software Foundation software is covered by 20 | the GNU Lesser General Public License instead.) You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not 24 | price. Our General Public Licenses are designed to make sure that you 25 | have the freedom to distribute copies of free software (and charge for 26 | this service if you wish), that you receive source code or can get it 27 | if you want it, that you can change the software or use pieces of it 28 | in new free programs; and that you know you can do these things. 29 | 30 | To protect your rights, we need to make restrictions that forbid 31 | anyone to deny you these rights or to ask you to surrender the rights. 32 | These restrictions translate to certain responsibilities for you if you 33 | distribute copies of the software, or if you modify it. 34 | 35 | For example, if you distribute copies of such a program, whether 36 | gratis or for a fee, you must give the recipients all the rights that 37 | you have. You must make sure that they, too, receive or can get the 38 | source code. And you must show them these terms so they know their 39 | rights. 40 | 41 | We protect your rights with two steps: **(1)** copyright the software, and 42 | **(2)** offer you this license which gives you legal permission to copy, 43 | distribute and/or modify the software. 44 | 45 | Also, for each author's protection and ours, we want to make certain 46 | that everyone understands that there is no warranty for this free 47 | software. If the software is modified by someone else and passed on, we 48 | want its recipients to know that what they have is not the original, so 49 | that any problems introduced by others will not reflect on the original 50 | authors' reputations. 51 | 52 | Finally, any free program is threatened constantly by software 53 | patents. We wish to avoid the danger that redistributors of a free 54 | program will individually obtain patent licenses, in effect making the 55 | program proprietary. To prevent this, we have made it clear that any 56 | patent must be licensed for everyone's free use or not licensed at all. 57 | 58 | The precise terms and conditions for copying, distribution and 59 | modification follow. 60 | 61 | ### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 62 | 63 | **0.** This License applies to any program or other work which contains 64 | a notice placed by the copyright holder saying it may be distributed 65 | under the terms of this General Public License. The “Program”, below, 66 | refers to any such program or work, and a “work based on the Program” 67 | means either the Program or any derivative work under copyright law: 68 | that is to say, a work containing the Program or a portion of it, 69 | either verbatim or with modifications and/or translated into another 70 | language. (Hereinafter, translation is included without limitation in 71 | the term “modification”.) Each licensee is addressed as “you”. 72 | 73 | Activities other than copying, distribution and modification are not 74 | covered by this License; they are outside its scope. The act of 75 | running the Program is not restricted, and the output from the Program 76 | is covered only if its contents constitute a work based on the 77 | Program (independent of having been made by running the Program). 78 | Whether that is true depends on what the Program does. 79 | 80 | **1.** You may copy and distribute verbatim copies of the Program's 81 | source code as you receive it, in any medium, provided that you 82 | conspicuously and appropriately publish on each copy an appropriate 83 | copyright notice and disclaimer of warranty; keep intact all the 84 | notices that refer to this License and to the absence of any warranty; 85 | and give any other recipients of the Program a copy of this License 86 | along with the Program. 87 | 88 | You may charge a fee for the physical act of transferring a copy, and 89 | you may at your option offer warranty protection in exchange for a fee. 90 | 91 | **2.** You may modify your copy or copies of the Program or any portion 92 | of it, thus forming a work based on the Program, and copy and 93 | distribute such modifications or work under the terms of Section 1 94 | above, provided that you also meet all of these conditions: 95 | 96 | * **a)** You must cause the modified files to carry prominent notices 97 | stating that you changed the files and the date of any change. 98 | * **b)** You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | * **c)** If the modified program normally reads commands interactively 103 | when run, you must cause it, when started running for such 104 | interactive use in the most ordinary way, to print or display an 105 | announcement including an appropriate copyright notice and a 106 | notice that there is no warranty (or else, saying that you provide 107 | a warranty) and that users may redistribute the program under 108 | these conditions, and telling the user how to view a copy of this 109 | License. (Exception: if the Program itself is interactive but 110 | does not normally print such an announcement, your work based on 111 | the Program is not required to print an announcement.) 112 | 113 | These requirements apply to the modified work as a whole. If 114 | identifiable sections of that work are not derived from the Program, 115 | and can be reasonably considered independent and separate works in 116 | themselves, then this License, and its terms, do not apply to those 117 | sections when you distribute them as separate works. But when you 118 | distribute the same sections as part of a whole which is a work based 119 | on the Program, the distribution of the whole must be on the terms of 120 | this License, whose permissions for other licensees extend to the 121 | entire whole, and thus to each and every part regardless of who wrote it. 122 | 123 | Thus, it is not the intent of this section to claim rights or contest 124 | your rights to work written entirely by you; rather, the intent is to 125 | exercise the right to control the distribution of derivative or 126 | collective works based on the Program. 127 | 128 | In addition, mere aggregation of another work not based on the Program 129 | with the Program (or with a work based on the Program) on a volume of 130 | a storage or distribution medium does not bring the other work under 131 | the scope of this License. 132 | 133 | **3.** You may copy and distribute the Program (or a work based on it, 134 | under Section 2) in object code or executable form under the terms of 135 | Sections 1 and 2 above provided that you also do one of the following: 136 | 137 | * **a)** Accompany it with the complete corresponding machine-readable 138 | source code, which must be distributed under the terms of Sections 139 | 1 and 2 above on a medium customarily used for software interchange; or, 140 | * **b)** Accompany it with a written offer, valid for at least three 141 | years, to give any third party, for a charge no more than your 142 | cost of physically performing source distribution, a complete 143 | machine-readable copy of the corresponding source code, to be 144 | distributed under the terms of Sections 1 and 2 above on a medium 145 | customarily used for software interchange; or, 146 | * **c)** Accompany it with the information you received as to the offer 147 | to distribute corresponding source code. (This alternative is 148 | allowed only for noncommercial distribution and only if you 149 | received the program in object code or executable form with such 150 | an offer, in accord with Subsection b above.) 151 | 152 | The source code for a work means the preferred form of the work for 153 | making modifications to it. For an executable work, complete source 154 | code means all the source code for all modules it contains, plus any 155 | associated interface definition files, plus the scripts used to 156 | control compilation and installation of the executable. However, as a 157 | special exception, the source code distributed need not include 158 | anything that is normally distributed (in either source or binary 159 | form) with the major components (compiler, kernel, and so on) of the 160 | operating system on which the executable runs, unless that component 161 | itself accompanies the executable. 162 | 163 | If distribution of executable or object code is made by offering 164 | access to copy from a designated place, then offering equivalent 165 | access to copy the source code from the same place counts as 166 | distribution of the source code, even though third parties are not 167 | compelled to copy the source along with the object code. 168 | 169 | **4.** You may not copy, modify, sublicense, or distribute the Program 170 | except as expressly provided under this License. Any attempt 171 | otherwise to copy, modify, sublicense or distribute the Program is 172 | void, and will automatically terminate your rights under this License. 173 | However, parties who have received copies, or rights, from you under 174 | this License will not have their licenses terminated so long as such 175 | parties remain in full compliance. 176 | 177 | **5.** You are not required to accept this License, since you have not 178 | signed it. However, nothing else grants you permission to modify or 179 | distribute the Program or its derivative works. These actions are 180 | prohibited by law if you do not accept this License. Therefore, by 181 | modifying or distributing the Program (or any work based on the 182 | Program), you indicate your acceptance of this License to do so, and 183 | all its terms and conditions for copying, distributing or modifying 184 | the Program or works based on it. 185 | 186 | **6.** Each time you redistribute the Program (or any work based on the 187 | Program), the recipient automatically receives a license from the 188 | original licensor to copy, distribute or modify the Program subject to 189 | these terms and conditions. You may not impose any further 190 | restrictions on the recipients' exercise of the rights granted herein. 191 | You are not responsible for enforcing compliance by third parties to 192 | this License. 193 | 194 | **7.** If, as a consequence of a court judgment or allegation of patent 195 | infringement or for any other reason (not limited to patent issues), 196 | conditions are imposed on you (whether by court order, agreement or 197 | otherwise) that contradict the conditions of this License, they do not 198 | excuse you from the conditions of this License. If you cannot 199 | distribute so as to satisfy simultaneously your obligations under this 200 | License and any other pertinent obligations, then as a consequence you 201 | may not distribute the Program at all. For example, if a patent 202 | license would not permit royalty-free redistribution of the Program by 203 | all those who receive copies directly or indirectly through you, then 204 | the only way you could satisfy both it and this License would be to 205 | refrain entirely from distribution of the Program. 206 | 207 | If any portion of this section is held invalid or unenforceable under 208 | any particular circumstance, the balance of the section is intended to 209 | apply and the section as a whole is intended to apply in other 210 | circumstances. 211 | 212 | It is not the purpose of this section to induce you to infringe any 213 | patents or other property right claims or to contest validity of any 214 | such claims; this section has the sole purpose of protecting the 215 | integrity of the free software distribution system, which is 216 | implemented by public license practices. Many people have made 217 | generous contributions to the wide range of software distributed 218 | through that system in reliance on consistent application of that 219 | system; it is up to the author/donor to decide if he or she is willing 220 | to distribute software through any other system and a licensee cannot 221 | impose that choice. 222 | 223 | This section is intended to make thoroughly clear what is believed to 224 | be a consequence of the rest of this License. 225 | 226 | **8.** If the distribution and/or use of the Program is restricted in 227 | certain countries either by patents or by copyrighted interfaces, the 228 | original copyright holder who places the Program under this License 229 | may add an explicit geographical distribution limitation excluding 230 | those countries, so that distribution is permitted only in or among 231 | countries not thus excluded. In such case, this License incorporates 232 | the limitation as if written in the body of this License. 233 | 234 | **9.** The Free Software Foundation may publish revised and/or new versions 235 | of the General Public License from time to time. Such new versions will 236 | be similar in spirit to the present version, but may differ in detail to 237 | address new problems or concerns. 238 | 239 | Each version is given a distinguishing version number. If the Program 240 | specifies a version number of this License which applies to it and “any 241 | later version”, you have the option of following the terms and conditions 242 | either of that version or of any later version published by the Free 243 | Software Foundation. If the Program does not specify a version number of 244 | this License, you may choose any version ever published by the Free Software 245 | Foundation. 246 | 247 | **10.** If you wish to incorporate parts of the Program into other free 248 | programs whose distribution conditions are different, write to the author 249 | to ask for permission. For software which is copyrighted by the Free 250 | Software Foundation, write to the Free Software Foundation; we sometimes 251 | make exceptions for this. Our decision will be guided by the two goals 252 | of preserving the free status of all derivatives of our free software and 253 | of promoting the sharing and reuse of software generally. 254 | 255 | ### NO WARRANTY 256 | 257 | **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 258 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 259 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 260 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 261 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 262 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 263 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 264 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 265 | REPAIR OR CORRECTION. 266 | 267 | **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 268 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 269 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 270 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 271 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 272 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 273 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 274 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 275 | POSSIBILITY OF SUCH DAMAGES. 276 | 277 | END OF TERMS AND CONDITIONS 278 | 279 | ### How to Apply These Terms to Your New Programs 280 | 281 | If you develop a new program, and you want it to be of the greatest 282 | possible use to the public, the best way to achieve this is to make it 283 | free software which everyone can redistribute and change under these terms. 284 | 285 | To do so, attach the following notices to the program. It is safest 286 | to attach them to the start of each source file to most effectively 287 | convey the exclusion of warranty; and each file should have at least 288 | the “copyright” line and a pointer to where the full notice is found. 289 | 290 | 291 | Copyright (C) 292 | 293 | This program is free software; you can redistribute it and/or modify 294 | it under the terms of the GNU General Public License as published by 295 | the Free Software Foundation; either version 2 of the License, or 296 | (at your option) any later version. 297 | 298 | This program is distributed in the hope that it will be useful, 299 | but WITHOUT ANY WARRANTY; without even the implied warranty of 300 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 301 | GNU General Public License for more details. 302 | 303 | You should have received a copy of the GNU General Public License along 304 | with this program; if not, write to the Free Software Foundation, Inc., 305 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 306 | 307 | Also add information on how to contact you by electronic and paper mail. 308 | 309 | If the program is interactive, make it output a short notice like this 310 | when it starts in an interactive mode: 311 | 312 | Gnomovision version 69, Copyright (C) year name of author 313 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 314 | This is free software, and you are welcome to redistribute it 315 | under certain conditions; type `show c' for details. 316 | 317 | The hypothetical commands `show w` and `show c` should show the appropriate 318 | parts of the General Public License. Of course, the commands you use may 319 | be called something other than `show w` and `show c`; they could even be 320 | mouse-clicks or menu items--whatever suits your program. 321 | 322 | You should also get your employer (if you work as a programmer) or your 323 | school, if any, to sign a “copyright disclaimer” for the program, if 324 | necessary. Here is a sample; alter the names: 325 | 326 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 327 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 328 | 329 | , 1 April 1989 330 | Ty Coon, President of Vice 331 | 332 | This General Public License does not permit incorporating your program into 333 | proprietary programs. If your program is a subroutine library, you may 334 | consider it more useful to permit linking proprietary applications with the 335 | library. If this is what you want to do, use the GNU Lesser General 336 | Public License instead of this License. 337 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(as.character,external_info) 4 | S3method(as.character,packages_info) 5 | S3method(as.character,platform_info) 6 | S3method(as.character,python_info) 7 | S3method(as.character,session_info) 8 | S3method(c,platform_info) 9 | S3method(format,external_info) 10 | S3method(format,packages_info) 11 | S3method(format,platform_info) 12 | S3method(format,python_info) 13 | S3method(format,session_diff) 14 | S3method(format,session_info) 15 | S3method(print,external_info) 16 | S3method(print,packages_info) 17 | S3method(print,platform_info) 18 | S3method(print,python_info) 19 | S3method(print,session_diff) 20 | S3method(print,session_info) 21 | export(external_info) 22 | export(os_name) 23 | export(package_info) 24 | export(platform_info) 25 | export(python_info) 26 | export(session_diff) 27 | export(session_info) 28 | importFrom(cli,symbol) 29 | importFrom(utils,packageVersion) 30 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # sessioninfo (development version) 2 | 3 | 4 | # sessioninfo 1.2.3 5 | 6 | * `session_info()` no longer produces an error when `info` has length > 1 7 | (@nash-delcamp-slp, #96). 8 | 9 | * Update pkgdown url to sessioninfo.r-lib.org. 10 | 11 | * `session_diff()` now accepts the URL to a GitHub Actions log as the 12 | source for `new` and/or `old` (@jennybc, #68). 13 | 14 | * `session_info()` output now includes an explanation for symbol 15 | highlighting packages attached to the search path (@IndrajeetPatil). 16 | 17 | * `session_info()` and `platform_info()` now print the host name if the 18 | `sessioninfo.include_hostname` global option is set to `TRUE` 19 | (@certara-jcraig, #99). 20 | 21 | * sessioninfo now does not leave behind detritus in the temporary 22 | directory. 23 | 24 | # sessioninfo 1.2.2 25 | 26 | * This version does not add an emoji hash to the output. 27 | 28 | * The `source` column of the output data frame of `package_info()` (also 29 | part of `session_info()`), now contains the full SHA for packages installed 30 | from GitHub, instead of only the first seven characters. This makes it 31 | easier to use the SHA programmatically. Note that this does not affect 32 | formatting and printing, which still use the abbreviated SHA. 33 | (@muschellij2, #61). 34 | 35 | * RStudio Package Manager (RSPM) and other repository sources are 36 | now shown in the `source` column, if they set the `Repository` 37 | field in `DESCRIPTION`. 38 | 39 | # sessioninfo 1.2.1 40 | 41 | * `package_info()` and `session_info()` now do not fail if the version 42 | number of an installed package is invalid. 43 | 44 | * Better aliases for the list of attached, loaded and installed packages 45 | in `package_inf()` and `session_info()`. 46 | 47 | # sessioninfo 1.2.0 48 | 49 | * New function `external_info()`, information about external software. 50 | It can be also requested with the new `info` argument of 51 | `session_info()` (@llrs). 52 | 53 | * New function `python_info()`, information about Python configuration. 54 | It is automatically included in `session_info()` if the reticulate 55 | package is loaded and Python is available. You can also request it 56 | manually via the new `info` argument of `session_info()` (#33). 57 | 58 | * The output of `session_info()` now has an emoji hash, consisting of 59 | three emojis. This allows quick comparison of two session infos (#26). 60 | 61 | * All `*_info()` functions use ANSI colors on systems that support them. 62 | In particular, it highlights unusual package versions and sources, 63 | and possible package problems (#3). 64 | 65 | * New `session_diff()` function, to compare two session infos from 66 | various sources (#6). 67 | 68 | * `session_info()` has a new argument named `info`, to select which parts 69 | of the session information should be printed. 70 | 71 | * `session_info()` now has a `to_file` argument, to write the output to a 72 | file (#30). 73 | 74 | * `session_inf()` has a `dependencies` argument now, and passes it to 75 | `package_info()`. 76 | 77 | * `package_info()` and `session_info()` can now list the attached or 78 | installed packages, see the `pkgs` argument in the manual for 79 | details (#42). 80 | 81 | * `platform_info()` and `session_info()` now include the Windows build 82 | number in the output (#40). 83 | 84 | * sessioninfo now never wraps the output if the screen is too narrow (#31). 85 | 86 | * All `*_info()` functions have a `format()` S3 method now. 87 | 88 | * `platform_info()` and `session_info()` now include the RStudio version if 89 | the R session is in RStudio (#29). 90 | 91 | * The `source` column of the package list is now more informative. 92 | 93 | # sessioninfo 1.1.1 94 | 95 | * `package_info()` and `session_info()` now detect locally installed packages 96 | correctly if they have an empty `biocViews` field in `DESCRIPTION (@llrs, #25) 97 | 98 | * `package_info()` and `session_info()` now handle the case when a loaded 99 | package was removed from the disk. 100 | 101 | # sessioninfo 1.1.0 102 | 103 | * `package_info()` now has a `dependencies` argument, to filter the type 104 | of dependent packages in the output (#22). 105 | 106 | * `session_info()` and `package_info()` now show the library search path, 107 | and also which library each package was loaded from. They also warn 108 | if the on-disk version of the package has a different path than the 109 | loaded version (#9, #20). 110 | 111 | * `package_info()`'s `ondiskversion` entry is now correct. 112 | 113 | * `session_info()` and `package_info()` now verify the MD5 hashes of DLL 114 | files on Windows, and warns for micmatches, as these are usually 115 | broken packages (#12, #16). 116 | 117 | * We use now the cli package, instead of clisymbols, and this fixes 118 | printing bugs in LaTeX documents (#14). 119 | 120 | * `session_info()` and `platform_info()` now include the `LC_CTYPE` 121 | locale category (@patperry, #11) 122 | 123 | * `session_info()` and `package_info()` now print source of the CRAN 124 | packages in uppercase, always, even if they were installed by devtools. 125 | 126 | * `session_info()` and `platform_info()` now handle the case when 127 | `utils::sessionInfo()$running` is `NULL` (@HenrikBengtsson, #7). 128 | 129 | * `session_info()` and `package_info()` now only list loaded versions 130 | for namespaces which are already loaded. This only makes a difference 131 | if the `pkgs` argument is given (#4). 132 | 133 | * Do not consult the `max.print` option, for platform and package info 134 | (@jennybc, #13). 135 | 136 | # sessioninfo 1.0.0 137 | 138 | First public release. 139 | -------------------------------------------------------------------------------- /R/clipboard.R: -------------------------------------------------------------------------------- 1 | get_os <- function() { 2 | if (.Platform$OS.type == "windows") { 3 | "win" 4 | } else if (Sys.info()["sysname"] == "Darwin") { 5 | "mac" 6 | } else { 7 | "unix" 8 | } 9 | } 10 | 11 | clipboard_read <- function() { 12 | os <- get_os() 13 | 14 | switch( 15 | os, 16 | win = utils::readClipboard(), 17 | mac = clipboard_read_mac(), 18 | clipboard_read_x11() 19 | ) 20 | } 21 | 22 | clipboard_read_mac <- function() { 23 | on.exit(try(close(con), silent = TRUE), add = TRUE) 24 | con <- pipe("pbpaste") 25 | scan(con, what = "", sep = "\n", blank.lines.skip = FALSE, quiet = TRUE) 26 | } 27 | 28 | clipboard_read_x11 <- function() { 29 | on.exit(try(close(con), silent = TRUE), add = TRUE) 30 | con <- file("clipboard") 31 | scan(con, what = "", sep = "\n", blank.lines.skip = FALSE, quiet = TRUE) 32 | } 33 | -------------------------------------------------------------------------------- /R/compare.R: -------------------------------------------------------------------------------- 1 | #' Compare session information from two sources 2 | #' 3 | #' @param old,new A `session_info` object (the return value of 4 | #' [session_info()]), or a pointer to [session_info()] output. See details 5 | #' below. 6 | #' @param packages How to compare the package info for `old` and `new`: 7 | #' * `"diff"`: line diffs, in the style of `diff -u` 8 | #' * `"merge"`: merge the information into one data frame, with one row per 9 | #' package. Only package `version` and `source` are compared. 10 | #' @param ... Passed to any new [session_info()] calls. 11 | #' 12 | #' @details 13 | #' Various way to specify `old` and `new`: 14 | #' * A `session_info` object. 15 | #' * `"local"` runs [session_info()] in the current 16 | #' session, and uses its output. 17 | #' * `"clipboard"` takes the session info from the system clipboard. 18 | #' If the clipboard contains a URL, it is followed to download the 19 | #' session info. 20 | #' * The URL where you inspect the results for a GitHub Actions job. 21 | #' Typically has this form: 22 | #' ``` 23 | #' https://github.com/OWNER/REPO/actions/runs/RUN_ID/jobs/HTML_ID 24 | #' ``` 25 | #' Internally, this URL is parsed so we can look up the job id, get the 26 | #' log file, and extract session info. 27 | #' * Any other URL starting with `http://` or `https://`. `session_diff()` 28 | #' searches the HTML (or text) page for the session info header to find the 29 | #' session info. 30 | #' 31 | #' 32 | #' @export 33 | #' @examplesIf FALSE 34 | #' session_diff() 35 | 36 | session_diff <- function( 37 | old = "local", 38 | new = "clipboard", 39 | packages = c("diff", "merge"), 40 | ... 41 | ) { 42 | packages <- match.arg(packages) 43 | 44 | oldname <- get_symbol_name(substitute(old)) 45 | newname <- get_symbol_name(substitute(new)) 46 | 47 | old <- get_session_info(old, oldname %||% "old", ...) 48 | new <- get_session_info(new, newname %||% "new", ...) 49 | 50 | ret <- list( 51 | old = old, 52 | new = new, 53 | diff = session_diff_text(old$text, new$text, packages) 54 | ) 55 | 56 | class(ret) <- c("session_diff", "list") 57 | ret 58 | } 59 | 60 | #' @export 61 | 62 | format.session_diff <- function(x, ...) { 63 | c( 64 | cli::style_bold(paste0("--- ", substr(x$old$name, 1, 78))), 65 | cli::style_bold(paste0("+++ ", substr(x$new$name, 1, 78))), 66 | format(x$diff, context = Inf) 67 | ) 68 | } 69 | 70 | #' @export 71 | 72 | print.session_diff <- function(x, ...) { 73 | writeLines(format(x, ...)) 74 | } 75 | 76 | get_session_info <- function(src, name = NULL, ...) { 77 | si <- if (is_string(src) == 1 && src == "local") { 78 | get_session_info_local(...) 79 | } else if (is_string(src) == 1 && src == "clipboard") { 80 | get_session_info_clipboard() 81 | } else if (is_string(src) && is_gha_url(src)) { 82 | get_session_info_gha(src) 83 | } else if (is_string(src) && grepl("https?://", src)) { 84 | get_session_info_url(src) 85 | } else { 86 | get_session_info_literal(src) 87 | } 88 | if (is.null(si$name)) si$name <- name 89 | si 90 | } 91 | 92 | get_session_info_local <- function(...) { 93 | si <- session_info(...) 94 | old <- options(cli.num_colors = 1) 95 | on.exit(options(old), add = TRUE) 96 | list(arg = "local", si = si, text = format(si)) 97 | } 98 | 99 | get_session_info_clipboard <- function() { 100 | cnt <- clipboard_read() 101 | if (is_string(cnt) && cnt == "clipboard") { 102 | si <- list(arg = "clipboard", si = cnt, text = cnt) 103 | } else { 104 | si <- get_session_info(cnt) 105 | } 106 | si$arg <- "" 107 | si 108 | } 109 | 110 | get_session_info_url <- function(url) { 111 | tmp <- tempfile("session-diff-") 112 | on.exit(unlink(tmp), add = TRUE) 113 | suppressWarnings(utils::download.file(url, tmp, quiet = TRUE, mode = "wb")) 114 | html <- readLines(url, warn = FALSE, encoding = "UTF-8") 115 | find_session_info_in_html(url, html) 116 | } 117 | 118 | find_session_info_in_html <- function(url, lines) { 119 | purl <- parse_url(url) 120 | re_start <- "[-=\u2500\u2550][ ]Session info[ ]" 121 | cand <- grep(re_start, lines) 122 | if (length(cand) == 0) stop("Cannot find session info at '", url, "'.") 123 | 124 | # in the new GH HTML the whole comment is a data field that we extract 125 | if (any(grepl("\\n", lines, fixed = TRUE))) { 126 | lines <- gsub("\\r", "", lines, fixed = TRUE) 127 | lines <- unlist(strsplit(lines, "\\n", fixed = TRUE)) 128 | cand <- grep(re_start, lines) 129 | if (length(cand) == 0) stop("Cannot find session info at '", url, "'.") 130 | } 131 | 132 | # check if the URL has an anchor and that the anchor exists in HTML 133 | # if yes, then we "skip" there 134 | if (purl$anchor != "") { 135 | anch <- which( 136 | grepl(paste0(" id=\"", purl$anchor, "\""), lines, fixed = TRUE) | 137 | grepl(paste0(" id='", purl$anchor, "'"), lines, fixed = TRUE) | 138 | grepl(paste0(" id = \"", purl$anchor, "\""), lines, fixed = TRUE) | 139 | grepl(paste0(" id = '", purl$anchor, "'"), lines, fixed = TRUE) 140 | )[1] 141 | if (!is.na(anch) && any(anch < cand)) { 142 | lines <- lines[anch:length(lines)] 143 | cand <- grep(re_start, lines) 144 | } else { 145 | url <- sub(paste0("#", purl$anchor), "", url) 146 | } 147 | } 148 | 149 | lines <- lines[cand[1]:length(lines)] 150 | lines[1] <- sub(paste0("^.*(", re_start, ")"), "\\1", lines[1]) 151 | 152 | grepl_end <- function(lines) { 153 | grepl("^(#>)?[ ]*\\[[0-9]\\] ", lines) | 154 | grepl("^(#>)?[ ]*[-\u2500]+$", lines) 155 | } 156 | 157 | end <- which(grepl_end(lines))[1] 158 | 159 | if (is.na(end)) stop("Cannot parse session info from '", url, "'.") 160 | while (end < length(lines) && grepl_end(lines[end + 1])) { 161 | end <- end + 1 162 | } 163 | 164 | si <- get_session_info_literal(lines[1:end]) 165 | si$arg <- url 166 | si$name <- url 167 | si 168 | } 169 | 170 | parse_url <- function(url) { 171 | re_url <- paste0( 172 | "^(?[a-zA-Z0-9]+)://", 173 | "(?:(?[^@/:]+)(?::(?[^@/]+))?@)?", 174 | "(?[^/]+)", 175 | "(?[^#]*)", 176 | "#?(?.*)$" 177 | ) 178 | re_match(url, re_url)$groups 179 | } 180 | 181 | get_session_info_literal <- function(si) { 182 | if (inherits(si, "session_info")) { 183 | old <- options(cli.num_colors = 1) 184 | on.exit(options(old), add = TRUE) 185 | list(arg = si, si = si, text = format(si)) 186 | } else if (is.character(si)) { 187 | # in case it has ANSI sequences 188 | text <- cli::ansi_strip(si) 189 | 190 | # Might be a single string 191 | text <- unlist(strsplitx(text, "\n", fixed = TRUE)) 192 | 193 | # reprex has the knitr output prefix, remove it 194 | # order is important here 195 | text <- sub("^#>[ ]?", "", text) 196 | text <- sub("^#[>#]?[ ]?", "", text) 197 | 198 | check_session_info(text) 199 | list(arg = si, si = si, text = text) 200 | } else { 201 | stop("Could not interpret a `", class(si), "` as a session info.") 202 | } 203 | } 204 | 205 | # strsplit("", "\n") -> character(), but it should be the empty string, 206 | # so we fix this. 207 | 208 | strsplitx <- function(...) { 209 | lapply(strsplit(...), paste0, "") 210 | } 211 | 212 | check_session_info <- function(x) { 213 | if (!any(grepl("[-=\u2500\u2550] Session info", x))) { 214 | warning("This does not look like a session info: '", beginning(x), "'.") 215 | } 216 | } 217 | 218 | beginning <- function(x) { 219 | x123 <- utils::head(unlist(strsplit(x, "\n", fixed = TRUE)), 3) 220 | trimws(substr(paste0(x123, sep = "\n"), 1, 100)) 221 | } 222 | 223 | session_diff_text <- function(old, new, packages = c("diff", "merge")) { 224 | packages <- match.arg(packages) 225 | 226 | old <- enc2utf8(old) 227 | new <- enc2utf8(new) 228 | 229 | old <- diff_drop_empty(old) 230 | new <- diff_drop_empty(new) 231 | 232 | old <- diff_no_date(old) 233 | new <- diff_no_date(new) 234 | 235 | min <- diff_min_line(c(old, new)) 236 | old <- diff_fix_lines(old, min) 237 | new <- diff_fix_lines(new, min) 238 | 239 | package_fixup_fun <- switch( 240 | packages, 241 | # expands package info with fewer columns to match one with more 242 | diff = expand_diff_text, 243 | # full-joins package info to concentrate each diff in a single row 244 | merge = merge_packages 245 | ) 246 | # do not error, in case we cannot parse sessioninfo output 247 | suppressWarnings(tryCatch( 248 | { 249 | exp <- package_fixup_fun(old, new) 250 | old <- exp$old 251 | new <- exp$new 252 | }, 253 | error = function(e) NULL 254 | )) 255 | 256 | old2 <- gsub("\\s+", " ", old) 257 | new2 <- gsub("\\s+", " ", new) 258 | 259 | diff <- cli::diff_chr(old2, new2) 260 | diff$old <- old 261 | diff$new <- new 262 | diff 263 | } 264 | 265 | # drop leading and trailing empty lines 266 | 267 | diff_drop_empty <- function(x) { 268 | len <- length(x) 269 | if (len == 0) return(x) 270 | 271 | empty <- rle(grepl("^\\s*$", x)) 272 | pre <- if (empty$values[1]) { 273 | 1:empty$lengths[1] 274 | } 275 | post <- if (utils::tail(empty$values, 1)) { 276 | (len - utils::tail(empty$lengths, 1) + 1):len 277 | } 278 | del <- as.integer(c(pre, post)) 279 | if (length(del)) x <- x[-del] 280 | 281 | x 282 | } 283 | 284 | # Drop the first `date` line 285 | 286 | diff_no_date <- function(x) { 287 | date <- grep("^[ ]*date[ ]+[0-9][0-9][0-9][0-9]-", x) 288 | if (length(date) > 0) { 289 | x <- x[-date[1]] 290 | } 291 | x 292 | } 293 | 294 | # Calculate the minimum width of the header lines 295 | 296 | diff_min_line <- function(x) { 297 | lines <- c( 298 | grep("[-\u2500][-\u2500][-\u2500]$", x), 299 | grep("[=\u2550][=\u2550][=\u2550]$", x) 300 | ) 301 | min(c(80, cli::utf8_nchar(x[lines], "width"))) 302 | } 303 | 304 | diff_fix_lines <- function(x, w) { 305 | slines <- grepl("[-\u2500]+$", x) 306 | dlines <- grepl("[=\u2550]+$", x) 307 | x[slines] <- gsub("[-\u2500]", cli::symbol$line, x[slines]) 308 | x[dlines] <- gsub("[=\u2550]", cli::symbol$double_line, x[dlines]) 309 | x[slines] <- substr(x[slines], 1, w) 310 | x[dlines] <- substr(x[dlines], 1, w) 311 | x 312 | } 313 | 314 | expand_diff_text <- function(old, new) { 315 | opkgs <- parse_pkgs(old) 316 | npkgs <- parse_pkgs(new) 317 | 318 | if (is.null(opkgs) || is.null(opkgs)) return(list(old = old, new = new)) 319 | 320 | # Add the "!" column if needed 321 | if ("!" %in% names(opkgs$pkgs) || "!" %in% names(npkgs$pkgs)) { 322 | if (!"!" %in% names(opkgs$pkgs)) { 323 | opkgs$pkgs <- cbind("!" = "", opkgs$pkgs, stringsAsFactors = FALSE) 324 | } 325 | if (!"!" %in% names(npkgs$pkgs)) { 326 | npkgs$pkgs <- cbind("!" = "", npkgs$pkgs, stringsAsFactors = FALSE) 327 | } 328 | } 329 | 330 | # If the column names differ, we keep it as is 331 | onms <- names(opkgs$pkgs) 332 | nnms <- names(npkgs$pkgs) 333 | if (length(onms) != length(nnms) || any(onms != nnms)) { 334 | return(list(old = old, new = new)) 335 | } 336 | 337 | cmn <- rbind(opkgs$pkgs, npkgs$pkgs) 338 | oldopts <- options(cli.num_colors = 1) 339 | on.exit(options(oldopts), add = TRUE) 340 | fmt <- format_df(cmn) 341 | 342 | oend <- opkgs$end - opkgs$begin + 1L 343 | nend <- oend + npkgs$end - npkgs$begin 344 | fmt_old <- c(fmt[1], fmt[2:oend]) 345 | fmt_new <- c(fmt[1], fmt[(oend + 1):(nend)]) 346 | 347 | old <- insert_instead(old, opkgs$begin, opkgs$end, fmt_old) 348 | new <- insert_instead(new, npkgs$begin, npkgs$end, fmt_new) 349 | 350 | list(old = old, new = new) 351 | } 352 | 353 | merge_packages <- function(old, new) { 354 | opkgs <- parse_pkgs(old) 355 | npkgs <- parse_pkgs(new) 356 | 357 | if (is.null(opkgs) || is.null(opkgs)) return(list(old = old, new = new)) 358 | 359 | names_to_keep <- c("package", "version", "source") 360 | opkgs$pkgs <- opkgs$pkgs[names_to_keep] 361 | npkgs$pkgs <- npkgs$pkgs[names_to_keep] 362 | names(opkgs$pkgs) <- c("package", "old_version", "old_source") 363 | names(npkgs$pkgs) <- c("package", "new_version", "new_source") 364 | 365 | merge_res <- merge(opkgs$pkgs, npkgs$pkgs, all = TRUE) 366 | merge_res <- with( 367 | merge_res, 368 | data.frame( 369 | package = package, 370 | "v!=" = mark(is_different(old_version, new_version)), 371 | old_version = old_version, 372 | new_version = new_version, 373 | "s!=" = mark(is_different(old_source, new_source)), 374 | old_source = old_source, 375 | new_source = new_source, 376 | stringsAsFactors = FALSE, 377 | row.names = NULL, 378 | check.names = FALSE 379 | ) 380 | ) 381 | 382 | oldopts <- options(cli.num_colors = 1) 383 | on.exit(options(oldopts), add = TRUE) 384 | fmt <- format_df(merge_res) 385 | 386 | old <- insert_instead(old, opkgs$begin, opkgs$end, fmt) 387 | new <- insert_instead(new, npkgs$begin, npkgs$end, fmt) 388 | 389 | list(old = old, new = new) 390 | } 391 | 392 | is_different <- function(x, y) { 393 | x_na <- is.na(x) 394 | y_na <- is.na(y) 395 | no_na <- !x_na & !y_na 396 | xor(x_na, y_na) | (no_na & (x != y)) 397 | } 398 | 399 | mark <- function(x, mark = ">>") { 400 | ifelse(x, mark, "") 401 | } 402 | 403 | insert_instead <- function(orig, from, to, new) { 404 | pre <- if (from > 1) orig[1:(from - 1)] 405 | pst <- if (to < length(orig)) orig[(to + 1):length(orig)] 406 | c(pre, new, pst) 407 | } 408 | 409 | parse_pkgs <- function(lines) { 410 | begin <- grep("^[-\u2500] Packages ", lines) + 1 411 | 412 | # back out if no Packages header 413 | if (length(begin) != 1 || length(begin) > length(lines)) return(NULL) 414 | 415 | # now find the end 416 | end <- begin + 417 | grep( 418 | "^\\s*[!a-zA-Z]", 419 | lines[begin:length(lines)], 420 | invert = TRUE, 421 | perl = TRUE 422 | )[1] - 423 | 2 424 | if (is.na(end)) end <- length(lines) 425 | 426 | pkgs <- parse_pkgs_section(lines[begin:end]) 427 | 428 | list(begin = begin, end = end, pkgs = pkgs) 429 | } 430 | 431 | parse_pkgs_section <- function(lines) { 432 | lines[1] <- sub(" date ", " date (UTC) ", fixed = TRUE, lines[1]) 433 | hdr <- sub("date (UTC)", "date-(UTC)", fixed = TRUE, lines[1]) 434 | wth <- find_word_lengths(hdr) 435 | wth[length(wth)] <- max(nchar(lines)) 436 | df <- utils::read.fwf(textConnection(lines), widths = wth) 437 | df[] <- lapply(df, trimws) 438 | names(df) <- as.character(df[1, ]) 439 | df <- df[-1, , drop = FALSE] 440 | rownames(df) <- NULL 441 | df 442 | } 443 | 444 | find_word_lengths <- function(x) { 445 | # add a dummy word to the end for simplicity 446 | tmp <- paste0(gsub("[^\\s]", "X", x, perl = TRUE), " X") 447 | 448 | # word & ws lengths, but first absorb leading space into the first word 449 | ltr <- strsplit(tmp, "")[[1]] 450 | rl <- rle(ltr) 451 | if (ltr[1] == " ") { 452 | rl$lengths[2] <- rl$lengths[2] + rl$lengths[1] 453 | rl$lengths <- rl$lengths[-1] 454 | rl$values <- rl$values[-1] 455 | } 456 | 457 | # positions of "X" and " " parts 458 | pos <- cumsum(c(1, rl$lengths)) 459 | 460 | # lengths of words, this drops the dummy word 461 | wpos <- which(rl$values == "X") 462 | pos[wpos[-1]] - pos[wpos[-length(wpos)]] 463 | } 464 | 465 | get_symbol_name <- function(x) { 466 | if (is.symbol(x)) { 467 | as.character(x) 468 | } else if (is_string(x)) { 469 | x 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /R/dependent-packages.R: -------------------------------------------------------------------------------- 1 | dependent_packages <- function(pkgs, dependencies) { 2 | ideps <- interpret_dependencies(dependencies) 3 | 4 | pkgs <- find_deps(pkgs, utils::installed.packages(), ideps[[1]], ideps[[2]]) 5 | desc <- lapply(pkgs, pkg_desc) 6 | 7 | loaded_pkgs <- pkgs %in% setdiff(loadedNamespaces(), "base") 8 | ondiskversion <- vapply( 9 | desc, 10 | function(x) x$Version %||% NA_character_, 11 | character(1) 12 | ) 13 | loadedversion <- rep(NA_character_, length(pkgs)) 14 | loadedversion[loaded_pkgs] <- vapply( 15 | pkgs[loaded_pkgs], 16 | getNamespaceVersion, 17 | "" 18 | ) 19 | loadedpath <- rep(NA_character_, length(pkgs)) 20 | loadedpath[loaded_pkgs] <- 21 | vapply(pkgs[loaded_pkgs], getNamespaceInfo, "", which = "path") 22 | res <- data.frame( 23 | package = pkgs, 24 | ondiskversion = ondiskversion, 25 | loadedversion = loadedversion, 26 | path = vapply(desc, pkg_path_disk, character(1)), 27 | loadedpath = loadedpath, 28 | attached = paste0("package:", pkgs) %in% search(), 29 | stringsAsFactors = FALSE, 30 | row.names = NULL 31 | ) 32 | 33 | res <- res[match(sort_ci(res$package), res$package), ] 34 | 35 | row.names(res) <- NULL 36 | res 37 | } 38 | 39 | pkg_path_disk <- function(desc) { 40 | if (is.null(desc)) { 41 | NA_character_ 42 | } else { 43 | system.file(package = desc$Package, lib.loc = .libPaths()) 44 | } 45 | } 46 | 47 | find_deps <- function( 48 | pkgs, 49 | available = utils::available.packages(), 50 | top_dep = c(dep_types_hard(), "Suggests"), 51 | rec_dep = dep_types_hard(), 52 | include_pkgs = TRUE 53 | ) { 54 | if (length(pkgs) == 0 || identical(top_dep, FALSE)) return(character()) 55 | 56 | if (length(top_dep) > 0) { 57 | top <- tools::package_dependencies(pkgs, db = available, which = top_dep) 58 | top_flat <- unlist(top, use.names = FALSE) 59 | } else { 60 | top_flat <- character() 61 | } 62 | 63 | if (length(rec_dep) != 0 && length(top_flat) > 0) { 64 | rec <- tools::package_dependencies( 65 | top_flat, 66 | db = available, 67 | which = rec_dep, 68 | recursive = TRUE 69 | ) 70 | rec_flat <- unlist(rec, use.names = FALSE) 71 | } else { 72 | rec_flat <- character() 73 | } 74 | 75 | unique(c(if (include_pkgs) pkgs, top_flat, rec_flat)) 76 | } 77 | 78 | dep_types_hard <- function() c("Depends", "Imports", "LinkingTo") 79 | dep_types_soft <- function() c("Suggests", "Enhances") 80 | dep_types <- function() c(dep_types_hard(), dep_types_soft()) 81 | 82 | is_na_scalar <- function(x) length(x) == 1 && is.na(x) 83 | 84 | interpret_dependencies <- function(dp) { 85 | hard <- dep_types_hard() 86 | 87 | if (isTRUE(dp)) { 88 | list(c(hard, "Suggests"), hard) 89 | } else if (identical(dp, FALSE)) { 90 | list(character(), character()) 91 | } else if (is_na_scalar(dp)) { 92 | list(hard, hard) 93 | } else { 94 | list(dp, dp) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /R/external-info.R: -------------------------------------------------------------------------------- 1 | #' Information about related software 2 | #' 3 | #' @details 4 | #' Note that calling this function will attempt to load the tcltk and 5 | #' grDevices packages. 6 | #' 7 | #' @return A list with elements: 8 | #' * `cairo`: The cairo version string. 9 | #' * `libpng`: The png version string. 10 | #' * `jpeg`: The jpeg version string. 11 | #' * `tiff`: The tiff library and version string used. 12 | #' * `tcl`: The tcl version string. 13 | #' * `curl`: The curl version string. 14 | #' * `zlib`: The zlib version string. 15 | #' * `bzlib`: The zlib version string. 16 | #' * `xz`: The zlib version string. 17 | #' * `PCRE`: The Perl Compatible Regular Expressions (PCRE) version string. 18 | #' * `ICU`: The International Components for Unicode (ICU) version string. 19 | #' * `TRE`: The TRE version string. 20 | #' * `iconv`: The iconv version string. 21 | #' * `readline`: The readline version string. 22 | #' * `BLAS`: The path with the implementation of BLAS in use. 23 | #' * `LAPACK`: The path with the implementation of LAPACK in use. 24 | #' 25 | #' 26 | #' @seealso Similar functions and objects in the base packages: 27 | #' [utils::sessionInfo()], [base::extSoftVersion], [tcltk::tclVersion()] 28 | #' [base::La_library], [base::La_version()], [base::libcurlVersion()]. 29 | #' 30 | #' @export 31 | #' @examplesIf FALSE 32 | #' external_info() 33 | 34 | external_info <- function() { 35 | ex <- c( 36 | get_grsoft_version(), 37 | tcl = get_tcl_version(), 38 | curl = libcurlVersion(), 39 | extSoftVersion() 40 | ) 41 | ex["lapack"] <- get_la_library() 42 | ex["lapack_version"] <- get_la_version() 43 | names(ex) <- gsub("^lib", "", names(ex)) 44 | 45 | structure(as.list(ex), class = c("external_info", "list")) 46 | } 47 | 48 | get_tcl_version <- function() { 49 | tryCatch( 50 | suppressWarnings(tcltk::tclVersion()), 51 | error = function(err) "" 52 | ) 53 | } 54 | 55 | get_grsoft_version <- function() { 56 | grDevices::grSoftVersion() 57 | } 58 | 59 | get_la_library <- function() { 60 | tryCatch(base::La_library(), error = function(err) NA_character_) 61 | } 62 | 63 | get_la_version <- function() { 64 | tryCatch(base::La_version(), error = function(err) NA_character_) 65 | } 66 | 67 | #' @export 68 | 69 | format.external_info <- function(x, ...) { 70 | df <- data.frame( 71 | setting = names(x), 72 | value = unlist(x), 73 | stringsAsFactors = FALSE 74 | ) 75 | format_df(df) 76 | } 77 | 78 | #' @export 79 | 80 | print.external_info <- function(x, ...) { 81 | cat(format(x, ...), sep = "\n") 82 | } 83 | 84 | #' @export 85 | 86 | as.character.external_info <- function(x, ...) { 87 | old <- options(cli.num_colors = 1) 88 | on.exit(options(old), add = TRUE) 89 | format(x, ...) 90 | } 91 | -------------------------------------------------------------------------------- /R/github-actions.R: -------------------------------------------------------------------------------- 1 | # taken from usethis 2 | # definitely designed for GitHub URLs but not overtly GitHub-specific 3 | # https://stackoverflow.com/questions/2514859/regular-expression-for-git-repository 4 | # https://git-scm.com/docs/git-clone#_git_urls 5 | # https://stackoverflow.com/questions/27745/getting-parts-of-a-url-regex 6 | github_url_regex <- paste0( 7 | "^", 8 | "(?\\w+://)?", 9 | "(?.+@)?", 10 | "(?[^/:]+)", 11 | "[/:]", 12 | "(?[^/]+)", 13 | "/", 14 | "(?[^/#]+)", 15 | "(?.*)", 16 | "$" 17 | ) 18 | 19 | github_fragment_regex <- paste0( 20 | "/runs/", 21 | "(?[0-9]+)", 22 | "/jobs?/", 23 | "(?[0-9]+)", 24 | "$" 25 | ) 26 | 27 | parse_as_gha_url <- function(url) { 28 | res <- re_match(url, github_url_regex)$groups 29 | res2 <- re_match(res$fragment, github_fragment_regex)$groups 30 | res$run_id <- res2$run_id 31 | res$html_id <- res2$html_id 32 | 33 | ok <- res$host %in% 34 | "github.com" & 35 | !is.na(res$repo_owner) & 36 | !is.na(res$repo_name) & 37 | !is.na(res$run_id) & 38 | !is.na(res$html_id) 39 | 40 | data.frame( 41 | owner = ifelse(ok, res$repo_owner, NA_character_), 42 | repo = ifelse(ok, res$repo_name, NA_character_), 43 | run_id = ifelse(ok, res$run_id, NA_character_), 44 | html_id = ifelse(ok, res$html_id, NA_character_), 45 | stringsAsFactors = FALSE, 46 | row.names = NULL 47 | ) 48 | } 49 | 50 | is_gha_url <- function(url) { 51 | res <- parse_as_gha_url(url) 52 | !is.na(res$run_id) 53 | } 54 | 55 | get_session_info_gha <- function(url) { 56 | if (!requireNamespace("gh", quietly = TRUE)) { 57 | stop( 58 | "The gh package is not available.\n", 59 | "This appears to be the URL for a GitHub Actions (GHA) log:\n", 60 | url, 61 | "\n", 62 | "You must install the gh package to get session info for GHA job logs." 63 | ) 64 | } 65 | 66 | dat <- parse_as_gha_url(url) 67 | # the last ID in the browser URL is not the job_id! 68 | # instead, we must lookup the job_id 69 | jobs <- gh::gh( 70 | "/repos/{owner}/{repo}/actions/runs/{run_id}/jobs", 71 | owner = dat$owner, 72 | repo = dat$repo, 73 | run_id = dat$run_id 74 | ) 75 | html_urls <- vapply(jobs[["jobs"]], function(x) x[["html_url"]], "") 76 | i <- which(html_urls == url) 77 | if (length(i) == 0) { 78 | stop( 79 | "Failed to find the job associated with '", 80 | url, 81 | "'; perhaps there was a later attempt?" 82 | ) 83 | } 84 | dat$job_id <- jobs[["jobs"]][[i]][["id"]] 85 | 86 | meta <- gh::gh( 87 | "/repos/{owner}/{repo}/actions/jobs/{job_id}", 88 | owner = dat$owner, 89 | repo = dat$repo, 90 | job_id = dat$job_id 91 | ) 92 | raw_log <- gh::gh( 93 | "/repos/{owner}/{repo}/actions/jobs/{job_id}/logs", 94 | owner = dat$owner, 95 | repo = dat$repo, 96 | job_id = dat$job_id 97 | ) 98 | timestamped_lines <- unlist(strsplit(raw_log$message, split = "\r\n|\n")) 99 | lines <- sub("^[^\\s]+\\s", "", timestamped_lines, perl = TRUE) 100 | 101 | re_start <- "[-=\u2500\u2550][ ]Session info[ ]" 102 | cand <- grep(re_start, lines) 103 | if (length(cand) == 0) stop("Cannot find session info at '", url, "'.") 104 | lines <- lines[cand[1]:length(lines)] 105 | lines[1] <- sub(paste0("^.*(", re_start, ")"), "\\1", lines[1]) 106 | 107 | grepl_end <- function(lines) { 108 | grepl("^[ ]*\\[[0-9]\\] ", lines) | 109 | grepl("^[ ]*[-\u2500]+$", lines) 110 | } 111 | end <- which(grepl_end(lines))[1] 112 | if (is.na(end)) stop("Cannot parse session info from '", url, "'.") 113 | while (end < length(lines) && grepl_end(lines[end + 1])) { 114 | end <- end + 1 115 | } 116 | 117 | si <- get_session_info_literal(lines[1:end]) 118 | 119 | si$arg <- "github-actions" 120 | si$name <- paste( 121 | meta$name, 122 | meta$conclusion, 123 | paste("job", meta$id), 124 | paste("run", meta$run_id), 125 | sep = " | " 126 | ) 127 | si 128 | } 129 | -------------------------------------------------------------------------------- /R/loaded-packages.R: -------------------------------------------------------------------------------- 1 | #' @importFrom utils packageVersion 2 | 3 | loaded_packages <- function() { 4 | get_package_info(loadedNamespaces()) 5 | } 6 | 7 | attached_packages <- function() { 8 | packages <- intersect( 9 | loadedNamespaces(), 10 | sub("^package:", "", search()) 11 | ) 12 | get_package_info(packages) 13 | } 14 | 15 | installed_packages <- function() { 16 | pkgs <- rownames(utils::installed.packages(noCache = TRUE)) 17 | dependent_packages(pkgs, dependencies = FALSE) 18 | } 19 | 20 | get_package_info <- function(packages) { 21 | ## 'base' is special, because getNamespaceInfo does not work on it. 22 | ## Luckily, the path for 'base' is just system.file() 23 | 24 | spackageVersion <- function(pkg) { 25 | ## Error may happen if the package was loaded, and then removed from 26 | ## the disk. In this case we'll have NA 27 | tryCatch( 28 | as.character(packageVersion(pkg, lib.loc = .libPaths())), 29 | error = function(e) NA_character_ 30 | ) 31 | } 32 | 33 | packages <- setdiff(packages, "base") 34 | 35 | loadedversion <- vapply(packages, getNamespaceVersion, "") 36 | ondiskversion <- vapply(packages, spackageVersion, "") 37 | 38 | path <- vapply( 39 | packages, 40 | function(p) system.file(package = p, lib.loc = .libPaths()), 41 | character(1) 42 | ) 43 | ## If we can't fine the package on disk, have NA instead of "" 44 | path[path == ""] <- NA_character_ 45 | loadedpath <- vapply(packages, getNamespaceInfo, "", which = "path") 46 | 47 | attached <- paste0("package:", packages) %in% search() 48 | 49 | res <- data.frame( 50 | package = c(packages, "base"), 51 | ondiskversion = c(ondiskversion, spackageVersion("base")), 52 | loadedversion = c(loadedversion, getNamespaceVersion("base")), 53 | path = c(path, system.file()), 54 | loadedpath = c(loadedpath, NA_character_), 55 | attached = c(attached, TRUE), 56 | stringsAsFactors = FALSE, 57 | row.names = NULL 58 | ) 59 | 60 | res <- res[match(sort_ci(res$package), res$package), ] 61 | 62 | row.names(res) <- NULL 63 | res 64 | } 65 | -------------------------------------------------------------------------------- /R/mocks.r: -------------------------------------------------------------------------------- 1 | Sys.which <- NULL 2 | getNamespaceInfo <- NULL 3 | getNamespaceVersion <- NULL 4 | loadedNamespaces <- NULL 5 | search <- NULL 6 | system <- NULL 7 | system2 <- NULL 8 | -------------------------------------------------------------------------------- /R/osname.R: -------------------------------------------------------------------------------- 1 | #' Human readable name of the current operating system 2 | #' 3 | #' For example Windows 8.1 instead of Windows version 6.3.9600. 4 | #' On macOS it includes the code names, on Linux it includes the 5 | #' distribution names and codenames if appropriate. 6 | #' 7 | #' It uses [utils::sessionInfo()], but simplifies its output a bit 8 | #' on Windows, to make it more concise. 9 | #' 10 | #' @return A character scalar. 11 | #' 12 | #' @export 13 | 14 | os_name <- function() { 15 | x <- suppressWarnings(utils::sessionInfo("base")$running) 16 | if (is.null(x)) return(NA_character_) 17 | 18 | x <- gsub("Service Pack", "SP", x) 19 | 20 | x 21 | } 22 | -------------------------------------------------------------------------------- /R/package-info.R: -------------------------------------------------------------------------------- 1 | #' Information about the currently loaded packages, or about a chosen set 2 | #' 3 | #' @param pkgs Which packages to show. It may be: 4 | #' * `NULL` or `"loaded"`: show all loaded packages, 5 | #' * `"attached"`: show all attached packages, 6 | #' * `"installed"`: show all installed packages, 7 | #' * a character vector of package names. Their (hard) dependencies are 8 | #' also shown by default, see the `dependencies` argument. 9 | #' @param include_base Include base packages in summary? By default this is 10 | #' false since base packages should always match the R version. 11 | #' @param dependencies Whether to include the (recursive) dependencies 12 | #' as well. See the `dependencies` argument of [utils::install.packages()]. 13 | #' @return A data frame with columns: 14 | #' * `package`: package name. 15 | #' * `ondiskversion`: package version (on the disk, which is sometimes 16 | #' not the same as the loaded version). 17 | #' * `loadedversion`: package version. This is the version of the loaded 18 | #' namespace if `pkgs` is `NULL`, and it is the version of the package 19 | #' on disk otherwise. The two of them are almost always the same, 20 | #' though. 21 | #' * `path`: path to the package on disk. 22 | #' * `loadedpath`: the path the package was originally loaded from. 23 | #' * `attached`: logical, whether the package is attached to the search 24 | #' path. 25 | #' * `is_base`: logical, whether the package is a base package. 26 | #' * `date`: the date the package was installed or built, in UTC. 27 | #' * `source`: where the package was installed from. E.g. 28 | #' `CRAN`, `GitHub`, `local` (from the local machine), etc. 29 | #' * `md5ok`: Whether MD5 hashes for package DLL files match, on Windows. 30 | #' `NA` on other platforms. 31 | #' * `library`: factor, which package library the package was loaded from. 32 | #' For loaded packages, this is (the factor representation of) 33 | #' `loadedpath`, for others `path`. 34 | #' 35 | #' See [session_info()] for the description of the *printed* columns 36 | #' by `package_info` (as opposed to the *returned* columns). 37 | #' 38 | #' @export 39 | #' @examplesIf FALSE 40 | #' package_info() 41 | #' package_info("sessioninfo") 42 | 43 | package_info <- function( 44 | pkgs = c("loaded", "attached", "installed")[1], 45 | include_base = FALSE, 46 | dependencies = NA 47 | ) { 48 | if (is.null(pkgs)) pkgs <- "loaded" 49 | if (identical(pkgs, "!loaded") || identical(pkgs, "loaded")) { 50 | pkgs <- loaded_packages() 51 | } else if (identical(pkgs, "!attached") || identical(pkgs, "attached")) { 52 | pkgs <- attached_packages() 53 | } else if (identical(pkgs, "!installed") || identical(pkgs, "installed")) { 54 | pkgs <- installed_packages() 55 | } else { 56 | pkgs <- dependent_packages(pkgs, dependencies) 57 | } 58 | 59 | desc <- lapply(pkgs$package, pkg_desc, lib.loc = .libPaths()) 60 | 61 | pkgs$is_base <- vapply( 62 | desc, 63 | function(x) identical(x$Priority, "base"), 64 | logical(1) 65 | ) 66 | 67 | pkgs$date <- vapply(desc, pkg_date, character(1)) 68 | pkgs$source <- vapply(desc, pkg_source, character(1)) 69 | pkgs$md5ok <- vapply(desc, pkg_md5ok_dlls, logical(1)) 70 | 71 | libpath <- pkg_lib_paths() 72 | path <- ifelse(is.na(pkgs$loadedpath), pkgs$path, pkgs$loadedpath) 73 | pkgs$library <- factor(dirname(path), levels = libpath) 74 | 75 | if (!include_base) pkgs <- pkgs[!pkgs$is_base, ] 76 | 77 | rownames(pkgs) <- pkgs$package 78 | class(pkgs) <- c("packages_info", "data.frame") 79 | pkgs 80 | } 81 | 82 | pkg_desc <- function(package, lib.loc = NULL) { 83 | desc <- suppressWarnings( 84 | utils::packageDescription(package, lib.loc = lib.loc) 85 | ) 86 | if (inherits(desc, "packageDescription")) desc else NULL 87 | } 88 | 89 | pkg_lib_paths <- function() { 90 | normalizePath(.libPaths(), winslash = "/") 91 | } 92 | 93 | pkg_date <- function(desc) { 94 | if (!is.null(desc$`Date/Publication`)) { 95 | date <- desc$`Date/Publication` 96 | } else if (!is.null(desc$Built)) { 97 | built <- strsplit(desc$Built, "; ")[[1]] 98 | date <- built[3] 99 | } else { 100 | date <- NA_character_ 101 | } 102 | 103 | as.character(as.Date(strptime(date, "%Y-%m-%d"))) 104 | } 105 | 106 | pkg_source <- function(desc) { 107 | if (is.null(desc)) { 108 | NA_character_ 109 | } else if (!is.null(desc$GithubSHA1)) { 110 | str <- paste0( 111 | "Github (", 112 | desc$GithubUsername, 113 | "/", 114 | desc$GithubRepo, 115 | "@", 116 | desc$GithubSHA1, 117 | ")" 118 | ) 119 | } else if (!is.null(desc$RemoteType) && desc$RemoteType == "standard") { 120 | if (!is.null(desc$Repository) && desc$Repository == "CRAN") { 121 | pkg_source_cran(desc) 122 | } else if (!is.null(desc$Repository)) { 123 | str_trim(desc$Repository, 10) 124 | } else if (!is.null(desc$biocViews) && desc$biocViews != "") { 125 | "Bioconductor" 126 | } else { 127 | "Custom" 128 | } 129 | } else if (!is.null(desc$RemoteType) && desc$RemoteType != "cran") { 130 | # want to generate these: 131 | # remoteType (username/repo@commit) 132 | # remoteType (username/repo) 133 | # remoteType (@commit) 134 | # remoteType 135 | remote_type <- desc$RemoteType 136 | 137 | # RemoteUsername and RemoteRepo should always be present together 138 | if (!is.null(desc$RemoteUsername) && (!is.null(desc$RemoteRepo))) { 139 | user_repo <- paste0(desc$RemoteUsername, "/", desc$RemoteRepo) 140 | } else if (!is.null(desc$RemoteUrl)) { 141 | user_repo <- desc$RemoteUrl 142 | } else { 143 | user_repo <- NULL 144 | } 145 | 146 | if (!is.null(desc$RemoteSha)) { 147 | sha <- paste0("@", desc$RemoteSha) 148 | } else { 149 | sha <- NULL 150 | } 151 | 152 | # in order to fulfill the expectation of formatting, we paste the user_repo 153 | # and sha together 154 | if (!is.null(user_repo) || !is.null(sha)) { 155 | user_repo_and_sha <- paste0(" (", user_repo, sha, ")") 156 | } else { 157 | user_repo_and_sha <- NULL 158 | } 159 | 160 | str <- paste0(remote_type, user_repo_and_sha) 161 | } else if (!is.null(desc$Repository)) { 162 | pkg_source_cran(desc) 163 | } else if (!is.null(desc$biocViews) && desc$biocViews != "") { 164 | "Bioconductor" 165 | } else if ( 166 | isNamespaceLoaded(desc$Package) && 167 | !is.null(asNamespace(desc$Package)$.__DEVTOOLS__) 168 | ) { 169 | "load_all()" 170 | } else { 171 | "local" 172 | } 173 | } 174 | 175 | pkg_source_cran <- function(desc) { 176 | repo <- desc$Repository 177 | 178 | if (!is.null(desc$Built)) { 179 | built <- strsplit(desc$Built, "; ")[[1]] 180 | ver <- sub("$R ", "", built[1]) 181 | repo <- paste0(repo, " (", ver, ")") 182 | } 183 | 184 | repo 185 | } 186 | 187 | pkg_md5ok_dlls <- function(desc) { 188 | if (is.null(desc)) return(NA) 189 | if (.Platform$OS.type != "windows") return(NA) 190 | pkgdir <- dirname(dirname(attr(desc, "file"))) 191 | if (!file.exists(file.path(pkgdir, "libs"))) return(TRUE) 192 | stored <- pkg_md5_stored(pkgdir) 193 | if (is.null(stored)) return(NA) 194 | disk <- pkg_md5_disk(pkgdir) 195 | identical(stored, disk) 196 | } 197 | 198 | pkg_md5_stored <- function(pkgdir) { 199 | md5file <- file.path(pkgdir, "MD5") 200 | md5 <- tryCatch( 201 | suppressWarnings(readLines(md5file)), 202 | error = function(e) NULL 203 | ) 204 | if (is.null(md5)) return(NULL) 205 | hash <- sub(" .*$", "", md5) 206 | filename <- sub("^[^ ]* \\*", "", md5) 207 | dll <- grep("[dD][lL][lL]$", filename) 208 | order_by_name(structure(hash[dll], names = tolower(filename[dll]))) 209 | } 210 | 211 | pkg_md5_disk <- function(pkgdir) { 212 | old <- getwd() 213 | on.exit(setwd(old), add = TRUE) 214 | setwd(pkgdir) 215 | dll_files <- file.path( 216 | "libs", 217 | dir("libs", pattern = "[dD][lL][lL]$", recursive = TRUE) 218 | ) 219 | md5_files <- tools::md5sum(dll_files) 220 | order_by_name(structure(unname(md5_files), names = tolower(dll_files))) 221 | } 222 | 223 | abbrev_long_sha <- function(x) { 224 | sub("([0-9a-f]{7})[0-9a-f]{33}", "\\1", x) 225 | } 226 | 227 | #' @export 228 | 229 | format.packages_info <- function(x, ...) { 230 | if (nrow(x) == 0) { 231 | return(cli::col_grey("No packages.")) 232 | } 233 | 234 | unloaded <- is.na(x$loadedversion) 235 | flib <- function(x) ifelse(is.na(x), "?", as.integer(x)) 236 | 237 | px <- data.frame( 238 | package = x$package, 239 | "*" = ifelse(x$attached, "*", ""), 240 | version = ifelse(unloaded, x$ondiskversion, x$loadedversion), 241 | "date (UTC)" = x$date, 242 | lib = paste0("[", flib(x$library), "]"), 243 | source = abbrev_long_sha(x$source), 244 | stringsAsFactors = FALSE, 245 | check.names = FALSE 246 | ) 247 | 248 | anyattached <- any(x$attached) 249 | badloaded <- package_version(x$loadedversion, strict = FALSE) != 250 | package_version(x$ondiskversion, strict = FALSE) 251 | badloaded <- !is.na(badloaded) & badloaded 252 | 253 | px$source <- ifelse( 254 | badloaded, 255 | paste0(px$source, " (on disk ", x$ondiskversion, ")"), 256 | px$source 257 | ) 258 | 259 | badmd5 <- !is.na(x$md5ok) & !x$md5ok 260 | 261 | badpath <- !is.na(x$loadedpath) & x$loadedpath != x$path 262 | 263 | baddel <- is.na(x$ondiskversion) 264 | badpath[baddel] <- FALSE 265 | 266 | if (any(badloaded) || any(badmd5) || any(badpath) || any(baddel)) { 267 | prob <- paste0( 268 | ifelse(badloaded, "V", ""), 269 | ifelse(badpath, "P", ""), 270 | ifelse(badmd5, "D", ""), 271 | ifelse(baddel, "R", "") 272 | ) 273 | px <- cbind("!" = prob, px) 274 | } 275 | 276 | dng <- function(x) cli::bg_red(cli::col_white(x)) 277 | 278 | highlighters <- list( 279 | "!" = function(x) { 280 | ifelse(empty(x), x, dng(x)) 281 | }, 282 | version = function(x) { 283 | highlight_version(x) 284 | }, 285 | "date (UTC)" = function(x) { 286 | cli::col_grey(x) 287 | }, 288 | lib = function(x) { 289 | cli::col_grey(x) 290 | }, 291 | source = function(x) { 292 | common <- grepl("^(Bioconductor|CRAN)", x) 293 | x[!common] <- cli::style_bold(cli::col_magenta(x[!common])) 294 | x[common] <- cli::col_grey(x[common]) 295 | x 296 | } 297 | ) 298 | 299 | fmt <- c(format_df(px, highlighters = highlighters), "") 300 | 301 | lapply( 302 | seq_along(levels(x$library)), 303 | function(i) { 304 | fmt <<- c(fmt, cli::col_grey(paste0(" [", i, "] ", levels(x$library)[i]))) 305 | } 306 | ) 307 | 308 | if ("!" %in% names(px)) fmt <- c(fmt, "") 309 | if (anyattached) { 310 | fmt <- c( 311 | fmt, 312 | paste0( 313 | " ", 314 | dng("*"), 315 | " ", 316 | dash(2), 317 | " Packages attached to the search path." 318 | ) 319 | ) 320 | } 321 | if (any(badloaded)) { 322 | fmt <- c( 323 | fmt, 324 | paste0( 325 | " ", 326 | dng("V"), 327 | " ", 328 | dash(2), 329 | " Loaded and on-disk version mismatch." 330 | ) 331 | ) 332 | } 333 | if (any(badpath)) { 334 | fmt <- c( 335 | fmt, 336 | paste0(" ", dng("P"), " ", dash(2), " Loaded and on-disk path mismatch.") 337 | ) 338 | } 339 | if (any(badmd5)) { 340 | fmt <- c( 341 | fmt, 342 | paste0( 343 | " ", 344 | dng("D"), 345 | " ", 346 | dash(2), 347 | " DLL MD5 mismatch, broken installation." 348 | ) 349 | ) 350 | } 351 | if (any(baddel)) { 352 | fmt <- c( 353 | fmt, 354 | paste0(" ", dng("R"), " ", dash(2), " Package was removed from disk.") 355 | ) 356 | } 357 | 358 | fmt 359 | } 360 | 361 | #' @export 362 | 363 | print.packages_info <- function(x, ...) { 364 | cat(format(x, ...), sep = "\n") 365 | } 366 | 367 | #' @export 368 | 369 | as.character.packages_info <- function(x, ...) { 370 | old <- options(cli.num_colors = 1) 371 | on.exit(options(old), add = TRUE) 372 | format(x, ...) 373 | } 374 | -------------------------------------------------------------------------------- /R/platform-info.R: -------------------------------------------------------------------------------- 1 | #' Information about the current platform 2 | #' 3 | #' @return A list with elements: 4 | #' * `version`: the R version string. 5 | #' * `os`: the OS name in human readable format, see [os_name()]. 6 | #' * `system`: CPU, and machine readable OS name, separated by a comma. 7 | #' * `ui`: the user interface, e.g. `Rgui`, `RTerm`, etc. see `GUI` 8 | #' in [base::.Platform]. 9 | #' * `hostname`: the name of the machine known on the network, see 10 | #' `nodename` in [base::Sys.info()]. For privacy, it is only included 11 | #' if the `sessioninfo.include_hostname` option is set to `TRUE`. 12 | #' * `language`: The current language setting. The `LANGUAGE` environment 13 | #' variable, if set, or `(EN)` if unset. 14 | #' * `collate`: Collation rule, from the current locale. 15 | #' * `ctype`: Native character encoding, from the current locale. 16 | #' * `tz`: The current time zone. 17 | #' * `date`: The current date. 18 | #' * `rstudio`: RStudio format string, only added in RStudio. 19 | #' * `pandoc`: pandoc version and path 20 | #' * `quarto`: quarto version and path 21 | #' 22 | #' @seealso Similar functions and objects in the base packages: 23 | #' [base::R.version.string], [utils::sessionInfo()], [base::version], 24 | #' [base::.Platform], [base::Sys.getlocale()], [base::Sys.timezone()]. 25 | #' 26 | #' @export 27 | #' @examplesIf FALSE 28 | #' platform_info() 29 | 30 | platform_info <- function() { 31 | include_hostname <- isTRUE(getOption("sessioninfo.include_hostname")) 32 | as_platform_info(drop_null(list( 33 | version = R.version.string, 34 | os = os_name(), 35 | system = version$system, 36 | hostname = if (include_hostname) { 37 | Sys.info()[["nodename"]] 38 | }, 39 | ui = .Platform$GUI, 40 | language = Sys.getenv("LANGUAGE", "(EN)"), 41 | collate = Sys.getlocale("LC_COLLATE"), 42 | ctype = Sys.getlocale("LC_CTYPE"), 43 | tz = Sys.timezone(), 44 | date = format(Sys.Date()), 45 | rstudio = get_rstudio_version(), 46 | pandoc = get_pandoc_version(), 47 | quarto = get_quarto_version() 48 | ))) 49 | } 50 | 51 | get_rstudio_version <- function() { 52 | tryCatch( 53 | { 54 | ver <- get("RStudio.Version", "tools:rstudio")() 55 | paste0( 56 | ver$long_version %||% ver$version, 57 | if (!is.null(ver$release_name)) paste0(" ", ver$release_name), 58 | " (", 59 | ver$mode, 60 | ")" 61 | ) 62 | }, 63 | error = function(e) NULL 64 | ) 65 | } 66 | 67 | get_pandoc_version <- function() { 68 | if (isNamespaceLoaded("rmarkdown")) { 69 | ver <- rmarkdown::find_pandoc() 70 | if (is.null(ver$dir)) { 71 | "NA (via rmarkdown)" 72 | } else { 73 | paste0(ver$version, " @ ", ver$dir, "/ (via rmarkdown)") 74 | } 75 | } else { 76 | path <- Sys.which("pandoc") 77 | if (path == "") { 78 | "NA" 79 | } else { 80 | ver <- parse_pandoc_version(path) 81 | paste0(ver, " @ ", path) 82 | } 83 | } 84 | } 85 | 86 | parse_pandoc_version <- function(path) { 87 | tryCatch( 88 | { 89 | out <- system2(path, "--version", stdout = TRUE)[1] 90 | last(strsplit(out, " ", fixed = TRUE)[[1]]) 91 | }, 92 | error = function(e) "NA" 93 | ) 94 | } 95 | 96 | get_quarto_version <- function() { 97 | path <- Sys.which("quarto") 98 | if (path == "") { 99 | "NA" 100 | } else { 101 | tmp <- tempfile() 102 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 103 | dir.create(tmp, recursive = TRUE, showWarnings = FALSE) 104 | tmp <- normalizePath(tmp, winslash = "/") 105 | ver <- system2("quarto", "-V", stdout = TRUE, env = paste0("TMPDIR=", tmp))[ 106 | 1 107 | ] 108 | paste0(ver, " @ ", path) 109 | } 110 | } 111 | 112 | #' @export 113 | 114 | format.platform_info <- function(x, ...) { 115 | df <- data.frame( 116 | setting = names(x), 117 | value = unlist(x), 118 | stringsAsFactors = FALSE 119 | ) 120 | format_df(df) 121 | } 122 | 123 | #' @export 124 | 125 | print.platform_info <- function(x, ...) { 126 | cat(format(x, ...), sep = "\n") 127 | } 128 | 129 | #' @export 130 | 131 | as.character.platform_info <- function(x, ...) { 132 | old <- options(cli.num_colors = 1) 133 | on.exit(options(old), add = TRUE) 134 | format(x, ...) 135 | } 136 | 137 | #' @export 138 | 139 | c.platform_info <- function(...) { 140 | as_platform_info(NextMethod()) 141 | } 142 | 143 | as_platform_info <- function(x) { 144 | stopifnot(is.list(x)) 145 | class(x) <- c("platform_info", "list") 146 | x 147 | } 148 | -------------------------------------------------------------------------------- /R/printing.R: -------------------------------------------------------------------------------- 1 | #' @importFrom cli symbol 2 | 3 | rule <- function(..., pad = NULL, double = FALSE) { 4 | if (is.null(pad)) pad <- if (double) symbol$double_line else symbol$line 5 | title <- if (length(list(...))) paste0(" ", ..., " ") else "" 6 | 7 | width <- max(cli::console_width() - cli::ansi_nchar(title, "width") - 3, 0) 8 | rule <- paste(pad, title, paste(rep(pad, width), collapse = ""), sep = "") 9 | cli::style_bold(cli::col_cyan(rule)) 10 | } 11 | 12 | dash <- function(n = 2) { 13 | paste(rep(symbol$line, n), collapse = "") 14 | } 15 | 16 | cat_ln <- function(..., sep = "") { 17 | cat(..., "\n", sep = sep) 18 | } 19 | 20 | col_align <- function(x, align = c("left", "center", "right")) { 21 | x <- encodeString(x) 22 | mw <- max(cli::ansi_nchar(x, "width")) 23 | cli::ansi_align(x, align = align, width = mw) 24 | } 25 | 26 | format_column <- function(name, x) { 27 | col_align(c(name, x)) 28 | } 29 | 30 | format_df <- function(x, highlighters = NULL) { 31 | cols <- mapply( 32 | names(x), 33 | x, 34 | FUN = format_column, 35 | USE.NAMES = FALSE, 36 | SIMPLIFY = FALSE 37 | ) 38 | 39 | cols <- lapply(cols, function(x) { 40 | if (length(x) > 0) x[1] <- cli::col_grey(cli::style_italic(x[1])) 41 | x 42 | }) 43 | 44 | for (idx in seq_along(highlighters)) { 45 | colname <- names(highlighters)[idx] 46 | colnum <- match(colname, names(x)) 47 | if (is.na(colnum)) next 48 | cols[[colnum]][-1] <- highlighters[[idx]](cols[[colnum]][-1]) 49 | } 50 | 51 | # remove trailing space, to avoid superfluous wrapping 52 | cli::ansi_trimws(do.call("paste", c("", cols)), "right") 53 | } 54 | 55 | highlight_version <- function(x) { 56 | ver <- tryCatch(package_version(trimws(x)), error = function(err) NULL) 57 | if (is.null(ver)) return(x) 58 | large <- vapply(ver, function(x) any(unlist(x) >= 1234), logical(1)) 59 | x[large] <- cli::style_bold(cli::col_magenta(x[large])) 60 | x 61 | } 62 | -------------------------------------------------------------------------------- /R/python-info.R: -------------------------------------------------------------------------------- 1 | should_show_python <- function(pkgs) { 2 | "reticulate" %in% 3 | pkgs || 4 | (isNamespaceLoaded("reticulate") && 5 | reticulate::py_available(initialize = FALSE)) 6 | } 7 | 8 | #' Python configuration 9 | #' 10 | #' @return 11 | #' Returns a [reticulate::py_config] object, which also has the 12 | #' `python_info` class. It is a named list of values. 13 | #' 14 | #' If reticulate is not installed or Python is not configured, 15 | #' then it return a `python_info` object that is a character vector, and 16 | #' it does not have a `py_config` class. 17 | #' 18 | #' @export 19 | #' @examplesIf FALSE 20 | #' python_info() 21 | #' session_info(info = "all") 22 | 23 | python_info <- function() { 24 | conf <- if ( 25 | isNamespaceLoaded("reticulate") && 26 | reticulate::py_available(initialize = FALSE) 27 | ) { 28 | tryCatch( 29 | reticulate::py_config(), 30 | error = function(err) { 31 | c("`reticulate::py_config()` failed with error:", conditionMessage(err)) 32 | } 33 | ) 34 | } else { 35 | "Python is not available" 36 | } 37 | 38 | class(conf) <- unique(c("python_info", class(conf), "list")) 39 | conf 40 | } 41 | 42 | #' @export 43 | 44 | format.python_info <- function(x, ...) { 45 | x <- NextMethod() 46 | paste0(" ", unlist(strsplit(x, "\n", fixed = TRUE))) 47 | } 48 | 49 | #' @export 50 | 51 | as.character.python_info <- function(x, ...) { 52 | old <- options(cli.num_colors = 1) 53 | on.exit(options(old), add = TRUE) 54 | format(x, ...) 55 | } 56 | 57 | #' @export 58 | 59 | print.python_info <- function(x, ...) { 60 | cat(format(x, ...), sep = "\n") 61 | } 62 | -------------------------------------------------------------------------------- /R/rematch2.R: -------------------------------------------------------------------------------- 1 | re_match <- function(text, pattern, perl = TRUE, ...) { 2 | stopifnot(is.character(pattern), length(pattern) == 1, !is.na(pattern)) 3 | text <- as.character(text) 4 | 5 | match <- regexpr(pattern, text, perl = perl, ...) 6 | 7 | start <- as.vector(match) 8 | length <- attr(match, "match.length") 9 | end <- start + length - 1L 10 | 11 | matchstr <- substring(text, start, end) 12 | matchstr[start == -1] <- NA_character_ 13 | 14 | empty <- data.frame(stringsAsFactors = FALSE, .text = text)[, numeric()] 15 | res <- list(match = !is.na(matchstr), groups = empty) 16 | 17 | if (!is.null(attr(match, "capture.start"))) { 18 | gstart <- attr(match, "capture.start") 19 | glength <- attr(match, "capture.length") 20 | gend <- gstart + glength - 1L 21 | 22 | groupstr <- substring(text, gstart, gend) 23 | groupstr[gstart == -1] <- NA_character_ 24 | dim(groupstr) <- dim(gstart) 25 | 26 | res$groups <- cbind(groupstr, res$groups, stringsAsFactors = FALSE) 27 | names(res$groups) <- attr(match, "capture.names") 28 | } 29 | 30 | res 31 | } 32 | -------------------------------------------------------------------------------- /R/session-info.R: -------------------------------------------------------------------------------- 1 | #' Print session information 2 | #' 3 | #' This is [utils::sessionInfo()] re-written from scratch to both exclude 4 | #' data that's rarely useful (e.g., the full collate string or base packages 5 | #' loaded) and include stuff you'd like to know (e.g., where a package was 6 | #' installed from). 7 | #' 8 | #' @details 9 | #' Columns in the *printed* package list: 10 | #' * `package`: package name 11 | #' * `*`: whether the package is attached to the search path 12 | #' * `version`: package version. If the version is marked with `(!)` that 13 | #' means that the loaded and the on-disk version of the package are 14 | #' different. 15 | #' * `date`: when the package was built, if this information is available. 16 | #' This is the `Date/Publication` or the `Built` field from 17 | #' `DESCRIPTION`. (These are usually added automatically by R.) 18 | #' Sometimes this data is not available, then it is `NA`. 19 | #' * `source`: where the package was built or installed from, if available. 20 | #' Examples: `CRAN (R 3.3.2)`, `Github (r-lib/pkgbuild@8aab60b)`, 21 | #' `Bioconductor`, `local`. 22 | #' 23 | #' See [package_info()] for the list of columns in the data frame that 24 | #' is *returned* (as opposed to *printed*). 25 | #' 26 | #' @inheritParams package_info 27 | #' @param info What information to show, it can be `"auto"` to choose 28 | #' automatically, `"all"` to show everything, or a character vector 29 | #' with elements from: 30 | #' * `"platform"`: show platform information via [platform_info()], 31 | #' * `"packages"`: show package information via [package_info()], 32 | #' * `"python"`: show Python configuration via [python_info()], 33 | #' * `"external"`: show information about external software, via 34 | #' [external_info()]. 35 | #' @param to_file Whether to print the session information to a file. 36 | #' If `TRUE` the name of the file will be `session-info.txt`, but 37 | #' `to_file` may also be a string to specify the file name. 38 | #' 39 | #' @return 40 | #' A `session_info` object. 41 | #' 42 | #' If `to_file` is not `FALSE` then it is 43 | #' returned invisibly. (To print it to both a file and to the screen, 44 | #' use `(session_info(to_file = TRUE))`.) 45 | #' 46 | #' @export 47 | #' @examplesIf FALSE 48 | #' session_info() 49 | #' session_info("sessioninfo") 50 | 51 | session_info <- function( 52 | pkgs = c("loaded", "attached", "installed")[1], 53 | include_base = FALSE, 54 | info = c("auto", "all", "platform", "packages", "python", "external"), 55 | dependencies = NA, 56 | to_file = FALSE 57 | ) { 58 | if (missing(info)) info <- "auto" 59 | choices <- c("platform", "packages", "python", "external") 60 | if ("all" %in% info) { 61 | info <- choices 62 | } else if ("auto" %in% info) { 63 | info <- c( 64 | "platform", 65 | "packages", 66 | if (should_show_python(pkgs)) "python" 67 | ) 68 | } else { 69 | info <- match.arg(info, choices, several.ok = TRUE) 70 | } 71 | 72 | stopifnot(is_flag(to_file) || is_string(to_file)) 73 | if (is_flag(to_file) && to_file) to_file <- "session-info.txt" 74 | 75 | si <- structure( 76 | drop_null(list( 77 | platform = if ("platform" %in% info) platform_info(), 78 | packages = if ("packages" %in% info) { 79 | package_info( 80 | pkgs, 81 | include_base = include_base, 82 | dependencies = dependencies 83 | ) 84 | }, 85 | external = if ("external" %in% info) external_info(), 86 | python = if ("python" %in% info) python_info() 87 | )), 88 | class = c("session_info", "list") 89 | ) 90 | 91 | if (is_string(to_file)) { 92 | old <- options(cli.num_colors = 1) 93 | on.exit(options(old), add = TRUE) 94 | writeLines(format(si), to_file) 95 | invisible(si) 96 | } else { 97 | si 98 | } 99 | } 100 | 101 | #' @export 102 | 103 | format.session_info <- function(x, ...) { 104 | has_platform <- !is.null(x$platform) 105 | 106 | c( 107 | if (!"platform" %in% names(x)) { 108 | rule("Session info", double = TRUE) 109 | }, 110 | if ("platform" %in% names(x)) { 111 | c(rule(paste("Session info")), format(x$platform), "") 112 | }, 113 | if ("packages" %in% names(x) && nrow(x$packages) > 0) { 114 | c(rule("Packages"), format(x$packages), "") 115 | }, 116 | if ("external" %in% names(x)) { 117 | c(rule("External software"), format(x$external), "") 118 | }, 119 | if ("python" %in% names(x)) { 120 | c(rule("Python configuration"), format(x$python), "") 121 | }, 122 | rule() 123 | ) 124 | } 125 | 126 | #' @export 127 | 128 | as.character.session_info <- function(x, ...) { 129 | old <- options(cli.num_colors = 1) 130 | on.exit(options(old), add = TRUE) 131 | format(x, ...) 132 | } 133 | 134 | has_emoji <- function() { 135 | if (isTRUE(opt <- getOption("sessioninfo.emoji"))) { 136 | TRUE 137 | } else if (identical(opt, FALSE)) { 138 | FALSE 139 | } else if (!cli::is_utf8_output()) { 140 | FALSE 141 | } else { 142 | Sys.info()[["sysname"]] == "Darwin" 143 | } 144 | } 145 | 146 | #' @export 147 | 148 | print.session_info <- function(x, ...) { 149 | cat(format(x), sep = "\n") 150 | } 151 | -------------------------------------------------------------------------------- /R/sessioninfo-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | ## usethis namespace: end 6 | NULL 7 | -------------------------------------------------------------------------------- /R/sysdata.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/R/sysdata.rda -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | `%||%` <- function(l, r) if (is.null(l)) r else l 2 | 3 | sort_ci <- function(x) { 4 | old <- Sys.getlocale("LC_COLLATE") 5 | on.exit(Sys.setlocale("LC_COLLATE", old), add = TRUE) 6 | Sys.setlocale("LC_COLLATE", "C") 7 | x[order(tolower(x), x)] 8 | } 9 | 10 | is_string <- function(x) { 11 | is.character(x) && length(x) == 1 && !is.na(x) 12 | } 13 | 14 | is_flag <- function(x) { 15 | is.logical(x) && length(x) == 1 && !is.na(x) 16 | } 17 | 18 | order_by_name <- function(x) { 19 | if (!length(x)) { 20 | x 21 | } else if (is.null(names(x))) { 22 | stop("Cannot order by name, no names") 23 | } else { 24 | x[order(names(x))] 25 | } 26 | } 27 | 28 | drop_null <- function(x) { 29 | x[!vapply(x, is.null, logical(1))] 30 | } 31 | 32 | last <- function(x) { 33 | x[length(x)] 34 | } 35 | 36 | empty <- function(x) { 37 | grepl("^\\s*$", x) 38 | } 39 | 40 | str_trim <- function(x, width) { 41 | stopifnot(width >= 2) 42 | if (nchar(x) <= width) { 43 | x 44 | } else { 45 | paste0(substr(x, 1, width - 1), "~") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # sessioninfo 3 | 4 | > R Session Information 5 | 6 | 7 | [![Lifecycle: stable](https://lifecycle.r-lib.org/articles/figures/lifecycle-stable.svg)](https://lifecycle.r-lib.org/articles/stages.html) 8 | [![R-CMD-check](https://github.com/r-lib/sessioninfo/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/sessioninfo/actions/workflows/R-CMD-check.yaml) 9 | [![](https://www.r-pkg.org/badges/version/sessioninfo)](https://www.r-pkg.org/pkg/sessioninfo) 10 | [![CRAN RStudio mirror downloads](https://cranlogs.r-pkg.org/badges/sessioninfo)](https://www.r-pkg.org/pkg/sessioninfo) 11 | [![Codecov test coverage](https://codecov.io/gh/r-lib/sessioninfo/graph/badge.svg)](https://app.codecov.io/gh/r-lib/sessioninfo) 12 | 13 | 14 | Query and print information about the current R session. It is similar to 15 | `utils::sessionInfo()`, but includes more information about packages, and 16 | where they were installed from. 17 | 18 | ## Differences from `utils::sessionInfo()` 19 | 20 | * Additional platform details: time zone, pandoc version, RStudio version, 21 | etc. 22 | * Information about package sources, e.g. GitHub repo and hash for packages 23 | installed from GitHub. 24 | * Highlight package installation problems, e.g. if the loaded and on-disk 25 | versions are different, if the MD5 checksum of the package DLL is wrong, 26 | etc. 27 | * Highlight packages from unusual sources. 28 | * Information about external software via `external_info()`. 29 | * Information about the Python configuration if the reticulate package is 30 | loaded and configured. 31 | * Information about package libraries. 32 | * Compare two session info outputs with the `session_diff()` function. 33 | * Option to show loaded (default), attached or installed packages, or 34 | the recursive dependencies of the specified packages. 35 | 36 | ## Installation 37 | 38 | Install the released version from CRAN 39 | 40 | ```r 41 | install.packages("sessioninfo") 42 | ``` 43 | 44 | Or the development version from GitHub: 45 | 46 | ```r 47 | # install.packages("pak") 48 | pak::pak("r-lib/sessioninfo") 49 | ``` 50 | 51 | ## Usage 52 | 53 | Example output: 54 | 55 | ```r 56 | sessioninfo::session_info() 57 | ``` 58 | 59 | Screenshot of a terminal window demonstrating example output of the sessioninfo function. 60 | 61 | ### Copying to the clipboard 62 | 63 | You can use the 64 | [`clipr` package](https://cran.r-project.org/package=clipr) to copy 65 | the session info to the clipboard: 66 | 67 | ```r 68 | clipr::write_clip(session_info()) 69 | ``` 70 | 71 | (The current `clipr` version prints a warning, but you can ignore that.) 72 | 73 | ### Writing to a file 74 | 75 | You can use the `to_file` argument of `session_info()`: 76 | 77 | ```r 78 | session_info(to_file = "session.log") 79 | ``` 80 | 81 | ### External software 82 | 83 | ```r 84 | sessioninfo::session_info(info = "external") 85 | ``` 86 | 87 | ``` 88 | ═ Session info ═══════════════════════════════════════════════════════════════ 89 | ─ External software ────────────────────────────────────────────────────────── 90 | setting value 91 | cairo 1.14.12 92 | cairoFT 2.10.0/2.13.1 93 | pango 94 | png 1.6.37 95 | jpeg 9.4 96 | tiff LIBTIFF, Version 4.1.0 97 | tcl 8.6.6 98 | curl 7.54.0 99 | zlib 1.2.11 100 | bzlib 1.0.6, 6-Sept-2010 101 | xz 5.2.4 102 | PCRE 10.34 2019-11-21 103 | ICU 62.1 104 | TRE TRE 0.8.0 R_fixes (BSD) 105 | iconv GNU libiconv 1.11 106 | readline 5.2 107 | BLAS /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRblas.0.dylib 108 | lapack /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib 109 | lapack_version 3.9.0 110 | 111 | ────────────────────────────────────────────────────────────────────────────── 112 | ``` 113 | 114 | ### Python configuration 115 | 116 | ```r 117 | sessioninfo::session_info(info = "python") 118 | ``` 119 | 120 | ``` 121 | ═ Session info ═══════════════════════════════════════════════════════════════ 122 | ─ Python configuration ─────────────────────────────────────────────────────── 123 | python: /Users/gaborcsardi/Library/r-miniconda/envs/r-reticulate/bin/python 124 | libpython: /Users/gaborcsardi/Library/r-miniconda/envs/r-reticulate/lib/libpython3.6m.dylib 125 | pythonhome: /Users/gaborcsardi/Library/r-miniconda/envs/r-reticulate:/Users/gaborcsardi/Library/r-miniconda/envs/r-reticulate 126 | version: 3.6.13 | packaged by conda-forge | (default, Sep 23 2021, 07:55:15) [GCC Clang 11.1.0] 127 | numpy: /Users/gaborcsardi/Library/r-miniconda/envs/r-reticulate/lib/python3.6/site-packages/numpy 128 | numpy_version: 1.19.5 129 | 130 | ────────────────────────────────────────────────────────────────────────────── 131 | ``` 132 | 133 | ### Comparing session information 134 | 135 | `session_diff()` can retrieve the session info from an URL or the clipboard 136 | and compare it to the current session information: 137 | 138 | ```r 139 | sessioninfo::session_diff(new = "https://github.com/r-lib/sessioninfo/issues/6") 140 | ``` 141 | 142 | ```diff 143 | --- local 144 | +++ https://github.com/r-lib/sessioninfo/issues/6 145 | Session info ────────────────────────────────────────────────────────────────── 146 | setting value 147 | version R version 4.1.1 (2021-08-10) 148 | os macOS Mojave 10.14.6 149 | system x86_64, darwin17.0 150 | ui X11 151 | language (EN) 152 | collate en_US.UTF-8 153 | ctype en_US.UTF-8 154 | tz Europe/Madrid 155 | pandoc 2.7.3 @ /usr/local/bin/pandoc 156 | 157 | ─ Packages ───────────────────────────────────────────────────────────────────── 158 | package * version date (UTC) lib source 159 | - asciicast 1.0.0.9000 2021-10-10 [1] local 160 | - cli 3.0.1.9000 2021-10-13 [1] local 161 | + cachem 1.0.6 2021-08-19 [1] CRAN (R 4.1.0) 162 | + callr 3.7.0.9000 2021-10-01 [1] Github (r-lib/callr@ea5c3df) 163 | + cli 3.0.1.9000 2021-10-07 [1] Github (r-lib/cli@e9758aa) 164 | + clipr 0.7.1 2020-10-08 [1] CRAN (R 4.1.0) 165 | + commonmark 1.7 2018-12-01 [1] CRAN (R 4.1.0) 166 | crayon 1.4.1 2021-02-08 [1] CRAN (R 4.1.0) 167 | - curl 4.3.2 2021-06-23 [1] CRAN (R 4.1.0) 168 | desc 1.4.0.9000 2021-10-04 [1] local 169 | + devtools 2.4.2 2021-06-07 [1] CRAN (R 4.1.0) 170 | + digest 0.6.28 2021-09-23 [1] CRAN (R 4.1.0) 171 | ellipsis 0.3.2 2021-04-29 [1] CRAN (R 4.1.0) 172 | fansi 0.5.0 2021-05-25 [1] CRAN (R 4.1.0) 173 | + fastmap 1.1.0 2021-01-25 [1] CRAN (R 4.1.0) 174 | + fs 1.5.0 2020-07-31 [1] CRAN (R 4.1.0) 175 | glue 1.4.2 2021-10-04 [1] local 176 | - jsonlite 1.7.2 2020-12-09 [1] CRAN (R 4.1.0) 177 | + knitr 1.34 2021-09-09 [1] CRAN (R 4.1.0) 178 | lifecycle 1.0.1 2021-09-24 [1] CRAN (R 4.1.0) 179 | magrittr 2.0.1 2020-11-17 [1] CRAN (R 4.1.0) 180 | + memoise 2.0.0 2021-01-26 [1] CRAN (R 4.1.0) 181 | pillar 1.6.3 2021-09-26 [1] CRAN (R 4.1.1) 182 | + pkgbuild 1.2.0 2020-12-15 [1] CRAN (R 4.1.0) 183 | pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.1.0) 184 | pkgload 1.2.2 2021-09-11 [1] CRAN (R 4.1.0) 185 | prettycode 1.1.0 2019-12-16 [1] CRAN (R 4.1.0) 186 | + prettyunits 1.1.1 2020-01-24 [1] CRAN (R 4.1.0) 187 | processx 3.5.2.9000 2021-09-15 [1] local 188 | prompt 1.0.0 2021-03-02 [1] local 189 | ps 1.6.0 2021-02-28 [1] CRAN (R 4.1.0) 190 | + purrr 0.3.4 2020-04-17 [1] CRAN (R 4.1.0) 191 | R6 2.5.1 2021-08-19 [1] CRAN (R 4.1.0) 192 | - Rcpp 1.0.7 2021-07-07 [1] CRAN (R 4.1.0) 193 | - rlang 0.4.11 2021-04-30 [1] CRAN (R 4.1.0) 194 | + remotes 2.4.0 2021-06-02 [1] CRAN (R 4.1.0) 195 | + rlang 0.99.0.9000 2021-10-07 [1] Github (r-lib/rlang@3ba19df) 196 | + roxygen2 7.1.2 2021-10-04 [1] local 197 | rprojroot 2.0.2 2020-11-15 [1] CRAN (R 4.1.0) 198 | - sessioninfo * 1.1.1.9000 2021-10-13 [?] load_all() 199 | - testthat * 3.1.0 2021-10-04 [1] CRAN (R 4.1.0) 200 | - tibble 3.1.5 2021-09-30 [1] CRAN (R 4.1.0) 201 | + rstudioapi 0.13 2020-11-12 [1] CRAN (R 4.1.0) 202 | + sessioninfo * 1.1.1.9000 2021-10-05 [?] load_all() 203 | + stringi 1.7.4 2021-08-25 [1] CRAN (R 4.1.0) 204 | + stringr 1.4.0 2019-02-10 [1] CRAN (R 4.1.0) 205 | + testthat * 3.0.4 2021-07-01 [1] CRAN (R 4.1.0) 206 | + tibble 3.1.4 2021-08-25 [1] CRAN (R 4.1.0) 207 | + usethis 2.0.1 2021-02-10 [1] CRAN (R 4.1.0) 208 | utf8 1.2.2 2021-07-24 [1] CRAN (R 4.1.0) 209 | - uuid 0.1-4 2020-02-26 [1] CRAN (R 4.1.0) 210 | - V8 3.4.2 2021-05-01 [1] CRAN (R 4.1.0) 211 | vctrs 0.3.8 2021-04-29 [1] CRAN (R 4.1.0) 212 | withr 2.4.2 2021-04-18 [1] CRAN (R 4.1.0) 213 | + xfun 0.26 2021-09-14 [1] CRAN (R 4.1.0) 214 | + xml2 1.3.2 2020-04-23 [1] CRAN (R 4.1.0) 215 | 216 | [1] /Users/gaborcsardi/Library/R/x86_64/4.1/library 217 | [2] /Library/Frameworks/R.framework/Versions/4.1/Resources/library 218 | ``` 219 | 220 | ## Code of Conduct 221 | 222 | Please note that the sessioninfo project is released with a 223 | [Contributor Code of Conduct]( 224 | https://sessioninfo.r-lib.org/dev/CODE_OF_CONDUCT.html). By contributing to 225 | this project, you agree to abide by its terms. 226 | 227 | ## License 228 | 229 | GPL-2 230 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://sessioninfo.r-lib.org 2 | 3 | template: 4 | package: tidytemplate 5 | bootstrap: 5 6 | 7 | includes: 8 | in_header: | 9 | 10 | 11 | destination: docs 12 | 13 | development: 14 | mode: auto 15 | 16 | reference: 17 | - title: Session information 18 | contents: 19 | - session_info 20 | 21 | - title: Advanced session information 22 | contents: 23 | - external_info 24 | - package_info 25 | - platform_info 26 | - python_info 27 | 28 | - title: Comparing sessions 29 | contents: 30 | - session_diff 31 | 32 | - title: Utility functions 33 | contents: 34 | - os_name 35 | -------------------------------------------------------------------------------- /air.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/air.toml -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /man/external_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/external-info.R 3 | \name{external_info} 4 | \alias{external_info} 5 | \title{Information about related software} 6 | \usage{ 7 | external_info() 8 | } 9 | \value{ 10 | A list with elements: 11 | \itemize{ 12 | \item \code{cairo}: The cairo version string. 13 | \item \code{libpng}: The png version string. 14 | \item \code{jpeg}: The jpeg version string. 15 | \item \code{tiff}: The tiff library and version string used. 16 | \item \code{tcl}: The tcl version string. 17 | \item \code{curl}: The curl version string. 18 | \item \code{zlib}: The zlib version string. 19 | \item \code{bzlib}: The zlib version string. 20 | \item \code{xz}: The zlib version string. 21 | \item \code{PCRE}: The Perl Compatible Regular Expressions (PCRE) version string. 22 | \item \code{ICU}: The International Components for Unicode (ICU) version string. 23 | \item \code{TRE}: The TRE version string. 24 | \item \code{iconv}: The iconv version string. 25 | \item \code{readline}: The readline version string. 26 | \item \code{BLAS}: The path with the implementation of BLAS in use. 27 | \item \code{LAPACK}: The path with the implementation of LAPACK in use. 28 | } 29 | } 30 | \description{ 31 | Information about related software 32 | } 33 | \details{ 34 | Note that calling this function will attempt to load the tcltk and 35 | grDevices packages. 36 | } 37 | \examples{ 38 | \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 39 | external_info() 40 | \dontshow{\}) # examplesIf} 41 | } 42 | \seealso{ 43 | Similar functions and objects in the base packages: 44 | \code{\link[utils:sessionInfo]{utils::sessionInfo()}}, \link[base:extSoftVersion]{base::extSoftVersion}, \code{\link[tcltk:TclInterface]{tcltk::tclVersion()}} 45 | \link[base:La_library]{base::La_library}, \code{\link[base:La_version]{base::La_version()}}, \code{\link[base:libcurlVersion]{base::libcurlVersion()}}. 46 | } 47 | -------------------------------------------------------------------------------- /man/figures/session-info2.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 37 | 40 | 50 | 58 | 60 | 66 | 68 | 70 | 77 | 78 | 79 | 86 | 93 | 99 | > 104 | library(usethis) 109 | > 114 | sessioninfo::session_info() 119 | 124 | Session 129 | info 134 | ────────────────────────────────────────────────────────────── 139 | setting 144 | value 149 | version 154 | R 159 | version 164 | 4.1.1 169 | (2021-08-10) 174 | os 179 | macOS 184 | Mojave 189 | 10.14.6 194 | system 199 | x86_64, 204 | darwin17.0 209 | ui 214 | X11 219 | language 224 | (EN) 229 | collate 234 | en_US.UTF-8 239 | ctype 244 | en_US.UTF-8 249 | tz 254 | Europe/Madrid 259 | date 264 | 2021-10-13 269 | pandoc 274 | 2.7.3 279 | @ 284 | /usr/local/bin/pandoc 289 | 294 | Packages 299 | ─────────────────────────────────────────────────────────────────── 304 | package 309 | * 314 | version 319 | date 324 | (UTC) 329 | lib 334 | source 339 | cli 344 | 3.0.1.9000 349 | 2021-10-12 354 | [1] 359 | Github 364 | (r-lib/cli@340100c) 369 | fs 374 | 1.5.0 379 | 2020-07-31 384 | [1] 389 | CRAN 394 | (R 399 | 4.1.0) 404 | glue 409 | 1.4.2 414 | 2021-10-04 419 | [1] 424 | local 429 | lifecycle 434 | 1.0.1 439 | 2021-09-24 444 | [1] 449 | CRAN 454 | (R 459 | 4.1.0) 464 | magrittr 469 | 2.0.1 474 | 2020-11-17 479 | [1] 484 | CRAN 489 | (R 494 | 4.1.0) 499 | purrr 504 | 0.3.4 509 | 2020-04-17 514 | [1] 519 | CRAN 524 | (R 529 | 4.1.0) 534 | rlang 539 | 0.4.11 544 | 2021-04-30 549 | [1] 554 | CRAN 559 | (R 564 | 4.1.0) 569 | sessioninfo 574 | 1.1.1.9000 579 | 2021-10-13 584 | [1] 589 | local 594 | usethis 599 | * 604 | 2.0.1 609 | 2021-02-10 614 | [1] 619 | CRAN 624 | (R 629 | 4.1.0) 634 | [1] 639 | /Users/gaborcsardi/Library/R/x86_64/4.1/library 644 | [2] 649 | /Library/Frameworks/R.framework/Versions/4.1/Resources/library 654 | ────────────────────────────────────────────────────────────────────────────── 659 | > 664 | 665 | 666 | 667 | 668 | 669 | 670 | -------------------------------------------------------------------------------- /man/os_name.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/osname.R 3 | \name{os_name} 4 | \alias{os_name} 5 | \title{Human readable name of the current operating system} 6 | \usage{ 7 | os_name() 8 | } 9 | \value{ 10 | A character scalar. 11 | } 12 | \description{ 13 | For example Windows 8.1 instead of Windows version 6.3.9600. 14 | On macOS it includes the code names, on Linux it includes the 15 | distribution names and codenames if appropriate. 16 | } 17 | \details{ 18 | It uses \code{\link[utils:sessionInfo]{utils::sessionInfo()}}, but simplifies its output a bit 19 | on Windows, to make it more concise. 20 | } 21 | -------------------------------------------------------------------------------- /man/package_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/package-info.R 3 | \name{package_info} 4 | \alias{package_info} 5 | \title{Information about the currently loaded packages, or about a chosen set} 6 | \usage{ 7 | package_info( 8 | pkgs = c("loaded", "attached", "installed")[1], 9 | include_base = FALSE, 10 | dependencies = NA 11 | ) 12 | } 13 | \arguments{ 14 | \item{pkgs}{Which packages to show. It may be: 15 | \itemize{ 16 | \item \code{NULL} or \code{"loaded"}: show all loaded packages, 17 | \item \code{"attached"}: show all attached packages, 18 | \item \code{"installed"}: show all installed packages, 19 | \item a character vector of package names. Their (hard) dependencies are 20 | also shown by default, see the \code{dependencies} argument. 21 | }} 22 | 23 | \item{include_base}{Include base packages in summary? By default this is 24 | false since base packages should always match the R version.} 25 | 26 | \item{dependencies}{Whether to include the (recursive) dependencies 27 | as well. See the \code{dependencies} argument of \code{\link[utils:install.packages]{utils::install.packages()}}.} 28 | } 29 | \value{ 30 | A data frame with columns: 31 | \itemize{ 32 | \item \code{package}: package name. 33 | \item \code{ondiskversion}: package version (on the disk, which is sometimes 34 | not the same as the loaded version). 35 | \item \code{loadedversion}: package version. This is the version of the loaded 36 | namespace if \code{pkgs} is \code{NULL}, and it is the version of the package 37 | on disk otherwise. The two of them are almost always the same, 38 | though. 39 | \item \code{path}: path to the package on disk. 40 | \item \code{loadedpath}: the path the package was originally loaded from. 41 | \item \code{attached}: logical, whether the package is attached to the search 42 | path. 43 | \item \code{is_base}: logical, whether the package is a base package. 44 | \item \code{date}: the date the package was installed or built, in UTC. 45 | \item \code{source}: where the package was installed from. E.g. 46 | \code{CRAN}, \code{GitHub}, \code{local} (from the local machine), etc. 47 | \item \code{md5ok}: Whether MD5 hashes for package DLL files match, on Windows. 48 | \code{NA} on other platforms. 49 | \item \code{library}: factor, which package library the package was loaded from. 50 | For loaded packages, this is (the factor representation of) 51 | \code{loadedpath}, for others \code{path}. 52 | } 53 | 54 | See \code{\link[=session_info]{session_info()}} for the description of the \emph{printed} columns 55 | by \code{package_info} (as opposed to the \emph{returned} columns). 56 | } 57 | \description{ 58 | Information about the currently loaded packages, or about a chosen set 59 | } 60 | \examples{ 61 | \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 62 | package_info() 63 | package_info("sessioninfo") 64 | \dontshow{\}) # examplesIf} 65 | } 66 | -------------------------------------------------------------------------------- /man/platform_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/platform-info.R 3 | \name{platform_info} 4 | \alias{platform_info} 5 | \title{Information about the current platform} 6 | \usage{ 7 | platform_info() 8 | } 9 | \value{ 10 | A list with elements: 11 | \itemize{ 12 | \item \code{version}: the R version string. 13 | \item \code{os}: the OS name in human readable format, see \code{\link[=os_name]{os_name()}}. 14 | \item \code{system}: CPU, and machine readable OS name, separated by a comma. 15 | \item \code{ui}: the user interface, e.g. \code{Rgui}, \code{RTerm}, etc. see \code{GUI} 16 | in \link[base:Platform]{base::.Platform}. 17 | \item \code{hostname}: the name of the machine known on the network, see 18 | \code{nodename} in \code{\link[base:Sys.info]{base::Sys.info()}}. For privacy, it is only included 19 | if the \code{sessioninfo.include_hostname} option is set to \code{TRUE}. 20 | \item \code{language}: The current language setting. The \code{LANGUAGE} environment 21 | variable, if set, or \code{(EN)} if unset. 22 | \item \code{collate}: Collation rule, from the current locale. 23 | \item \code{ctype}: Native character encoding, from the current locale. 24 | \item \code{tz}: The current time zone. 25 | \item \code{date}: The current date. 26 | \item \code{rstudio}: RStudio format string, only added in RStudio. 27 | \item \code{pandoc}: pandoc version and path 28 | \item \code{quarto}: quarto version and path 29 | } 30 | } 31 | \description{ 32 | Information about the current platform 33 | } 34 | \examples{ 35 | \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 36 | platform_info() 37 | \dontshow{\}) # examplesIf} 38 | } 39 | \seealso{ 40 | Similar functions and objects in the base packages: 41 | \link[base:Version]{base::R.version.string}, \code{\link[utils:sessionInfo]{utils::sessionInfo()}}, \link[base:Version]{base::version}, 42 | \link[base:Platform]{base::.Platform}, \code{\link[base:locales]{base::Sys.getlocale()}}, \code{\link[base:timezones]{base::Sys.timezone()}}. 43 | } 44 | -------------------------------------------------------------------------------- /man/python_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/python-info.R 3 | \name{python_info} 4 | \alias{python_info} 5 | \title{Python configuration} 6 | \usage{ 7 | python_info() 8 | } 9 | \value{ 10 | Returns a \link[reticulate:py_config]{reticulate::py_config} object, which also has the 11 | \code{python_info} class. It is a named list of values. 12 | 13 | If reticulate is not installed or Python is not configured, 14 | then it return a \code{python_info} object that is a character vector, and 15 | it does not have a \code{py_config} class. 16 | } 17 | \description{ 18 | Python configuration 19 | } 20 | \examples{ 21 | \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 22 | python_info() 23 | session_info(info = "all") 24 | \dontshow{\}) # examplesIf} 25 | } 26 | -------------------------------------------------------------------------------- /man/session_diff.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compare.R 3 | \name{session_diff} 4 | \alias{session_diff} 5 | \title{Compare session information from two sources} 6 | \usage{ 7 | session_diff( 8 | old = "local", 9 | new = "clipboard", 10 | packages = c("diff", "merge"), 11 | ... 12 | ) 13 | } 14 | \arguments{ 15 | \item{old, new}{A \code{session_info} object (the return value of 16 | \code{\link[=session_info]{session_info()}}), or a pointer to \code{\link[=session_info]{session_info()}} output. See details 17 | below.} 18 | 19 | \item{packages}{How to compare the package info for \code{old} and \code{new}: 20 | \itemize{ 21 | \item \code{"diff"}: line diffs, in the style of \code{diff -u} 22 | \item \code{"merge"}: merge the information into one data frame, with one row per 23 | package. Only package \code{version} and \code{source} are compared. 24 | }} 25 | 26 | \item{...}{Passed to any new \code{\link[=session_info]{session_info()}} calls.} 27 | } 28 | \description{ 29 | Compare session information from two sources 30 | } 31 | \details{ 32 | Various way to specify \code{old} and \code{new}: 33 | \itemize{ 34 | \item A \code{session_info} object. 35 | \item \code{"local"} runs \code{\link[=session_info]{session_info()}} in the current 36 | session, and uses its output. 37 | \item \code{"clipboard"} takes the session info from the system clipboard. 38 | If the clipboard contains a URL, it is followed to download the 39 | session info. 40 | \item The URL where you inspect the results for a GitHub Actions job. 41 | Typically has this form: 42 | 43 | \if{html}{\out{
}}\preformatted{https://github.com/OWNER/REPO/actions/runs/RUN_ID/jobs/HTML_ID 44 | }\if{html}{\out{
}} 45 | 46 | Internally, this URL is parsed so we can look up the job id, get the 47 | log file, and extract session info. 48 | \item Any other URL starting with \verb{http://} or \verb{https://}. \code{session_diff()} 49 | searches the HTML (or text) page for the session info header to find the 50 | session info. 51 | } 52 | } 53 | \examples{ 54 | \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 55 | session_diff() 56 | \dontshow{\}) # examplesIf} 57 | } 58 | -------------------------------------------------------------------------------- /man/session_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/session-info.R 3 | \name{session_info} 4 | \alias{session_info} 5 | \title{Print session information} 6 | \usage{ 7 | session_info( 8 | pkgs = c("loaded", "attached", "installed")[1], 9 | include_base = FALSE, 10 | info = c("auto", "all", "platform", "packages", "python", "external"), 11 | dependencies = NA, 12 | to_file = FALSE 13 | ) 14 | } 15 | \arguments{ 16 | \item{pkgs}{Which packages to show. It may be: 17 | \itemize{ 18 | \item \code{NULL} or \code{"loaded"}: show all loaded packages, 19 | \item \code{"attached"}: show all attached packages, 20 | \item \code{"installed"}: show all installed packages, 21 | \item a character vector of package names. Their (hard) dependencies are 22 | also shown by default, see the \code{dependencies} argument. 23 | }} 24 | 25 | \item{include_base}{Include base packages in summary? By default this is 26 | false since base packages should always match the R version.} 27 | 28 | \item{info}{What information to show, it can be \code{"auto"} to choose 29 | automatically, \code{"all"} to show everything, or a character vector 30 | with elements from: 31 | \itemize{ 32 | \item \code{"platform"}: show platform information via \code{\link[=platform_info]{platform_info()}}, 33 | \item \code{"packages"}: show package information via \code{\link[=package_info]{package_info()}}, 34 | \item \code{"python"}: show Python configuration via \code{\link[=python_info]{python_info()}}, 35 | \item \code{"external"}: show information about external software, via 36 | \code{\link[=external_info]{external_info()}}. 37 | }} 38 | 39 | \item{dependencies}{Whether to include the (recursive) dependencies 40 | as well. See the \code{dependencies} argument of \code{\link[utils:install.packages]{utils::install.packages()}}.} 41 | 42 | \item{to_file}{Whether to print the session information to a file. 43 | If \code{TRUE} the name of the file will be \code{session-info.txt}, but 44 | \code{to_file} may also be a string to specify the file name.} 45 | } 46 | \value{ 47 | A \code{session_info} object. 48 | 49 | If \code{to_file} is not \code{FALSE} then it is 50 | returned invisibly. (To print it to both a file and to the screen, 51 | use \code{(session_info(to_file = TRUE))}.) 52 | } 53 | \description{ 54 | This is \code{\link[utils:sessionInfo]{utils::sessionInfo()}} re-written from scratch to both exclude 55 | data that's rarely useful (e.g., the full collate string or base packages 56 | loaded) and include stuff you'd like to know (e.g., where a package was 57 | installed from). 58 | } 59 | \details{ 60 | Columns in the \emph{printed} package list: 61 | \itemize{ 62 | \item \code{package}: package name 63 | \item \code{*}: whether the package is attached to the search path 64 | \item \code{version}: package version. If the version is marked with \verb{(!)} that 65 | means that the loaded and the on-disk version of the package are 66 | different. 67 | \item \code{date}: when the package was built, if this information is available. 68 | This is the \code{Date/Publication} or the \code{Built} field from 69 | \code{DESCRIPTION}. (These are usually added automatically by R.) 70 | Sometimes this data is not available, then it is \code{NA}. 71 | \item \code{source}: where the package was built or installed from, if available. 72 | Examples: \verb{CRAN (R 3.3.2)}, \verb{Github (r-lib/pkgbuild@8aab60b)}, 73 | \code{Bioconductor}, \code{local}. 74 | } 75 | 76 | See \code{\link[=package_info]{package_info()}} for the list of columns in the data frame that 77 | is \emph{returned} (as opposed to \emph{printed}). 78 | } 79 | \examples{ 80 | \dontshow{if (FALSE) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 81 | session_info() 82 | session_info("sessioninfo") 83 | \dontshow{\}) # examplesIf} 84 | } 85 | -------------------------------------------------------------------------------- /man/sessioninfo-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sessioninfo-package.R 3 | \docType{package} 4 | \name{sessioninfo-package} 5 | \alias{sessioninfo} 6 | \alias{sessioninfo-package} 7 | \title{sessioninfo: R Session Information} 8 | \description{ 9 | Query and print information about the current R session. It is similar to 'utils::sessionInfo()', but includes more information about packages, and where they were installed from. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/r-lib/sessioninfo#readme} 15 | \item \url{https://sessioninfo.r-lib.org} 16 | \item Report bugs at \url{https://github.com/r-lib/sessioninfo/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Gábor Csárdi \email{csardi.gabor@gmail.com} 22 | 23 | Authors: 24 | \itemize{ 25 | \item Hadley Wickham 26 | \item Winston Chang 27 | \item Robert Flight 28 | \item Kirill Müller 29 | \item Jim Hester 30 | } 31 | 32 | Other contributors: 33 | \itemize{ 34 | \item R Core team [contributor] 35 | \item Posit Software, PBC [copyright holder, funder] 36 | } 37 | 38 | } 39 | \keyword{internal} 40 | -------------------------------------------------------------------------------- /sessioninfo.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: XeLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(sessioninfo) 3 | 4 | test_check("sessioninfo") 5 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/platform-info.md: -------------------------------------------------------------------------------- 1 | # get_quarto_version 2 | 3 | Code 4 | get_quarto_version() 5 | Output 6 | [1] "NA" 7 | 8 | --- 9 | 10 | Code 11 | get_quarto_version() 12 | Output 13 | [1] "1.3.450 @ /path/to/quarto" 14 | 15 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/utils.md: -------------------------------------------------------------------------------- 1 | # order_by_name 2 | 3 | Code 4 | order_by_name(c("foo", "bar")) 5 | Condition 6 | Error in `order_by_name()`: 7 | ! Cannot order by name, no names 8 | 9 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/MD5: -------------------------------------------------------------------------------- 1 | a429f780423be984b698e4c36d285b33 *DESCRIPTION 2 | bff899621c77de080a3ef27cfb70b33b *INDEX 3 | 22cd8a05255133c305564b516e7a3746 *Meta/Rd.rds 4 | 429724b30f0322e2d56778dd130867d3 *Meta/features.rds 5 | 01a94e36648a1f8302fc451ef692c986 *Meta/hsearch.rds 6 | 9bd30c296e0e705b742b51f44f99b61e *Meta/links.rds 7 | 06019c01d899dd9ac5bcbc1ab93c316c *Meta/nsInfo.rds 8 | 55be8b99dcf16cc28f441c279ea55bc6 *Meta/package.rds 9 | 5156c45818e1683a3d9e3ec41e3843df *Meta/vignette.rds 10 | 96ec3b945479563c93da75299c577a76 *NAMESPACE 11 | 049fdaedb9fff0f07342ca6a6c75e3bc *NEWS.md 12 | ebf0fc819595d631b8bf280c4b049940 *R/fansi 13 | 97dca9d3f744065714257d1d0de6bb63 *R/fansi.rdb 14 | 880e55930e831680e23992e6f03c83f2 *R/fansi.rdx 15 | 5079e586ae2ba970be4944ad0dd123aa *doc/index.html 16 | 7642f25e75f84d5811fefb105694ff48 *doc/sgr-in-rmd.R 17 | 34fe1d9bbffdb860fd7a2fa35a24c327 *doc/sgr-in-rmd.Rmd 18 | 9653f9785b8daba190d1b04e9acd080c *doc/sgr-in-rmd.html 19 | c2d517b6a0191bc228b782c576f3cdb0 *help/AnIndex 20 | c5d03d5e1c296bf829dd6edad6a4e103 *help/aliases.rds 21 | 6ecf56a6d11f78f93e28b849908942ed *help/fansi.rdb 22 | 37b6d5ce67db287a9a8a4b507e9f1dc3 *help/fansi.rdx 23 | 062471f7f5e674f589302364ed41ee13 *help/paths.rds 24 | 0d5b55bbd16d41e19ff10a8ab36aee54 *html/00Index.html 25 | b6763e6916890c631fdc3f2643803b1a *html/R.css 26 | 7b96ab4bf019b0cfed86425634d640e8 *libs/i386/fansi.dll 27 | 24b4d526fc30094c13969216ceb1dd56 *libs/i386/symbols.rds 28 | 6503170d698e5a7916bf2457edc5de8d *libs/x64/fansi.dll 29 | 598b845e7cfc22b2d72fbae5de46a21f *libs/x64/symbols.rds 30 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/biobase.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/biobase.rda -------------------------------------------------------------------------------- /tests/testthat/fixtures/descs.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/descs.rda -------------------------------------------------------------------------------- /tests/testthat/fixtures/devtools-deps.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/devtools-deps.rda -------------------------------------------------------------------------------- /tests/testthat/fixtures/devtools-info-unix.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/devtools-info-unix.rda -------------------------------------------------------------------------------- /tests/testthat/fixtures/devtools-info-windows.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/devtools-info-windows.rda -------------------------------------------------------------------------------- /tests/testthat/fixtures/devtools.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/devtools.rda -------------------------------------------------------------------------------- /tests/testthat/fixtures/fsdfgwetdhsdfhq4yqh_0.0.0.9000.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/fsdfgwetdhsdfhq4yqh_0.0.0.9000.tar.gz -------------------------------------------------------------------------------- /tests/testthat/fixtures/gh.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/gh.html.gz -------------------------------------------------------------------------------- /tests/testthat/fixtures/installed.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/installed.rda -------------------------------------------------------------------------------- /tests/testthat/fixtures/lines1.txt: -------------------------------------------------------------------------------- 1 | - Session info ------------------------------------------ 2 | hash: squid, supervillain: medium skin tone, ewe 3 | 4 | setting value 5 | version R version 4.1.1 (2021-08-10) 6 | os macOS Mojave 10.14.6 7 | system x86_64, darwin17.0 8 | ui X11 9 | language (EN) 10 | collate C 11 | ctype en_US.UTF-8 12 | tz Europe/Madrid 13 | date 2021-10-12 14 | pandoc 2.7.3 @ /usr/local/bin/pandoc 15 | 16 | - Packages ----------------------------------------------- 17 | ! package * version date (UTC) lib source 18 | cli 3.0.1.9000 2021-10-11 [1] local 19 | covr 3.5.1 2020-09-16 [1] CRAN (R 4.1.0) 20 | crayon 1.4.1 2021-02-08 [1] CRAN (R 4.1.0) 21 | desc 1.4.0.9000 2021-10-04 [1] local 22 | diffobj 0.3.4 2021-03-22 [1] CRAN (R 4.1.0) 23 | ellipsis 0.3.2 2021-04-29 [1] CRAN (R 4.1.0) 24 | evaluate 0.14 2019-05-28 [1] CRAN (R 4.1.0) 25 | fansi 0.5.0 2021-05-25 [1] CRAN (R 4.1.0) 26 | glue 1.4.2 2021-10-04 [1] local 27 | lazyeval 0.2.2 2019-03-15 [1] CRAN (R 4.1.0) 28 | lifecycle 1.0.1 2021-09-24 [1] CRAN (R 4.1.0) 29 | magrittr 2.0.1 2020-11-17 [1] CRAN (R 4.1.0) 30 | mockery 0.4.2 2019-09-03 [1] CRAN (R 4.1.0) 31 | pillar 1.6.3 2021-09-26 [1] CRAN (R 4.1.1) 32 | pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.1.0) 33 | pkgload 1.2.2 2021-09-11 [1] CRAN (R 4.1.0) 34 | prettycode 1.1.0 2019-12-16 [1] CRAN (R 4.1.0) 35 | prompt 1.0.0 2021-03-02 [1] local 36 | ps 1.6.0 2021-02-28 [1] CRAN (R 4.1.0) 37 | R6 2.5.1 2021-08-19 [1] CRAN (R 4.1.0) 38 | rematch2 2.1.2 2020-05-01 [1] CRAN (R 4.1.0) 39 | rex 1.2.0 2020-04-21 [1] CRAN (R 4.1.0) 40 | rlang 0.4.11 2021-04-30 [1] CRAN (R 4.1.0) 41 | rprojroot 2.0.2 2020-11-15 [1] CRAN (R 4.1.0) 42 | rstudioapi 0.13 2020-11-12 [1] CRAN (R 4.1.0) 43 | P sessioninfo * 1.1.1.9000 2021-10-12 [?] local 44 | testthat * 3.0.4 2021-07-01 [1] CRAN (R 4.1.0) 45 | testthatlabs 0.0.0.9000 2021-09-15 [1] local 46 | tibble 3.1.4 2021-08-25 [1] CRAN (R 4.1.0) 47 | utf8 1.2.2 2021-07-24 [1] CRAN (R 4.1.0) 48 | vctrs 0.3.8 2021-04-29 [1] CRAN (R 4.1.0) 49 | waldo 0.3.1 2021-09-14 [1] CRAN (R 4.1.0) 50 | withr 2.4.2 2021-04-18 [1] CRAN (R 4.1.0) 51 | 52 | [1] /Users/gaborcsardi/Library/R/x86_64/4.1/library 53 | [2] /Library/Frameworks/R.framework/Versions/4.1/Resources/library 54 | 55 | P -- Loaded and on-disk path mismatch. 56 | 57 | ---------------------------------------------------------- 58 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/lines2.txt: -------------------------------------------------------------------------------- 1 | - Session info ------------------------------------------ 2 | hash: squid, supervillain: medium skin tone, ewe 3 | 4 | setting value 5 | version R version 4.1.1 (2021-08-10) 6 | os macOS Mojave 10.14.6 7 | system x86_64, darwin17.0 8 | ui X11 9 | language (EN) 10 | collate C 11 | ctype en_US.UTF-8 12 | tz Europe/Madrid 13 | date 2021-10-12 14 | pandoc 2.7.3 @ /usr/local/bin/pandoc 15 | 16 | - Packages ----------------------------------------------- 17 | package * version date (UTC) lib source 18 | diffobj 0.3.4 2021-03-22 [1] CRAN (R 4.1.0) 19 | ellipsis 0.3.2 2021-04-29 [1] CRAN (R 4.1.0) 20 | evaluate 0.14 2019-05-28 [1] CRAN (R 4.1.0) 21 | fansi 0.5.0 2021-05-25 [1] CRAN (R 4.1.0) 22 | glue 1.4.2 2021-10-04 [1] local 23 | lazyeval 0.2.2 2019-03-15 [1] CRAN (R 4.1.0) 24 | lifecycle 1.0.1 2021-09-24 [1] CRAN (R 4.1.0) 25 | magrittr 2.0.1 2020-11-17 [1] CRAN (R 4.1.0) 26 | mockery 0.4.2 2019-09-03 [1] CRAN (R 4.1.0) 27 | pillar 1.6.3 2021-09-26 [1] CRAN (R 4.1.1) 28 | pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.1.0) 29 | pkgload 1.2.2 2021-09-11 [1] CRAN (R 4.1.0) 30 | prettycode 1.1.0 2019-12-16 [1] CRAN (R 4.1.0) 31 | prompt 1.0.0 2021-03-02 [1] local 32 | ps 1.6.0 2021-02-28 [1] CRAN (R 4.1.0) 33 | R6 2.5.1 2021-08-19 [1] CRAN (R 4.1.0) 34 | rematch2 2.1.2 2020-05-01 [1] CRAN (R 4.1.0) 35 | rex 1.2.0 2020-04-21 [1] CRAN (R 4.1.0) 36 | rlang 0.4.11 2021-04-30 [1] CRAN (R 4.1.0) 37 | rprojroot 2.0.2 2020-11-15 [1] CRAN (R 4.1.0) 38 | rstudioapi 0.13 2020-11-12 [1] CRAN (R 4.1.0) 39 | sessioninfo * 1.1.1 2021-10-12 [?] local 40 | testthat * 3.0.4 2021-07-01 [1] CRAN (R 4.1.0) 41 | tibble 3.1.4 2021-08-25 [1] CRAN (R 4.1.0) 42 | utf8 1.2.2 2021-07-24 [1] CRAN (R 4.1.0) 43 | vctrs 0.3.8 2021-04-29 [1] CRAN (R 4.1.0) 44 | waldo 0.3.1 2021-09-14 [1] CRAN (R 4.1.0) 45 | withr 2.4.2 2021-04-18 [1] CRAN (R 4.1.0) 46 | 47 | [1] /Users/gaborcsardi/Library/R/x86_64/4.1/library 48 | [2] /Library/Frameworks/R.framework/Versions/4.1/Resources/library 49 | 50 | ---------------------------------------------------------- 51 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/lines3.txt: -------------------------------------------------------------------------------- 1 | ! package * version date (UTC) lib source 2 | cli 3.0.1.9000 2021-10-11 [1] local 3 | covr 3.5.1 2020-09-16 [1] CRAN (R 4.1.0) 4 | crayon 1.4.1 2021-02-08 [1] CRAN (R 4.1.0) 5 | desc 1.4.0.9000 2021-10-04 [1] local 6 | diffobj 0.3.4 2021-03-22 [1] CRAN (R 4.1.0) 7 | ellipsis 0.3.2 2021-04-29 [1] CRAN (R 4.1.0) 8 | fansi 0.5.0 2021-05-25 [1] CRAN (R 4.1.0) 9 | glue 1.4.2 2021-10-04 [1] local 10 | lazyeval 0.2.2 2019-03-15 [1] CRAN (R 4.1.0) 11 | lifecycle 1.0.1 2021-09-24 [1] CRAN (R 4.1.0) 12 | magrittr 2.0.1 2020-11-17 [1] CRAN (R 4.1.0) 13 | pillar 1.6.3 2021-09-26 [1] CRAN (R 4.1.1) 14 | pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.1.0) 15 | pkgload 1.2.2 2021-09-11 [1] CRAN (R 4.1.0) 16 | prettycode 1.1.0 2019-12-16 [1] CRAN (R 4.1.0) 17 | prompt 1.0.0 2021-03-02 [1] local 18 | ps 1.6.0 2021-02-28 [1] CRAN (R 4.1.0) 19 | R6 2.5.1 2021-08-19 [1] CRAN (R 4.1.0) 20 | rematch2 2.1.2 2020-05-01 [1] CRAN (R 4.1.0) 21 | rex 1.2.0 2020-04-21 [1] CRAN (R 4.1.0) 22 | rlang 0.4.11 2021-04-30 [1] CRAN (R 4.1.0) 23 | rprojroot 2.0.2 2020-11-15 [1] CRAN (R 4.1.0) 24 | rstudioapi 0.13 2020-11-12 [1] CRAN (R 4.1.0) 25 | P sessioninfo * 1.1.1.9000 2021-10-07 [?] local 26 | testthat * 3.0.4 2021-07-01 [1] CRAN (R 4.1.0) 27 | testthatlabs 0.0.0.9000 2021-09-15 [1] local 28 | tibble 3.1.4 2021-08-25 [1] CRAN (R 4.1.0) 29 | utf8 1.2.2 2021-07-24 [1] CRAN (R 4.1.0) 30 | vctrs 0.3.8 2021-04-29 [1] CRAN (R 4.1.0) 31 | waldo 0.3.1 2021-09-14 [1] CRAN (R 4.1.0) 32 | withr 2.4.2 2021-04-18 [1] CRAN (R 4.1.0) 33 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/lines4.txt: -------------------------------------------------------------------------------- 1 | - Session info ------------------------------------------------ 2 | hash: envelope, right arrow curving down, thumbs up: light skin tone 3 | 4 | setting value 5 | version R version 4.1.1 (2021-08-10) 6 | os macOS Mojave 10.14.6 7 | system x86_64, darwin17.0 8 | ui X11 9 | language (EN) 10 | collate C 11 | ctype en_US.UTF-8 12 | tz Europe/Madrid 13 | date 2021-10-12 14 | pandoc 2.7.3 @ /usr/local/bin/pandoc 15 | 16 | - Packages ----------------------------------------------------- 17 | ! package * version date (UTC) lib source 18 | cli 3.0.1.9000 2021-10-11 [1] local 19 | covr 3.5.1 2020-09-16 [1] CRAN (R 4.1.0) 20 | crayon 1.4.1 2021-02-08 [1] CRAN (R 4.1.0) 21 | desc 1.4.0.9000 2021-10-04 [1] local 22 | evaluate 0.14 2019-05-28 [1] CRAN (R 4.1.0) 23 | glue 1.4.2 2021-10-04 [1] local 24 | lazyeval 0.2.2 2019-03-15 [1] CRAN (R 4.1.0) 25 | lifecycle 1.0.1 2021-09-24 [1] CRAN (R 4.1.0) 26 | magrittr 2.0.1 2020-11-17 [1] CRAN (R 4.1.0) 27 | pkgload 1.2.2 2021-09-11 [1] CRAN (R 4.1.0) 28 | prettycode 1.1.0 2019-12-16 [1] CRAN (R 4.1.0) 29 | prompt 1.0.0 2021-03-02 [1] local 30 | ps 1.6.0 2021-02-28 [1] CRAN (R 4.1.0) 31 | R6 2.5.1 2021-08-19 [1] CRAN (R 4.1.0) 32 | rex 1.2.0 2020-04-21 [1] CRAN (R 4.1.0) 33 | rlang 0.4.11 2021-04-30 [1] CRAN (R 4.1.0) 34 | rprojroot 2.0.2 2020-11-15 [1] CRAN (R 4.1.0) 35 | rstudioapi 0.13 2020-11-12 [1] CRAN (R 4.1.0) 36 | P sessioninfo * 1.1.1.9000 2021-10-12 [?] local 37 | testthat * 3.0.4 2021-07-01 [1] CRAN (R 4.1.0) 38 | testthatlabs 0.0.0.9000 2021-09-15 [1] local 39 | waldo 0.3.1 2021-09-14 [1] CRAN (R 4.1.0) 40 | withr 2.4.2 2021-04-18 [1] CRAN (R 4.1.0) 41 | 42 | [1] /Users/gaborcsardi/Library/R/x86_64/4.1/library 43 | [2] /Library/Frameworks/R.framework/Versions/4.1/Resources/library 44 | 45 | P -- Loaded and on-disk path mismatch. 46 | 47 | ---------------------------------------------------------------- 48 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/memoise.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/memoise.rda -------------------------------------------------------------------------------- /tests/testthat/fixtures/no-remote-repo.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/no-remote-repo.rda -------------------------------------------------------------------------------- /tests/testthat/fixtures/no-sha.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/sessioninfo/5d07442705c3932afa236263d0730ba251ad84b1/tests/testthat/fixtures/no-sha.rda -------------------------------------------------------------------------------- /tests/testthat/test-dependent-packages.R: -------------------------------------------------------------------------------- 1 | test_that("dependent_packages", { 2 | ins <- readRDS("fixtures/installed.rda") 3 | dep <- readRDS("fixtures/devtools-deps.rda") 4 | alldsc <- readRDS("fixtures/descs.rda") 5 | 6 | local_mocked_bindings(installed.packages = function() ins, .package = "utils") 7 | local_mocked_bindings(pkg_desc = function(x) alldsc[[x]]) 8 | local_mocked_bindings(loadedNamespaces = function() ins) 9 | 10 | local_mocked_bindings(getNamespaceVersion = function(x) alldsc[[x]]$Version) 11 | local_mocked_bindings( 12 | search = function() paste0("package:", dep$package[dep$attached]) 13 | ) 14 | local_mocked_bindings(getNamespaceInfo = function(x, ...) alldsc[[x]]$Version) 15 | 16 | exp <- dep[, setdiff(colnames(dep), c("path", "loadedpath"))] 17 | tec <- dependent_packages("devtools", NA) 18 | tec <- tec[, setdiff(colnames(tec), c("path", "loadedpath"))] 19 | expect_equal(exp, tec) 20 | }) 21 | 22 | test_that("pkg_path_disk", { 23 | p1 <- pkg_path_disk(utils::packageDescription("stats")) 24 | expect_equal( 25 | read.dcf(file.path(p1, "DESCRIPTION"))[, "Package"], 26 | c(Package = "stats") 27 | ) 28 | }) 29 | 30 | test_that("find_deps", { 31 | ins <- readRDS("fixtures/installed.rda") 32 | expect_equal( 33 | sort_ci(find_deps("devtools", ins)), 34 | sort_ci( 35 | c( 36 | "devtools", 37 | "callr", 38 | "httr", 39 | "utils", 40 | "tools", 41 | "methods", 42 | "memoise", 43 | "whisker", 44 | "digest", 45 | "rstudioapi", 46 | "jsonlite", 47 | "stats", 48 | "git2r", 49 | "withr", 50 | "pkgbuild", 51 | "pkgload", 52 | "curl", 53 | "crayon", 54 | "testthat", 55 | "BiocInstaller", 56 | "Rcpp", 57 | "MASS", 58 | "rmarkdown", 59 | "knitr", 60 | "hunspell", 61 | "lintr", 62 | "bitops", 63 | "roxygen2", 64 | "evaluate", 65 | "rversions", 66 | "covr", 67 | "gmailr", 68 | "processx", 69 | "R6", 70 | "assertthat", 71 | "debugme", 72 | "grDevices", 73 | "graphics", 74 | "mime", 75 | "openssl", 76 | "desc", 77 | "rprojroot", 78 | "backports", 79 | "praise", 80 | "magrittr", 81 | "xml2", 82 | "BH", 83 | "yaml", 84 | "htmltools", 85 | "caTools", 86 | "base64enc", 87 | "stringr", 88 | "highr", 89 | "markdown", 90 | "stringi", 91 | "rex", 92 | "codetools", 93 | "stringdist", 94 | "xmlparsedata", 95 | "lazyeval", 96 | "parallel", 97 | "brew", 98 | "commonmark" 99 | ) 100 | ) 101 | ) 102 | 103 | ## An edge case 104 | expect_equal( 105 | find_deps("foobar", top_dep = character(), rec_dep = character()), 106 | "foobar" 107 | ) 108 | }) 109 | 110 | test_that("find_deps", { 111 | ins <- readRDS("fixtures/installed.rda") 112 | expect_equal( 113 | find_deps("devtools", ins, top_dep = FALSE), 114 | character() 115 | ) 116 | }) 117 | 118 | test_that("dep_types", { 119 | skip_on_cran() 120 | withr::local_options(repos = c(CRAN = "https://cloud.r-project.org")) 121 | expect_silent( 122 | tools::package_dependencies("sessioninfo", which = dep_types()) 123 | ) 124 | }) 125 | 126 | test_that("interpret_dependencies", { 127 | skip_on_cran() 128 | withr::local_options(repos = c(CRAN = "https://cloud.r-project.org")) 129 | expect_silent( 130 | tools::package_dependencies( 131 | "sessioninfo", 132 | which = interpret_dependencies(TRUE)[[1]] 133 | ) 134 | ) 135 | expect_silent( 136 | tools::package_dependencies( 137 | "sessioninfo", 138 | which = interpret_dependencies(TRUE)[[2]] 139 | ) 140 | ) 141 | 142 | expect_equal( 143 | interpret_dependencies(FALSE), 144 | list(character(), character()) 145 | ) 146 | 147 | expect_silent( 148 | tools::package_dependencies( 149 | "sessioninfo", 150 | which = interpret_dependencies(NA)[[1]] 151 | ) 152 | ) 153 | expect_silent( 154 | tools::package_dependencies( 155 | "sessioninfo", 156 | which = interpret_dependencies(NA)[[2]] 157 | ) 158 | ) 159 | 160 | expect_equal( 161 | interpret_dependencies("Depends"), 162 | list("Depends", "Depends") 163 | ) 164 | }) 165 | -------------------------------------------------------------------------------- /tests/testthat/test-diff.R: -------------------------------------------------------------------------------- 1 | test_that("session_diff", { 2 | lines1 <- readLines(test_path("fixtures", "lines1.txt")) 3 | lines2 <- readLines(test_path("fixtures", "lines2.txt")) 4 | sd <- session_diff(lines1, lines2) 5 | expect_equal(sd$old$si, lines1) 6 | expect_equal(sd$old$arg, lines1) 7 | expect_equal(sd$old$text, lines1) 8 | expect_equal(sd$new$si, lines2) 9 | expect_equal(sd$new$arg, lines2) 10 | expect_equal(sd$new$text, lines2) 11 | 12 | sd$old$si <- sd$old$arg <- sd$old$text <- NULL 13 | sd$new$si <- sd$new$arg <- sd$new$text <- NULL 14 | expect_snapshot(class(sd)) 15 | expect_snapshot(unclass(sd)) 16 | }) 17 | 18 | test_that("format.session_diff", { 19 | # tested via print 20 | expect_true(TRUE) 21 | }) 22 | 23 | test_that("print.session_diff", { 24 | lines1 <- readLines(test_path("fixtures", "lines1.txt")) 25 | lines2 <- readLines(test_path("fixtures", "lines2.txt")) 26 | expect_snapshot(print(session_diff(lines1, lines2))) 27 | }) 28 | 29 | test_that("get_session_info 1", { 30 | arg <- NULL 31 | abort <- function(...) stop("test failure") 32 | local_mocked_bindings(get_session_info_local = function() abort) 33 | local_mocked_bindings(get_session_info_clipboard = function() abort) 34 | local_mocked_bindings(get_session_info_url = function() abort) 35 | local_mocked_bindings(get_session_info_literal = function() abort) 36 | 37 | local_mocked_bindings( 38 | get_session_info_local = function(...) arg <<- list(...) 39 | ) 40 | get_session_info("local", pkgs = "foo") 41 | expect_equal(arg, list(pkgs = "foo")) 42 | 43 | arg <- NULL 44 | local_mocked_bindings(get_session_info_local = function() abort) 45 | local_mocked_bindings( 46 | get_session_info_clipboard = function(...) arg <<- list(...) 47 | ) 48 | get_session_info("clipboard", pkgs = "foo") 49 | expect_equal(arg, list()) 50 | 51 | arg <- NULL 52 | local_mocked_bindings(get_session_info_clipboard = function() abort) 53 | local_mocked_bindings(get_session_info_url = function(...) arg <<- list(...)) 54 | get_session_info("https://acme.com", pkgs = "foo") 55 | expect_equal(arg, list("https://acme.com")) 56 | 57 | arg <- NULL 58 | local_mocked_bindings(get_session_info_url = function() abort) 59 | local_mocked_bindings( 60 | get_session_info_literal = function(...) arg <<- list(...) 61 | ) 62 | get_session_info(c("foo", "bar"), pkgs = "foo") 63 | expect_equal(arg, list(c("foo", "bar"))) 64 | }) 65 | 66 | test_that("get_session_info_local", { 67 | expect_equal( 68 | get_session_info_local()$text, 69 | format(session_info()) 70 | ) 71 | }) 72 | 73 | test_that("get_session_info_clipboard", { 74 | lines <- readLines(test_path("fixtures", "lines1.txt")) 75 | local_mocked_bindings(clipboard_read = function() lines) 76 | clp <- get_session_info_clipboard() 77 | expect_equal(clp$arg, "") 78 | expect_equal( 79 | clp$text, 80 | get_session_info_literal(lines)$text 81 | ) 82 | 83 | local_mocked_bindings(clipboard_read = function() "clipboard") 84 | expect_equal(get_session_info_clipboard()$text, "clipboard") 85 | }) 86 | 87 | test_that("get_session_info_url", { 88 | html <- readLines( 89 | gz <- gzfile(test_path("fixtures", "gh.html.gz")), 90 | encoding = "UTF-8" 91 | ) 92 | close(gz) 93 | 94 | local_mocked_bindings( 95 | download.file = function(url, destfile, ...) writeLines(html, destfile), 96 | .package = "utils" 97 | ) 98 | 99 | # Needs Internet connection 100 | skip_on_cran() 101 | url <- "https://github.com/r-lib/sessioninfo/issues/6" 102 | expect_equal( 103 | get_session_info_url(url), 104 | find_session_info_in_html(url, html) 105 | ) 106 | }) 107 | 108 | test_that("find_session_info_in_html", { 109 | # We skip this on old R, because it does not calculate the width 110 | # of the emojis properly, and that messes up the output of the 111 | # character vector of lines. We also cannot compare the UTF-8 text 112 | # on Windows. 113 | if (getRversion() < "4.0") skip("Needs R 4.0 at least") 114 | skip_on_os("windows") 115 | 116 | html <- readLines( 117 | gz <- gzfile(test_path("fixtures", "gh.html.gz")), 118 | encoding = "UTF-8" 119 | ) 120 | close(gz) 121 | 122 | url <- "https://github.com/r-lib/sessioninfo/issues/6" 123 | expect_snapshot( 124 | find_session_info_in_html(url, html)$text 125 | ) 126 | 127 | url2 <- paste0(url, "#issuecomment-937782988") 128 | expect_snapshot( 129 | find_session_info_in_html(url2, html)$text 130 | ) 131 | 132 | url3 <- paste0(url, "#dfgdfgdfgdfgdfgdfg") 133 | expect_equal( 134 | find_session_info_in_html(url, html)$text, 135 | find_session_info_in_html(url3, html)$text 136 | ) 137 | 138 | html <- html[ 139 | !grepl("^(#>)?[ ]*\\[[0-9]\\] ", html) & 140 | !grepl("^(#>)?[ ]*[-\u2500]+$", html) 141 | ] 142 | expect_snapshot(error = TRUE, find_session_info_in_html(url, html)) 143 | 144 | re_start <- "[-=\u2500\u2550][ ]Session info[ ]" 145 | html <- html[!grepl(re_start, html)] 146 | expect_snapshot(error = TRUE, find_session_info_in_html(url, html)) 147 | }) 148 | 149 | test_that("parse_url", { 150 | expect_snapshot(parse_url( 151 | "https://github.com/r-lib/sessioninfo/issues/6" 152 | )) 153 | expect_snapshot(parse_url( 154 | "https://github.com/r-lib/sessioninfo/issues/6#issuecomment-937772467" 155 | )) 156 | }) 157 | 158 | test_that("get_session_info_literal", { 159 | si <- session_info() 160 | expect_equal( 161 | get_session_info_literal(si), 162 | list(arg = si, si = si, text = format(si)) 163 | ) 164 | 165 | lines <- format(si) 166 | expect_equal( 167 | get_session_info_literal(lines), 168 | list(arg = lines, si = lines, text = lines) 169 | ) 170 | 171 | col <- paste0("\033[31m", lines, "\033[39m") 172 | expect_equal( 173 | get_session_info_literal(col), 174 | list(arg = col, si = col, text = lines) 175 | ) 176 | 177 | str <- paste0(lines, collapse = "\n") 178 | expect_equal( 179 | get_session_info_literal(str), 180 | list(arg = str, si = str, text = lines) 181 | ) 182 | 183 | ktr <- paste0("#> ", lines) 184 | expect_equal( 185 | get_session_info_literal(ktr), 186 | list(arg = ktr, si = ktr, text = lines) 187 | ) 188 | 189 | ktr2 <- paste0("#> ", lines) 190 | expect_equal( 191 | get_session_info_literal(ktr2), 192 | list(arg = ktr2, si = ktr2, text = lines) 193 | ) 194 | 195 | expect_snapshot( 196 | error = TRUE, 197 | get_session_info_literal(structure(1, class = "foo")) 198 | ) 199 | }) 200 | 201 | test_that("strsplitx", { 202 | expect_equal(strsplitx("", "\n"), list("")) 203 | }) 204 | 205 | test_that("check_session_info", { 206 | expect_warning( 207 | check_session_info("foo"), 208 | "This does not look like" 209 | ) 210 | expect_silent(check_session_info("- Session info")) 211 | expect_silent(check_session_info("= Session info")) 212 | expect_silent(check_session_info("\u2500 Session info")) 213 | expect_silent(check_session_info("\u2550 Session info")) 214 | }) 215 | 216 | test_that("beginning", { 217 | expect_snapshot(beginning("foo\nbar\nfoobar\nnotthis")) 218 | expect_snapshot(beginning(c("foo", "bar", "foobar", "notthis"))) 219 | expect_snapshot(beginning(strrep("123456789 ", 20))) 220 | }) 221 | 222 | test_that("session_diff_text", { 223 | x <- c("", " ", " date 2020-01-01", "foo", "bar", " ", "") 224 | y <- c(" date 2010-01-01", "foo", "baz", "") 225 | expect_snapshot(print(session_diff_text(x, y))) 226 | 227 | # if matching packages fails, we still have meaningful output 228 | local_mocked_bindings(expand_diff_text = function(...) stop()) 229 | expect_snapshot(print(session_diff_text(x, y))) 230 | }) 231 | 232 | test_that("diff_drop_empty", { 233 | cases <- list( 234 | list(character(), character()), 235 | list("foo", "foo"), 236 | list(c("", "foo"), "foo"), 237 | list(c(" ", "", "foo"), "foo"), 238 | list(c("", "foo", ""), "foo"), 239 | list(c(" ", "", "foo", "", " "), "foo"), 240 | list(c("foo", "", " "), "foo") 241 | ) 242 | 243 | for (c in cases) { 244 | expect_equal(diff_drop_empty(c[[1]]), c[[2]], info = c[[1]]) 245 | } 246 | }) 247 | 248 | test_that("diff_no_date", { 249 | x <- c("foo", "date 2000-01-01", "date 2000-01-01") 250 | expect_equal(diff_no_date(x), x[-2]) 251 | x2 <- c("foo", " date 2000-01-01", "date 2000-01-01") 252 | expect_equal(diff_no_date(x2), x2[-2]) 253 | x3 <- c("foo", "bar") 254 | expect_equal(diff_no_date(x3), x3) 255 | }) 256 | 257 | test_that("diff_min_line", { 258 | x <- c("= 3456 ============", "- 345678 -----------", strrep("x", 1000)) 259 | x2 <- gsub("=", "\u2550", gsub("-", "\u2500", x)) 260 | expect_equal(diff_min_line(x), 19) 261 | expect_equal(diff_min_line(x2), 19) 262 | expect_equal(diff_min_line(strrep("-", 100)), 80) 263 | }) 264 | 265 | test_that("diff_fix_lines", { 266 | x <- c("= 3456 ============", "foo", "- 345678 ----------", "bar") 267 | x2 <- gsub("=", "\u2550", gsub("-", "\u2500", x)) 268 | exp <- c("= 3456 ===", "foo", "- 345678 -", "bar") 269 | exp2 <- gsub("=", "\u2550", gsub("-", "\u2500", exp)) 270 | expect_equal(diff_fix_lines(x, 10), exp) 271 | expect_equal(diff_fix_lines(x2, 10), exp) 272 | 273 | withr::local_options(cli.unicode = TRUE) 274 | expect_equal(diff_fix_lines(x, 10), exp2) 275 | expect_equal(diff_fix_lines(x2, 10), exp2) 276 | }) 277 | 278 | test_that("expand_diff_text", { 279 | lines1 <- readLines(test_path("fixtures", "lines1.txt")) 280 | lines2 <- readLines(test_path("fixtures", "lines2.txt")) 281 | xp1 <- expand_diff_text(lines1, lines2) 282 | expect_snapshot(xp1) 283 | 284 | xp2 <- expand_diff_text(lines2, lines1) 285 | expect_equal(xp1$old, xp2$new) 286 | expect_equal(xp1$new, xp2$old) 287 | 288 | expect_equal( 289 | expand_diff_text(lines1, "foobar"), 290 | list(old = lines1, new = "foobar") 291 | ) 292 | }) 293 | 294 | test_that("insert_instead", { 295 | cases <- list( 296 | list(1:10, 1, 1, 11:15, c(11:15, 2:10)), 297 | list(1:10, 1, 3, 11:15, c(11:15, 4:10)), 298 | list(1:10, 2, 2, 11:15, c(1, 11:15, 3:10)), 299 | list(1:10, 2, 5, 11:15, c(1, 11:15, 6:10)), 300 | list(1:10, 5, 10, 11:15, c(1:4, 11:15)), 301 | list(1:10, 10, 10, 11:15, c(1:9, 11:15)), 302 | list(1:10, 1, 10, 11:15, c(11:15)), 303 | list(1, 1, 1, 11:15, c(11:15)), 304 | list(1:10, 0, 0, 11:15, c(11:15, 1:10)), 305 | list(1:10, 11, 11, 11:15, c(1:10, 11:15)), 306 | list(1:10, 2, 1, 11:15, c(1, 11:15, 2:10)) 307 | ) 308 | 309 | for (i in seq_along(cases)) { 310 | c <- cases[[i]] 311 | expect_equal( 312 | insert_instead(c[[1]], c[[2]], c[[3]], c[[4]]), 313 | c[[5]], 314 | info = i 315 | ) 316 | } 317 | }) 318 | 319 | test_that("parse_pkgs", { 320 | lines <- readLines(test_path("fixtures", "lines4.txt")) 321 | pkgs <- parse_pkgs(lines) 322 | expect_equal(pkgs$begin, 17) 323 | expect_equal(pkgs$end, 40) 324 | expect_snapshot(names(pkgs$pkgs)) 325 | expect_snapshot(pkgs$pkgs[["!"]]) 326 | expect_snapshot(pkgs$pkgs$package) 327 | expect_snapshot(pkgs$pkgs) 328 | 329 | pkgs2 <- parse_pkgs(lines[1:40]) 330 | expect_equal(pkgs, pkgs2) 331 | 332 | expect_null(parse_pkgs("foobar")) 333 | }) 334 | 335 | test_that("parse_pkgs_section", { 336 | lines <- readLines(test_path("fixtures", "lines3.txt")) 337 | pkgs <- parse_pkgs_section(lines) 338 | expect_snapshot(names(pkgs)) 339 | expect_snapshot(pkgs[["!"]]) 340 | expect_snapshot(pkgs$package) 341 | expect_snapshot(pkgs) 342 | 343 | lines2 <- c( 344 | " package * version date (UTC) lib source", 345 | " cli 3.0.1.9000 2021-10-11 [1] local", 346 | " crayon 1.4.1 2021-02-08 [1] CRAN (R 4.1.0)", 347 | " prettycode 1.1.0 2019-12-16 [1] CRAN (R 4.1.0)", 348 | " prompt 1.0.0 2021-03-02 [1] local", 349 | " ps 1.6.0 2021-02-28 [1] CRAN (R 4.1.0)", 350 | " sessioninfo 1.1.1.9000 2021-10-12 [1] local", 351 | " withr 2.4.2 2021-04-18 [1] CRAN (R 4.1.0)" 352 | ) 353 | pkgs2 <- parse_pkgs_section(lines2) 354 | expect_snapshot(names(pkgs2)) 355 | expect_snapshot(pkgs2[["!"]]) 356 | expect_snapshot(pkgs2$package) 357 | expect_snapshot(pkgs2) 358 | }) 359 | 360 | test_that("find_word_lengths", { 361 | cases <- list( 362 | list("", numeric()), 363 | list(" ", numeric()), 364 | list(" ", numeric()), 365 | list("x", 2), 366 | list(" x", 3), 367 | list(" x ", 4), 368 | list("foo bar", c(4, 4)), 369 | list("foo bar", c(7, 4)), 370 | list(" foo", 5), 371 | list(" foo bar", c(8, 4)) 372 | ) 373 | for (c in cases) { 374 | expect_equal(find_word_lengths(c[[1]]), c[[2]], info = c[[1]]) 375 | } 376 | }) 377 | 378 | test_that("get_symbol_name", { 379 | expect_equal(get_symbol_name(as.symbol("x")), "x") 380 | expect_equal(get_symbol_name("x"), "x") 381 | expect_null(get_symbol_name(call("foo", "bar"))) 382 | }) 383 | -------------------------------------------------------------------------------- /tests/testthat/test-loaded-packages.R: -------------------------------------------------------------------------------- 1 | test_that("loaded_packages", { 2 | lp <- loaded_packages() 3 | expect_equal(sort_ci(lp$package), sort_ci(loadedNamespaces())) 4 | expect_identical( 5 | gsub("-", ".", lp$loadedversion), 6 | unlist(lapply(lp$package, function(x) as.character(packageVersion(x)))) 7 | ) 8 | expect_true(all(file.exists(lp$path))) 9 | expect_equal( 10 | paste0("package:", lp$package) %in% search(), 11 | lp$attached 12 | ) 13 | }) 14 | -------------------------------------------------------------------------------- /tests/testthat/test-os-name.R: -------------------------------------------------------------------------------- 1 | test_that("unknown os name", { 2 | local_mocked_bindings( 3 | sessionInfo = function(...) list(running = NULL), 4 | .package = "utils" 5 | ) 6 | expect_equal(os_name(), NA_character_) 7 | }) 8 | -------------------------------------------------------------------------------- /tests/testthat/test-package-info.R: -------------------------------------------------------------------------------- 1 | test_that("package_info, loaded", { 2 | descs <- readRDS("fixtures/devtools-deps.rda") 3 | alldsc <- readRDS("fixtures/descs.rda") 4 | exp <- readRDS(paste0("fixtures/devtools-info-", .Platform$OS.type, ".rda")) 5 | 6 | local_mocked_bindings(loaded_packages = function() descs) 7 | local_mocked_bindings(pkg_desc = function(x, ...) alldsc[[x]]) 8 | local_mocked_bindings(pkg_lib_paths = function() levels(exp$library)) 9 | 10 | pi <- package_info() 11 | expect_identical(pi, exp) 12 | }) 13 | 14 | test_that("package_info, dependent", { 15 | descs <- readRDS("fixtures/devtools-deps.rda") 16 | alldsc <- readRDS("fixtures/descs.rda") 17 | exp <- readRDS(paste0("fixtures/devtools-info-", .Platform$OS.type, ".rda")) 18 | 19 | local_mocked_bindings(dependent_packages = function(...) descs) 20 | local_mocked_bindings(pkg_desc = function(x, ...) alldsc[[x]]) 21 | local_mocked_bindings(pkg_lib_paths = function() levels(exp$library)) 22 | 23 | pi <- package_info("devtools") 24 | expect_identical(pi, exp) 25 | }) 26 | 27 | test_that("pkg_date", { 28 | crayon <- readRDS("fixtures/descs.rda")$crayon 29 | expect_equal(pkg_date(crayon), "2016-06-28") 30 | 31 | crayon$`Date/Publication` <- NULL 32 | expect_equal(pkg_date(crayon), "2016-06-29") 33 | 34 | crayon$`Built` <- NULL 35 | expect_identical(pkg_date(crayon), NA_character_) 36 | }) 37 | 38 | test_that("pkg_source", { 39 | descs <- readRDS("fixtures/descs.rda") 40 | 41 | expect_identical( 42 | pkg_source(descs$debugme), 43 | "Github (gaborcsardi/debugme@df8295a75f8e26bec90386beb1c82b529427029f)" 44 | ) 45 | 46 | expect_identical(pkg_source(descs$curl), "CRAN (R 3.3.3)") 47 | 48 | biobase <- readRDS("fixtures/biobase.rda") 49 | expect_identical(pkg_source(biobase), "Bioconductor") 50 | 51 | memoise <- readRDS("fixtures/memoise.rda") 52 | expect_identical(pkg_source(memoise), "CRAN (R 3.3.3)") 53 | }) 54 | 55 | test_that("pkg_source edge case, remote repo, but no RemoteSha", { 56 | desc <- readRDS("fixtures/no-sha.rda") 57 | expect_identical(pkg_source(desc), "github (r-pkgs/desc)") 58 | }) 59 | 60 | test_that("pkg_source edge case, remote repo, no RemoteRepo", { 61 | desc <- readRDS("fixtures/no-remote-repo.rda") 62 | expect_identical(pkg_source(desc), "github") 63 | }) 64 | 65 | test_that("pkg_md5_stored", { 66 | md5 <- pkg_md5_stored("fixtures") 67 | exp <- c( 68 | `libs/i386/fansi.dll` = "7b96ab4bf019b0cfed86425634d640e8", 69 | `libs/x64/fansi.dll` = "6503170d698e5a7916bf2457edc5de8d" 70 | ) 71 | expect_identical(md5, exp) 72 | }) 73 | 74 | test_that("pkg_md5_disk", { 75 | dir.create(tmp <- tempfile()) 76 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 77 | dir.create(file.path(tmp, "libs")) 78 | dir.create(file.path(tmp, "libs", "i386")) 79 | dir.create(file.path(tmp, "libs", "x64")) 80 | writeBin(charToRaw("foo\n"), con = file.path(tmp, "libs", "i386", "foo.dll")) 81 | writeBin(charToRaw("bar\n"), con = file.path(tmp, "libs", "x64", "foo.dll")) 82 | md5 <- pkg_md5_disk(tmp) 83 | 84 | exp <- c( 85 | `libs/i386/foo.dll` = "d3b07384d113edec49eaa6238ad5ff00", 86 | `libs/x64/foo.dll` = "c157a79031e1c40f85931829bc5fc552" 87 | ) 88 | expect_identical(md5, exp) 89 | }) 90 | 91 | test_that("print.packages_info", { 92 | info <- readRDS(paste0("fixtures/devtools-info-", .Platform$OS.type, ".rda")) 93 | expect_output( 94 | print(info), 95 | "package * version date (UTC) lib source", 96 | fixed = TRUE 97 | ) 98 | }) 99 | 100 | test_that("print.packages_info ignores max.print", { 101 | info <- readRDS(paste0("fixtures/devtools-info-", .Platform$OS.type, ".rda")) 102 | withr::local_options(list(max.print = 1)) 103 | out <- capture_output(print(info)) 104 | out <- tail(strsplit(out, split = "\r?\n")[[1]], -1) 105 | expect_length(out, nrow(info) + 3) 106 | }) 107 | -------------------------------------------------------------------------------- /tests/testthat/test-platform-info.R: -------------------------------------------------------------------------------- 1 | test_that("platform_info", { 2 | withr::local_options(sessioninfo.include_hostname = FALSE) 3 | pi <- platform_info() 4 | nms <- c( 5 | "version", 6 | "os", 7 | "system", 8 | "hostname", 9 | "ui", 10 | "language", 11 | "collate", 12 | "ctype", 13 | "tz", 14 | "date", 15 | "pandoc", 16 | "quarto" 17 | ) 18 | expect_equal(names(pi), setdiff(nms, "hostname")) 19 | 20 | withr::local_options(sessioninfo.include_hostname = TRUE) 21 | pi <- platform_info() 22 | expect_equal(names(pi), nms) 23 | 24 | ## This can be a variety of strings, e.g. "R Under development" 25 | expect_match(pi$version, "R ") 26 | expect_true(is_string(pi$os)) 27 | expect_true(is_string(pi$system) && grepl(",", pi$system)) 28 | expect_true(is_string(pi$ui)) 29 | expect_true(is_string(pi$language)) 30 | expect_true(is_string(pi$tz) || identical(pi$tz, NA_character_)) 31 | expect_true(is_string(pi$date)) 32 | expect_equal(pi$date, as.character(as.Date(pi$date))) 33 | }) 34 | 35 | test_that("print.platform_info", { 36 | expect_output(print(platform_info()), "setting[ ]+value") 37 | }) 38 | 39 | test_that("print.platform_info ignores max.print", { 40 | pi <- platform_info() 41 | withr::local_options(list(max.print = 1)) 42 | out <- capture_output(print(pi)) 43 | out <- tail(strsplit(out, split = "\r?\n")[[1]], -1) 44 | expect_length(out, length(pi)) 45 | }) 46 | 47 | test_that("get_quarto_version", { 48 | local_mocked_bindings(Sys.which = function(...) "") 49 | expect_snapshot(get_quarto_version()) 50 | 51 | local_mocked_bindings(Sys.which = function(...) "/path/to/quarto") 52 | local_mocked_bindings(system2 = function(...) "1.3.450") 53 | expect_snapshot(get_quarto_version()) 54 | }) 55 | -------------------------------------------------------------------------------- /tests/testthat/test-printing.R: -------------------------------------------------------------------------------- 1 | test_that("rule", { 2 | expect_match( 3 | withr::with_options(list(width = 20), rule("test", pad = "-")), 4 | "- test -----------", 5 | fixed = TRUE 6 | ) 7 | }) 8 | -------------------------------------------------------------------------------- /tests/testthat/test-session-info.R: -------------------------------------------------------------------------------- 1 | test_that("session_info", { 2 | info <- readRDS(paste0("fixtures/devtools-info-", .Platform$OS.type, ".rda")) 3 | local_mocked_bindings(package_info = function(...) pi) 4 | 5 | si <- session_info() 6 | expect_equal(si$platform, platform_info()) 7 | expect_equal(si$packages, pi) 8 | }) 9 | 10 | test_that("print.session_info", { 11 | si <- session_info() 12 | expect_output(print(si), "setting[ ]+value") 13 | expect_output( 14 | print(si), 15 | "package[ ]+\\* version[ ]+date[ ][(]UTC[)][ ]+lib[ ]+source" 16 | ) 17 | }) 18 | 19 | test_that("`info` can include one or multiple values", { 20 | choices <- c("platform", "packages", "python", "external") 21 | 22 | # including "all" results in all info being selected 23 | expect_named( 24 | session_info(info = "all"), 25 | choices, 26 | ignore.order = TRUE 27 | ) 28 | # even when other items are included 29 | expect_named( 30 | session_info(info = c("all", "platform")), 31 | choices, 32 | ignore.order = TRUE 33 | ) 34 | 35 | # including "auto" (and not "all") results in auto info being included 36 | expect_gte( 37 | length(names(session_info(info = "auto"))), 38 | 2 39 | ) 40 | # even when other items are included 41 | expect_gte( 42 | length(names(session_info(info = c("auto", "platform")))), 43 | 2 44 | ) 45 | 46 | # with neither "all" or "auto", valid items are included 47 | expect_named( 48 | session_info(info = c("platform", "python", "external", "nonvalid-info")), 49 | c("platform", "python", "external"), 50 | ignore.order = TRUE 51 | ) 52 | }) 53 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | test_that("sort_ci", { 2 | expect_equal( 3 | sort_ci(c("ant", "\u00a0nt", "bottom")), 4 | c("ant", "bottom", "\u00a0nt") 5 | ) 6 | }) 7 | 8 | test_that("order_by_name", { 9 | expect_equal(order_by_name(list()), list()) 10 | 11 | expect_snapshot(error = TRUE, order_by_name(c("foo", "bar"))) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/testthat/test-warnings.R: -------------------------------------------------------------------------------- 1 | test_that("broken dll", { 2 | if (.Platform$OS.type != "windows") { 3 | expect_true(TRUE) 4 | return() 5 | } 6 | skip_on_cran() 7 | 8 | ## To check this, we need a package with a dll. 9 | ## We need to install it into some temporary library, and then mess 10 | ## up the MD5 sum. We can use testhat itself to do this, as long as it 11 | ## has compiled code. We also run package_info() in another process, 12 | ## to avoid changing the current one. 13 | 14 | dir.create(lib <- tempfile()) 15 | on.exit(unlink(lib, recursive = TRUE), add = TRUE) 16 | file.copy(system.file(package = "testthat"), lib, recursive = TRUE) 17 | 18 | md5file <- file.path(lib, "testthat", "MD5") 19 | if (!file.exists(md5file)) skip("Cannot test broken DLLs") 20 | l <- readLines(md5file) 21 | dllline <- grep("testthat.dll", l)[1] 22 | substr(l[dllline], 2, 5) <- "xxxx" 23 | writeLines(l, md5file) 24 | 25 | pi <- callr::r( 26 | function(lib) { 27 | library(testthat, lib.loc = lib) 28 | sessioninfo::package_info() 29 | }, 30 | args = list(lib = lib), 31 | libpath = c(lib, .libPaths()), 32 | timeout = 10 33 | ) 34 | 35 | expect_false(pi$md5ok[pi$package == "testthat"]) 36 | expect_output(print(pi), "DLL MD5 mismatch, broken installation") 37 | }) 38 | 39 | test_that("loaded & on-disk path mismatch", { 40 | skip_on_cran() 41 | 42 | ## Copy testthat to another library, load it from there, and then 43 | ## remove that lib from the library path. 44 | 45 | dir.create(lib <- tempfile()) 46 | on.exit(unlink(lib, recursive = TRUE), add = TRUE) 47 | file.copy(system.file(package = "testthat"), lib, recursive = TRUE) 48 | 49 | pi <- callr::r( 50 | function(lib) { 51 | library(testthat, lib.loc = lib) 52 | .libPaths(.libPaths()[-1]) 53 | sessioninfo::package_info() 54 | }, 55 | args = list(lib = lib), 56 | libpath = c(lib, .libPaths()), 57 | timeout = 10 58 | ) 59 | 60 | wh <- which(pi$package == "testthat") 61 | expect_false(pi$path[wh] == pi$loadedpath[wh]) 62 | expect_output(print(pi), "Loaded and on-disk path mismatch") 63 | }) 64 | 65 | test_that("loaded & on-disk version mismatch", { 66 | skip_on_cran() 67 | 68 | ## Copy testthat to another library and change the version, after 69 | ## loading it. 70 | 71 | dir.create(lib <- tempfile()) 72 | on.exit(unlink(lib, recursive = TRUE), add = TRUE) 73 | file.copy(system.file(package = "testthat"), lib, recursive = TRUE) 74 | 75 | pi <- callr::r( 76 | function(lib) { 77 | library(testthat, lib.loc = lib) 78 | desc_file <- file.path(lib, "testthat", "DESCRIPTION") 79 | desc <- readLines(desc_file) 80 | desc <- sub("^Version:.*$", "Version: 0.0.1", desc) 81 | writeLines(desc, desc_file) 82 | 83 | binary_desc <- file.path(lib, "testthat", "Meta", "package.rds") 84 | if (file.exists(binary_desc)) { 85 | pkg_desc <- readRDS(binary_desc) 86 | desc <- as.list(pkg_desc$DESCRIPTION) 87 | desc$Version <- "0.0.1" 88 | pkg_desc$DESCRIPTION <- stats::setNames(as.character(desc), names(desc)) 89 | saveRDS(pkg_desc, binary_desc) 90 | } 91 | sessioninfo::package_info() 92 | }, 93 | args = list(lib = lib), 94 | libpath = c(lib, .libPaths()), 95 | timeout = 10 96 | ) 97 | 98 | wh <- which(pi$package == "testthat") 99 | expect_false(pi$ondiskversion[wh] == pi$loadedversion[wh]) 100 | expect_output(print(pi), "Loaded and on-disk version mismatch") 101 | 102 | # testthat is attached 103 | expect_output(print(pi), "testthat * ") 104 | expect_output(print(pi), "Packages attached to the search path") 105 | }) 106 | 107 | test_that("deleted package", { 108 | skip_on_cran() 109 | 110 | foo <- "fsdfgwetdhsdfhq4yqh" 111 | 112 | dir.create(lib <- tempfile()) 113 | on.exit(unlink(lib, recursive = TRUE), add = TRUE) 114 | pkgfile <- normalizePath(paste0("fixtures/", foo, "_0.0.0.9000.tar.gz")) 115 | install.packages( 116 | pkgfile, 117 | lib = lib, 118 | repos = NULL, 119 | type = "source", 120 | quiet = TRUE 121 | ) 122 | 123 | pis <- callr::r( 124 | function(lib, foo) { 125 | library(foo, character.only = TRUE, lib.loc = lib) 126 | unlink(file.path(lib, foo), recursive = TRUE) 127 | list( 128 | sessioninfo::session_info(), 129 | sessioninfo::session_info(pkgs = foo) 130 | ) 131 | }, 132 | args = list(lib = lib, foo = foo), 133 | libpath = c(lib, .libPaths()), 134 | timeout = 10, 135 | error = "stack" 136 | ) 137 | 138 | expect_true(is.list(pis)) 139 | expect_equal(length(pis), 2) 140 | 141 | for (i in seq_along(pis)) { 142 | pi <- pis[[i]]$packages 143 | wh <- which(pi$package == foo) 144 | expect_equal(pi$ondiskversion[wh], NA_character_) 145 | expect_equal(pi$path[wh], NA_character_) 146 | expect_equal(pi$date[wh], NA_character_) 147 | expect_equal(pi$source[wh], NA_character_) 148 | } 149 | }) 150 | -------------------------------------------------------------------------------- /tools/bad-emoji.R: -------------------------------------------------------------------------------- 1 | # emoji 0.2.0 2 | bad <- c( 3 | 11, 4 | 24, 5 | 33, 6 | 34, 7 | 37, 8 | 43:45, 9 | 50, 10 | 67, 11 | 71, 12 | 76, 13 | 86, 14 | 101, 15 | 144:147, 16 | 155, 17 | 157, 18 | 170, 19 | 209:232, 20 | 239:250, 21 | 264:269, 22 | 325:330, 23 | 379:384, 24 | 403:422, 25 | 454:455, 26 | 474:479, 27 | 487:488, 28 | 496, 29 | 545:568, 30 | 605:610, 31 | 617:622, 32 | 629:634, 33 | 641:670, 34 | 695:718, 35 | 725:748, 36 | 755:778, 37 | 785:808, 38 | 815:838, 39 | 845:898, 40 | 905:928, 41 | 935:958, 42 | 965:1000, 43 | 1025:1030, 44 | 1043:1048, 45 | 1061:1072, 46 | seq(1074, 1096, by = 2), 47 | 1097:1102, 48 | 1115:1120, 49 | 1133:1138, 50 | 1151:1156, 51 | 1169:1174, 52 | 1187:1192, 53 | 1205:1210, 54 | 1223:1228, 55 | 1241:1246, 56 | 1259:1270, 57 | seq(1272, 1294, by = 2), 58 | 1295:1300, 59 | 1313:1318, 60 | 1337, 61 | 1360, 62 | 1368:1395, 63 | 1402:1431, 64 | 1438:1467, 65 | 1486:1509, 66 | 1528:1551, 67 | 1558:1581, 68 | 1588:1599, 69 | 1606:1623, 70 | 1642:1647, 71 | 1654:1677, 72 | 1684:1707, 73 | 1714:1737, 74 | 1744:1767, 75 | 1774:1797, 76 | 1804:1827, 77 | 1834:1857, 78 | 1859:1862, 79 | 1864:1868, 80 | 1875:1898, 81 | 1905:1928, 82 | 1935:2072, 83 | 2079:2102, 84 | 2123:2126, 85 | 2133:2156, 86 | 2163:2186, 87 | 2209:2236, 88 | 2243:2521, 89 | 2528:2551, 90 | 2558:2581, 91 | 2588:2611, 92 | 2624:2649, 93 | 2652:2656, 94 | 2658:2662, 95 | 2664:2668, 96 | 2670:2674, 97 | 2678:2682, 98 | 2684:2688, 99 | 2690:2684, 100 | 2696:2700, 101 | 2704:2708, 102 | 2710:2714, 103 | 2716:2720, 104 | 2722:2726, 105 | 2729:2773, 106 | 2775:2825, 107 | 2827:2877, 108 | 2879:2929, 109 | 2931:2975, 110 | 2977:3027, 111 | 3029:3079, 112 | 3081:3131, 113 | 3162, 114 | 3176, 115 | 3179, 116 | 3180, 117 | 3187, 118 | 3197, 119 | 3214, 120 | 3225, 121 | 3229, 122 | 3230, 123 | 3233:3235, 124 | 3253:3255, 125 | 3270, 126 | 3277, 127 | 3283, 128 | 3286, 129 | 3293, 130 | 3294, 131 | 3299, 132 | 3309, 133 | 3322, 134 | 3323, 135 | 3338, 136 | 3341, 137 | 3350, 138 | 3354, 139 | 3355, 140 | 3358, 141 | 3363, 142 | 3367, 143 | 3380, 144 | 3382, 145 | 3387, 146 | 3391, 147 | 3415, 148 | 3433, 149 | 3444, 150 | 3446:3449, 151 | 3456, 152 | 3489, 153 | 3490, 154 | 3516, 155 | 3535, 156 | 3565, 157 | 3574:3576, 158 | 3580, 159 | 3589, 160 | 3596, 161 | 3613, 162 | 3678, 163 | 3778, 164 | 3784, 165 | 3785, 166 | 3788, 167 | 3790, 168 | 3798:3800, 169 | 3819:3821, 170 | 3827, 171 | 3837:3840, 172 | 3849, 173 | 3856, 174 | 3863, 175 | 3892, 176 | 3897, 177 | 3899, 178 | 3908, 179 | 3944, 180 | 3965, 181 | 4041, 182 | 4053, 183 | 4057, 184 | 4059, 185 | 4067, 186 | 4071, 187 | 4074, 188 | 4087:4089, 189 | 4092, 190 | 4093, 191 | 4098, 192 | 4100, 193 | 4103, 194 | 4104, 195 | 4110, 196 | 4112, 197 | 4113, 198 | 4120, 199 | 4124, 200 | 4125, 201 | 4399, 202 | 4436:4441 203 | ) 204 | --------------------------------------------------------------------------------