├── renv ├── .editorconfig ├── .gitignore ├── settings.json └── activate.R ├── .Rprofile ├── .gitattributes ├── _bookdown.yml ├── 04-references.Rmd ├── .gitignore ├── .github ├── renovate.json5 └── workflows │ └── deploy_bookdown.yml ├── book.bib ├── d4all_docs.Rproj ├── .editorconfig ├── packages.bib ├── images ├── pgp-public.asc ├── tip.svg ├── important.svg ├── smime.crt ├── warning.svg ├── warning.eps ├── important.eps └── tip.eps ├── README.md ├── .pre-commit-config.yaml ├── _output.yml ├── style.css ├── 01-getting-started.Rmd ├── index.Rmd ├── 03-COC.Rmd ├── preamble.tex ├── 02-using-draupnir.Rmd └── renv.lock /renv/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | -------------------------------------------------------------------------------- /.Rprofile: -------------------------------------------------------------------------------- 1 | source("renv/activate.R") 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | renv/** linguist-generated 3 | -------------------------------------------------------------------------------- /renv/.gitignore: -------------------------------------------------------------------------------- 1 | library/ 2 | local/ 3 | cellar/ 4 | lock/ 5 | python/ 6 | sandbox/ 7 | staging/ 8 | -------------------------------------------------------------------------------- /_bookdown.yml: -------------------------------------------------------------------------------- 1 | edit: https://github.com/MTRNord/draupnir4all_docs/edit/main/%s 2 | delete_merged_file: true 3 | language: 4 | ui: 5 | chapter_name: "Chapter " 6 | -------------------------------------------------------------------------------- /04-references.Rmd: -------------------------------------------------------------------------------- 1 | \cleardoublepage 2 | 3 | # (APPENDIX) Appendix {-} 4 | 5 | \backmatter 6 | 7 | `r if (knitr::is_html_output()) ' 8 | # References {-} 9 | '` 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | _book 5 | _bookdown_files 6 | rsconnect 7 | *.log 8 | *.tex 9 | *.pdf 10 | _main_files 11 | content-license.html 12 | 404.html 13 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Aminda Suomalainen 2 | // 3 | // SPDX-License-Identifier: CC0-1.0 4 | { 5 | extends: ["github>the-draupnir-project/.github:renovate-shared"], 6 | } 7 | -------------------------------------------------------------------------------- /book.bib: -------------------------------------------------------------------------------- 1 | @Software{Draupnir2023, 2 | title = {Draupnir}, 3 | date = {2023-10-20T09:44:12Z}, 4 | url = {https://github.com/the-draupnir-project/Draupnir}, 5 | urldate = {2023-11-02}, 6 | abstract = {A moderation tool for Matrix}, 7 | organization = {{The Draupnir Project}}, 8 | } 9 | -------------------------------------------------------------------------------- /d4all_docs.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | BuildType: Website 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # See https://editorconfig.org for more information 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [{LICENSE,*.{md,yml,yaml,json,Rmd}}] 13 | trim_trailing_whitespace = false 14 | indent_style = space 15 | indent_size = unset 16 | 17 | [*.go] 18 | indent_style = tab 19 | -------------------------------------------------------------------------------- /renv/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "bioconductor.version": null, 3 | "external.libraries": [], 4 | "ignored.packages": [], 5 | "package.dependency.fields": [ 6 | "Imports", 7 | "Depends", 8 | "LinkingTo" 9 | ], 10 | "ppm.enabled": null, 11 | "ppm.ignored.urls": [], 12 | "r.version": null, 13 | "snapshot.type": "implicit", 14 | "use.cache": true, 15 | "vcs.ignore.cellar": true, 16 | "vcs.ignore.library": true, 17 | "vcs.ignore.local": true, 18 | "vcs.manage.ignores": true 19 | } 20 | -------------------------------------------------------------------------------- /packages.bib: -------------------------------------------------------------------------------- 1 | @Manual{R-base, 2 | title = {R: A Language and Environment for Statistical Computing}, 3 | author = {{R Core Team}}, 4 | organization = {R Foundation for Statistical Computing}, 5 | address = {Vienna, Austria}, 6 | year = {2023}, 7 | url = {https://www.R-project.org/}, 8 | } 9 | 10 | @Manual{R-bookdown, 11 | title = {bookdown: Authoring Books and Technical Documents with R Markdown}, 12 | author = {Yihui Xie}, 13 | year = {2023}, 14 | note = {R package version 0.36.2}, 15 | url = {https://github.com/rstudio/bookdown}, 16 | } 17 | -------------------------------------------------------------------------------- /images/pgp-public.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | xjMEZdydnRYJKwYBBAHaRw8BAQdAfm5iYSIP+ys9tSCmJE6rb7ULXtdJXvm1PVtN 4 | MAr2djrNSERyYXVwbmlyNEFsbCBTdXBwb3J0ICh6YW1tYWQgc3lzdGVtIGtleSkg 5 | PGRyYXVwbmlyNGFsbEBub3JkZ2VkYW5rZW4uZGV2PsKTBBMWCgA7FiEEYS5PkiE8 6 | u1FF6rBwWs5QtCoTjUkFAmXcnZ0CGwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcC 7 | F4AACgkQWs5QtCoTjUkyGAD7BwrlaTeGInVMJu++rUJRKaVX4OHJiDCHzulqp/EZ 8 | RhoA/2jXssUGJTpBoNSQApGeIZHEkvR5vXr5YKz0sW2TZi0FzjgEZdydnRIKKwYB 9 | BAGXVQEFAQEHQEfL0/R0vCef7H9gy2JuuW+tv3jJE8oyH/6Dd//ELUFvAwEIB8J4 10 | BBgWCgAgFiEEYS5PkiE8u1FF6rBwWs5QtCoTjUkFAmXcnZ0CGwwACgkQWs5QtCoT 11 | jUlnPAEAghJv1uIyJwFVL+/RgikhBztByYYxA6i1rgqrzLFCxlYA/0ppDUt/z0b0 12 | WTORngjIYNcHE7v/iYSPRmlj+sjuydoG 13 | =X6YY 14 | -----END PGP PUBLIC KEY BLOCK----- 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the documentation for Draupnir4All 2 | 3 | # Content License 4 | 5 |

Draupnir4All Documentation is marked with CC0 1.0

6 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | # See https://pre-commit.ci for more information 4 | ci: 5 | autoupdate_schedule: weekly 6 | repos: 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: v5.0.0 9 | hooks: 10 | - id: trailing-whitespace 11 | args: ["--markdown-linebreak-ext", "md,Rmd"] 12 | exclude: ^renv\/.*$ 13 | exclude_types: [svg] 14 | - id: end-of-file-fixer 15 | exclude_types: [svg] 16 | - id: check-yaml 17 | - id: check-added-large-files 18 | - repo: https://github.com/editorconfig-checker/editorconfig-checker.python 19 | rev: "3.2.0" 20 | hooks: 21 | - id: editorconfig-checker 22 | alias: ec 23 | -------------------------------------------------------------------------------- /_output.yml: -------------------------------------------------------------------------------- 1 | bookdown::gitbook: 2 | css: style.css 3 | config: 4 | toc: 5 | before: | 6 |
  • Draupnir4All
  • 7 | after: | 8 |
  • Published with bookdown
  • 9 | edit: https://github.com/MTRNord/draupnir4all_docs/edit/main/%s 10 | download: ["pdf", "epub"] 11 | sharing: 12 | facebook: false 13 | github: true 14 | twitter: false 15 | linkedin: false 16 | weibo: false 17 | # instapaper: false 18 | vk: false 19 | whatsapp: false 20 | all: ['github'] 21 | bookdown::pdf_book: 22 | includes: 23 | in_header: preamble.tex 24 | latex_engine: xelatex 25 | citation_package: biblatex 26 | keep_tex: yes 27 | bookdown::epub_book: default 28 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | .rmdcaution, .rmdimportant, .rmdnote, .rmdtip, .rmdwarning { 2 | padding: 1em 1em 1em 4em; 3 | margin-bottom: 10px; 4 | background: #f5f5f5 5px center/3em no-repeat; 5 | } 6 | /*.rmdcaution { 7 | background-image: url("../images/caution.png"); 8 | }*/ 9 | .rmdimportant { 10 | background-image: url("./images/important.svg"); 11 | } 12 | /*.rmdnote { 13 | background-image: url("../images/note.png"); 14 | }*/ 15 | .rmdtip { 16 | background-image: url("./images/tip.svg"); 17 | } 18 | .rmdwarning { 19 | background-image: url("./images/warning.svg"); 20 | } 21 | .color-theme-2 * > .rmdimportant { 22 | color: #000; 23 | } 24 | 25 | p.caption { 26 | color: #777; 27 | margin-top: 10px; 28 | } 29 | p code { 30 | white-space: inherit; 31 | } 32 | pre { 33 | word-break: normal; 34 | word-wrap: normal; 35 | } 36 | pre code { 37 | white-space: inherit; 38 | } 39 | -------------------------------------------------------------------------------- /01-getting-started.Rmd: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | All the things you need to know to get set up will be here. If some are missing 4 | please feel free to open an issue at https://github.com/the-draupnir-project/draupnir4all_docs 5 | 6 | ## Registration 7 | 8 | ```{block2, type='rmdimportant'} 9 | In the early stages I am going to limit the amount of people that can register 10 | to figure out proper scaling of the components. 11 | There will be announcements on the status page when the registrations are paused. 12 | ``` 13 | 14 | To register you need to write an email to `draupnir4all@midnightthoughts.space` or open a ticket directly at https://osticket.midnightthoughts.space/open.php. 15 | 16 | Inside of the email you need to provide these infos: 17 | 18 | - The matrix ID of the person that is the admin of the provisioned bot. This is NOT the name or id of the bot itself. 19 | - An estimate of the amount of rooms going to be moderated. This doesn't need to be accurate but helps estimating the resources needed in the early stages. 20 | 21 | After you have sent the email you will get a reply by me (MTRNord) when you are registered with further instructions how to get your draupnir instance. 22 | -------------------------------------------------------------------------------- /images/tip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/deploy_bookdown.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | - rbookdown 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 16 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 17 | concurrency: 18 | group: "pages" 19 | cancel-in-progress: false 20 | jobs: 21 | bookdown: 22 | name: Render-Book 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: r-lib/actions/setup-r@v2 27 | - uses: r-lib/actions/setup-pandoc@v2 28 | - uses: r-lib/actions/setup-tinytex@v2 29 | - name: Install rmarkdown 30 | run: Rscript -e 'install.packages(c("rmarkdown","bookdown"))' 31 | - name: Render Book 32 | run: | 33 | Rscript -e 'bookdown::render_book("index.Rmd", "bookdown::gitbook")' 34 | Rscript -e 'bookdown::render_book("index.Rmd", "bookdown::pdf_book")' 35 | Rscript -e 'bookdown::render_book("index.Rmd", "bookdown::epub_book")' 36 | cp -R ./images _book/ 37 | - name: Upload results for deployment 38 | uses: actions/upload-pages-artifact@v3 39 | with: 40 | path: _book/ 41 | 42 | # Need to first create an empty gh-pages branch 43 | # see https://pkgdown.r-lib.org/reference/deploy_site_github.html 44 | # and also add secrets for a GH_PAT and EMAIL to the repository 45 | # gh-action from Cecilapp/GitHub-Pages-deploy 46 | checkout-and-deploy: 47 | runs-on: ubuntu-latest 48 | needs: bookdown 49 | if: github.ref == 'refs/heads/main' 50 | environment: 51 | name: github-pages 52 | url: ${{ steps.deployment.outputs.page_url }} 53 | steps: 54 | - name: Deploy to GitHub Pages 55 | id: deployment 56 | uses: actions/deploy-pages@v4 57 | -------------------------------------------------------------------------------- /images/important.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /images/smime.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFwTCCA6mgAwIBAgIQSbg1v3jLnExjd7Cx2wDZCTANBgkqhkiG9w0BAQsFADCB 3 | gTELMAkGA1UEBhMCSVQxEDAOBgNVBAgMB0JlcmdhbW8xGTAXBgNVBAcMEFBvbnRl 4 | IFNhbiBQaWV0cm8xFzAVBgNVBAoMDkFjdGFsaXMgUy5wLkEuMSwwKgYDVQQDDCNB 5 | Y3RhbGlzIENsaWVudCBBdXRoZW50aWNhdGlvbiBDQSBHMzAeFw0yNDAyMjYxMzQ3 6 | NTVaFw0yNTAyMjYxMzQ3NTRaMCgxJjAkBgNVBAMMHWRyYXVwbmlyNGFsbEBub3Jk 7 | Z2VkYW5rZW4uZGV2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0AFK 8 | i4DK+4cmVJ+LvG4+IgWLrvef99J0JGQRcDXoIWTXVR8zePCHvYYaodIC8T3FqyF9 9 | f5YXC5OruCXOvdWpeiUDmBwQr3qHge9pHnBq5fvei2Sc4vJy/hsqlZXw4mumYtj2 10 | l1Y3XWKYXuNrTMSjqjia9RN/hbeZGmlTfVD0MW0fx6wUZNuWoWh5N4Q/rJ3ssH+o 11 | +Y6c8pXeedGKwjBz/2F3GYlexRkX9G51iKQMc7R1w7WJPkRv8YMYSovgRYvJtqgD 12 | aoNItvn9RYkXqnjQ1MVATZvvlb8tcKjEHgU11F1F3JUBrZkryVW4qWRBTxFHYv1N 13 | DwAbYgP8/ySo6AcDawIDAQABo4IBizCCAYcwDAYDVR0TAQH/BAIwADAfBgNVHSME 14 | GDAWgBS+l6mqhL+AvxBTfQky+eEuMhvPdzB+BggrBgEFBQcBAQRyMHAwOwYIKwYB 15 | BQUHMAKGL2h0dHA6Ly9jYWNlcnQuYWN0YWxpcy5pdC9jZXJ0cy9hY3RhbGlzLWF1 16 | dGNsaWczMDEGCCsGAQUFBzABhiVodHRwOi8vb2NzcDA5LmFjdGFsaXMuaXQvVkEv 17 | QVVUSENMLUczMCgGA1UdEQQhMB+BHWRyYXVwbmlyNGFsbEBub3JkZ2VkYW5rZW4u 18 | ZGV2MBQGA1UdIAQNMAswCQYHZ4EMAQUBATAdBgNVHSUEFjAUBggrBgEFBQcDAgYI 19 | KwYBBQUHAwQwSAYDVR0fBEEwPzA9oDugOYY3aHR0cDovL2NybDA5LmFjdGFsaXMu 20 | aXQvUmVwb3NpdG9yeS9BVVRIQ0wtRzMvZ2V0TGFzdENSTDAdBgNVHQ4EFgQURDTb 21 | OkDNYHZ+TDIZN2m2KwGladYwDgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3DQEBCwUA 22 | A4ICAQADvMlzeUZUufYqlKJLyjOYm4bWafZ/rE6Es1S9mJgKESSTI//wofFWobD9 23 | EvQXE4bfEM1DMtdCfebE5sLMvp+dpoJYzaKbbg8qPdnkRUTDY018CMjXf1ND9PDf 24 | IkIMIfXcAlXaQSl2dFOo0+cxg5apz51fsGpQ+9kVfpSVTGlVfLX+ZBUUTG/vEOHB 25 | P3wx7mBWNtwOeAeGHaI+fGkTFjyu+rSHXyU9dVd77ck1d7znvhOlW156UHg1A1Tt 26 | uczoYSaQBWPO6ODXT9h3vtIhVBWIdsbpoDWa7tZ7CfJSZSgeTPtQlj70TwdcQCwy 27 | IKTOmlqQpXLim4VV4Z7VgsjX0DfXJ4+AwaDYQjND3ycDwKSF7JiRYuoGTwyVl1zX 28 | J9+cGs6ciqz5ViRQ8pT5LgE0VoXieHoxFdG4zbPXD0s4NnAK1G+1EX79tlFLqCl3 29 | tsc0sONwP31vqyB9MDzi31D8FqxQ8dAiXvdbJH1aOlSWGc5dWq+a/4bPjWz/rWbq 30 | eRkpF/c3Ysr7nPU+NKcHLAgAXjZiAPGKN1WsDjsM26tDV7ME7FiexppLdM2+lsPB 31 | X7fGEWS68UcyGx6T78J3+kiKn28h8QASL4X8vem3YixL3a/Fr4q6bXbggWt7sOi8 32 | xdQ+0leoYAEg43+tbxajRQseN1c/yDk6wKKTZ7Tm0XRUQDT8dA== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /images/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Draupnir4All" 3 | author: "MTRNord" 4 | date: "`r Sys.Date()`" 5 | site: bookdown::bookdown_site 6 | documentclass: book 7 | bibliography: [book.bib, packages.bib] 8 | description: | 9 | Draupnir4All is a quick and free community hosted way to get a Moderation 10 | Draupnir bot for your community. 11 | link-citations: yes 12 | colorlinks: true 13 | github-repo: the-draupnir-project/Draupnir 14 | biblio-style: apa 15 | knit: "bookdown::render_book" 16 | --- 17 | 18 | # About Draupnir4All 19 | 20 | ## What is this? 21 | 22 | Draupnir4all is a service that provisions @Draupnir2023 instances for communities and moderators. 23 | Providing all the features of a normal Draupnir instance without needing to host Draupnir yourself. 24 | 25 | The name originates from the Project name as well as a prior similar attempt named "Mjolnir4All". 26 | 27 | ## What is Draupnir? 28 | 29 | Draupnir allows moderators to manage user and server bans across a community, 30 | subscribe to community curated spam lists, and organize their community. 31 | It works for both small and large communities. Read more at "[Using Draupnir]". 32 | Draupnir is a continuation of the Mjolnir project. 33 | Both bots are moderation bots for managing rooms in sync. 34 | 35 | ## Who hosts this service? 36 | 37 | Currently this is hosted by [MTRNord](https://github.com/MTRNord) on the `draupnir.midnightthoughts.space` server 38 | which is a dedicated synapse instance for the draupnir instances. 39 | 40 | It is running on a Kubernetes cluster and the infrastructure files can be found here: 41 | 42 | - https://git.nordgedanken.dev/kubernetes/gitops/src/branch/main/apps/base/matrix/draupnir-synapse 43 | - https://git.nordgedanken.dev/kubernetes/gitops/src/branch/main/apps/base/matrix/draupnir4all 44 | - https://git.nordgedanken.dev/kubernetes/gitops/src/branch/main/apps/production/synapse-draupnir.yaml 45 | - https://git.nordgedanken.dev/kubernetes/dns/src/branch/main/midnightthoughts.js#L41-L42 46 | - https://git.nordgedanken.dev/kubernetes/grafana/src/branch/main/draupnir4all.json 47 | 48 | This also implies it is community run. While I try to keep a very high uptime 49 | I also have to be realistic. 50 | 51 | ## How do I register? 52 | 53 | To get started please follow the "[Getting Started]" section. 54 | 55 | ## Is it free? 56 | 57 | Yes as of now this is completely free. However please make sure to be on fair use terms. This means dont try to crash it or cause disruption actively. This will cause you to be banned. 58 | 59 | Additionally if you want to support me the best way is to go via https://en.liberapay.com/MTRNord 60 | -------------------------------------------------------------------------------- /03-COC.Rmd: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | All participants of Draupnir4all are expected to abide by our Code of Conduct, both online and during in-person events that are hosted and/or associated with Draupnir4all. 4 | 5 | ## The Pledge 6 | 7 | In the interest of fostering an open and welcoming environment, we pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 8 | 9 | ## The Standards 10 | 11 | Examples of behaviour that contributes to creating a positive environment include: 12 | 13 | - Using welcoming and inclusive language 14 | - Being respectful of differing viewpoints and experiences 15 | - Gracefully accepting constructive criticism 16 | - Referring to people by their preferred pronouns and using gender-neutral pronouns when uncertain 17 | 18 | Examples of unacceptable behaviour by participants include: 19 | 20 | - Trolling, insulting/derogatory comments, public or private harassment 21 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 22 | - Not being respectful to reasonable communication boundaries, such as 'leave me alone,' 'go away,' or 'I’m not discussing this with you.' 23 | - The usage of sexualised language or imagery and unwelcome sexual attention or advances 24 | - Swearing, usage of strong or disturbing language 25 | - Demonstrating the graphics or any other content you know may be considered disturbing 26 | - Assuming or promoting any kind of inequality including but not limited to: age, body size, disability, ethnicity, gender identity and expression, nationality and race, personal appearance, religion, or sexual identity and orientation 27 | - Drug promotion of any kind 28 | - Attacking personal tastes 29 | - Other conduct which you know could reasonably be considered inappropriate in a professional setting. 30 | 31 | ## Enforcement 32 | 33 | Violations of the Code of Conduct may be reported by sending an email to . All reports will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Further details of specific enforcement policies may be posted separately. 34 | 35 | We hold the right and responsibility to remove comments or other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any members for other behaviours that they deem inappropriate, threatening, offensive, or harmful. 36 | 37 | ## Attribution 38 | 39 | This Code of Conduct is adapted from [dev.to](https://dev.to). 40 | -------------------------------------------------------------------------------- /preamble.tex: -------------------------------------------------------------------------------- 1 | \usepackage{booktabs} 2 | \usepackage{longtable} 3 | \usepackage[bf,singlelinecheck=off]{caption} 4 | \usepackage{hyperref} 5 | \usepackage{lmodern} 6 | 7 | \usepackage{framed,color} 8 | \definecolor{shadecolor}{RGB}{248,248,248} 9 | 10 | \renewcommand{\textfraction}{0.05} 11 | \renewcommand{\topfraction}{0.8} 12 | \renewcommand{\bottomfraction}{0.8} 13 | \renewcommand{\floatpagefraction}{0.75} 14 | 15 | \renewenvironment{quote}{\begin{VF}}{\end{VF}} 16 | \let\oldhref\href 17 | \renewcommand{\href}[2]{#2\footnote{\url{#1}}} 18 | 19 | \ifxetex 20 | \usepackage{letltxmacro} 21 | \setlength{\XeTeXLinkMargin}{1pt} 22 | \LetLtxMacro\SavedIncludeGraphics\includegraphics 23 | \def\includegraphics#1#{% #1 catches optional stuff (star/opt. arg.) 24 | \IncludeGraphicsAux{#1}% 25 | }% 26 | \newcommand*{\IncludeGraphicsAux}[2]{% 27 | \XeTeXLinkBox{% 28 | \SavedIncludeGraphics#1{#2}% 29 | }% 30 | }% 31 | \fi 32 | 33 | \makeatletter 34 | \newenvironment{kframe}{% 35 | \medskip{} 36 | \setlength{\fboxsep}{.8em} 37 | \def\at@end@of@kframe{}% 38 | \ifinner\ifhmode% 39 | \def\at@end@of@kframe{\end{minipage}}% 40 | \begin{minipage}{\columnwidth}% 41 | \fi\fi% 42 | \def\FrameCommand##1{\hskip\@totalleftmargin \hskip-\fboxsep 43 | \colorbox{shadecolor}{##1}\hskip-\fboxsep 44 | % There is no \\@totalrightmargin, so: 45 | \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth}% 46 | \MakeFramed {\advance\hsize-\width 47 | \@totalleftmargin\z@ \linewidth\hsize 48 | \@setminipage}}% 49 | {\par\unskip\endMakeFramed% 50 | \at@end@of@kframe} 51 | \makeatother 52 | 53 | \makeatletter 54 | \@ifundefined{Shaded}{ 55 | }{\renewenvironment{Shaded}{\begin{kframe}}{\end{kframe}}} 56 | \makeatother 57 | 58 | \newenvironment{rmdblock}[1] 59 | { 60 | \begin{itemize} 61 | \renewcommand{\labelitemi}{ 62 | \raisebox{-.7\height}[0pt][0pt]{ 63 | {\setkeys{Gin}{width=3em,keepaspectratio}\includegraphics{images/#1.eps}} 64 | } 65 | } 66 | \setlength{\fboxsep}{1em} 67 | \begin{kframe} 68 | \item 69 | } 70 | { 71 | \end{kframe} 72 | \end{itemize} 73 | } 74 | \newenvironment{rmdnote} 75 | {\begin{rmdblock}{note}} 76 | {\end{rmdblock}} 77 | \newenvironment{rmdcaution} 78 | {\begin{rmdblock}{caution}} 79 | {\end{rmdblock}} 80 | \newenvironment{rmdimportant} 81 | {\begin{rmdblock}{important}} 82 | {\end{rmdblock}} 83 | \newenvironment{rmdtip} 84 | {\begin{rmdblock}{tip}} 85 | {\end{rmdblock}} 86 | \newenvironment{rmdwarning} 87 | {\begin{rmdblock}{warning}} 88 | {\end{rmdblock}} 89 | 90 | \usepackage{makeidx} 91 | \makeindex 92 | 93 | \urlstyle{tt} 94 | \hypersetup{colorlinks=true,urlcolor=blue} 95 | 96 | \usepackage{amsthm} 97 | \makeatletter 98 | \def\thm@space@setup{% 99 | \thm@preskip=8pt plus 2pt minus 4pt 100 | \thm@postskip=\thm@preskip 101 | } 102 | \makeatother 103 | 104 | % \frontmatter 105 | \let\cleardoublepage=\clearpage 106 | -------------------------------------------------------------------------------- /02-using-draupnir.Rmd: -------------------------------------------------------------------------------- 1 | # Using Draupnir 2 | 3 | ## Setting up Draupnir 4 | 5 | After following the registration you will be invited to a Managment room for your draupnir. 6 | This room will be the most important room for using Draupnir. Most actions will happen within this room. 7 | 8 | ```{block2, type='rmdwarning'} 9 | Do not leave or part from the management room. 10 | 11 | Losing access to the management room means that you will lose access to your bot. 12 | ``` 13 | 14 | ### Inviting the bot to rooms 15 | 16 | ```{block2, type='rmdtip'} 17 | Commands are run in the management room by sending a message into the room. 18 | ``` 19 | 20 | Before we can get started you will need to invite the bot into your rooms, so that 21 | the bot can protect them. 22 | 23 | In order for Draupnir to protect rooms, you will need to run the following command for 24 | each room that you want to protect: 25 | 26 | ``` 27 | !draupnir rooms add 28 | ``` 29 | 30 | ```{block2, type='rmdtip'} 31 | You can only protect rooms you have admin access to. Abusing the bot for spam will 32 | also result in a ban from the service. 33 | ``` 34 | 35 | After you have protected your rooms, you will need to give your Draupnir bot the role of Admin. 36 | As Matrix uses a [power level model](https://spec.matrix.org/v1.8/client-server-api/#mroompower_levels), 37 | the simplist way to give Draupnir the required privileges is by providing the bot the Admin role. 38 | This is necessary because Draupnir requires the privilege to ban servers. 39 | 40 | To do this we suggest running the following command in each room you invited the bot into: 41 | 42 | ``` 43 | /op @yourMjolnirBot:example.com 100 44 | ``` 45 | 46 | Make sure to use the userid of the bot that invited 47 | you to the managment room and not the user id you used from the email. 48 | 49 | Alternatively you can find the bot in the user's section of your client and provide 50 | it with the Admin role there. 51 | 52 | ```{block2, type='rmdtip'} 53 | For more advanced set-ups, read the [spec covering power levels](https://spec.matrix.org/v1.5/client-server-api/#mroompower_levels). 54 | 55 | Our recommendation for advanced use cases is to keep the power level of the bot above 50 (moderators) 56 | and below 100 (admins), since admins cannot be demoted by a user of the same power level. 57 | This can, as an example, be done using the "Change server ACLs" permission option in 58 | Element-Web's room settings. 59 | ``` 60 | 61 | ### Creating additional policy lists 62 | 63 | The core feature of Draupnir is the use policy lists. Policy lists allow moderators to share or 64 | subscribe to moderation actions. 65 | In the most basic setup, policy lists can be thought of as a database of all your bans for your bot. 66 | However in a more advanced setup, policy lists allow you 67 | to share moderation decisions with other communities. 68 | This includes watching the lists from other communities in Matrix or 69 | collaborating with them on the same lists. 70 | 71 | Draupnir should have already provided you with a policy list. 72 | You should be able to find your list by issuing the `!draupnir status` command, where it will 73 | then be listed under `Subscribed and protected policy lists`. 74 | 75 | Although not necessary, later you may find that you need to create additional lists. 76 | 77 | There is a list creation command dedicated for this. 78 | 79 | ``` 80 | !draupnir list create 81 | ``` 82 | 83 | - `shortcode` is a short name given to this list. It should be short and easy to type. 84 | However contrary to mjolnir you are likely not going to type it often. 85 | - `alias localpart` is the local part of the address draupnir is going to create for this list. 86 | This is useful if you ever want to share your policy list with other communities. 87 | 88 | For example, the following command will create a policy list with the short code `spam` and the 89 | address `#my-community-spam-policy-list:draupnir.midnightthoughts.space`: 90 | 91 | ``` 92 | !draupnir list create spam my-community-spam-ban-list 93 | ``` 94 | 95 | #### Subscribing to policy lists 96 | 97 | Policy lists are a clever mechanism that allows moderation teams to ban users for different motives 98 | (e.g. one list for `spam` and one for `coc`). 99 | Such a distinction can be useful when several communities want to collaborate together. 100 | 101 | Not all communities will share a similar Code of Conduct, 102 | but a lot of them will agree on what is spam. 103 | Being able to subscribe to another community's spam list means your own community 104 | will be protected from spammers that the other community has already met, 105 | all while observing different code of conducts. 106 | 107 | To subscribe to a public policy list, you need to retrieve the address of this list. 108 | That list being technically nothing more than a Matrix room, 109 | its address follows the usual `#room_name:server.tld` format. 110 | 111 | Then to make draupnir follow this list, you need to issue the following command in its control room 112 | 113 | ``` 114 | !draupnir watch 115 | ``` 116 | 117 | For example to subscribe to the `#matrix-org-coc-bl:matrix.org` ban list maintained 118 | by the Matrix Foundation, you would issue the following command 119 | 120 | ``` 121 | !draupnir watch #matrix-org-coc-bl:matrix.org 122 | ``` 123 | 124 | ```{block2, type='rmdtip'} 125 | An alternative list we suggest is the CME (Community Moderation Effort) list which is being 126 | managed by a group of active community members and has proven itself over time to be effective, 127 | fast and objective with their decissions. Their list has the alias `#community-moderation-effort-bl:neko.dev`. 128 | ``` 129 | 130 | ## Moderating with Draupnir 131 | 132 | If you are coming from a Mjolnir instance, you may just use the same commands as before. 133 | However draupnir in most cases has simpler ways of running commands which will be explained in 134 | the following sections. 135 | 136 | ### Banning users 137 | 138 | Users can be banned from your community by adding them to a policy list. 139 | When you ban a user from a room, a prompt will be shown in the management room for Draupnir asking 140 | if the ban should be added to a list. Selecting a list from this prompt will publish a policy, 141 | and the ban will be synchronised with all of your protected rooms. 142 | 143 | Alternatively, the ban command can be used within the management room to ban users directly. 144 | 145 | ``` 146 | !draupnir ban entity list [...reason] 147 | ``` 148 | 149 | - `entity` A Matrix user ID, a reference to a room (either an alias, room ID, or a matrix.to URL), 150 | or a server name. 151 | - `list` A reference to a room (either an alias, room ID, or a matrix.to URL) or a list shortcode. 152 | - `reason` A reason for the ban to be shown in the list (and potentially to other communities that 153 | are watching the list). 154 | 155 | So to ban a user with the username `@spam:example.com` we would write the following: 156 | 157 | ``` 158 | !draupnir ban @spam:example.com list spam 159 | ``` 160 | 161 | ### Unbanning users 162 | 163 | Users can be unbanned from your community conveniently by first unbanning the user from a protected 164 | room from your client interface. A prompt should then show in your management room which will allow 165 | you to easily remove all of the policies related to this user and unban them from all protected 166 | rooms. 167 | 168 | Alternatively the unban command can be used from within the management room. 169 | 170 | ``` 171 | !draupnir unban entity list [--true] 172 | ``` 173 | 174 | This will remove a policy relating to the entity from a policy list. 175 | If the entity is a user, then it is likely that their membership has been set to `ban` in the 176 | protected rooms they were joined to. In order to force Draupnir to remove these "room level" bans, 177 | then an additional option of `--true` must be provided with the unban command. 178 | 179 | ### Kicking users 180 | 181 | ### Redacting users 182 | -------------------------------------------------------------------------------- /renv.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.3.2", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://packagemanager.posit.co/cran/latest" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "R6": { 13 | "Package": "R6", 14 | "Version": "2.5.1", 15 | "Source": "Repository", 16 | "Repository": "RSPM", 17 | "Requirements": [ 18 | "R" 19 | ], 20 | "Hash": "470851b6d5d0ac559e9d01bb352b4021" 21 | }, 22 | "base64enc": { 23 | "Package": "base64enc", 24 | "Version": "0.1-3", 25 | "Source": "Repository", 26 | "Repository": "RSPM", 27 | "Requirements": [ 28 | "R" 29 | ], 30 | "Hash": "543776ae6848fde2f48ff3816d0628bc" 31 | }, 32 | "bookdown": { 33 | "Package": "bookdown", 34 | "Version": "0.37.2", 35 | "Source": "GitHub", 36 | "RemoteType": "github", 37 | "RemoteUsername": "rstudio", 38 | "RemoteRepo": "bookdown", 39 | "RemoteRef": "HEAD", 40 | "RemoteSha": "9a238189267a75de49d1ebc93a87f743b3aad823", 41 | "RemoteHost": "api.github.com", 42 | "Requirements": [ 43 | "R", 44 | "htmltools", 45 | "jquerylib", 46 | "knitr", 47 | "rmarkdown", 48 | "tinytex", 49 | "xfun", 50 | "yaml" 51 | ], 52 | "Hash": "0c3894444c57ae4436b243c0a54a0a79" 53 | }, 54 | "bslib": { 55 | "Package": "bslib", 56 | "Version": "0.6.1", 57 | "Source": "Repository", 58 | "Repository": "RSPM", 59 | "Requirements": [ 60 | "R", 61 | "base64enc", 62 | "cachem", 63 | "grDevices", 64 | "htmltools", 65 | "jquerylib", 66 | "jsonlite", 67 | "lifecycle", 68 | "memoise", 69 | "mime", 70 | "rlang", 71 | "sass" 72 | ], 73 | "Hash": "c0d8599494bc7fb408cd206bbdd9cab0" 74 | }, 75 | "cachem": { 76 | "Package": "cachem", 77 | "Version": "1.0.8", 78 | "Source": "Repository", 79 | "Repository": "RSPM", 80 | "Requirements": [ 81 | "fastmap", 82 | "rlang" 83 | ], 84 | "Hash": "c35768291560ce302c0a6589f92e837d" 85 | }, 86 | "cli": { 87 | "Package": "cli", 88 | "Version": "3.6.2", 89 | "Source": "Repository", 90 | "Repository": "RSPM", 91 | "Requirements": [ 92 | "R", 93 | "utils" 94 | ], 95 | "Hash": "1216ac65ac55ec0058a6f75d7ca0fd52" 96 | }, 97 | "digest": { 98 | "Package": "digest", 99 | "Version": "0.6.34", 100 | "Source": "Repository", 101 | "Repository": "CRAN", 102 | "Requirements": [ 103 | "R", 104 | "utils" 105 | ], 106 | "Hash": "7ede2ee9ea8d3edbf1ca84c1e333ad1a" 107 | }, 108 | "ellipsis": { 109 | "Package": "ellipsis", 110 | "Version": "0.3.2", 111 | "Source": "Repository", 112 | "Repository": "RSPM", 113 | "Requirements": [ 114 | "R", 115 | "rlang" 116 | ], 117 | "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077" 118 | }, 119 | "evaluate": { 120 | "Package": "evaluate", 121 | "Version": "0.23", 122 | "Source": "Repository", 123 | "Repository": "CRAN", 124 | "Requirements": [ 125 | "R", 126 | "methods" 127 | ], 128 | "Hash": "daf4a1246be12c1fa8c7705a0935c1a0" 129 | }, 130 | "fastmap": { 131 | "Package": "fastmap", 132 | "Version": "1.1.1", 133 | "Source": "Repository", 134 | "Repository": "RSPM", 135 | "Hash": "f7736a18de97dea803bde0a2daaafb27" 136 | }, 137 | "fontawesome": { 138 | "Package": "fontawesome", 139 | "Version": "0.5.2", 140 | "Source": "Repository", 141 | "Repository": "RSPM", 142 | "Requirements": [ 143 | "R", 144 | "htmltools", 145 | "rlang" 146 | ], 147 | "Hash": "c2efdd5f0bcd1ea861c2d4e2a883a67d" 148 | }, 149 | "fs": { 150 | "Package": "fs", 151 | "Version": "1.6.3", 152 | "Source": "Repository", 153 | "Repository": "RSPM", 154 | "Requirements": [ 155 | "R", 156 | "methods" 157 | ], 158 | "Hash": "47b5f30c720c23999b913a1a635cf0bb" 159 | }, 160 | "glue": { 161 | "Package": "glue", 162 | "Version": "1.7.0", 163 | "Source": "Repository", 164 | "Repository": "CRAN", 165 | "Requirements": [ 166 | "R", 167 | "methods" 168 | ], 169 | "Hash": "e0b3a53876554bd45879e596cdb10a52" 170 | }, 171 | "highr": { 172 | "Package": "highr", 173 | "Version": "0.10", 174 | "Source": "Repository", 175 | "Repository": "RSPM", 176 | "Requirements": [ 177 | "R", 178 | "xfun" 179 | ], 180 | "Hash": "06230136b2d2b9ba5805e1963fa6e890" 181 | }, 182 | "htmltools": { 183 | "Package": "htmltools", 184 | "Version": "0.5.7", 185 | "Source": "Repository", 186 | "Repository": "CRAN", 187 | "Requirements": [ 188 | "R", 189 | "base64enc", 190 | "digest", 191 | "ellipsis", 192 | "fastmap", 193 | "grDevices", 194 | "rlang", 195 | "utils" 196 | ], 197 | "Hash": "2d7b3857980e0e0d0a1fd6f11928ab0f" 198 | }, 199 | "jquerylib": { 200 | "Package": "jquerylib", 201 | "Version": "0.1.4", 202 | "Source": "Repository", 203 | "Repository": "RSPM", 204 | "Requirements": [ 205 | "htmltools" 206 | ], 207 | "Hash": "5aab57a3bd297eee1c1d862735972182" 208 | }, 209 | "jsonlite": { 210 | "Package": "jsonlite", 211 | "Version": "1.8.8", 212 | "Source": "Repository", 213 | "Repository": "RSPM", 214 | "Requirements": [ 215 | "methods" 216 | ], 217 | "Hash": "e1b9c55281c5adc4dd113652d9e26768" 218 | }, 219 | "knitr": { 220 | "Package": "knitr", 221 | "Version": "1.45", 222 | "Source": "Repository", 223 | "Repository": "CRAN", 224 | "Requirements": [ 225 | "R", 226 | "evaluate", 227 | "highr", 228 | "methods", 229 | "tools", 230 | "xfun", 231 | "yaml" 232 | ], 233 | "Hash": "1ec462871063897135c1bcbe0fc8f07d" 234 | }, 235 | "lifecycle": { 236 | "Package": "lifecycle", 237 | "Version": "1.0.4", 238 | "Source": "Repository", 239 | "Repository": "CRAN", 240 | "Requirements": [ 241 | "R", 242 | "cli", 243 | "glue", 244 | "rlang" 245 | ], 246 | "Hash": "b8552d117e1b808b09a832f589b79035" 247 | }, 248 | "magrittr": { 249 | "Package": "magrittr", 250 | "Version": "2.0.3", 251 | "Source": "Repository", 252 | "Repository": "RSPM", 253 | "Requirements": [ 254 | "R" 255 | ], 256 | "Hash": "7ce2733a9826b3aeb1775d56fd305472" 257 | }, 258 | "memoise": { 259 | "Package": "memoise", 260 | "Version": "2.0.1", 261 | "Source": "Repository", 262 | "Repository": "RSPM", 263 | "Requirements": [ 264 | "cachem", 265 | "rlang" 266 | ], 267 | "Hash": "e2817ccf4a065c5d9d7f2cfbe7c1d78c" 268 | }, 269 | "mime": { 270 | "Package": "mime", 271 | "Version": "0.12", 272 | "Source": "Repository", 273 | "Repository": "RSPM", 274 | "Requirements": [ 275 | "tools" 276 | ], 277 | "Hash": "18e9c28c1d3ca1560ce30658b22ce104" 278 | }, 279 | "rappdirs": { 280 | "Package": "rappdirs", 281 | "Version": "0.3.3", 282 | "Source": "Repository", 283 | "Repository": "RSPM", 284 | "Requirements": [ 285 | "R" 286 | ], 287 | "Hash": "5e3c5dc0b071b21fa128676560dbe94d" 288 | }, 289 | "renv": { 290 | "Package": "renv", 291 | "Version": "1.0.4", 292 | "Source": "Repository", 293 | "Repository": "RSPM", 294 | "Requirements": [ 295 | "utils" 296 | ], 297 | "Hash": "11abaf7c540ff33f94514d50f929bfd1" 298 | }, 299 | "rlang": { 300 | "Package": "rlang", 301 | "Version": "1.1.3", 302 | "Source": "Repository", 303 | "Repository": "CRAN", 304 | "Requirements": [ 305 | "R", 306 | "utils" 307 | ], 308 | "Hash": "42548638fae05fd9a9b5f3f437fbbbe2" 309 | }, 310 | "rmarkdown": { 311 | "Package": "rmarkdown", 312 | "Version": "2.25", 313 | "Source": "Repository", 314 | "Repository": "RSPM", 315 | "Requirements": [ 316 | "R", 317 | "bslib", 318 | "evaluate", 319 | "fontawesome", 320 | "htmltools", 321 | "jquerylib", 322 | "jsonlite", 323 | "knitr", 324 | "methods", 325 | "stringr", 326 | "tinytex", 327 | "tools", 328 | "utils", 329 | "xfun", 330 | "yaml" 331 | ], 332 | "Hash": "d65e35823c817f09f4de424fcdfa812a" 333 | }, 334 | "sass": { 335 | "Package": "sass", 336 | "Version": "0.4.8", 337 | "Source": "Repository", 338 | "Repository": "RSPM", 339 | "Requirements": [ 340 | "R6", 341 | "fs", 342 | "htmltools", 343 | "rappdirs", 344 | "rlang" 345 | ], 346 | "Hash": "168f9353c76d4c4b0a0bbf72e2c2d035" 347 | }, 348 | "stringi": { 349 | "Package": "stringi", 350 | "Version": "1.8.3", 351 | "Source": "Repository", 352 | "Repository": "RSPM", 353 | "Requirements": [ 354 | "R", 355 | "stats", 356 | "tools", 357 | "utils" 358 | ], 359 | "Hash": "058aebddea264f4c99401515182e656a" 360 | }, 361 | "stringr": { 362 | "Package": "stringr", 363 | "Version": "1.5.1", 364 | "Source": "Repository", 365 | "Repository": "CRAN", 366 | "Requirements": [ 367 | "R", 368 | "cli", 369 | "glue", 370 | "lifecycle", 371 | "magrittr", 372 | "rlang", 373 | "stringi", 374 | "vctrs" 375 | ], 376 | "Hash": "960e2ae9e09656611e0b8214ad543207" 377 | }, 378 | "tinytex": { 379 | "Package": "tinytex", 380 | "Version": "0.49", 381 | "Source": "Repository", 382 | "Repository": "RSPM", 383 | "Requirements": [ 384 | "xfun" 385 | ], 386 | "Hash": "5ac22900ae0f386e54f1c307eca7d843" 387 | }, 388 | "vctrs": { 389 | "Package": "vctrs", 390 | "Version": "0.6.5", 391 | "Source": "Repository", 392 | "Repository": "RSPM", 393 | "Requirements": [ 394 | "R", 395 | "cli", 396 | "glue", 397 | "lifecycle", 398 | "rlang" 399 | ], 400 | "Hash": "c03fa420630029418f7e6da3667aac4a" 401 | }, 402 | "xfun": { 403 | "Package": "xfun", 404 | "Version": "0.42", 405 | "Source": "Repository", 406 | "Repository": "CRAN", 407 | "Requirements": [ 408 | "grDevices", 409 | "stats", 410 | "tools" 411 | ], 412 | "Hash": "fd1349170df31f7a10bd98b0189e85af" 413 | }, 414 | "yaml": { 415 | "Package": "yaml", 416 | "Version": "2.3.8", 417 | "Source": "Repository", 418 | "Repository": "RSPM", 419 | "Hash": "29240487a071f535f5e5d5a323b7afbd" 420 | } 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /images/warning.eps: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-3.0 EPSF-3.0 2 | %%Creator: cairo 1.17.8 (https://cairographics.org) 3 | %%CreationDate: Fri Nov 3 10:15:49 2023 4 | %%Pages: 1 5 | %%DocumentData: Clean7Bit 6 | %%LanguageLevel: 3 7 | %%BoundingBox: 0 0 37 37 8 | %%EndComments 9 | %%BeginProlog 10 | 50 dict begin 11 | /q { gsave } bind def 12 | /Q { grestore } bind def 13 | /cm { 6 array astore concat } bind def 14 | /w { setlinewidth } bind def 15 | /J { setlinecap } bind def 16 | /j { setlinejoin } bind def 17 | /M { setmiterlimit } bind def 18 | /d { setdash } bind def 19 | /m { moveto } bind def 20 | /l { lineto } bind def 21 | /c { curveto } bind def 22 | /h { closepath } bind def 23 | /re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto 24 | 0 exch rlineto 0 rlineto closepath } bind def 25 | /S { stroke } bind def 26 | /f { fill } bind def 27 | /f* { eofill } bind def 28 | /n { newpath } bind def 29 | /W { clip } bind def 30 | /W* { eoclip } bind def 31 | /BT { } bind def 32 | /ET { } bind def 33 | /BDC { mark 3 1 roll /BDC pdfmark } bind def 34 | /EMC { mark /EMC pdfmark } bind def 35 | /cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def 36 | /Tj { show currentpoint cairo_store_point } bind def 37 | /TJ { 38 | { 39 | dup 40 | type /stringtype eq 41 | { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse 42 | } forall 43 | currentpoint cairo_store_point 44 | } bind def 45 | /cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore 46 | cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def 47 | /Tf { pop /cairo_font exch def /cairo_font_matrix where 48 | { pop cairo_selectfont } if } bind def 49 | /Td { matrix translate cairo_font_matrix matrix concatmatrix dup 50 | /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point 51 | /cairo_font where { pop cairo_selectfont } if } bind def 52 | /Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def 53 | cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def 54 | /g { setgray } bind def 55 | /rg { setrgbcolor } bind def 56 | /d1 { setcachedevice } bind def 57 | /cairo_data_source { 58 | CairoDataIndex CairoData length lt 59 | { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def } 60 | { () } ifelse 61 | } def 62 | /cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def 63 | /cairo_image { image cairo_flush_ascii85_file } def 64 | /cairo_imagemask { imagemask cairo_flush_ascii85_file } def 65 | %%EndProlog 66 | %%BeginSetup 67 | %%EndSetup 68 | %%Page: 1 1 69 | %%BeginPageSetup 70 | %%PageBoundingBox: 0 0 37 37 71 | %%EndPageSetup 72 | q 0 0 37 37 rectclip 73 | 1 0 0 -1 0 37 cm q 74 | q 75 | 0 0 37 37 re W n 76 | % Fallback Image: x=0 y=0 w=37 h=37 res=300ppi size=72075 77 | [ 37.2 0 0 -37.2 0 37.2 ] concat 78 | /cairo_ascii85_file currentfile /ASCII85Decode filter def 79 | /DeviceRGB setcolorspace 80 | << 81 | /ImageType 1 82 | /Width 155 83 | /Height 155 84 | /Interpolate false 85 | /BitsPerComponent 8 86 | /Decode [ 0 1 0 1 0 1 ] 87 | /DataSource cairo_ascii85_file /FlateDecode filter 88 | /ImageMatrix [ 155 0 0 -155 0 155 ] 89 | >> 90 | cairo_image 91 | Gb"/lG?bjFf#L2^;%V)^O"I*2&QF]k:pH^g@ZMNAYp,&gM>+\F=LM_DA#s'.Ht%LB88ge!DOS2YcQBUc[GR3F%a31OMH"VH*Jm>`s4?d0fN0[HS[K7QS$\1SZ?&+O9,ai] 97 | 5CooOsPmcXmsACh;,VZ3%l%1PY1s22b->o6\dERASuC;g9h*lC4V4G\'A0b.XUPdYgi4'R1 98 | !qV=;]Z7k>bV]P:#NI[LNeseQ#+<0leP6A5)"L$oGu:Sip>&JldNKNu3FQ::4r^>\m>#Ft?< 100 | cB?;`/!):U:XbuoK@sm=q[Vap7I*$LqGYuVCNb7^6BliDZ9aF*eRV40(0*J^* 104 | 6'.f[b_A">Y(Kr?%Uo]pkieg5b65r!p;>$>YW/d.i8bEjU'2r\3QMapAcf:At!P4.,$7o6* 105 | YqU24M>$H(p_73'`p-bS-V+V?Rg#30BRPp/kcIFnfH1BljX&eHaBNJ7Dc`B!>7(0NM!h*e? 106 | Y4;#_TUEuh#sCD`@<'0:WPV57f"EXYb>d%Mb[M3cl+1XMq6l-eUO)tPTR' 107 | sH4bT'UCD"?dhg?i\W.8L4dMfo`C=+V+qo3]aQ&2)@mt4rm,*?n#U+12@Ra&J88l[&-C$@0 108 | ?@R?bLd>[VHpqAOA.[M7)b9>OBP/,#[^cMWQJ"mG((]6i 109 | d9P&!F>+AE^*#d(2\k9b]XWM1]1150i/.-T>nBp%LraDr4m9@c+=ohQ'^_pUE9Mq2H>?kS8X6,=h%:F6V+@ 111 | $Oc[tgq-Y\Y#=H-,6b&ee0k[fOsTZ:1P4j+qm>bF\aFQ^RdTJ\;Qh.6^2"ounFAp'*cKo*#u=H^M0R5],;^jkA>7YW=l5]^ 116 | BrUXmd4SZ0G-,[irGo0>j^Y]eCEHLth+:-ca8qbg%;BS>b^i;q+_#=A%hCTg;lrej:J]`PM 117 | %l`uB4mm*Z!`,phRl5JS2YcfCp)SjI<9AJ&G580j+T9R/9cOBSUtFm-U^Uu 118 | ph(1M-V`\=Y+Z]/*ZopOkl94]*5a'+n*'"#bKa!X4@ca&efmMMi;.9+rW+\W[`'E%qTd?kB 119 | -S555,m4i5[3CM-TOSaOr3r>F1[!u%9J/R,O<4_N"h-;@H;c^sJsc-0-#-YE/LJL*,,C:.g 120 | 4LlCEK+&uP!A&eps^jLQm-FfNC;H^K31,cT91dR!$#=q-1DUSf@_H?YFoB,n^hql'(k3$gQ 121 | Q)^k^]Y%'%a"jB)=/;7lAkc0e-L>s&q=@6I';X#hOpTl"?qO3jTEuq_4-gbLAK3kU[:?5:< 122 | 5HEI=[VT1b5(WXTPN./a,FI$J,T5DKS5N:"KB8TfI+#5C*b`TSDidlG.(*aCs::e*V9:0Rq 123 | pn\4lsA+I>$JMTQ7i8n8-U1I2EV/:^WX>n`\Ar+B8gh"uJ2YkK5,HA6/'Y]682]SuS]qmFr 124 | "h^m6Ofn5P>`_1)-c[8dNeV5">p?bAq`q\Il2\tJ) 126 | "U*a.](3[ifu(-n,F)A9(7H*Y)]Ol5B><*R^V"o[CHkf.] 127 | mBH-KV$B-]")6o;Fr5.la&WWqTjYWOX<]+?i_]IV(OAc'219#g:k.@ofMN@=**c/WZg=cr_ 128 | \aZ()Vkd.I5l`4Zo2l#F71)pa[r)o,fsB3bAfRHSgplA0bI,5;]Af/1fB7V 130 | 8fOV-iM)`OXFgW,U*+B#Xc#Pq-*dXto:)64 133 | lo;njI!-+O,iK$V39cZ5l$LT;GBSc'q9j.W3cDBMp`u7VL8Jf\4f&qX?$/7=$ 137 | o>\\9V&0oce5X]i>E8;r(7nG)/1)SqXs06dfcY2*$G=tg"NI(:'I:u&KcFs+3:j+q<%KoY' 138 | jG%FI#\?3n:5F';"2AD-3?s4CGsOn!ik;n]$-/s8D]3BqiQ6mNeZDY-(#'Ybl\k0e;ss5Q8 139 | a30=b/-=BIj!(ju`5pkh$X3I:>2UEGC]@N=U?D;3o?'9jOZ:>5R\euu9K4;i[L5PsdkIq(e&!3Oc-;4J%13Gg0258D<`[AqZ)""X 141 | `7Z#AJgn^#hb-1UPPE*5>l0aYBau1pEkiAQ5"JU0IQY4QCY)UY3E+->MN*UT&YOmi+"e$t: 142 | e#Q6T]hN03_QieKVkcI9EDalDl`9.=79j((>"#s7as^kSNH.Y^q+X8LZb[;U`;)YfWiDk#m 143 | 6c2c'fLNHM6^Yim:(bB?_V(BkjP@'[Xsc<>-]!TH@Eu?*Jipf5`+T++Gt;gaYNQ$r<^t-[R 144 | h?Rk#^-MFeKBkic[_BQS?<0kl6HM2A%ADlQg47oM@qacipUgM5*+LJfSQhQ:JQ]m=h2>s%\ 145 | o`U;RT#M?1]6-94$R51W61dTF4c:3i2C*i:$4M4-8X 146 | CBmqJ,[7\kjFAgcKGRk3nDW__Z`m84^0H&ST`4oe)Zcs8.#Le6PU2TXp:at@XRP^ETE9FD% 147 | IA7C*7mBW3;rgJklH*A48RE%0g%5o"%!^4;0Vl+P1M63$j"M/bXLOV57eG&e%S3TL%r=WOd4PEH*;!$(<\b&dbMM]mWId;F. 149 | If=H*$bpYJie_d"@!RiZ.N^0GSHEl?jD/ 153 | Ifp8Yr'dqW=8c83l"eh+L6OF<`k/R;hSO5^3&W"k:^glJ]";0(U@eq.HkDDCRfsBQhj-b(A 154 | Tip#3H#+XmXiZ?C*LBS&-\hFmu3HM$E!daUi%*)$D`XB/1XH8GV%NZB'15A=,WMbQTdHXe- 155 | .g"C*(.N9"5BD1h2e4:gHP`@)(fhRo7K5!;!(6O07j-6"h"3P)5Z'q 158 | ;^SrAp,b-QYJn_io2h&=@:o`j3qo77BY-J?E7oSjYB4LfeAC18CR7Hf*==8OJ9MbnM^phH$ 159 | [h`?rHW&(f.8Rl/B`_ip;e->_.#UK!P5-B*5/A>+DFh03G+M3:W$5RLD/Rl1sW:J\s.;$Ur 160 | SJuVnWM;6:@$Za%[9[OZTRPBF3Sm/XfOX[(4c!#YI]*e''e@4U`sn1mG1km16_55SX"F(a0?U9(K!=b.VXI(3EpJo[$<8UK=rh- 163 | qK.:$g]*EC,pC2F/??e@iKna6Dg/KP,>sk3V'kjJ5@C0@BEF6ke(G8J>-tB@!/$$dIIN,nC 164 | 8(!DR=Y#\[*o@`1jL,#"7S2PK!>=.badAu:qY)el=b]LX)?":(80:!cBPIDcIL-75V&BS(+ 165 | O'LI.n;NIV+/jL($WF*E??>LhSp$8B5NnB$nSNA(@beV">_@s?!]m>qg9c3SOo#W8\5Xcko 166 | H-s:$POcmM8O,]R)CR^1d)&tn#cc%d-=f2^#13[>.Xc`M;Q#XdF,KqnbE4-M?b_&k5a=rQ3 167 | RS&lC:,8&9a"/Lj?s"h5!6g/Xm-[kTSh"A:M*mSG1MH3o&ZU#%GAX?^ju!P#oZ!TGYsplk2 168 | sWf$S!JtP^;@Z&l<#MHLdas!N9J[Yk.Lq>*UTrOg8[7P67\R59Iel8?k2:n`/_(',2#jZ+l 169 | *&Lg0:(.HTV9[;/:t?0]HP,>dWt5m>4S^AX@59#['(Z+9()]oZi<5QE5<9^L_8mPKj.86]C 170 | WDsO4PZ%E09dF(q\mLDM@-QPWoX6jcNTW/]q&:O5:^$BX6$Q%*Z3sUCB' 172 | @PAZ1F.VjUupep2`LPo+^d#HIiMF1MG5@t>Zm-jk3n?re,&\[F]0:uBd0"dNZE1^F/r6;%P 173 | oODRWijG-(K3L%K'D2NaRZS3(qt&X6k#$-GGQL'1I`AsVTE$:*R!)V;AI1T.:t`?o%1/AQEMYd);P\s03 175 | r*Z-:O*EHQVToOsa"jQQ+E5TIBWpSQMRt;tZ6@,:,7^c$[+A`?l,P6tpdMA=F%N;(^jFX'm 176 | OX??(6HT=u1WMoXc#Md`)5.B%d%Q)KBS(+i8!EY\+\8FmHNI%>TKrnV.X+^#/N.ToqXbp,* 177 | &-ccAJEMQb9_gm?_WfcqR()c)UmEpgD^A)MN*TQ"jOY?V+mOT$& 178 | <34=NhcS+rWGk:d]qjKpdrJkdFHZ8=4]2FO0MV5i(4Y 181 | HhFCl`?rk?*mE\q;;nL\P`J";%o58KNEC!XRNN[o+6,2I2Jif)2]^.i3[r1IKSHO&&S&bmH0?=0`1eAd\e#!_poCL:&8eWH]#]+?pN<&q9LEB2a'6GTbS[?!3#![>>B` 183 | J&\]mDOG_Jiju,!Z+7X"-*j9^C8#'272](&KYjEFV^HRRq4f%NRSu3n\--baC.t0f;[)qH% 184 | $dJAQ+TX\U#g:Aa^r5]<]C?>LhGaH;&oYc1Q`VOBV\:YW+I!bgSf_3HNP4?YhZ/1_X&mG#, 185 | -\ohcl`$XdKrVQ>,hFo+5U*\0W^'cd6,d+;od8efMXPo%KrgPje]6)H`J@0=!^O%S;7`k`F 186 | [V1es40?AA1lF!7_Oq^b5:93r84Z9*h'hJn&`'@Mn^C:P+>l*Ep?Ipo[kHBFqFh_9MbPUPJ 187 | s[6]`QX`@P?;Y(<\;u85?'>];QL%e023oeDr4;:4=qAhI@=,+QrCe?PcLUNnN 190 | 8l:FpPSAU,=6)q!V6NQT1q\jnX@8OoYRn3PH]7M$,H1NGQbEe9Jbu*H1CKKVkH)l`p9bSfr 191 | QP((eg&b!k55k,@SK7Pbbg!8!BYJf^+GASuLiIFNnC0's/@;Zj%o4Q#mCQ;^)G'JY_hYdbO 192 | efr3JM&_iBS'NFM*o5tRn3JJ977;;KnX-T!XqkmRiA?+b2?[!-7Y"FB.gR"7\DGt3D%7a"V 193 | dGNSt!?IcA.8RoM_Qb?*m-n._aTAV."W"A=B0]!sae?8Q4?P#F]&;DV^\TX&Q]TRhTQ,^]& 194 | po'N2EC5!/oK83(&`1a>/SVl9p4J%lSg67XRcM[S5V#7'nm\KO=6$M6`qDYhI.$uU2EWG5I 195 | VlPaJMErZ-EYJ4+SgfPOprpYP[;uPlDKhT0oPAs7qn]1Bmk,4g[/S"D7pR:fjs#"\^>IGAP 196 | qA3Lp55;pLUVOgM>>me6p.V8\4^h+^iq@4&dU@.72Xdmn*Gm\+`Ub('<56pR 199 | AUBBp@ibg!+1/cbS=-g":t([Tu6Kug9mfQ$$r&*`N0aoD:hZFF65P@MZPITc([JH8@&TKZI192ZtqR_SXb_fO1g( 205 | EiAP=HTPq)C)8.`3gYD^IkK^%O7tQp*c$R@7X>Gh:/K7D"TOjp%'PR#MVKD,NE-.%PhL%!T 206 | Hr*WCSq`g.p".ES1:A.Gb\UYB\mQ]4f)'<$p"#d%0eL>`V?CLJ?!OF;^\G7@?G5?ePuS,ak 207 | E_hHq"E4:Z\M<9/2.ooXBSa\1f9[7U&4(o2/hSlqs<]Yb$U8f;6TEF:YWV^9YZ%mF:'41A[ 208 | 1&lieOoP8.pF"hYk9%l-96,ojjYaLacf:D:'cn$90@MWiQraPCBk?fKnRD4gNR5thWWe-0*__hqWk>`5Q:k 210 | unGDYd^\r>)f<4UMIm(8^kLgk;_LD=B%/d9:Da1XH)q`97>L>cern3)^0>i")rnmb6'B#Y$ 211 | i%s?0i@D_%]-@:M"2=M=:_9u7)ABGi&$u\4GBNrbpk&gF[=TVnT^ec:a-/kXTeECC:IEa,K 212 | C\MNAOHZC3B\iAj3f5m6M?Zpi/jF0YC^i`>eaV'Y/J9A2f#GuaS-`j\q6k.c35#'p;uJK?t"ncX7DI>"1YJ"bJK= 217 | >M]f(p)XDE#1LYOK;8,"Ku.HI_?:t6bX[nEp6k8Uh0_`7/.s\@o?2;1a=:m"e 218 | #S/2u(oh,GD)=-`AFcpsD9Mn,PKW?%2M6fo$Y"i.hb+n,,fO?uP;JKIZU%HA>Ur^I>DZ>;0 219 | ID2btV*^1H5S_[ZDc@SiRiZ",";'YFh1StM94-O-;R4N!2L":ZO6FVPh1 221 | E+D9JIY!k>_-CB9W&20,_DS?>"=C1.@kAD!5W2PbhDfS?"g9+T#SoeA$t?$V.&i"2rtf#QD 222 | o>FZ#\#%pJr&r!PTpj)2%?CU(p%t#"XgO;AR^t9)Dc=eE\VeN0gcg)eO`Y,;HiiNR8/R!+U 223 | B/r'I'D1+\0&6@$VBO%`*B#\mTcdKFZQEP_NY%9ME7-Op1;/@;?^jR!Zgq7PUQ58iAF(X4G 224 | Cs4t2]AL?WF]7*IJtifW2U@ic'#l?]WbW;nFQ/.RDcCn[6H4WSPb;suD\OY7oupgkkK=J)? 225 | 3Aa_FSL.k2_6P-R)&RqI"C/L!K0I2VcaYGT/=(B.P2<5.3464S<0OCI_P<"OJHkt!)1edHs 226 | bhck91(]lN,Q-Wl( 229 | Q 230 | Q Q 231 | showpage 232 | %%Trailer 233 | end 234 | %%EOF 235 | -------------------------------------------------------------------------------- /images/important.eps: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-3.0 EPSF-3.0 2 | %%Creator: cairo 1.17.8 (https://cairographics.org) 3 | %%CreationDate: Thu Nov 2 20:01:51 2023 4 | %%Pages: 1 5 | %%DocumentData: Clean7Bit 6 | %%LanguageLevel: 3 7 | %%BoundingBox: 0 0 37 37 8 | %%EndComments 9 | %%BeginProlog 10 | 50 dict begin 11 | /q { gsave } bind def 12 | /Q { grestore } bind def 13 | /cm { 6 array astore concat } bind def 14 | /w { setlinewidth } bind def 15 | /J { setlinecap } bind def 16 | /j { setlinejoin } bind def 17 | /M { setmiterlimit } bind def 18 | /d { setdash } bind def 19 | /m { moveto } bind def 20 | /l { lineto } bind def 21 | /c { curveto } bind def 22 | /h { closepath } bind def 23 | /re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto 24 | 0 exch rlineto 0 rlineto closepath } bind def 25 | /S { stroke } bind def 26 | /f { fill } bind def 27 | /f* { eofill } bind def 28 | /n { newpath } bind def 29 | /W { clip } bind def 30 | /W* { eoclip } bind def 31 | /BT { } bind def 32 | /ET { } bind def 33 | /BDC { mark 3 1 roll /BDC pdfmark } bind def 34 | /EMC { mark /EMC pdfmark } bind def 35 | /cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def 36 | /Tj { show currentpoint cairo_store_point } bind def 37 | /TJ { 38 | { 39 | dup 40 | type /stringtype eq 41 | { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse 42 | } forall 43 | currentpoint cairo_store_point 44 | } bind def 45 | /cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore 46 | cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def 47 | /Tf { pop /cairo_font exch def /cairo_font_matrix where 48 | { pop cairo_selectfont } if } bind def 49 | /Td { matrix translate cairo_font_matrix matrix concatmatrix dup 50 | /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point 51 | /cairo_font where { pop cairo_selectfont } if } bind def 52 | /Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def 53 | cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def 54 | /g { setgray } bind def 55 | /rg { setrgbcolor } bind def 56 | /d1 { setcachedevice } bind def 57 | /cairo_data_source { 58 | CairoDataIndex CairoData length lt 59 | { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def } 60 | { () } ifelse 61 | } def 62 | /cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def 63 | /cairo_image { image cairo_flush_ascii85_file } def 64 | /cairo_imagemask { imagemask cairo_flush_ascii85_file } def 65 | %%EndProlog 66 | %%BeginSetup 67 | %%EndSetup 68 | %%Page: 1 1 69 | %%BeginPageSetup 70 | %%PageBoundingBox: 0 0 37 37 71 | %%EndPageSetup 72 | q 0 0 37 37 rectclip 73 | 1 0 0 -1 0 37 cm q 74 | q 75 | 0 0 37 37 re W n 76 | % Fallback Image: x=0 y=0 w=37 h=37 res=300ppi size=72075 77 | [ 37.2 0 0 -37.2 0 37.2 ] concat 78 | /cairo_ascii85_file currentfile /ASCII85Decode filter def 79 | /DeviceRGB setcolorspace 80 | << 81 | /ImageType 1 82 | /Width 155 83 | /Height 155 84 | /Interpolate false 85 | /BitsPerComponent 8 86 | /Decode [ 0 1 0 1 0 1 ] 87 | /DataSource cairo_ascii85_file /FlateDecode filter 88 | /ImageMatrix [ 155 0 0 -155 0 155 ] 89 | >> 90 | cairo_image 91 | Gb"/lGC3B?o"[gm#jIF?67UY61^/M0"f)E%,"6oUT>(&IT:Z\+#`)#M5Ysa8":H\%+sNT)+ 92 | bUVGH7-E#lm=tG+n7L<-hhkqGB__7G&o:WI-(sr1OJl?I.,:I1[!Q*J,]82aM#0rioK0bj2 93 | \A$qJ4$\FU+6afe\+Akk<,^IgZY%Ito[](:enme_bgK4*@P[4h^j?*S.!7,#&+iZD+)=Cto 94 | ,ep^S@Bl]:GLBEHM,ueonTh 96 | eJo59qDpF7XhP].jP]%:=Vi\IgqV1hil>!_'T.fQ[VQ?]S_:.dnbc?2UQsE]gH@[EFR>OR^ 97 | OOeTOgX5heP;Q4qoUEml,ta3^@Y=JnDQ5,#F>#I($k%,`7=NNHi$5I(/t"3R,dD?l.&k1s7 98 | c\Z3GT^!%Kb<:G0cb>j2Og"YTRBLZ*j6GJ,c<.IB$V"8I'XsBP8g`'*#= 99 | p022,nZG!Gud+">6j2)[:qp<"U/ebT%<558O"RiC>]RB>kUY$F[iN)0bak*9#J?[fOpba=P 100 | 0?S!0T/H:D?EWuuqH2dW@!5!DJ;XLM,6cItXHN'LP:j$3Q(Pn/k.k2lpIr/-7<;9?(`gpj'j,61PP^OBn1H 102 | `&c'e"1t'E9Fq(6HR<2E*#0k/`Xk$$lUD[d3I2nGH7M!Ci7+\DIrM,47>Fsbh?9aQMd,;Da 103 | od7+o.Ce;d>qY2%q;B+G.Lh7uYM13"RJpS)?[/55/=MTKs.($M*SJ-Iop3m+o990^tmMqbf 104 | %SV\gBQo_RlLY_o8>,NHBB'4:tE?4&JDqT7D.GR6^B3]+3Oq!a5kr?Z[;+FXEbp<^N4CGd%F7e9UZ*H!r9'c 106 | 3JnOuh_HUta:(.HF9RXB^&>p2/A%AL[-S%P7,=^`9u'`[:*KkNS(]TRb`#Z 107 | 6+.P[&3T^"1,FFi^C]l8Q[#JFOAS/I;[L5d+:5L`!P"DAKWiV-/ZH71890J`8l$LDDgW%bc 108 | ^!H6_2\h,0/%;[opPO@>M='j@FRjmU\0,FseY>)T0Jo)&O.7XF8lM!r"CWkoe#aEHhF5O1k 109 | )k'Hq`hL.E2Y!,E0!US).L8iSdIei9_@$k/huHfYTM:E*OWmb^7=A4[V>q7gicT%p4SSR\A 110 | eOBR7d7^)RR><[A?dY'>b'Mgkl*>EV>c#&o3&JRmYl)&Qu9iPXb_]"kED]q(_"2ra3695N/^oI4d,pYsK>!d!t_^3rR\HX3TruYCtLG\fGJ9S[BqJ&3g@uBJJlpEXWF 114 | p>rnMQ0A&d#Y+0Xn]'::-CprkHIJ[K[ncCM%NZrE\J^e+,R)k4uIUI^i-)2GcW%BTe$LXo. 115 | dJ.'n$5JMp7k-1R?Z"=G\-I%dpL5,)Z-R6lq:/5i>A,/mdj-OB.C%f>n8uBot#>F_MGK)E& 116 | ^nO9!iA=&JcCB_JIr]O+KSGZ7On;._@F>dBl5ca$7DF;eZm6c%'t6duo[-11q<00a9:$jIX 117 | W9IPnhacYGncU5/_>rqI:5R>D!;0.Q-3\gXfT;o&aS#QVPL>arV#!C9^CV5_e7]/;552a&k 118 | Esq]Mpb3/C(.>1>+EUb:jTnkWf&Sc0F7#[r3?HJj+,):4:HXF`8Ze@g`J_Z6X*CZC&r^)9(;C53+VXB:%-cnS>B>4/a%eEZ:HqCqZ81f`3Mj 122 | GI9jNOH8f^TCheRc.TT;eMJsWU`fj[B8OL2mW\s@.#s&CVc)UMPm'NTOfn3,'.fRORf-@=d 123 | )eljT"s9G/UAdJtG(3LCIb]1$NK,d%U"_?%?XhY:iRL9*5:bhR^[pkFY!=lX+m90/YEpVbS 124 | XURJZ93;!$]DIt(/)7pK?9NI0g4@nSWRkY[j2[lZTYZ;jScn98[9[7%>$.(>)5%LQBRAHak 125 | +_o-o*Y3nY!T?5kE?^8r$ModlHh)7n!l`XCYfd#Cd=/Asdgf_@?]h6Oqoi6`?oD.Z"`Q'@- 126 | 9q.E]HEjJp%tFL3L?HNNSSG^U#KiEWKW:Nr8V>cJiNQa]?GX%15drd8Jj7S"g8&(Rg5*DG& 127 | >/;n@r5L-A-ec_B"Lg;DXR3Qg^+CE%@R_lb-`-[A[8kDVK8Q5E$05nUbD1l0.(+L.^Pc 128 | \.>Ie]U^[T%(s`-147`S;(11m2&ulsI8!^UuBrGt%uj4L[=3@X'Al6EFC=V4W7o4EF-HXC2 129 | Sq/L7-L!t)WVb%d6%c&ISfrDM)e5J?TAnrYtCj'i9imdtbT^k'^X;UjKc!(BPH0HWDj:1,1 130 | G^#\G2RS0$*X=W-LK;D2bTL%"/Gim>>d6*mEV-eBqjnJ#g 131 | nT\oE9\'oLdcg8`]_,=s/B[e7J!^b\aTN[K"n!82,:cgY'qG]BD:u)B0SmV:;P>p@G?EWZt 134 | W0c7h._4(s^!320YBEsBNr/^0Js2fX*tZ<8c_d6+Y1pIYs7bg%FZ 135 | cT_Ttm9UKO1>R@Hr#nq&$9p44]KY,pj\f,]M09>q5"e+T9L-Xe4]a]0>qu49UF.AcYqVU#\ 136 | .XLiDn19j]=h_K"=C(OM92dS^%G 139 | %)3%51*D1nsjU0g+bRQGaJF0!8`^4*buVH%HfaF)WL9aVBUF`ASF!iHkSf9V]d&a62I^-J= 140 | prr+dmLBCAPe[MJ'](a?>_aKGh]JBgqt0-!"H'BCL"M`9klTgH1h$T/HW=hK47DBJ>$b;Jo 142 | 3n]LGZWKpDf6%^#c/835HQT-4u 143 | rQ#jVqFY0GBX32fIsbB\^=M#W1)T$S*FC53%_ZEhokhAW@hVI+To+\P&8GIN%%c]b)[ii^L 144 | N\%:?OP?fP0.&>4Emo>HL0NE)ljIJa#Cj.![/,]M=$V5EnOR:'om5VI.j(s[R(SR-hq5A?: 145 | d+u1s,/(]^s;)*5FF(7(@K.]q51u,dfI)$XW`l+ZV-3La?kd<$pkudaf)Ja,"M*$$*abqf- 146 | [oA":e)a$:p#Y;Z#HS5FgUuWu(%PRC+.AZP%1I&5brUO[/#%!c]3%./P%73je=2dWBlDh0CY+pa=G+UR3T]c/U: 148 | &S'e#f]TrbbN[oK2O(-4^T@&m+(rc)f=SQ$Tj9:,*ip#*!hHa`)>IS)$?'jbIu^7n+]L3/I 149 | O9>lgnBhZKb)!tf@AC3_d82`J$__0r]=['USMNS1]IJR8*EQ?VXAfe+mSSK!HiT^iG)6:IG@^WoDJD"!^4;4 151 | '*?GMop'ai%R?F,e#]CW]H+7f`"Tgj+mC 153 | BV%j^[?oDR$r@Ug,@XM,8^fji*&_04L&G#?%7VL;t5IV3fagGmrBH_T,K42<1c,<$)#9k'*TjBT):t-=4X2N;SRpf0qS 157 | R%td,m$6TkCa%0(T70,"RG@4Um7T*(mIY!_Y?hNA?86i#fEM#s7/DV 158 | ]Ci%IOA)%R%W+(00@f6S/EZ^OJHL:%\'W7d/3mdb3H2Ott>9)9PU'lY`VrBOfbebfnW@W`1 159 | 4i&X:eQS5fZB\%kF#kYF74'Yh&rU;OOPT_3,IXX$=g8X\uNH=9 160 | i1%",bbH252Q#"E3PknI.bp,99S9SV*Zjf`g&)HYFZ)-CTh[4Y+jbRYFnikdg[S;N%aJR(` 162 | &^I:Mm^%GdT71;/_6r1D.GVcXaH 166 | E4)VI]78M;]uD%[Akh6^\SuEuH?<.cOgLJRCsEI5&Ch6iL=5Q$%72hk]&_DEaTUWgtmD/A]LC7APaaM2"7$'HcR_]Mt)j:BJpPG*a+jmMJ.QOr@Urk!4GI\ 173 | UEg$F]:[hPf/:m:K>IXfGObqtCoNSI&p"DKK\FXo 174 | OtNn-^1a$?*m_@(Pq$<.j_k>'uf"]?u*iq_[Zk1eG44f,V%s=ss1A0H[D^bIQZ=TW?hUfWkf=d?5/!N2#RWEoPE90*6pP_dm[7VZE:XD=&5,Zp*C2;6]m_V^tLZ$GWX0+H0g#R'BCm^M$8 181 | R$`tBG=k_^o!t#C:V0[f.LmJ.5#e9;[=7Z&h@Q:IGO0#,TAU=&_8Vbi^$dKA.iED`hVNpusp.2: 183 | :#WA`"#pn_QE#SId=6O.,!8Y[q+O1h^]H)+_i;F;;R"Y,mZ-c'eG626MB)b,nme%D\I"/u: 184 | c//G%g4-+-QukK^DmNE]iYWD4?N)aE`=#FSn;F_s0omHlL)Nd\RPHd8+!(,&lmZ>sOUZt[W 185 | FH8$ERX`:58Q$a1LOXj)=e`@*Z]hbW=qs3:hn,\ 186 | $Q0$EPH'Yo[V]6ns6-[tG@;bnjO\P^!s7V*5CqYe[?+Kunb^EacQL\(r4/])J,lSA"/VmU< 187 | .$Rt@RfkD\e-/jU!]ct<+KRb[^3m7?W-\%.T2a0-)GRiPGrVALCW7RGqL^,,pfWar[#sAr. 188 | .-@WImmAKcV;_XBd2>NA3=<4kgf,Y:q2r.E9B,-Vq$[.$WVd:.;$e=Beku5PUiE-lh/Z9oV 189 | %h-BEPk3b9?kFWT+O"1OO!-I\5CHQpq2ppF,NK.sC_-kV/\7G^jH.[+:>B^O`o%JT=tVPdU 190 | *1<%;I6$MiN6-p%^oYB-p*rl"AAROF7Xq4dl\atgOEn9#&8a_io1UsQ1nt$:Aj>%\pR/<)@ 191 | 6@DC<(2g$+e"3F9VN,k&GIh*KdpmdnD-G-\D:YViY_ 193 | _>s@fZ*IG&&7L7CW&WIB=q-%8aIgYHP,o[qSbX=-hkK'VmHclJj+g,:`uHjL/nUq'CISM,]QrPWNOZl]n[sbM 196 | YrHOb1;;iRRd8u(pnck-;cE7)"Q_h$O3^.16Y`c^9A945i"0i83!W\69ro1s@[+:.[5Bg!$ 197 | ACf!QRA=a1RHjS=#MX):"/n^!F*;.dQ;g5Ce\WqNhfh)UTUcK\;*QiqYFgB`.0AR+lF5P@M 198 | L.j3[tHYWiE)Ab0#U!MY(]9Q?DMJ9Z+Ag))A$TC4]9AOiD[BAh2@qq,BLa-Bi<-o?sYQIY]E51Mj[!uh 200 | ->tWqsE+>g#feM[<%#%gdAk[T4!&`TaGHljUZGJOUg+mqo@=CD-%t5o[0*d8_B<_*r=t 201 | ,?D7H.]+Z9YqT>u1Q3!50@CtS=D:Xc?=.dnbr2i1)O=sCX!OXAk+0M"K;Jh95$m) 203 | mMpbK\;VcHa.Mil"_YrIV2Z[*7Mg+A`G,G"e"A@5E%pA7\b7'gHa,GBapkRA!KMo^*S>'a; 204 | U/a^@)q%o*++J4%+(FJpB^E$3H+XiXNg9atg&%SX)W-RT--bfn;TaN-nH:>S$Iehf!4@7+a 205 | aULOIm[4kbt-2AGaW-jID7%*6,rs_2&c=khtpVi'>;H(D*0G[=d_o5^I"'.ub7P=o>$>)JQq9(;-d_*Z-`2Uu8sUjaGuL5I[@s"QPa%Q&(` 208 | +!tC\?ue\4761h`!?LFE8+'XU5?TF*12hnSZ-UmS3\7*ZiiB%LGu2hS/q^Y%8'R^t1Mmj2Q 209 | b*">D;c3>i5.1Vm0I=X@!-:=[UeL9$HTad0)Dkpf4XDpk%S9J\jr*e;8K`*A.Vg,*s;9Km7 210 | N$^&:fFJ-5WU_qC"F\T9K1%[4+Q6GMdp+Vt;5<'6'\*X(kIWfZQ;>G*b%U8,B^NS]G(]riU 211 | R;0gVFQlFOB*<@\h@m*Tn5Pflkpcu%9e["h&XWmEK#qC[dYs3H^30t`ETQQ/P8iD.HNr*n6 212 | j9K:B4mQ)2'RiC]TdYfk;YO*gfQ`D)Z2AY^Y_A%N`g^?-"e4:.Hi$*1#V$eQd"f]Yt;c!j9#Z!al"Z&_pK)#B\>]K 214 | H%e]X:4?b1>..Os`Un`/M&Rl6&j/>7N#B"$11.?iZ":uUelJ7:/b@$0R3";qhE:nSK&l?/4 215 | C*Q&>J?ss'B**0dVmG$g9]>M<*N^U29FgY/G+78IQjYhCRqH]XQrB,#Tg!cj-^bBR)$7:9H 216 | (o1OX(^O_g+/FL[K0a-J!`X6TV:R/,p&l$7:q0R=.#')N-/f/J\TmZ(KaS^BT0Ii!m+KI9L 217 | gQH">8e,0REo]J$dHr*3&nj$H<.'9^M5/P)9fsL8Pl`/G4$@/m_7*"Fq2JGbgc:Frr'=0=q 218 | u>ZoXT&+@&O&>L(/>O"t9Nu5mfk38BFO4XroO"b#ODCXK2)9/9_7d7l0]%t\;4p 220 | ue%.dkqrXGn_KYF,LkpS4_=/!a%/;OW8uGpZ'p6Z8Q2h(]`=WG@+[EkF#Sapqo&RpQa,_<( 221 | G3k:Y^AI3?2\uT#:#QIQ>,N;Y0=S^g[7>\@QQfYSj!_,PsTpgK4)le[);pZB^SJ.j;*^^UK8MN!K>5V_AXJRZb:!2 223 | XI:mHYt6!S+CUV&Oi)CIjp-(["U';Xb2N"^`^bDIb!s.5F&4o]VD^7h-V)]Q_DV*u6D02.k 224 | &pJ,^rs51GLFR@1lSoBH&?RhdU0^M=X5*IK<_KE8kB?n`Fh0K^0tL*rBViT?s!:mH_eh)H$ 225 | +"4aMCotGGY<4SB=l/o+j74>-UeR`$t0#A`"VBe\99Yn!T1d!nui5($N%NO."qsR`QqeTP? 226 | fZKOeqQOVY3?B5MU26epVcd09f;[5nOH<8XNE\VW`g/Jtp?pcHQ\F`I3ckYA#jrl;9c+M:8 227 | ]$Zj6sAa-Ah6CD]55lbFgVWUFXn7oimeU7/iEmh5B3%(m(j,g_'UVERqA=:Y0&n(nZJ;&I24P9_4"?MA=u:iJ/(1? 231 | P6L+_\27QO?^3tW!/$EP,N4FbQ:.Pd-)lJ!o-R0F19DZW+1gVZ8@F8?4_`;X-WJpYRCK4H% 232 | TNO13a-Ml6^+A*,%!`sE^V265N,u/VZ[QFN[2GU5cLo?X#\XDTP:'&8/nEEEj*&k(NHA\Yg 233 | UpV"Y`YO!132O&L1k0Ef(Jr&Pb!kR4Sr!Jg"Q+!F0F+q5.U.NOR,8&9I)D,@MI9U2=]<]o#"QV87#!j 238 | _GD.umlO(D5,\$"tLhC..Q(pfS5[!eOm;RQ1^A''Dl0kGonbOquPep\Y$rQg_RXcrlFMr,uneE8oPS#`EY)Yh481oTt 241 | (>oi>e-lrL&NQ$Fohc%;LPd3d28BqpY(//L7`kijbo3ETCEXe9/!(.uou6\/"N:p,(eJ%^b 242 | W)Ve##=GD&]7VjQQW.S'@;Ci_6'c"O-9qq3#':Y+=KdYl6&9JH*nrM-4;#an0RU_LhY!71p 243 | SI<$#qPnRM)&-Jtq!*@ZV.K!`>Lr=seX9LhY!7Zl$8EU$7Bp"u#%hp_@qa&9]loRc*4p:A$ 244 | +o@[]'(-ELq@"M4:n5"K#iYE.R0fKI&`+DKo4JX?8He>n^YPc4HuEY3?F-e+a/'6tctjG@O 245 | prstf-NB$~> 246 | Q 247 | Q Q 248 | showpage 249 | %%Trailer 250 | end 251 | %%EOF 252 | -------------------------------------------------------------------------------- /images/tip.eps: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-3.0 EPSF-3.0 2 | %%Creator: cairo 1.17.8 (https://cairographics.org) 3 | %%CreationDate: Fri Nov 3 10:19:51 2023 4 | %%Pages: 1 5 | %%DocumentData: Clean7Bit 6 | %%LanguageLevel: 3 7 | %%BoundingBox: 0 0 37 37 8 | %%EndComments 9 | %%BeginProlog 10 | 50 dict begin 11 | /q { gsave } bind def 12 | /Q { grestore } bind def 13 | /cm { 6 array astore concat } bind def 14 | /w { setlinewidth } bind def 15 | /J { setlinecap } bind def 16 | /j { setlinejoin } bind def 17 | /M { setmiterlimit } bind def 18 | /d { setdash } bind def 19 | /m { moveto } bind def 20 | /l { lineto } bind def 21 | /c { curveto } bind def 22 | /h { closepath } bind def 23 | /re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto 24 | 0 exch rlineto 0 rlineto closepath } bind def 25 | /S { stroke } bind def 26 | /f { fill } bind def 27 | /f* { eofill } bind def 28 | /n { newpath } bind def 29 | /W { clip } bind def 30 | /W* { eoclip } bind def 31 | /BT { } bind def 32 | /ET { } bind def 33 | /BDC { mark 3 1 roll /BDC pdfmark } bind def 34 | /EMC { mark /EMC pdfmark } bind def 35 | /cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def 36 | /Tj { show currentpoint cairo_store_point } bind def 37 | /TJ { 38 | { 39 | dup 40 | type /stringtype eq 41 | { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse 42 | } forall 43 | currentpoint cairo_store_point 44 | } bind def 45 | /cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore 46 | cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def 47 | /Tf { pop /cairo_font exch def /cairo_font_matrix where 48 | { pop cairo_selectfont } if } bind def 49 | /Td { matrix translate cairo_font_matrix matrix concatmatrix dup 50 | /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point 51 | /cairo_font where { pop cairo_selectfont } if } bind def 52 | /Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def 53 | cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def 54 | /g { setgray } bind def 55 | /rg { setrgbcolor } bind def 56 | /d1 { setcachedevice } bind def 57 | /cairo_data_source { 58 | CairoDataIndex CairoData length lt 59 | { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def } 60 | { () } ifelse 61 | } def 62 | /cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def 63 | /cairo_image { image cairo_flush_ascii85_file } def 64 | /cairo_imagemask { imagemask cairo_flush_ascii85_file } def 65 | %%EndProlog 66 | %%BeginSetup 67 | %%EndSetup 68 | %%Page: 1 1 69 | %%BeginPageSetup 70 | %%PageBoundingBox: 0 0 37 37 71 | %%EndPageSetup 72 | q 0 0 37 37 rectclip 73 | 1 0 0 -1 0 37 cm q 74 | q 75 | 0 0 37 37 re W n 76 | % Fallback Image: x=0 y=0 w=37 h=37 res=300ppi size=72075 77 | [ 37.2 0 0 -37.2 0 37.2 ] concat 78 | /cairo_ascii85_file currentfile /ASCII85Decode filter def 79 | /DeviceRGB setcolorspace 80 | << 81 | /ImageType 1 82 | /Width 155 83 | /Height 155 84 | /Interpolate false 85 | /BitsPerComponent 8 86 | /Decode [ 0 1 0 1 0 1 ] 87 | /DataSource cairo_ascii85_file /FlateDecode filter 88 | /ImageMatrix [ 155 0 0 -155 0 155 ] 89 | >> 90 | cairo_image 91 | Gb"/lpKpp%f'r^)b`eI:eBDHp-dOT4R7sKRbf%nrW%iFRRTO%f]f2`8-n8 92 | <]bL8d>C]]bu7M3U<2`eh6,l+t.&0en=rSE4P_5rOo9"IJ/-\A6d,c>fL>[`arDpAR*5d>f 93 | L>[SuW804_eftWiBt>]j92cFM@kV%6oMQNV1h]Mlct"J9<ojW@-&>ep$p30"q49EApKKGfP>GN>=h#qSnI]n=>D10Bq^?*:(6:c]S3XTpYJ874t:K 96 | prNF0E0U%Y+1a,X',#H$(bTF%a5[CUAMl%l-a15WM<@u\PkEYWp4:ITa<'ZPq'!k(NuuF"\ 97 | G++6T^ObfY>3h:BrV+Ff 98 | >#FoC;hKol6(>JLcIJ*UD+!Tk3H8GE@g^D3"((L@hAc\3c4efC)n[c2.L$;L:G.d+>SGgeK 99 | .i>8?qUA@LN&_dC.O$')PR2"]U+O5(*jn5T^?q[Xn9om"-[oPleMX*H2I`h2RrVk^2^_+6CdpqZgb!1RUFO/'EJ,IG\CAmk] 101 | UIj-M@m-,f/.g_^HZUr6OBt7U9ZXluhUOP9A3KlsRLU1gCSO%Yia4^bh9k$`]U!n4r5)mc@ 102 | fEOR%j#nQosg$55W/X\lF55Z;\9j*-on!4*sP#DpkH!+q`'.2hqSO;P_1KA0B&$$)?4&hi4 103 | =[a%[,JrVL(Y0"0Z^[OdaoNE3t2^J1P6S5ja'939VIoY&7nll]HW;&s[dpmd'hPWVa>\diZ 104 | $PA37\NVl"WZpX_6FgoG><^K7%3quF+^?3C,7VP,"Mpo':.g^12O`MC/;?V^,N':h)5j8BC 105 | _IpcrLB9_uDIl(RE"B.Ael]`Uo._=LSSQ:%f,\s_^r60#Ehd:F#O0anArX&8`@oVCjZaPX* 106 | nP(tYOr]fg>'Rfu3Fs/8Q$'2?ki!;A\$j-uZL:#_^Js+?3I.#RQ%cS0-GAIh9G5S:gss9L# 107 | @lXskkE(JK$U7d-L*Oga-lp0f/G]Qp_XiC 108 | PGDA 109 | `R=6mipLL@ldI1qcab/&!@=V7eDCMRSO_6[i?rP2F$(h=C_O#;^?Of7Tb5>sFGTX7D;XVed 110 | )]j936pqi"$u=]>ho 111 | 8,;N]8Ltcq)C(N2TioOr14.aZ7365s1+Bh_cE<6nbk0FZdboB"jChX>K?2^DQ0N`ks#/b1W 112 | VB[lc.=boN:4DB@Fqg.iPJPrV:)6?F5G3cb#I?`V&NphjQQh'5l%:m,Jh^":3Tlu^^oH\n' 113 | Bd4M+>\@UW-/c8D/FWpNK:/D:Ee2]U5HF_4@k(E`hmY2][`6:sS&t;Np0%)A!3:$LV3H7B!+a 116 | Wr;3'JMlJg_aWVa.k$ROHXbNVf^cUS&ZZUGn!#d1H'TYMmLMu3CJ)JZLn!u.s7I>gP0,,lL 117 | 'bi9kpq%/T791'H[Hj/U`0$\-Q/"EJ\%F6^qSSpM3sHg1l=7/13/Q16d9".+,6h*"aA<)"8'_-'n 119 | O/dm-L4T@9/*K2=Ja.`n,]BC?6\X_nnJ(DNA8`]!;:08DG 121 | SR1U6rAM'+:K\hrKj#hjC7;#hh1$d=NEJH>@HqQf"-e+62!iWLa 122 | 9\7$-D3!#oO#ef0V+jcq5sX*K2GO3Us.41BM_oAkX8(innS*XGbkGpi6/ 124 | r*ZKR:I_%LOSZTG8V@Aa4)AMl(1>WGXKm=2BAMQZI^]C,1-b:B`:5dRc1N>m2-?PlIlbHDJd[;R6)BYma[M$?"q2N>I80.PSPAf'qRhM6:[* 126 | 8&SLcZm=p_Z.qJRlV3u5mBX`'b6)jTAmll&VWhU8=@l>`8M1CX3"h#o]AO3uXq%%>krD\%T 127 | >5Bu.lG[/>Y:="C5#_FAb)>%-Fg"Ud/h^TJgu$dg'a&d6!TN72gm;h31u[\\*gq`IZOUsRJ 128 | A4]VAV*s=GW$G*3=uPLdu0">]l@QVc-f@mR'jtlRMn(Jl]^qaP(%09Qf?PTUWE+*AI-o'8s 129 | J09_#'[CE6WJNupTKf,i/gFH5\RnMH.SS,TtNQm`!((c:,Z1^uAoq,lce#tiGfH&`(s!q)h 130 | kU.t_p#C9g9rbqT9N&AI@j$g86YaR!S99pnW%kP6B^bb(eR>!8e_U)"_))/L7I=n9bKM21? 131 | ^V+D$g?iQS5%pFu,*4+X-;E@A=qQtiZ.-T&PSQbSnEHfC.hBS#p5VXKF3)e;TSGsAI45*ZT$jPA#@OeXX9meNZ0CXCsN#F:#!$IQ 133 | V>J&3=O==l8$-fuA)rD-Ab*2bPABU-uo[;48hW%T1*aKAC)Qes'.C#[6LiZgL#E^?O(Q%2U 134 | m-IrVKc(I/G>(0SOe9nTjXR^#k0Wh44@=PMKEoo[>jjP6jPK'=pPEZ_>37UCYV0-J4dX_mI 135 | h`_G/;0'@BG;;h=2loAZ:iS/mrf5L&OV[DG>phrc!@efO>+f#9iWF'<35%6+/[K 137 | ;N4I`5.N:[U-:jHMl)RLltV/eO]fLqdrUNb2PYIF`=h`!@p, 139 | n?:hoFg+qFJdgHlK#8:Q-hJ1aHq$n_On=4?`J9BCYGk-YS8T\R1*"Oh(K)6HCCWcrFT+j>>HYHVN;&ijnp483rF 144 | @`pUCc9+FKNgK@B53o2K6Qf@eX=:$4?)rZ`BE1l\A`ne3%?jt4&qI'ka3kQiom!DQ+M"pQ1 145 | *YAKj!(B*@gE)XX[>XF_pXD+e?CunA+U+5pO.oVUuqFB[fNgG]o]IEo]_IlRVWnt5J*i0tT 146 | g2tY11-9)Y[`7$Rp:%g01X]Hjk$Nj1F/;Ek1JHW@:+%Gub[KRFmYnLT.W?)PId8%2@qW`pG 147 | G'\C,2=i[>@c,);lk]e-Dh,6AF^Z(IRU,QZP/W.d`RdmBhNd`<:P'HhjeuQ=\$oM)l:E\BR 148 | aEik\SLd60l0n`C'uY5u)-s@Pg*_1jlBrO]%F9lS^VIGN?G@7mQ0F7L.YU^a&QR?'1TDN!- 149 | Gb#hL2W2JZ\H:>#3Sn4hs!YF6o[dg\h,mBRtIKXm7lpR)'t)LW/[l"WoWCf>@)B^"O*>#oR 150 | ABjrHUd^0E]pTYo7*;6T1>e+bS*=bdH>#s7`E8T2K]Hj>F>jp'c>WiOn;hc-Ro;[9QHWcs3??Bc8<,k%@k>Z 152 | V\Mh_oR>f&d@FKf0Ao3qE;=](H*^C$j>55 153 | f5C#Rm'J8EJBbL%jo*&^YC/EV+m4rh2DZ.7BqDCrSW`fr@B:hEB6O:$QF5d+jR:28dq&AUB 154 | eqEecR20_0;Y7b[s?1`93rFKnpFf6a8MFc"\j)K8EV#1c>`b+(/SlDMj*/bc;Xb1ADH@l!I 155 | $NO6F!o@MIbd;e#f.-=[ZllR'rr.faJ;R^U"\M^-0PU`E6'gnnMfu*6/?D5^s8>?LZ$\P=E 156 | s6+?DX$ICrrnBgI$6qd]2$W::%>`!#&h"(g?$[W%NbcitW(9sACQ[-VQ`Te?t#F(B?Rq5\r-d6jD.fm(gWoZcPIB*$gi@CId?(j 159 | r9YNJmk6QM;:B@MJ>PBW*rdsVb*H-Y4dC,L\jB+*f<9n[&G;cGSJ_aKrJ@=m#P8f2V'.%jC 160 | 9hGi%P;;I&^8[V$anFPY>Q:7D8o/s2$EsI[.7CX=?*\0mO7&F1)RHjIEhFAardK8%AmbU6@ 161 | :&f^[Mn]k/CSRs(AW&I/6S=A'"SnXR=Y.u.^8 162 | h_P.Fr:7e^.F)InrABthXUt/eCR1K=.;ujs)q`hl?nYnTF@t9X-Pa8VJ7jqF21aGOI5(Q-S 163 | s\fA++RjM2q9[PMP/PUBpL,L0/oQ*G26qHCd\aEZqS7Ub*oj3*SPJ"cl&c@MWKE;a\*R(oE 164 | -&7NTQV2mKF)Kje/h+#G$\+fM;*n[H*PA5-.,@5VOF0iaA#)):A%CJ+V 166 | (.oV,4qjYV/Rad.6hO,TW%$bBO_%W(CUHjdL+1<.bZ[J,OGmSI45Q%MT;U#@dF:_>]4n\9P 167 | u.Z64DqLkG_4B$-KeDXc'(-A!*6#EcoD7BU%K3mKjmQ9\QR<6/X3h0;M^5s%j[l%Od,fZYu 168 | `-#X-kcVO+iiZ@5D7WpT%aA8t"*kOqCAh)Sm+.DfT`lAY^S/EqSV.mf.`UE3rXb^[A?R1H.@DS6rT!sP!tl 170 | %Yo9JHSme![BeWOHhPGeXO!j0W#6h<#S5V$K48@c3!@gdHr+(s@h0F`=TDSVNe"V,fEZlB7 171 | 9+iD]6;c4]goNr.`C`P_Gp+R%qYfn?VI^@4l(UZ4MZ5jQQG:X*$'au1i#4M3==knZV@)`3R 172 | Hj4l!E5bSA2#9;MSi"NVf,s6>T[Q2K0t\L)`fpK9OE&jA]nTUiK2UsT6ZG/-RKJ2'IQWJAV*Er$I:WCaYs4\cL 177 | 5'92AnGg<>:t#4h#j`TBGRD@WQ3.]8!5AA1n_`nJc\3<)ETnTAZYGo?0.7O==ph6Oq"M('/ 178 | iP7LoMXt1S.cq0+FeEj&KV[RZXcC^1m]iUg@,9)*oDJ^E"KE_Ya!mBVrBN1Rd]:IIAf\B9S 179 | qE_`YIj7<>p,h58KK+jC.BVOm(Sp.5jH3aF!hTEF"rlGdSrl`5G4=+QfNSZ&FfuK'$C=G1d$IrhXh7=1"]bFgEhsf01qmd&u1l/Y` 181 | 88@c'js`3BoZWU]>9S(uLF*H,)W+Kog91UD=8g67):35u1Y3\[^?2Ak#$H\G3FOaEbPbU<= 182 | cT:4eb_nUnV/j?bH`@hO_QJC5"-<:rUU,5r.AKsN=`&cl"'XF+"[(d#12S@;E5c7erWpZdc 183 | At9c`-7*>Y'%HB'9qL=sZl[G0"MC+>TT^VGtq(<]>2>&%K1[+Y%1WdpFA 184 | "Q*6Bg!if%t8)X/QEO[c8+@pYMq!2lq+UuO]1m8K=^L!qtciYnZsH7U>298D`,-Yj/_d8Mu 185 | "Xmfl3OM]]9M3*lI$ni#E%(-lDNhiaWe./OnlL.UC_lfP]=mJkZCO/)`aBD[TCLp2' 186 | btQ5gC=P(+0I7U?,9o']dZY9R)ULYNC3eXcSlb.NJ%IdQKKuH<$D;<.RNV 187 | F*[c8;NkCH0UeLmqh[bJX?TQRP4aum)UQ?0KrqATWOb*mBDTUCMaFSqhc*(`X:EX&4L.C 193 | `G4VmmaQrGTrGnlt*l`H4Xh%C+X9M0\[;FpWmu4UYFZBYpul2Ltq25#_=F7*6=Xcid;$idV%3+p.0!mHl.X!,-4J\*NkFD#me4 196 | (08[`2,eY5e>Q19Q7m"^E^iYn(ni9VVFHjDmEA,S+?-ZL#ma#koOprd]C9]C>Mf"],ur315 197 | 6:B1$%.1prN2.k3A\ta84(\VP0fU(M("UM5SJm#l`!PXg1LI%R*&MK:q$gXgOlf@#A.l^1p 198 | NX`WLKn.;m#H&JuP")nj^d7mWR0d=*rab'b;mhB'%IteL)KL=5Ef(5g*nGKQh4tm$!"W/o+ 199 | u$MmAtJp9uRn10[+JHTElWh:\5R2=lLt&J>otp2-^G;@?J[V.'FAmWQ?iCZh+U*C6>Z0RT',L#uilso2`tck5a"/!<&MgQIVR!oM5b7Xf4:oY?H@JMj+T-$DkoiJSPP6p,oR 204 | ^meeF(&f6%4\0/W6WT>$l^eE,\[Cf+=[X$GbHC>2PBo3:KukF2AcCJPct)Eh58X#\oJR^7^ 205 | qL,&_6]7g8N4PIONtDO3Nbl(sJ<#sdaCJ.-*_AWrJn%4ctggkt<6Kli,DDGB^:S1RhSN 207 | ;:aA!8?84"7>OEmNLQqBM6[&YbJfInm0Pud]e>&Sn4e!qd.L/LuVJSWLOku?ItY@2EM=(N8 208 | COIP(@dupB_Kc,>!c_Q7WBLVQkcsLnk@lf+,n'e2G)'rN0+I9$rijk9f"I_2\L]/HI=TPst 209 | !s$pK!adW7,?[d1m6f*hD$.FU)`7Qf5m?Gl;rN)q]2X@Y-(,$n?nFm['pPV#$rg!Vj[AW*: 210 | f:g,LSmHF!cqja(o/j#(8!.ogbTWS 212 | [^4IRPJrO&IAJ[2[3)5^VsM$QO]G*/X@m14CQG7!<_'+e^_p&%FCdR/\Qo`ms84kFMCf=7d 213 | LiLWSr(:7S7Dn)m>11Y$DmpQ8+K6ki3*gJS'tm.CrY=R]Sf95L&7IG7@+pU:je9FO';.'<' 214 | cO(T,Dm1*V-dK$b^8!S$VJ/:b%O/b?G&\^dJ<buf;lkEa1^jsF:g`'#5i]+oB1*SK9/Hen 220 | 9hkIjpb^qPcJ>1\)[=G^YqSnbGL#51n*[*tp36l2-`2DjNVo")?C2 222 | uq[ZeortEhLUKp^f3!ccKbhJ/f.!^=0Kc)bk?YA08a:GH3#F;'i8UGiW&[?KL^L.XLsLUm] 223 | 4]/.[PuBJf%>I7Ap@=lk,?o=6TDc)3It(=90+eesScb3]]8b2uh 225 | PTMeu8JI5+9Tk7.&?)n(()%d'L(io^+b&DkP^O#ofA2+oMot%dCcV]]^XH"96@G< 226 | 406R.92QAd7c/"*4DB\BG,pdd4MVtG&W6/II.#orLoSTc2^%CO%8bf^K%S%b#S^LQqWoS$E 227 | DJ*86B0YAM'J[5pUt^Bbj,UEPdUT-M6'm]r;-S5f1!\OK`cY?&Q$.ZFU%L-df[iFD,=h4?Yh\PPSg.so_d 229 | 6eqR3S$G,$X8o(;IfLTA^\^4gktNJ4eS)&?&((-4">:MZY2C^kq(`6*n[JSjf@'dh3-YN1] 230 | Nd.1@[&rl=%9Z-)>PK?R/kQkEGM4kE1p5;3'7-ji?5V\">KipZHFeF($\6LJ;,1,ct6X9cA 231 | PQ0Ue/+Bp7Z4,XpGkT\h9C[7Q6tTP_7K'Bh/EMRY2RP9R0=i 232 | #jJSZfGrgDZ#MuAlK'`L7D_ZceFrLRTDChme?[PP+e6b 233 | 0M!Q3YRN#uIrH3:g 234 | gVhA7r4SKe#(D'tO@I&AB!mM+'::srIU\Xdi"!,-S#GM=f>CZfFtH]Mp4U:["*;?2t$$Y"V 235 | BB#7!m.Cp^IML7a2PlA+eZ:W]&IWRL^dl*fWQR]Ob?Ips\3u(IbQ7EeJC(I687j^S6g[oO2 236 | W>5MJX!pepf!kAL7[MP0Y23/d7;/8MiGptPh95J2g<5JalfHP!&T-KQn9]3ToE3^j;;HK^M 237 | 0)>t6reOS(>7)ZE/@)l_reVBr^9lWB=D,sXX4Zm[nhm,Oc)EXd`V`85ZRtJ5\=4eM_KJ*)) 238 | TG=&c1PNK?I_QNmaiZo?9#f:@[AZF6n0KHU?Qk-Tm4q" 239 | c&:h7'7A>?+:FS"\#m84,B2_alnO1Aa.Q?u"J;^5\]_"ES2`Icp/5N<%UZaV'\&EE@+`Is/[ 241 | @Y"PkiY\UP_6'hA0PO/,P:RHdopcm=N4".W'tqL!f0urR@JPI6(arrV[/M27K)TeJ#J=HoJ 242 | V%]%Eg;(bPeFJamAeZL0`G\;qo]'2W><*7\IR51?CfCqZ*ms/a6'm-"4/#6:cj5pBg>mK%n 243 | 5:$4>!CTc<-seR4%O6lg34;/TeRe;qjJg,X/7qBq>bc?%V$#M=E-15*=E%K=^n,WJjf7mrN 244 | Z-4'=I[^WN#-7>pLJjcUq@S&lj/"PVYd4D^\EW]suNO[W3-Sm0h[H?8(>iGZ[8C-*2CeGuFEtIVJ7J?&c@$(&,1h0fD 246 | "%pNeci`5q`d0k+-B4Ud`!7L]Jjj!B,d0EeJR6[+;4;,r>IUh0`lQc%3#9%I4a8kZ1=AI!] 247 | 'Xa*R-6$j,4rI/@Mk@OCuJH9)Y7WaBs,0j^:UjmTX1u2Go-T9K,UYjV8k*naB3#UU:HP=)F 248 | ?,R>CS.,:mM^\3bA!\"S;9EG)Q7+i7/4Z^rFVDng\4i)P>ZV'eq0CuQ0k'H9kI.hOC&oR" 255 | Ddlb%9=u_!n>C@89/P6@_aU'Heb`s'WZEGHTCu"#Y3>+Ok<_*7W 257 | a>\V=<7_UP#5%%I%u?>Rl/6%!eor)GWp2S2=9DaWI2+9P!*[WD&1bV-o\.F3tZ.9A)f7o6= 258 | RV%>jbYCNYrU,]eZucVPFT(h6f?D*H,u_;cGl>+S;*qG/ 261 | Va`-\[G.^;D/Ni="Zr`_mq?D%Ib<3Rg4\1?Wnu,A?3X_^7mIhn,suD73b?6(;Q#nUPO8/.. 262 | ;_sNUZ\f$jTjce1jN9??+fI-G.Y[r:u-9d^i:)+hfl`:X"FCmk,f'L^i["HBrp'pDW4b;?j-Wf?kognC4S+\I2j37H 264 | >s1kfebV#;6TRL;5eb9+)gRWQuaZ)()Al9UJdg`.i5:\6i'cjY:nT=gpgb(;)!;+XdZQWlf 265 | kgS++@+"OhtUWl&6K/e]:)AJJnR/Lbgbm@lu`f6oD6.?>nPg%Aj=V123,#-P>8fR`Rfc+3r 266 | hIP""(IKS6ik8d;d!`.J*tljR"StODLZ)<5pB_XZ,4qbVPbeM.-1tV]%8h!Sa7&dNm=(!a# 267 | '^cXHoqQo'!_"CR/O%A7#?CU@ScUYkUM0?Q 270 | ^le<5$6p')OH8g$h`&sHe=!GX_@pa$#rLsNdCBZsob2d=]Ail24NZq*M]r]2TO'-),dQ=_. 271 | ^7D%@VmU`l)hf^Um*(DG6VdI_L*eMgMlAXI*!.5AJ8'F)f4sc!G+]I2j'r;^Gi^Ep@-VHtT 272 | S-",WTnhQ)V/Yc2>@eAIRoM1+9t7N-O<8Tbq!Je+a9E9V:9;HRYH,?gZ3$Kf!W4LqhQq&ms 273 | TQ\)6HIIM*NcnmI^@oV7o+e-sbAR^6^U/ZMJ6:Y&H(Ji-=c7_J^-XcPoCD;"lJL2%A,iC[a 274 | 2]qoMIMnVD:/Mg<=_@ehu)Ni!RQI7!PZ])$Q<6V:-Yhq\&,_NVhJ]fL"[7:#4]do4H7U1u) 275 | FKa[lnq.Vn)L,flX$C[>f0k5QtFE@o2oP90L 277 | %@LL@D2eA@j?;G+qHJ0MNs;]i9G&d&V5GQod06RjkiQDX:BmEA^#,dG_?<+Am04kQ9uS;0jdpCJ\W^K6DjpTLh4lTZ%WVK`e_[\,41+25T9S 281 | j2[3&4aOKqBUjGd^\cM2US*KS.]#_8l%QOg]]/FJ6A_\0pJU`Y:22\kmMF4C>tk%!6XU^gk 282 | PV=iq5("*]#LiE8\/oRN^l)T#>M9YjU)05UN\O'dGn)OSEuZcGMdg-1o+u\f\>0H'pL9,b: 283 | Ae4\0WX`mT4>/$t^4)Y4kLc1GqP"lI9G.KS>&JitiV(1K-/\,p'tKUXT`oSPN.2G%FbDTL` 284 | "%Z_0T]H#iXEZV@&o(`XA:(ap6(YpSU([)8g6,bn_FR(&l##XF%:/pCRDE3iJPZJ=O]&=JQAG%JIM4GD] 286 | =2/ts()S#.,2?0;#&UHPWC8mo8j?Yb0"%IL""q3r\bY4%LOnFKE\6mGIMsPX,_`4t4>;`Kr 287 | S#i$@N6bZ'0^PC-JcsNNTS:hHmR=5Wno/\iLb9Gi@`cX*Cgd>FK,+#W 292 | 63fsoZaSP'@ZM[&7?no5[[]%E+"K`.1@+'h2;qB0rN!S27gB1g=tB@;Oa,D)B'OZlKW,!C> 293 | _S(b0!=L95bOZc.XF^iAjAp82=KJ=)HVi9G 294 | ]XJqMm,iqf@l\`^.2-g+LeX_`_NZ%H`.8fO#&NUF/Q7Hn8MdZYGN^g^R+SP"HQj6Ci8Q9 296 | ?o=;J3l!4YHWP]&;cd[UCN4Ji_>T/bpE,]#[t*$fFg6tmE7C8BDml;8S-*4"UqU8#_G 297 | 8psD>D=)PgP8(FTs-=N9R:%KT\q6bU*k=-&L`#kiJqg5^0OQ3OI):M'qb?Zgr%9#GY*Cn_D 298 | RCH%'37*k*361,:Is*TILP6DP[.&2hrSd;rD9HQJojBp0eLD-k)#>,^g9;/d7 299 | -S/N=CJ['Hu8#+D*RIH*fZc9mqI2Hc1@U:rPAF0FIY\[2rh-NWW@Yp)RrgS*?JdKOd#ZDg%C_F?>B[\/Z%Cm)^[g"bs[g>":J 302 | ANF*$YB"SMj>0a(lGrT@T!>1$ZR8?7 305 | ?rb00?&!ro/ohS3*.As6=7pT=\H_HUC5FjoJ`G)sVQ0Ib7HYhAb2Ej;:D?Oh^bnG!AJU&aD 306 | m>Fo#U6.L8>Tdunje?k0aRn2oCKKQU2gV!>rDP(G;1JS=qrd:kHC_SV%[fLLrmV:k1$r$'_ 307 | 6[i_D@@:=ATE&eCB:I=5ebiZJjf2V\O$sBa+?"3EmsCR*pp0I4s0I(f0,fS=,cOhsKa]YJ^)S`[;oQ+YGC 310 | mmnDB(N@02NO_lF-9RV3GhH-WB#8Nj"H4#)^K=r?[oC4kj&M=SH&hXnO(bQa[*c;!rKZ-Ze 311 | 4jh`abk;90'8?9hTgb#:?#0U0HGE2hmN'$f'J"K;[+>$IprKBNY_C1,&-%;I(\N(=O!hM4> 312 | &\/ef@bs10lT]tQ-@9(P].jd43TB5$c5X?9kuJ$^6kKm.4e!+6:Nk5-!"W[&;/=91P8-%iK 313 | ;OGMEb>0qjV<;$?.6A=/WWX]#&[SJ[8bsd?Gk\(ckb-QcppjPd4`',3=O$Jt*0(>9Uro#+^FMWOF]+"?o:g/=-!Q=DVfSL 316 | mUj3_975TbklmCGnjFka&mbPkHg<2cO-]RbflV8`^,*J(Ti[tr,_ltb%t]j0LifSDe2N,\CZo$DCp?\i'F(i0F+K-Vfl-g;6a)h 318 | 1%.qTPca^YhY[n6br6r!Hd50?1bY5DM0`I.4S'+Bft:1dXUFMN%\[&_dJ&Ot9XCk(=0;`L0 319 | C;7=dOp.2*UN-!O_`O#7Dn@tY5H,)dQ[!qHN,&.jKE+Cu+'L7+n]EMXd2'_i@>>Bgf<5`-( 320 | 9%LKuU^h(=#+@HthiI^$o"cd8Z/FE66`QG(i1g&mtd:P*T7W1uZU?+/E&>>Ki,%>M;[rj.L 321 | V8*ed;JnK/GQE.Gr[khp]HP1J2@(DI#biVCLb(s2/Ppd^NrNN7ku:cH`Te2a%7I_.XfVY3( 322 | 3:'B3'*kZ5bg?+l;D(`YpA[;pe!A("qK>&Rj1*4'*WutUG>/(A&M(\rW>LJW5rIS?o0!oLQ 323 | R-@3HN@tB]tk(a$iCTPVN/hTIhC+'SPm>&/GWP/\DD()nn,_jp40fI4:c7KEU6XiDSWd&`P 324 | c?#=A6\B4ct9+rFkoNOB5s+cL7f24T)Pka$4)X(6C,"L;MALt;VC1JVmg":#M$?I;E@UINT 325 | QGcWeb]b_3DZoc9&3;FBj(>fAa%c8?m$U/YPhfG+(VL5;b,@-$D$GH^Oci/fL>[`arCE=oKMLoQgl~> 327 | Q 328 | Q Q 329 | showpage 330 | %%Trailer 331 | end 332 | %%EOF 333 | -------------------------------------------------------------------------------- /renv/activate.R: -------------------------------------------------------------------------------- 1 | 2 | local({ 3 | 4 | # the requested version of renv 5 | version <- "1.0.4" 6 | attr(version, "sha") <- NULL 7 | 8 | # the project directory 9 | project <- getwd() 10 | 11 | # use start-up diagnostics if enabled 12 | diagnostics <- Sys.getenv("RENV_STARTUP_DIAGNOSTICS", unset = "FALSE") 13 | if (diagnostics) { 14 | start <- Sys.time() 15 | profile <- tempfile("renv-startup-", fileext = ".Rprof") 16 | utils::Rprof(profile) 17 | on.exit({ 18 | utils::Rprof(NULL) 19 | elapsed <- signif(difftime(Sys.time(), start, units = "auto"), digits = 2L) 20 | writeLines(sprintf("- renv took %s to run the autoloader.", format(elapsed))) 21 | writeLines(sprintf("- Profile: %s", profile)) 22 | print(utils::summaryRprof(profile)) 23 | }, add = TRUE) 24 | } 25 | 26 | # figure out whether the autoloader is enabled 27 | enabled <- local({ 28 | 29 | # first, check config option 30 | override <- getOption("renv.config.autoloader.enabled") 31 | if (!is.null(override)) 32 | return(override) 33 | 34 | # if we're being run in a context where R_LIBS is already set, 35 | # don't load -- presumably we're being run as a sub-process and 36 | # the parent process has already set up library paths for us 37 | rcmd <- Sys.getenv("R_CMD", unset = NA) 38 | rlibs <- Sys.getenv("R_LIBS", unset = NA) 39 | if (!is.na(rlibs) && !is.na(rcmd)) 40 | return(FALSE) 41 | 42 | # next, check environment variables 43 | # TODO: prefer using the configuration one in the future 44 | envvars <- c( 45 | "RENV_CONFIG_AUTOLOADER_ENABLED", 46 | "RENV_AUTOLOADER_ENABLED", 47 | "RENV_ACTIVATE_PROJECT" 48 | ) 49 | 50 | for (envvar in envvars) { 51 | envval <- Sys.getenv(envvar, unset = NA) 52 | if (!is.na(envval)) 53 | return(tolower(envval) %in% c("true", "t", "1")) 54 | } 55 | 56 | # enable by default 57 | TRUE 58 | 59 | }) 60 | 61 | # bail if we're not enabled 62 | if (!enabled) { 63 | 64 | # if we're not enabled, we might still need to manually load 65 | # the user profile here 66 | profile <- Sys.getenv("R_PROFILE_USER", unset = "~/.Rprofile") 67 | if (file.exists(profile)) { 68 | cfg <- Sys.getenv("RENV_CONFIG_USER_PROFILE", unset = "TRUE") 69 | if (tolower(cfg) %in% c("true", "t", "1")) 70 | sys.source(profile, envir = globalenv()) 71 | } 72 | 73 | return(FALSE) 74 | 75 | } 76 | 77 | # avoid recursion 78 | if (identical(getOption("renv.autoloader.running"), TRUE)) { 79 | warning("ignoring recursive attempt to run renv autoloader") 80 | return(invisible(TRUE)) 81 | } 82 | 83 | # signal that we're loading renv during R startup 84 | options(renv.autoloader.running = TRUE) 85 | on.exit(options(renv.autoloader.running = NULL), add = TRUE) 86 | 87 | # signal that we've consented to use renv 88 | options(renv.consent = TRUE) 89 | 90 | # load the 'utils' package eagerly -- this ensures that renv shims, which 91 | # mask 'utils' packages, will come first on the search path 92 | library(utils, lib.loc = .Library) 93 | 94 | # unload renv if it's already been loaded 95 | if ("renv" %in% loadedNamespaces()) 96 | unloadNamespace("renv") 97 | 98 | # load bootstrap tools 99 | `%||%` <- function(x, y) { 100 | if (is.null(x)) y else x 101 | } 102 | 103 | catf <- function(fmt, ..., appendLF = TRUE) { 104 | 105 | quiet <- getOption("renv.bootstrap.quiet", default = FALSE) 106 | if (quiet) 107 | return(invisible()) 108 | 109 | msg <- sprintf(fmt, ...) 110 | cat(msg, file = stdout(), sep = if (appendLF) "\n" else "") 111 | 112 | invisible(msg) 113 | 114 | } 115 | 116 | header <- function(label, 117 | ..., 118 | prefix = "#", 119 | suffix = "-", 120 | n = min(getOption("width"), 78)) 121 | { 122 | label <- sprintf(label, ...) 123 | n <- max(n - nchar(label) - nchar(prefix) - 2L, 8L) 124 | if (n <= 0) 125 | return(paste(prefix, label)) 126 | 127 | tail <- paste(rep.int(suffix, n), collapse = "") 128 | paste0(prefix, " ", label, " ", tail) 129 | 130 | } 131 | 132 | startswith <- function(string, prefix) { 133 | substring(string, 1, nchar(prefix)) == prefix 134 | } 135 | 136 | bootstrap <- function(version, library) { 137 | 138 | friendly <- renv_bootstrap_version_friendly(version) 139 | section <- header(sprintf("Bootstrapping renv %s", friendly)) 140 | catf(section) 141 | 142 | # attempt to download renv 143 | catf("- Downloading renv ... ", appendLF = FALSE) 144 | withCallingHandlers( 145 | tarball <- renv_bootstrap_download(version), 146 | error = function(err) { 147 | catf("FAILED") 148 | stop("failed to download:\n", conditionMessage(err)) 149 | } 150 | ) 151 | catf("OK") 152 | on.exit(unlink(tarball), add = TRUE) 153 | 154 | # now attempt to install 155 | catf("- Installing renv ... ", appendLF = FALSE) 156 | withCallingHandlers( 157 | status <- renv_bootstrap_install(version, tarball, library), 158 | error = function(err) { 159 | catf("FAILED") 160 | stop("failed to install:\n", conditionMessage(err)) 161 | } 162 | ) 163 | catf("OK") 164 | 165 | # add empty line to break up bootstrapping from normal output 166 | catf("") 167 | 168 | return(invisible()) 169 | } 170 | 171 | renv_bootstrap_tests_running <- function() { 172 | getOption("renv.tests.running", default = FALSE) 173 | } 174 | 175 | renv_bootstrap_repos <- function() { 176 | 177 | # get CRAN repository 178 | cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") 179 | 180 | # check for repos override 181 | repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) 182 | if (!is.na(repos)) { 183 | 184 | # check for RSPM; if set, use a fallback repository for renv 185 | rspm <- Sys.getenv("RSPM", unset = NA) 186 | if (identical(rspm, repos)) 187 | repos <- c(RSPM = rspm, CRAN = cran) 188 | 189 | return(repos) 190 | 191 | } 192 | 193 | # check for lockfile repositories 194 | repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) 195 | if (!inherits(repos, "error") && length(repos)) 196 | return(repos) 197 | 198 | # retrieve current repos 199 | repos <- getOption("repos") 200 | 201 | # ensure @CRAN@ entries are resolved 202 | repos[repos == "@CRAN@"] <- cran 203 | 204 | # add in renv.bootstrap.repos if set 205 | default <- c(FALLBACK = "https://cloud.r-project.org") 206 | extra <- getOption("renv.bootstrap.repos", default = default) 207 | repos <- c(repos, extra) 208 | 209 | # remove duplicates that might've snuck in 210 | dupes <- duplicated(repos) | duplicated(names(repos)) 211 | repos[!dupes] 212 | 213 | } 214 | 215 | renv_bootstrap_repos_lockfile <- function() { 216 | 217 | lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") 218 | if (!file.exists(lockpath)) 219 | return(NULL) 220 | 221 | lockfile <- tryCatch(renv_json_read(lockpath), error = identity) 222 | if (inherits(lockfile, "error")) { 223 | warning(lockfile) 224 | return(NULL) 225 | } 226 | 227 | repos <- lockfile$R$Repositories 228 | if (length(repos) == 0) 229 | return(NULL) 230 | 231 | keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) 232 | vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) 233 | names(vals) <- keys 234 | 235 | return(vals) 236 | 237 | } 238 | 239 | renv_bootstrap_download <- function(version) { 240 | 241 | sha <- attr(version, "sha", exact = TRUE) 242 | 243 | methods <- if (!is.null(sha)) { 244 | 245 | # attempting to bootstrap a development version of renv 246 | c( 247 | function() renv_bootstrap_download_tarball(sha), 248 | function() renv_bootstrap_download_github(sha) 249 | ) 250 | 251 | } else { 252 | 253 | # attempting to bootstrap a release version of renv 254 | c( 255 | function() renv_bootstrap_download_tarball(version), 256 | function() renv_bootstrap_download_cran_latest(version), 257 | function() renv_bootstrap_download_cran_archive(version) 258 | ) 259 | 260 | } 261 | 262 | for (method in methods) { 263 | path <- tryCatch(method(), error = identity) 264 | if (is.character(path) && file.exists(path)) 265 | return(path) 266 | } 267 | 268 | stop("All download methods failed") 269 | 270 | } 271 | 272 | renv_bootstrap_download_impl <- function(url, destfile) { 273 | 274 | mode <- "wb" 275 | 276 | # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 277 | fixup <- 278 | Sys.info()[["sysname"]] == "Windows" && 279 | substring(url, 1L, 5L) == "file:" 280 | 281 | if (fixup) 282 | mode <- "w+b" 283 | 284 | args <- list( 285 | url = url, 286 | destfile = destfile, 287 | mode = mode, 288 | quiet = TRUE 289 | ) 290 | 291 | if ("headers" %in% names(formals(utils::download.file))) 292 | args$headers <- renv_bootstrap_download_custom_headers(url) 293 | 294 | do.call(utils::download.file, args) 295 | 296 | } 297 | 298 | renv_bootstrap_download_custom_headers <- function(url) { 299 | 300 | headers <- getOption("renv.download.headers") 301 | if (is.null(headers)) 302 | return(character()) 303 | 304 | if (!is.function(headers)) 305 | stopf("'renv.download.headers' is not a function") 306 | 307 | headers <- headers(url) 308 | if (length(headers) == 0L) 309 | return(character()) 310 | 311 | if (is.list(headers)) 312 | headers <- unlist(headers, recursive = FALSE, use.names = TRUE) 313 | 314 | ok <- 315 | is.character(headers) && 316 | is.character(names(headers)) && 317 | all(nzchar(names(headers))) 318 | 319 | if (!ok) 320 | stop("invocation of 'renv.download.headers' did not return a named character vector") 321 | 322 | headers 323 | 324 | } 325 | 326 | renv_bootstrap_download_cran_latest <- function(version) { 327 | 328 | spec <- renv_bootstrap_download_cran_latest_find(version) 329 | type <- spec$type 330 | repos <- spec$repos 331 | 332 | baseurl <- utils::contrib.url(repos = repos, type = type) 333 | ext <- if (identical(type, "source")) 334 | ".tar.gz" 335 | else if (Sys.info()[["sysname"]] == "Windows") 336 | ".zip" 337 | else 338 | ".tgz" 339 | name <- sprintf("renv_%s%s", version, ext) 340 | url <- paste(baseurl, name, sep = "/") 341 | 342 | destfile <- file.path(tempdir(), name) 343 | status <- tryCatch( 344 | renv_bootstrap_download_impl(url, destfile), 345 | condition = identity 346 | ) 347 | 348 | if (inherits(status, "condition")) 349 | return(FALSE) 350 | 351 | # report success and return 352 | destfile 353 | 354 | } 355 | 356 | renv_bootstrap_download_cran_latest_find <- function(version) { 357 | 358 | # check whether binaries are supported on this system 359 | binary <- 360 | getOption("renv.bootstrap.binary", default = TRUE) && 361 | !identical(.Platform$pkgType, "source") && 362 | !identical(getOption("pkgType"), "source") && 363 | Sys.info()[["sysname"]] %in% c("Darwin", "Windows") 364 | 365 | types <- c(if (binary) "binary", "source") 366 | 367 | # iterate over types + repositories 368 | for (type in types) { 369 | for (repos in renv_bootstrap_repos()) { 370 | 371 | # retrieve package database 372 | db <- tryCatch( 373 | as.data.frame( 374 | utils::available.packages(type = type, repos = repos), 375 | stringsAsFactors = FALSE 376 | ), 377 | error = identity 378 | ) 379 | 380 | if (inherits(db, "error")) 381 | next 382 | 383 | # check for compatible entry 384 | entry <- db[db$Package %in% "renv" & db$Version %in% version, ] 385 | if (nrow(entry) == 0) 386 | next 387 | 388 | # found it; return spec to caller 389 | spec <- list(entry = entry, type = type, repos = repos) 390 | return(spec) 391 | 392 | } 393 | } 394 | 395 | # if we got here, we failed to find renv 396 | fmt <- "renv %s is not available from your declared package repositories" 397 | stop(sprintf(fmt, version)) 398 | 399 | } 400 | 401 | renv_bootstrap_download_cran_archive <- function(version) { 402 | 403 | name <- sprintf("renv_%s.tar.gz", version) 404 | repos <- renv_bootstrap_repos() 405 | urls <- file.path(repos, "src/contrib/Archive/renv", name) 406 | destfile <- file.path(tempdir(), name) 407 | 408 | for (url in urls) { 409 | 410 | status <- tryCatch( 411 | renv_bootstrap_download_impl(url, destfile), 412 | condition = identity 413 | ) 414 | 415 | if (identical(status, 0L)) 416 | return(destfile) 417 | 418 | } 419 | 420 | return(FALSE) 421 | 422 | } 423 | 424 | renv_bootstrap_download_tarball <- function(version) { 425 | 426 | # if the user has provided the path to a tarball via 427 | # an environment variable, then use it 428 | tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) 429 | if (is.na(tarball)) 430 | return() 431 | 432 | # allow directories 433 | if (dir.exists(tarball)) { 434 | name <- sprintf("renv_%s.tar.gz", version) 435 | tarball <- file.path(tarball, name) 436 | } 437 | 438 | # bail if it doesn't exist 439 | if (!file.exists(tarball)) { 440 | 441 | # let the user know we weren't able to honour their request 442 | fmt <- "- RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." 443 | msg <- sprintf(fmt, tarball) 444 | warning(msg) 445 | 446 | # bail 447 | return() 448 | 449 | } 450 | 451 | catf("- Using local tarball '%s'.", tarball) 452 | tarball 453 | 454 | } 455 | 456 | renv_bootstrap_download_github <- function(version) { 457 | 458 | enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") 459 | if (!identical(enabled, "TRUE")) 460 | return(FALSE) 461 | 462 | # prepare download options 463 | pat <- Sys.getenv("GITHUB_PAT") 464 | if (nzchar(Sys.which("curl")) && nzchar(pat)) { 465 | fmt <- "--location --fail --header \"Authorization: token %s\"" 466 | extra <- sprintf(fmt, pat) 467 | saved <- options("download.file.method", "download.file.extra") 468 | options(download.file.method = "curl", download.file.extra = extra) 469 | on.exit(do.call(base::options, saved), add = TRUE) 470 | } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { 471 | fmt <- "--header=\"Authorization: token %s\"" 472 | extra <- sprintf(fmt, pat) 473 | saved <- options("download.file.method", "download.file.extra") 474 | options(download.file.method = "wget", download.file.extra = extra) 475 | on.exit(do.call(base::options, saved), add = TRUE) 476 | } 477 | 478 | url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) 479 | name <- sprintf("renv_%s.tar.gz", version) 480 | destfile <- file.path(tempdir(), name) 481 | 482 | status <- tryCatch( 483 | renv_bootstrap_download_impl(url, destfile), 484 | condition = identity 485 | ) 486 | 487 | if (!identical(status, 0L)) 488 | return(FALSE) 489 | 490 | renv_bootstrap_download_augment(destfile) 491 | 492 | return(destfile) 493 | 494 | } 495 | 496 | # Add Sha to DESCRIPTION. This is stop gap until #890, after which we 497 | # can use renv::install() to fully capture metadata. 498 | renv_bootstrap_download_augment <- function(destfile) { 499 | sha <- renv_bootstrap_git_extract_sha1_tar(destfile) 500 | if (is.null(sha)) { 501 | return() 502 | } 503 | 504 | # Untar 505 | tempdir <- tempfile("renv-github-") 506 | on.exit(unlink(tempdir, recursive = TRUE), add = TRUE) 507 | untar(destfile, exdir = tempdir) 508 | pkgdir <- dir(tempdir, full.names = TRUE)[[1]] 509 | 510 | # Modify description 511 | desc_path <- file.path(pkgdir, "DESCRIPTION") 512 | desc_lines <- readLines(desc_path) 513 | remotes_fields <- c( 514 | "RemoteType: github", 515 | "RemoteHost: api.github.com", 516 | "RemoteRepo: renv", 517 | "RemoteUsername: rstudio", 518 | "RemotePkgRef: rstudio/renv", 519 | paste("RemoteRef: ", sha), 520 | paste("RemoteSha: ", sha) 521 | ) 522 | writeLines(c(desc_lines[desc_lines != ""], remotes_fields), con = desc_path) 523 | 524 | # Re-tar 525 | local({ 526 | old <- setwd(tempdir) 527 | on.exit(setwd(old), add = TRUE) 528 | 529 | tar(destfile, compression = "gzip") 530 | }) 531 | invisible() 532 | } 533 | 534 | # Extract the commit hash from a git archive. Git archives include the SHA1 535 | # hash as the comment field of the tarball pax extended header 536 | # (see https://www.kernel.org/pub/software/scm/git/docs/git-archive.html) 537 | # For GitHub archives this should be the first header after the default one 538 | # (512 byte) header. 539 | renv_bootstrap_git_extract_sha1_tar <- function(bundle) { 540 | 541 | # open the bundle for reading 542 | # We use gzcon for everything because (from ?gzcon) 543 | # > Reading from a connection which does not supply a 'gzip' magic 544 | # > header is equivalent to reading from the original connection 545 | conn <- gzcon(file(bundle, open = "rb", raw = TRUE)) 546 | on.exit(close(conn)) 547 | 548 | # The default pax header is 512 bytes long and the first pax extended header 549 | # with the comment should be 51 bytes long 550 | # `52 comment=` (11 chars) + 40 byte SHA1 hash 551 | len <- 0x200 + 0x33 552 | res <- rawToChar(readBin(conn, "raw", n = len)[0x201:len]) 553 | 554 | if (grepl("^52 comment=", res)) { 555 | sub("52 comment=", "", res) 556 | } else { 557 | NULL 558 | } 559 | } 560 | 561 | renv_bootstrap_install <- function(version, tarball, library) { 562 | 563 | # attempt to install it into project library 564 | dir.create(library, showWarnings = FALSE, recursive = TRUE) 565 | output <- renv_bootstrap_install_impl(library, tarball) 566 | 567 | # check for successful install 568 | status <- attr(output, "status") 569 | if (is.null(status) || identical(status, 0L)) 570 | return(status) 571 | 572 | # an error occurred; report it 573 | header <- "installation of renv failed" 574 | lines <- paste(rep.int("=", nchar(header)), collapse = "") 575 | text <- paste(c(header, lines, output), collapse = "\n") 576 | stop(text) 577 | 578 | } 579 | 580 | renv_bootstrap_install_impl <- function(library, tarball) { 581 | 582 | # invoke using system2 so we can capture and report output 583 | bin <- R.home("bin") 584 | exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" 585 | R <- file.path(bin, exe) 586 | 587 | args <- c( 588 | "--vanilla", "CMD", "INSTALL", "--no-multiarch", 589 | "-l", shQuote(path.expand(library)), 590 | shQuote(path.expand(tarball)) 591 | ) 592 | 593 | system2(R, args, stdout = TRUE, stderr = TRUE) 594 | 595 | } 596 | 597 | renv_bootstrap_platform_prefix <- function() { 598 | 599 | # construct version prefix 600 | version <- paste(R.version$major, R.version$minor, sep = ".") 601 | prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") 602 | 603 | # include SVN revision for development versions of R 604 | # (to avoid sharing platform-specific artefacts with released versions of R) 605 | devel <- 606 | identical(R.version[["status"]], "Under development (unstable)") || 607 | identical(R.version[["nickname"]], "Unsuffered Consequences") 608 | 609 | if (devel) 610 | prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") 611 | 612 | # build list of path components 613 | components <- c(prefix, R.version$platform) 614 | 615 | # include prefix if provided by user 616 | prefix <- renv_bootstrap_platform_prefix_impl() 617 | if (!is.na(prefix) && nzchar(prefix)) 618 | components <- c(prefix, components) 619 | 620 | # build prefix 621 | paste(components, collapse = "/") 622 | 623 | } 624 | 625 | renv_bootstrap_platform_prefix_impl <- function() { 626 | 627 | # if an explicit prefix has been supplied, use it 628 | prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) 629 | if (!is.na(prefix)) 630 | return(prefix) 631 | 632 | # if the user has requested an automatic prefix, generate it 633 | auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) 634 | if (auto %in% c("TRUE", "True", "true", "1")) 635 | return(renv_bootstrap_platform_prefix_auto()) 636 | 637 | # empty string on failure 638 | "" 639 | 640 | } 641 | 642 | renv_bootstrap_platform_prefix_auto <- function() { 643 | 644 | prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) 645 | if (inherits(prefix, "error") || prefix %in% "unknown") { 646 | 647 | msg <- paste( 648 | "failed to infer current operating system", 649 | "please file a bug report at https://github.com/rstudio/renv/issues", 650 | sep = "; " 651 | ) 652 | 653 | warning(msg) 654 | 655 | } 656 | 657 | prefix 658 | 659 | } 660 | 661 | renv_bootstrap_platform_os <- function() { 662 | 663 | sysinfo <- Sys.info() 664 | sysname <- sysinfo[["sysname"]] 665 | 666 | # handle Windows + macOS up front 667 | if (sysname == "Windows") 668 | return("windows") 669 | else if (sysname == "Darwin") 670 | return("macos") 671 | 672 | # check for os-release files 673 | for (file in c("/etc/os-release", "/usr/lib/os-release")) 674 | if (file.exists(file)) 675 | return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) 676 | 677 | # check for redhat-release files 678 | if (file.exists("/etc/redhat-release")) 679 | return(renv_bootstrap_platform_os_via_redhat_release()) 680 | 681 | "unknown" 682 | 683 | } 684 | 685 | renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { 686 | 687 | # read /etc/os-release 688 | release <- utils::read.table( 689 | file = file, 690 | sep = "=", 691 | quote = c("\"", "'"), 692 | col.names = c("Key", "Value"), 693 | comment.char = "#", 694 | stringsAsFactors = FALSE 695 | ) 696 | 697 | vars <- as.list(release$Value) 698 | names(vars) <- release$Key 699 | 700 | # get os name 701 | os <- tolower(sysinfo[["sysname"]]) 702 | 703 | # read id 704 | id <- "unknown" 705 | for (field in c("ID", "ID_LIKE")) { 706 | if (field %in% names(vars) && nzchar(vars[[field]])) { 707 | id <- vars[[field]] 708 | break 709 | } 710 | } 711 | 712 | # read version 713 | version <- "unknown" 714 | for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { 715 | if (field %in% names(vars) && nzchar(vars[[field]])) { 716 | version <- vars[[field]] 717 | break 718 | } 719 | } 720 | 721 | # join together 722 | paste(c(os, id, version), collapse = "-") 723 | 724 | } 725 | 726 | renv_bootstrap_platform_os_via_redhat_release <- function() { 727 | 728 | # read /etc/redhat-release 729 | contents <- readLines("/etc/redhat-release", warn = FALSE) 730 | 731 | # infer id 732 | id <- if (grepl("centos", contents, ignore.case = TRUE)) 733 | "centos" 734 | else if (grepl("redhat", contents, ignore.case = TRUE)) 735 | "redhat" 736 | else 737 | "unknown" 738 | 739 | # try to find a version component (very hacky) 740 | version <- "unknown" 741 | 742 | parts <- strsplit(contents, "[[:space:]]")[[1L]] 743 | for (part in parts) { 744 | 745 | nv <- tryCatch(numeric_version(part), error = identity) 746 | if (inherits(nv, "error")) 747 | next 748 | 749 | version <- nv[1, 1] 750 | break 751 | 752 | } 753 | 754 | paste(c("linux", id, version), collapse = "-") 755 | 756 | } 757 | 758 | renv_bootstrap_library_root_name <- function(project) { 759 | 760 | # use project name as-is if requested 761 | asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") 762 | if (asis) 763 | return(basename(project)) 764 | 765 | # otherwise, disambiguate based on project's path 766 | id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) 767 | paste(basename(project), id, sep = "-") 768 | 769 | } 770 | 771 | renv_bootstrap_library_root <- function(project) { 772 | 773 | prefix <- renv_bootstrap_profile_prefix() 774 | 775 | path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) 776 | if (!is.na(path)) 777 | return(paste(c(path, prefix), collapse = "/")) 778 | 779 | path <- renv_bootstrap_library_root_impl(project) 780 | if (!is.null(path)) { 781 | name <- renv_bootstrap_library_root_name(project) 782 | return(paste(c(path, prefix, name), collapse = "/")) 783 | } 784 | 785 | renv_bootstrap_paths_renv("library", project = project) 786 | 787 | } 788 | 789 | renv_bootstrap_library_root_impl <- function(project) { 790 | 791 | root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) 792 | if (!is.na(root)) 793 | return(root) 794 | 795 | type <- renv_bootstrap_project_type(project) 796 | if (identical(type, "package")) { 797 | userdir <- renv_bootstrap_user_dir() 798 | return(file.path(userdir, "library")) 799 | } 800 | 801 | } 802 | 803 | renv_bootstrap_validate_version <- function(version, description = NULL) { 804 | 805 | # resolve description file 806 | # 807 | # avoid passing lib.loc to `packageDescription()` below, since R will 808 | # use the loaded version of the package by default anyhow. note that 809 | # this function should only be called after 'renv' is loaded 810 | # https://github.com/rstudio/renv/issues/1625 811 | description <- description %||% packageDescription("renv") 812 | 813 | # check whether requested version 'version' matches loaded version of renv 814 | sha <- attr(version, "sha", exact = TRUE) 815 | valid <- if (!is.null(sha)) 816 | renv_bootstrap_validate_version_dev(sha, description) 817 | else 818 | renv_bootstrap_validate_version_release(version, description) 819 | 820 | if (valid) 821 | return(TRUE) 822 | 823 | # the loaded version of renv doesn't match the requested version; 824 | # give the user instructions on how to proceed 825 | remote <- if (!is.null(description[["RemoteSha"]])) { 826 | paste("rstudio/renv", description[["RemoteSha"]], sep = "@") 827 | } else { 828 | paste("renv", description[["Version"]], sep = "@") 829 | } 830 | 831 | # display both loaded version + sha if available 832 | friendly <- renv_bootstrap_version_friendly( 833 | version = description[["Version"]], 834 | sha = description[["RemoteSha"]] 835 | ) 836 | 837 | fmt <- paste( 838 | "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", 839 | "- Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", 840 | "- Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", 841 | sep = "\n" 842 | ) 843 | catf(fmt, friendly, renv_bootstrap_version_friendly(version), remote) 844 | 845 | FALSE 846 | 847 | } 848 | 849 | renv_bootstrap_validate_version_dev <- function(version, description) { 850 | expected <- description[["RemoteSha"]] 851 | is.character(expected) && startswith(expected, version) 852 | } 853 | 854 | renv_bootstrap_validate_version_release <- function(version, description) { 855 | expected <- description[["Version"]] 856 | is.character(expected) && identical(expected, version) 857 | } 858 | 859 | renv_bootstrap_hash_text <- function(text) { 860 | 861 | hashfile <- tempfile("renv-hash-") 862 | on.exit(unlink(hashfile), add = TRUE) 863 | 864 | writeLines(text, con = hashfile) 865 | tools::md5sum(hashfile) 866 | 867 | } 868 | 869 | renv_bootstrap_load <- function(project, libpath, version) { 870 | 871 | # try to load renv from the project library 872 | if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) 873 | return(FALSE) 874 | 875 | # warn if the version of renv loaded does not match 876 | renv_bootstrap_validate_version(version) 877 | 878 | # execute renv load hooks, if any 879 | hooks <- getHook("renv::autoload") 880 | for (hook in hooks) 881 | if (is.function(hook)) 882 | tryCatch(hook(), error = warnify) 883 | 884 | # load the project 885 | renv::load(project) 886 | 887 | TRUE 888 | 889 | } 890 | 891 | renv_bootstrap_profile_load <- function(project) { 892 | 893 | # if RENV_PROFILE is already set, just use that 894 | profile <- Sys.getenv("RENV_PROFILE", unset = NA) 895 | if (!is.na(profile) && nzchar(profile)) 896 | return(profile) 897 | 898 | # check for a profile file (nothing to do if it doesn't exist) 899 | path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) 900 | if (!file.exists(path)) 901 | return(NULL) 902 | 903 | # read the profile, and set it if it exists 904 | contents <- readLines(path, warn = FALSE) 905 | if (length(contents) == 0L) 906 | return(NULL) 907 | 908 | # set RENV_PROFILE 909 | profile <- contents[[1L]] 910 | if (!profile %in% c("", "default")) 911 | Sys.setenv(RENV_PROFILE = profile) 912 | 913 | profile 914 | 915 | } 916 | 917 | renv_bootstrap_profile_prefix <- function() { 918 | profile <- renv_bootstrap_profile_get() 919 | if (!is.null(profile)) 920 | return(file.path("profiles", profile, "renv")) 921 | } 922 | 923 | renv_bootstrap_profile_get <- function() { 924 | profile <- Sys.getenv("RENV_PROFILE", unset = "") 925 | renv_bootstrap_profile_normalize(profile) 926 | } 927 | 928 | renv_bootstrap_profile_set <- function(profile) { 929 | profile <- renv_bootstrap_profile_normalize(profile) 930 | if (is.null(profile)) 931 | Sys.unsetenv("RENV_PROFILE") 932 | else 933 | Sys.setenv(RENV_PROFILE = profile) 934 | } 935 | 936 | renv_bootstrap_profile_normalize <- function(profile) { 937 | 938 | if (is.null(profile) || profile %in% c("", "default")) 939 | return(NULL) 940 | 941 | profile 942 | 943 | } 944 | 945 | renv_bootstrap_path_absolute <- function(path) { 946 | 947 | substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( 948 | substr(path, 1L, 1L) %in% c(letters, LETTERS) && 949 | substr(path, 2L, 3L) %in% c(":/", ":\\") 950 | ) 951 | 952 | } 953 | 954 | renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { 955 | renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") 956 | root <- if (renv_bootstrap_path_absolute(renv)) NULL else project 957 | prefix <- if (profile) renv_bootstrap_profile_prefix() 958 | components <- c(root, renv, prefix, ...) 959 | paste(components, collapse = "/") 960 | } 961 | 962 | renv_bootstrap_project_type <- function(path) { 963 | 964 | descpath <- file.path(path, "DESCRIPTION") 965 | if (!file.exists(descpath)) 966 | return("unknown") 967 | 968 | desc <- tryCatch( 969 | read.dcf(descpath, all = TRUE), 970 | error = identity 971 | ) 972 | 973 | if (inherits(desc, "error")) 974 | return("unknown") 975 | 976 | type <- desc$Type 977 | if (!is.null(type)) 978 | return(tolower(type)) 979 | 980 | package <- desc$Package 981 | if (!is.null(package)) 982 | return("package") 983 | 984 | "unknown" 985 | 986 | } 987 | 988 | renv_bootstrap_user_dir <- function() { 989 | dir <- renv_bootstrap_user_dir_impl() 990 | path.expand(chartr("\\", "/", dir)) 991 | } 992 | 993 | renv_bootstrap_user_dir_impl <- function() { 994 | 995 | # use local override if set 996 | override <- getOption("renv.userdir.override") 997 | if (!is.null(override)) 998 | return(override) 999 | 1000 | # use R_user_dir if available 1001 | tools <- asNamespace("tools") 1002 | if (is.function(tools$R_user_dir)) 1003 | return(tools$R_user_dir("renv", "cache")) 1004 | 1005 | # try using our own backfill for older versions of R 1006 | envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") 1007 | for (envvar in envvars) { 1008 | root <- Sys.getenv(envvar, unset = NA) 1009 | if (!is.na(root)) 1010 | return(file.path(root, "R/renv")) 1011 | } 1012 | 1013 | # use platform-specific default fallbacks 1014 | if (Sys.info()[["sysname"]] == "Windows") 1015 | file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") 1016 | else if (Sys.info()[["sysname"]] == "Darwin") 1017 | "~/Library/Caches/org.R-project.R/R/renv" 1018 | else 1019 | "~/.cache/R/renv" 1020 | 1021 | } 1022 | 1023 | renv_bootstrap_version_friendly <- function(version, shafmt = NULL, sha = NULL) { 1024 | sha <- sha %||% attr(version, "sha", exact = TRUE) 1025 | parts <- c(version, sprintf(shafmt %||% " [sha: %s]", substring(sha, 1L, 7L))) 1026 | paste(parts, collapse = "") 1027 | } 1028 | 1029 | renv_bootstrap_exec <- function(project, libpath, version) { 1030 | if (!renv_bootstrap_load(project, libpath, version)) 1031 | renv_bootstrap_run(version, libpath) 1032 | } 1033 | 1034 | renv_bootstrap_run <- function(version, libpath) { 1035 | 1036 | # perform bootstrap 1037 | bootstrap(version, libpath) 1038 | 1039 | # exit early if we're just testing bootstrap 1040 | if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) 1041 | return(TRUE) 1042 | 1043 | # try again to load 1044 | if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { 1045 | return(renv::load(project = getwd())) 1046 | } 1047 | 1048 | # failed to download or load renv; warn the user 1049 | msg <- c( 1050 | "Failed to find an renv installation: the project will not be loaded.", 1051 | "Use `renv::activate()` to re-initialize the project." 1052 | ) 1053 | 1054 | warning(paste(msg, collapse = "\n"), call. = FALSE) 1055 | 1056 | } 1057 | 1058 | renv_json_read <- function(file = NULL, text = NULL) { 1059 | 1060 | jlerr <- NULL 1061 | 1062 | # if jsonlite is loaded, use that instead 1063 | if ("jsonlite" %in% loadedNamespaces()) { 1064 | 1065 | json <- tryCatch(renv_json_read_jsonlite(file, text), error = identity) 1066 | if (!inherits(json, "error")) 1067 | return(json) 1068 | 1069 | jlerr <- json 1070 | 1071 | } 1072 | 1073 | # otherwise, fall back to the default JSON reader 1074 | json <- tryCatch(renv_json_read_default(file, text), error = identity) 1075 | if (!inherits(json, "error")) 1076 | return(json) 1077 | 1078 | # report an error 1079 | if (!is.null(jlerr)) 1080 | stop(jlerr) 1081 | else 1082 | stop(json) 1083 | 1084 | } 1085 | 1086 | renv_json_read_jsonlite <- function(file = NULL, text = NULL) { 1087 | text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n") 1088 | jsonlite::fromJSON(txt = text, simplifyVector = FALSE) 1089 | } 1090 | 1091 | renv_json_read_default <- function(file = NULL, text = NULL) { 1092 | 1093 | # find strings in the JSON 1094 | text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n") 1095 | pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' 1096 | locs <- gregexpr(pattern, text, perl = TRUE)[[1]] 1097 | 1098 | # if any are found, replace them with placeholders 1099 | replaced <- text 1100 | strings <- character() 1101 | replacements <- character() 1102 | 1103 | if (!identical(c(locs), -1L)) { 1104 | 1105 | # get the string values 1106 | starts <- locs 1107 | ends <- locs + attr(locs, "match.length") - 1L 1108 | strings <- substring(text, starts, ends) 1109 | 1110 | # only keep those requiring escaping 1111 | strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) 1112 | 1113 | # compute replacements 1114 | replacements <- sprintf('"\032%i\032"', seq_along(strings)) 1115 | 1116 | # replace the strings 1117 | mapply(function(string, replacement) { 1118 | replaced <<- sub(string, replacement, replaced, fixed = TRUE) 1119 | }, strings, replacements) 1120 | 1121 | } 1122 | 1123 | # transform the JSON into something the R parser understands 1124 | transformed <- replaced 1125 | transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE) 1126 | transformed <- gsub("[[{]", "list(", transformed, perl = TRUE) 1127 | transformed <- gsub("[]}]", ")", transformed, perl = TRUE) 1128 | transformed <- gsub(":", "=", transformed, fixed = TRUE) 1129 | text <- paste(transformed, collapse = "\n") 1130 | 1131 | # parse it 1132 | json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] 1133 | 1134 | # construct map between source strings, replaced strings 1135 | map <- as.character(parse(text = strings)) 1136 | names(map) <- as.character(parse(text = replacements)) 1137 | 1138 | # convert to list 1139 | map <- as.list(map) 1140 | 1141 | # remap strings in object 1142 | remapped <- renv_json_read_remap(json, map) 1143 | 1144 | # evaluate 1145 | eval(remapped, envir = baseenv()) 1146 | 1147 | } 1148 | 1149 | renv_json_read_remap <- function(json, map) { 1150 | 1151 | # fix names 1152 | if (!is.null(names(json))) { 1153 | lhs <- match(names(json), names(map), nomatch = 0L) 1154 | rhs <- match(names(map), names(json), nomatch = 0L) 1155 | names(json)[rhs] <- map[lhs] 1156 | } 1157 | 1158 | # fix values 1159 | if (is.character(json)) 1160 | return(map[[json]] %||% json) 1161 | 1162 | # handle true, false, null 1163 | if (is.name(json)) { 1164 | text <- as.character(json) 1165 | if (text == "true") 1166 | return(TRUE) 1167 | else if (text == "false") 1168 | return(FALSE) 1169 | else if (text == "null") 1170 | return(NULL) 1171 | } 1172 | 1173 | # recurse 1174 | if (is.recursive(json)) { 1175 | for (i in seq_along(json)) { 1176 | json[i] <- list(renv_json_read_remap(json[[i]], map)) 1177 | } 1178 | } 1179 | 1180 | json 1181 | 1182 | } 1183 | 1184 | # load the renv profile, if any 1185 | renv_bootstrap_profile_load(project) 1186 | 1187 | # construct path to library root 1188 | root <- renv_bootstrap_library_root(project) 1189 | 1190 | # construct library prefix for platform 1191 | prefix <- renv_bootstrap_platform_prefix() 1192 | 1193 | # construct full libpath 1194 | libpath <- file.path(root, prefix) 1195 | 1196 | # run bootstrap code 1197 | renv_bootstrap_exec(project, libpath, version) 1198 | 1199 | invisible() 1200 | 1201 | }) 1202 | --------------------------------------------------------------------------------