├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ └── pkgdown.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── DESCRIPTION ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── close_socket_clients.R ├── eval_socket_server.R ├── get_socket.R ├── par_socket_server.R ├── process_socket_server.R ├── send_socket_clients.R ├── socket_client_connection.R ├── start_socket_server.R ├── stop_socket_server.R ├── svSocket-Internal.R └── svSocket-package.R ├── README.md ├── TODO.md ├── _pkgdown.yml ├── appveyor.yml ├── inst ├── WORDLIST ├── etc │ ├── ReadMe.txt │ ├── SimpleClient.Tcl │ └── SimpleClientSecure.Tcl └── testCLI │ ├── testCLI.R │ ├── testCLIcmd.R │ ├── testCLIcmd_RMacTermFR.out │ ├── testCLIcmd_RMacTermFR.save │ ├── testCLIcmd_RMacTermUS.out │ ├── testCLIcmd_RMacTermUS.save │ ├── testCLIcmd_RUbuntuTermUS.out │ ├── testCLIcmd_RUbuntuTermUS.save │ ├── testCLIcmd_RappFR.out │ ├── testCLIcmd_RappFR.save │ ├── testCLIcmd_RappUS.out │ ├── testCLIcmd_RappUS.save │ ├── testCLIcmd_RguiFR.out │ ├── testCLIcmd_RguiFR.save │ ├── testCLIcmd_RguiUS.out │ ├── testCLIcmd_RguiUS.save │ ├── testCLIcmd_RtermWinUS.out │ └── testCLIcmd_RtermWinUS.save ├── man ├── close_socket_clients.Rd ├── eval_socket_server.Rd ├── get_socket_clients.Rd ├── get_socket_server_name.Rd ├── get_socket_servers.Rd ├── par_socket_server.Rd ├── process_socket_server.Rd ├── send_socket_clients.Rd ├── socket_client_connection.Rd ├── start_socket_server.Rd └── svSocket-package.Rd ├── revdep ├── .gitignore ├── README.md ├── email.yml ├── failures.md └── problems.md ├── svSocket.Rproj ├── tests └── spelling.R └── vignettes ├── svSocket.R └── svSocket.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .gitignore 3 | .git 4 | .git/* 5 | README.md 6 | FAQ.md 7 | TODO.md 8 | LICENSE.md 9 | Makefile 10 | .Rprofile 11 | ^\.Rproj\.user$ 12 | ^\.*\Rproj$ 13 | ^\.travis\.yml$ 14 | ^appveyor\.yml$ 15 | ^.*\.Rproj$ 16 | ^doc$ 17 | ^Meta$ 18 | ^_pkgdown\.yml$ 19 | ^docs$ 20 | ^pkgdown$ 21 | ^\.github$ 22 | ^CODE_OF_CONDUCT\.md$ 23 | ^revdep$ 24 | data-raw 25 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macOS-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | #DISPLAY: 99.0 31 | R_DONT_USE_TK: 1 32 | 33 | steps: 34 | - uses: actions/checkout@v2 35 | 36 | - uses: r-lib/actions/setup-pandoc@v1 37 | 38 | - uses: r-lib/actions/setup-r@v1 39 | with: 40 | r-version: ${{ matrix.config.r }} 41 | http-user-agent: ${{ matrix.config.http-user-agent }} 42 | use-public-rspm: true 43 | 44 | - name: Install X11 dependencies on MacOS 45 | if: runner.os == 'macOS' 46 | run: | 47 | brew install --cask xquartz 48 | 49 | - uses: r-lib/actions/setup-r-dependencies@v1 50 | with: 51 | extra-packages: rcmdcheck 52 | 53 | - uses: r-lib/actions/check-r-package@v1 54 | 55 | - name: Show testthat output 56 | if: always() 57 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true 58 | shell: bash 59 | 60 | - name: Upload check results 61 | if: failure() 62 | uses: actions/upload-artifact@main 63 | with: 64 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 65 | path: check 66 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | release: 7 | types: [published] 8 | workflow_dispatch: 9 | 10 | name: pkgdown 11 | 12 | jobs: 13 | pkgdown: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - uses: r-lib/actions/setup-pandoc@v1 21 | 22 | - uses: r-lib/actions/setup-r@v1 23 | with: 24 | use-public-rspm: true 25 | 26 | - uses: r-lib/actions/setup-r-dependencies@v1 27 | with: 28 | extra-packages: pkgdown 29 | needs: website 30 | 31 | - name: Install svPkgdown 32 | run: | 33 | install.packages('remotes') 34 | remotes::install_github("SciViews/svPkgdown") 35 | shell: Rscript {0} 36 | 37 | - name: Deploy package 38 | run: | 39 | git config --local user.name "$GITHUB_ACTOR" 40 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 41 | Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # User-specific files 9 | .Ruserdata 10 | 11 | # Example code in package build process 12 | *-Ex.R 13 | 14 | # Output files from R CMD build 15 | /*.tar.gz 16 | 17 | # Output files from R CMD check 18 | /*.Rcheck/ 19 | 20 | # RStudio files 21 | .Rproj.user/ 22 | 23 | # produced vignettes 24 | vignettes/*.html 25 | vignettes/*.pdf 26 | 27 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 28 | .httr-oauth 29 | 30 | # knitr and R markdown default cache directories 31 | *_cache/ 32 | /cache/ 33 | 34 | # Temporary files created by R markdown 35 | *.utf8.md 36 | *.knit.md 37 | 38 | # R Environment Variables 39 | .Renviron 40 | 41 | # Locally generated pkgdown site 42 | docs 43 | docs/ 44 | 45 | # Mac files 46 | .DS_Store 47 | doc 48 | Meta 49 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards 42 | of acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies 54 | when an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail 56 | address, posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at [INSERT CONTACT 63 | METHOD]. All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, 118 | available at https://www.contributor-covenant.org/version/2/0/ 119 | code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at https:// 128 | www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: svSocket 2 | Type: Package 3 | Version: 1.1.5 4 | Date: 2024-02-29 5 | Title: 'SciViews' - Socket Server 6 | Description: A socket server allows to connect clients to R. 7 | Authors@R: c(person("Philippe", "Grosjean", role = c("aut", "cre"), 8 | email = "phgrosjean@sciviews.org", 9 | comment = c(ORCID = "0000-0002-2694-9471")), 10 | person("Matthew", "Dowle", role = "ctb", 11 | email = "mdowle@mdowle.plus.com")) 12 | Maintainer: Philippe Grosjean 13 | Depends: R (>= 2.6.0) 14 | Imports: tcltk, svMisc (>= 0.9-68), utils 15 | Suggests: svHttp, spelling, covr, knitr, rmarkdown 16 | License: GPL-2 17 | URL: https://github.com/SciViews/svSocket, https://www.sciviews.org/svSocket/ 18 | BugReports: https://github.com/SciViews/svSocket/issues 19 | Roxygen: list(markdown = TRUE) 20 | RoxygenNote: 7.2.3 21 | VignetteBuilder: knitr 22 | Encoding: UTF-8 23 | Language: en-US 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # GNU General Public License 2 | 3 | **Version 2, June 1991** 4 | 5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 6 | 7 | ## Preamble 8 | 9 | The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. 10 | 11 | When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. 12 | 13 | To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. 14 | 15 | For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. 16 | 17 | We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. 18 | 19 | Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. 20 | 21 | Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. 22 | 23 | The precise terms and conditions for copying, distribution and modification follow. 24 | 25 | ## GNU General Public License 26 | 27 | **Terms and conditions for copying, distributing and modification** 28 | 29 | (0.) This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". 30 | 31 | Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 32 | 33 | (1.) You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. 34 | 35 | You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 36 | 37 | (2.) You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: 38 | 39 | a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. 40 | 41 | b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. 42 | 43 | c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) 44 | 45 | These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. 46 | 47 | Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. 48 | 49 | In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 50 | 51 | (3.) You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: 52 | 53 | a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 54 | 55 | b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 56 | 57 | c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) 58 | 59 | The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. 60 | 61 | If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 62 | 63 | (4.) You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 64 | 65 | (5.) You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 66 | 67 | (6.) Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 68 | 69 | (7.) If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. 70 | 71 | If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. 72 | 73 | It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. 74 | 75 | This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 76 | 77 | (8.) If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 78 | 79 | (9.) The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 80 | 81 | Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 82 | 83 | (10.) If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. 84 | 85 | ## No warranty 86 | 87 | (11.) Because the program is licensed free of charge, there is no warranty for the program, to the extent permitted by applicable law. Except when otherwise stated in writing the copyright holders and/or other parties provide the program "as is" without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the program is with you. Should the program prove defective, you assume the cost of all necessary servicing, repair or correction. 88 | 89 | (12.) In no event unless required by applicable law or agreed to in writing will any copyright holder, or any other party who may modify and/or redistribute the program as permitted above, be liable to you for damages, including any general, special, incidental or consequential damages arising out of the use or inability to use the program (including but not limited to loss of data or data being rendered inaccurate or losses sustained by you or third parties or a failure of the program to operate with any other programs), even if such holder or other party has been advised of the possibility of such damages. 90 | 91 | ## End of terms and conditions 92 | 93 | **How to Apply These Terms to Your New Programs** 94 | 95 | If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. 96 | 97 | To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 98 | 99 | > Copyright (C) 100 | > 101 | > This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 102 | > 103 | > This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 104 | > 105 | > You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 106 | 107 | Also add information on how to contact you by electronic and paper mail. 108 | 109 | If the program is interactive, make it output a short notice like this when it starts in an interactive mode: 110 | 111 | > Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free software, and you are welcome to redistribute it under certain conditions; type 'show c' for details. 112 | 113 | The hypothetical commands 'show w' and 'show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than 'show w' and 'show c'; they could even be mouse-clicks or menu items--whatever suits your program. 114 | 115 | You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: 116 | 117 | > Yoyodyne, Inc., hereby disclaims all copyright interest in the program 'Gnomovision' (which makes passes at compilers) written by James Hacker. 118 | > 119 | > \, 1 April 1989 120 | > 121 | > Ty Coon, President of Vice 122 | 123 | This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. 124 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(summary,sockclientconn) 4 | export(closeSocketClients) 5 | export(close_socket_clients) 6 | export(evalServer) 7 | export(eval_socket_server) 8 | export(getSocketClients) 9 | export(getSocketClientsNames) 10 | export(getSocketServerName) 11 | export(getSocketServers) 12 | export(get_socket_clients) 13 | export(get_socket_clients_names) 14 | export(get_socket_server_name) 15 | export(get_socket_servers) 16 | export(parSocket) 17 | export(par_socket_server) 18 | export(processSocket) 19 | export(process_socket_server) 20 | export(sendSocketClients) 21 | export(send_socket_clients) 22 | export(socketClientConnection) 23 | export(socket_client_connection) 24 | export(startSocketServer) 25 | export(start_socket_server) 26 | export(stopSocketServer) 27 | export(stop_socket_server) 28 | importFrom(svMisc,assign_temp) 29 | importFrom(svMisc,capture_all) 30 | importFrom(svMisc,get_temp) 31 | importFrom(svMisc,parse_text) 32 | importFrom(svMisc,rm_temp) 33 | importFrom(svMisc,temp_env) 34 | importFrom(tcltk,.Tcl) 35 | importFrom(tcltk,.Tcl.callback) 36 | importFrom(tcltk,tcl) 37 | importFrom(tcltk,tclRequire) 38 | importFrom(tcltk,tclVar) 39 | importFrom(tcltk,tclvalue) 40 | importFrom(utils,URLdecode) 41 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # svSocket 1.1.5 2 | 3 | - CITATION eliminated because CRAN continues to complain. 4 | 5 | # svSocket 1.1.4 6 | 7 | - Small change in CITATION to avoid a note in CRAN. 8 | 9 | # svSocket 1.1.3 10 | 11 | - A link to the Komodo IDE web site in the vignette was 404. It is fixed. 12 | 13 | # svSocket 1.1.2 14 | 15 | - Minor correction in the man page of `svSocket-package`. 16 | 17 | # svSocket 1.1.1 18 | 19 | - Due to a change in the R event loop in R 4.3.x, the socket server does not work with all the 4.3.x versions of R. It is fixed in R 4.4.0. Notes are added in the documentation of the package and in `?startSocketServer`. 20 | 21 | # svSocket 1.1.0 22 | 23 | - All functions are renamed to use snake_case, e.g., `startSocketServer()` is renamed `start_socket_server()`. Old names remain for backward compatibility, but they are deprecated. 24 | 25 | # svSocket 1.0.2 26 | 27 | - Vignette reworked. Now it does not create a socket server on R CMD check. 28 | 29 | # svSocket 1.0.1 30 | 31 | - The Tcl client example in /etc now uses /usr/bin/tclsh. 32 | 33 | - {svHttp} package is added in suggests. 34 | 35 | # svSocket 1.0.0 36 | 37 | - Code adapted to R 4.0. 38 | 39 | - Repository refreshed. 40 | 41 | - Document rewritten using **Roxygen2**. 42 | 43 | - Web site done with **pkgdown**. 44 | 45 | - Vignettes. 46 | 47 | # svSocket 0.9-58 48 | 49 | - Switch to Github for development; use CI. 50 | 51 | - Added `importFrom()` for base packages in NAMESPACE. 52 | 53 | - `evalServer()` was not able to process correctly strings that contain double quotes. These are now escaped. Thanks to Adam Ryczkowski for the bug report. 54 | 55 | # svSocket 0.9-57 56 | 57 | - /testCLI directory moved to /inst/testCLI. 58 | 59 | - An example is added that tests/demonstrates how to start, query and stop a SciViews socket server from within R. 60 | 61 | # svSocket 0.9-56 62 | 63 | - Author and [Authors\@R](mailto:Authors@R){.email} fields reworked in the DESCRIPTION file. 64 | 65 | # svSocket 0.9-55 66 | 67 | - `evalServer()` now can pass objects that contain pointers as attributes (e.g., 'data.table' objects), but the pointers are set to `NULL` on the client side (they are probably meaningless there). This may result in corrupted or malfunctioning objects, unless they can cope with such a situation, like 'data.table' object do. 68 | 69 | - Slightly reworked examples of ?evalServer and added a details section to explain which R objects cannot be transferred between R processes through evalServer(). 70 | 71 | # svSocket 0.9-54 72 | 73 | - An example is added to `?sendSocketClients`. 74 | 75 | - It now needs **svMisc** \>= 0.9-68, and it uses `SciViews:TempEnv` instead of `TempEnv` to store data from clients and servers (stateful conditions). 76 | 77 | # svSocket 0.9-53 78 | 79 | - A bug in `evalServer()` leading to evaluation of a condition of length \> 1 in an `if()` construct in some case is corrected. Thanks to Xiaoqian Jiang. 80 | 81 | # svSocket 0.9-52 82 | 83 | - `processSocket()` now uses the new version of `captureAll()` from **svMisc** \>= 0.9-62 with the `split=` and `echo=` arguments. Commands and results are now interwoven like in a normal console output. 84 | 85 | - The socket server now accepts and respond to 'HEAD' HTTP requests. It can process simple R commands in synchronous mode. This should be reserved to sense if a R server is running on a port, and which one is it (socket or http). It is also used to change config parameters like options(width = ...) in a synchronous way before running 'more serious' code asynchronously. 86 | 87 | - Callback mechanisms used by the server now moved to **svKomodo** package, so that it can also be used by **svHttp**. 88 | 89 | # svSocket 0.9-51 90 | 91 | - `processSocket()` no longer adds en empty line at the top of R commands (bug corrected). 92 | 93 | # svSocket 0.9-50 94 | 95 | - `processSocket()` now calls `parseText()` from svMisc \>= 0.9-60 instead of `Parse()`. 96 | 97 | - When Echo is `TRUE` and we are not in hidden mode, results are echoed directly in the R console as they are available, and not any more at the end of the calculation. 98 | 99 | - A new type of connection is added: a 'sockclientconn' that allows to redirect output (append or write-only, for the moment) to a SciViews socket client. It is created by using `socketClientConnection()` and has a specific `summary()` method. It inherits from a 'sockconn' object and should behave similarly. 100 | 101 | - `parSocket()` has a new argument, `clientsocket=`, that allows to pass the Tcl name of the client's socket. This is required to use `socketClientConnection()` by providing only the client's name (and thus, the required Tcl socket name is obtained through the property `parSocket(....)$clientsocket`, if it was previously recorded). The default process function, `processSocket()` is changed to record the Tcl socket in `parSocket()` each time a client connects to the server and sends its first command through it. 102 | 103 | # svSocket 0.9-49 104 | 105 | - Small change in `startSocketServer()`: the Tcl/Tk callback function now calls a closure located in `SciViews:TempEnv` (`SocketServerProc`). 106 | 107 | # svSocket 0.9-48 108 | 109 | - `svTaskCallbackManager()` added to allow callbacks to be executed after each (complete) R code send by a client to the server, as well as, any top-level task run at the R console. 110 | 111 | # svSocket 0.9-47 112 | 113 | - The server now calls `taskCallbacks` on non-hidden mode after code evaluation. 114 | 115 | # svSocket 0.9-46 116 | 117 | - `evalServer()` slightly reworked. 118 | 119 | - `sendSocketServer()` eliminated (superseded by `evalServer()`). 120 | 121 | # svSocket 0.9-45 122 | 123 | - Bug correction in `evalServer()`. 124 | 125 | # svSocket 0.9-44 126 | 127 | - Added function `evalServer()` for R interprocess communication using this R socket server mechanism. 128 | 129 | # svSocket 0.9-43 130 | 131 | - Example added in `processSocket()`, implementing a simple REPL. 132 | 133 | - A new function, `sendSocketServer()` is added to send and evaluate commands from one R instance (client) to another one (a R socket server). 134 | 135 | # svSocket 0.9-43 136 | 137 | - Polishing package for CRAN submission. 138 | 139 | # svSocket 0.9-42 140 | 141 | - Made compatible with R 2.6.x (previous package was R \>= 2.7.0). 142 | 143 | # svSocket 0.9-41 144 | 145 | - Correction in `startSocketServer()`: the `SocketServerProc` function was not protected against garbage collection. Consequently, the socket server stopped working at unpredictable events. 146 | 147 | - Correction of a bug preventing `processSocket()` to display error messages. Instead, I got: `Error in ngettext(1, "Error: ", domain = "R") : argument "msg2" is missing, with no default`. 148 | 149 | # svSocket 0.9-40 150 | 151 | - This is the first version distributed on R-forge. It is completely refactored from older versions (on CRAN since 2003) to make it run with SciViews-K and Komodo Edit (Tinn-R is also supported, but not SciViews-R Console any more). 152 | -------------------------------------------------------------------------------- /R/close_socket_clients.R: -------------------------------------------------------------------------------- 1 | #' Close one or more clients currently connected 2 | #' 3 | #' The socket servers asks to clients to nicely disconnect (possibly doing 4 | #' further process on their side). This function is used by 5 | #' [stop_socket_server()], but it can also be invoked manually to ask for 6 | #' disconnection of a particular client. Note that, in this case, the client 7 | #' still can decide not to disconnect! The code send to ask for client 8 | #' disconnection is: `\\f`. 9 | #' 10 | #' @param sockets the list of socket client names (sockXXX) to close, or `"all"` 11 | #' (by default) to disconnect all currently connected clients. 12 | #' @param server_port the corresponding R socket server port. 13 | #' 14 | #' @export 15 | #' @seealso [send_socket_clients()] 16 | #' @keywords IO 17 | #' @concept stateful socket server interprocess communication 18 | close_socket_clients <- function(sockets = "all", server_port = 8888) { 19 | # Nicely close socket client(s) by sending "\f" 20 | # To be interpreted by a compatible client that manages to close connection 21 | if (sockets == "all") 22 | sockets <- get_socket_clients_names(port = server_port) 23 | if (!is.null(sockets) && length(sockets) > 0) 24 | for (i in 1:length(sockets)) 25 | tcl("puts", sockets[i], "\n\f") 26 | } 27 | 28 | # Old name of the function 29 | #' @export 30 | #' @rdname close_socket_clients 31 | closeSocketClients <- close_socket_clients 32 | -------------------------------------------------------------------------------- /R/eval_socket_server.R: -------------------------------------------------------------------------------- 1 | #' Evaluate R code in a server process 2 | #' 3 | #' This function is designed to connect two R processes together using the 4 | #' socket server. It allows for piloting the server R process from a client R 5 | #' process, to evaluate R code in the server and return its results to the 6 | #' client. 7 | #' 8 | #' @param con a socket connection with the server (see examples). 9 | #' @param expr an R expression to evaluate in the server. 10 | #' @param send optional data to send to the server. 11 | #' 12 | #' @return The object returned by the last evaluation in the server. 13 | #' 14 | #' @details 15 | #' The function serializes R objects using [dump()] on the server, and it 16 | #' [source()]s the data on the client side. It has, thus, the same limitations 17 | #' as [dump()], (see `?dump`), and in particular, environments, external 18 | #' pointers, weak references and objects of type `S4` are not serializable with 19 | #' [dump()] and will raise an error, or will produce unusable objects on the 20 | #' client side. Note also that lists or attributes of accepted objects may 21 | #' contain external pointers or environments, and thus, the whole object becomes 22 | #' unserializable. In that case, try to coerce your object, or extract a part of 23 | #' it on the server side to make sure you send just the part that is 24 | #' transferable between the two R processes. 25 | #' 26 | #' @seealso [send_socket_clients()] 27 | #' @author Matthew Dowle 28 | #' @export 29 | #' @keywords IO utilities 30 | #' @concept stateful socket server interprocess communication 31 | #' 32 | #' @examples 33 | #' \dontrun{ 34 | #' # Start an R process and make it a server 35 | #' library(svSocket) 36 | #' start_socket_server() 37 | #' 38 | #' # Start a second R process and run this code in it (the R client): 39 | #' library(svSocket) 40 | #' 41 | #' # Connect with the R socket server 42 | #' con <- socketConnection(host = "localhost", port = 8888, blocking = FALSE) 43 | #' 44 | #' L <- 10:20 45 | #' L 46 | #' eval_socket_server(con, L) # L is not an the server, hence the error 47 | #' eval_socket_server(con, L, L) # Send it to the server 48 | #' eval_socket_server(con, L) # Now it is there 49 | #' eval_socket_server(con, L, L + 2) 50 | #' L 51 | #' eval_socket_server(con, L) 52 | #' 53 | #' # More examples 54 | #' eval_socket_server(con, "x <- 42") # Set x 55 | #' eval_socket_server(con, "y <- 10") # Set y 56 | #' eval_socket_server(con, x + y) # Quotes not needed 57 | #' eval_socket_server(con, "x + y") # but you can put quotes if you like 58 | #' eval_socket_server(con, x) # Same as get x 59 | #' eval_socket_server(con, "x + Y") # Return server side-error to the client 60 | #' eval_socket_server(con, x) # Keep working after an error 61 | #' eval_socket_server(con, "x <- 'a'") # Embedded quotes are OK 62 | #' 63 | #' # Examples of sending data 64 | #' eval_socket_server(con, X, -42) # Alternative way to assign to X 65 | #' eval_socket_server(con, Y, 1:10) 66 | #' eval_socket_server(con, X + Y) 67 | #' X # Generates an error, X is not here in the client, only on the server 68 | #' eval_socket_server(con, X) 69 | #' eval_socket_server(con, "Z <- X + 3") # Send an assignment to execute remotely 70 | #' eval_socket_server(con, X + Z) 71 | #' eval_socket_server(con, "Z <- X + 1:1000; NULL") # Same but do not return Z 72 | #' eval_socket_server(con, length(Z)) 73 | #' Z <- eval_socket_server(con, Z) # Bring it back to client 74 | #' Z 75 | #' 76 | #' # Close connection with the R socket server 77 | #' close(con) 78 | #' 79 | #' # Now, switch back to the R server process and check 80 | #' # that the created variables are there 81 | #' L 82 | #' x 83 | #' y 84 | #' X 85 | #' Y 86 | #' Z 87 | #' 88 | #' # Stop the socket server 89 | #' stop_socket_server() 90 | #' } 91 | eval_socket_server <- function(con, expr, send = NULL) { 92 | # Evaluate expr on the R server, and return its value 93 | # con as returned by socketConnection(port = 8888) 94 | # send is optional. If supplied, expr must be a single unquoted object name. 95 | # Then send is evaluated on the client and the result is assigned 96 | # to that object on the server. 97 | # Robust flushing and dumping is just for windows. Linux is probably fine 98 | # without but no harm to leave in for now since binary mode will moot this. 99 | x <- substitute(expr) 100 | if (!missing(send) && (length(x) != 1 || mode(x) != "name")) 101 | stop("When send is supplied, expr must be a target variable name (unquoted) on the server to assign the result of the send expr to.") 102 | if (!is.character(x)) { 103 | x <- deparse(x) 104 | } else { 105 | x <- gsub('"', '\\\\"', x) 106 | } 107 | 108 | readLines(con) # Flush input stream in case previous call failed to clean up 109 | if (missing(send)) { 110 | cat('..Last.value <- try(eval(parse(text = "', x, 111 | '"))); .f <- file(); dump("..Last.value", file = .f); flush(.f); seek(.f, 0); cat("\\n<<>>", gsub("", "NULL", readLines(.f)), "<<>>\\n", sep = "\\n"); close(.f); rm(.f, ..Last.value); flush.console()\n', 112 | file = con, sep = "") 113 | # It is important that one line only is written, so that other clients 114 | # don't mix in with these lines. 115 | } else { 116 | .f <- file() 117 | on.exit(close(.f)) 118 | ..Last.value <- send 119 | # dump() can stop prematurely if file=con, but also good to remove the /n 120 | # from dump()'s output before sending (to avoid possible conflicts with 121 | # other clients) 122 | dump("..Last.value", file <- .f) 123 | flush(.f) 124 | seek(.f, 0) 125 | cat(readLines(.f), ';', x, 126 | ' <- ..Last.value; rm(..Last.value); cat("\\n<<>>\\n"); flush.console()\n', 127 | file = con, sep = "") 128 | } 129 | objdump <- "" 130 | endloc <- NULL 131 | while (!length(endloc)) { 132 | obj <- readLines(con, n = 1000, warn = FALSE) 133 | # Wait for data to come back. Without this sleep, you get 20-30 calls 134 | # to readLines before data arrives. 135 | if (!length(obj)) { 136 | Sys.sleep(0.01) 137 | next 138 | } 139 | endloc <- grep("<<>>", obj) 140 | if (length(endloc)) 141 | obj <- obj[0:(endloc[length(endloc)] - 1)] 142 | # This is more robust than paste'ing together a potentially very 143 | # large single string 144 | objdump <- c(objdump, obj) 145 | } 146 | if (!missing(send)) { 147 | if (!all(objdump == "")) stop(objdump) 148 | return(TRUE) 149 | } 150 | startloc <- grep("<<>>", objdump) 151 | if (!length(startloc)) 152 | stop("Unable to find <<>>") 153 | # The startflag is because sometimes (strangely rarely) seek, flush and dump 154 | # can write return value to stdout which do not source. 155 | objdump <- objdump[-(1:startloc[length(startloc)])] 156 | # Fix any output buffer wrap issues. There are line breaks mid number 157 | # sometimes which don't source. 158 | # This is why warn = FALSE appears above in the call to readLines since it 159 | # warns about these noncomplete lines otherwise. 160 | nospace <- grep("[^ ]$", objdump) 161 | nospace <- nospace[nospace < length(objdump)] 162 | for (i in rev(nospace)) { # Robust to consecutive lines to be joined 163 | objdump[i] <- paste(objdump[i], objdump[i + 1], sep = "") 164 | objdump[i + 1] <- "" 165 | } 166 | objcon <- textConnection(objdump) 167 | on.exit(close(objcon)) 168 | source(objcon, local = TRUE, echo = FALSE, verbose = FALSE) 169 | ..Last.value 170 | } 171 | 172 | # Old name of the function 173 | #' @export 174 | #' @rdname eval_socket_server 175 | evalServer <- eval_socket_server 176 | -------------------------------------------------------------------------------- /R/get_socket.R: -------------------------------------------------------------------------------- 1 | #' Get the ports of current R socket servers 2 | #' 3 | #' Returns a list with all the ports of currently running R socket servers. 4 | #' 5 | #' @return 6 | #' A character string vector, or `NULL` if no R socket server is currently 7 | #' running. 8 | #' @seealso [get_socket_clients()], [get_socket_server_name()], [start_socket_server] 9 | #' @export 10 | #' @keywords IO utilities 11 | #' @concept stateful socket server interprocess communication 12 | get_socket_servers <- function() { 13 | # Get the list of currently running socket servers 14 | temp_env()$socket_servers 15 | } 16 | 17 | # Old name of the function 18 | #' @export 19 | #' @rdname get_socket_servers 20 | getSocketServers <- get_socket_servers 21 | 22 | #' Get infos about socket clients 23 | #' 24 | #' List all clients currently connected to a given R socket server, or their 25 | #' names (`sockXXX`). 26 | #' 27 | #' @param port the port of the R socket server. 28 | #' 29 | #' @return 30 | #' [get_socket_clients()] returns a vector of character string with the address of 31 | #' clients in the form XXX.XXX.XXX.XXX:YYY where XXX.XXX.XXX.XXX is their ip 32 | #' address and YYY is their port. For security reasons, only localhost clients 33 | #' (on the same machine) can connect to the socket server. Thus, XXX.XXX.XXX.XXX 34 | #' is ALWAYS 127.0.0.1. However, the function returns the full IP address, just 35 | #' in case of further extensions in the future. The name of these items equals 36 | #' the corresponding Tcl socket name. 37 | #' 38 | #' [get_socket_clients_names()] returns only a list of the socket client names. 39 | #' 40 | #' @export 41 | #' @seealso [get_socket_servers()] 42 | #' @keywords IO utilities 43 | #' @concept stateful socket server interprocess communication 44 | get_socket_clients <- function(port = 8888) { 45 | if (!is.numeric(port[1]) || port[1] < 1) 46 | stop("'port' must be a positive integer!") 47 | portnum <- round(port[1]) 48 | port <- as.character(portnum) 49 | 50 | # Does a server exist on this port? 51 | servers <- get_socket_servers() 52 | if (!(port %in% servers)) 53 | return(NULL) # If no R socket server running on this port 54 | 55 | # Get the list of clients currently connected to this server 56 | clients <- as.character(.Tcl(paste("array names Rserver", port, sep = "_"))) 57 | # Eliminate "main", which is the connection socket 58 | clients <- clients[clients != "main"] 59 | 60 | # Are there client connected? 61 | if (length(clients) == 0) 62 | return(character(0)) 63 | 64 | # For each client, retrieve its address and port 65 | addresses <- NULL 66 | arrayname <- paste("Rserver", port, sep = "_") 67 | for (i in 1:length(clients)) { 68 | client <- as.character(.Tcl(paste("array get", arrayname, clients[i]))) 69 | addresses[i] <- sub(", ", ":", client[2]) 70 | } 71 | names(addresses) <- clients 72 | addresses 73 | } 74 | 75 | # Old name of the function 76 | #' @export 77 | #' @rdname get_socket_clients 78 | getSocketClients <- get_socket_clients 79 | 80 | #' @export 81 | #' @rdname get_socket_clients 82 | get_socket_clients_names <- function(port = 8888) 83 | names(get_socket_clients(port = port)) 84 | 85 | # Old name of the function 86 | #' @export 87 | #' @rdname get_socket_clients 88 | getSocketClientsNames <- get_socket_clients_names 89 | 90 | #' Get the name of a R socket server 91 | #' 92 | #' Get the internal name given to a particular R socket server. 93 | #' 94 | #' @param port the port of the R socket server. 95 | #' 96 | #' @return 97 | #' A string with the server name, or `NULL` if it does not exist. 98 | #' @seealso [get_socket_servers()] 99 | #' @keywords IO utilities 100 | #' @concept stateful socket server interprocess communication 101 | #' @export 102 | get_socket_server_name <- function(port = 8888) { 103 | if (!is.numeric(port[1]) || port[1] < 1) 104 | stop("'port' must be a positive integer!") 105 | portnum <- round(port[1]) 106 | port <- as.character(portnum) 107 | 108 | # Return the name of a given R socket server 109 | servers <- get_socket_servers() 110 | if (!(port %in% servers)) 111 | return(NULL) # If no R socket server running on this port 112 | 113 | server_names <- names(servers) 114 | server_names[servers == port] 115 | } 116 | 117 | # Old name of the function 118 | #' @export 119 | #' @rdname get_socket_clients 120 | getSocketServerName <- get_socket_server_name 121 | -------------------------------------------------------------------------------- /R/par_socket_server.R: -------------------------------------------------------------------------------- 1 | #' Get or set parameters specific to SciViews socket clients 2 | #' 3 | #' This function manage to persistently store sensible parameters for 4 | #' configuring communication between the server and the client, as well as, any 5 | #' other persistent data you may need. Parameters remain set even if the client 6 | #' disconnects and then reconnects to R, as long R was not restarted. 7 | #' 8 | #' @param client the client identification. By default, it is the socket 9 | #' identifier as it appears in [get_socket_clients()]. Since no attempt is made 10 | #' to check if the client really exists and is connected, you can create fake 11 | #' ones, outside of the socket server, to test your code for instance. 12 | #' @param server_port the port on which the server is running, 8888 by default. 13 | #' Not important for fake socket client configurations. 14 | #' @param client_socket the Tcl name of the socket where the client is connected. 15 | #' By default, it is the same as `client` name, but in case it was modified, do 16 | #' provide a correct `client_socket` string if you want to be able to activate a 17 | #' redirection to it (see [socket_client_connection()]). 18 | #' @param ... the parameters you want to change as named arguments. Non named 19 | #' arguments are ignored with a warning. If you specify `arg = NULL`, the 20 | #' corresponding variable is deleted from the environment. 21 | #' 22 | #' @return 23 | #' Returns the environment where parameters and data for the client are stored. 24 | #' To access those data, see examples below. 25 | #' 26 | #' @details 27 | #' You can assign the environment to a variable, and then, access its content 28 | #' like if it was a list (`e$var` or `e$var <- "new value"`). To get a list of 29 | #' the content, use `ls(par_socket_server(client, port))`, or 30 | #' `ls(par_socket_server(client, port), all.names = TRUE)`, but not 31 | #' `names(par_socket_server(client, port))`. As long as you keep a variable 32 | #' pointing on that environment alive, you have access to last values (i.e., 33 | #' changes done elsewhere are taken into account). If you want a frozen snapshot 34 | #' of the parameters, you should use 35 | #' `myvar <- as.list(par_socket_server(client, port)`. 36 | #' 37 | #' There is a convenient placeholder for code send by the client to insert 38 | #' automatically the right socket and server_port in 39 | #' `par_socket_server()`: `<<>>`. 40 | #' Hence, code that the client send to access or change its environment is just 41 | #' `par_socket_server(<<>>, bare = FALSE)` or 42 | #' `par_socket_server(<<>>)$bare` to set or get one parameter. Note that you 43 | #' can set or change many parameters at once. 44 | #' 45 | #' Currently, parameters are: 46 | #' - `bare = TRUE|FALSE` for "bare" mode (no prompt, no echo, no multiline; by 47 | #' default, `bare = TRUE`), 48 | #' - `multiline = TRUE|FALSE`: does the server accept code spread on multiple 49 | #' lines and send in several steps (by default, yes, but works only if 50 | #' `bare = FALSE`. 51 | #' - `echo = TRUE|FALSE` is the command echoed to the regular R console (by 52 | #' default `echo = FALSE`). 53 | #' - `last = ""` string to append to each output (for instance to indicate that 54 | #' processing is done), 55 | #' - `prompt = "> "`, the prompt to use (if not in bare mode) and 56 | #' - `continue = "+ "` the continuation prompt to use, when multiline mode is 57 | #' active. You can only cancel a multiline mode by completing the R code you are 58 | #' sending to the server, but you can break it too by sending `<<>>` before 59 | #' the next instruction. You can indicate `<<>>` or `<<>>` at the very 60 | #' beginning of an instruction to tell R to disconnect the connection after the 61 | #' command is processed and result is returned (with `<<>>`), or when the 62 | #' instructions are received but before they are processed (with `<<>>`). 63 | #' This is useful for "one shot" clients (clients that connect, send code and 64 | #' want to disconnect immediately after that). The code send by the server to 65 | #' the client to tell him to disconnect gracefully (and do some housekeeping) is 66 | #' `\\f` send at the beginning of one line. So, clients should detect this and 67 | #' perform the necessary actions to gracefully disconnect from the server as 68 | #' soon as possible, and he cannot send further instructions from this moment 69 | #' on. 70 | #' 71 | #' For clients that repeatedly connect and disconnect, but want persistent data, 72 | #' the default client identifier (the socket name) cannot be used, because that 73 | #' socket name would change from connection to connection. The client must then 74 | #' provide its own identifier. This is done by sending `<<>>` at the 75 | #' very beginning of a command. This must be done for all commands! `myID` must 76 | #' use only characters or digits. This code could be followed by `<<>>`, 77 | #' `<<>>` or `<<>>`. These commands are intended for R editors/IDE. The 78 | #' first code `<<>>` sets the server into a mode that is suitable to 79 | #' evaluate R code (including in a multi-line way). The other code temporarily 80 | #' configure the server to run the command (in single line mode only) in a 81 | #' hidden way. They can be used to execute R code without displaying it in the 82 | #' console (for instance, to start context help, to get a calltip, or a 83 | #' completion list, etc.). The differences between `<<>>` and `<<>>` is 84 | #' that the former waits for command completion and returns results of the 85 | #' command to the client before disconnecting, while the latter disconnects from 86 | #' the client before executing the command. 87 | #' 88 | #' There is a simple client (written in Tcl) available in the /etc subdirectory 89 | #' of this package installation. Please, read the 'ReadMe.txt' file in the same 90 | #' directory to learn how to use it. You can use this simple client to 91 | #' experiment with the communication using these sockets, but it does not 92 | #' provide advanced command line edition, no command history, and avoid pasting 93 | #' more than one line of code into it. 94 | #' 95 | #' @export 96 | #' @seealso [start_socket_server()], [send_socket_clients()], [get_socket_clients()], 97 | #' [socket_client_connection()] 98 | #' @keywords IO utilities 99 | #' @concept stateful socket server interprocess communication 100 | #' 101 | #' @examples 102 | #' # We use a fake socket client configuration environment 103 | #' e <- par_socket_server("fake") 104 | #' # Look at what it contains 105 | #' ls(e) 106 | #' # Get one data 107 | #' e$bare 108 | #' # ... or 109 | #' par_socket_server("fake")$bare 110 | #' 111 | #' # Change it 112 | #' par_socket_server("fake", bare = FALSE)$bare 113 | #' # Note it is changed too for e 114 | #' e$bare 115 | #' 116 | #' # You can change it too with 117 | #' e$bare <- TRUE 118 | #' e$bare 119 | #' par_socket_server("fake")$bare 120 | #' 121 | #' # Create a new entry 122 | #' e$foo <- "test" 123 | #' ls(e) 124 | #' par_socket_server("fake")$foo 125 | #' # Now delete it 126 | #' par_socket_server("fake", foo = NULL) 127 | #' ls(e) 128 | #' 129 | #' # Our fake socket config is in SciViews:TempEnv environment 130 | #' s <- search() 131 | #' l <- length(s) 132 | #' pos <- (1:l)[s == "SciViews:TempEnv"] 133 | #' ls(pos = pos) # It is named 'socket_client_fake' 134 | #' # Delete it 135 | #' rm(socket_client_fake, pos = pos) 136 | #' # Do some house keeping 137 | #' rm(list = c("s", "l", "pos")) 138 | par_socket_server <- function(client, server_port = 8888, 139 | client_socket = client, ...) { 140 | # Set or get parameters for a given socket client 141 | # No attempt is made to make sure this client exists 142 | sc <- paste("socket_client", client, sep = "_") 143 | if (!exists(sc, envir = temp_env(), inherits = FALSE, mode = "environment")) { 144 | # Create a new environment with default values 145 | e <- new.env(parent = temp_env()) 146 | e$client <- client 147 | e$client_socket <- client_socket 148 | e$server_port <- server_port 149 | e$prompt <- ":> " # Default prompt 150 | e$continue <- ":+ " # Default continuation prompt 151 | e$code <- "" # Current partial code for multiline mode 152 | e$last <- "" # String to add at the end of evaluations 153 | e$echo <- FALSE # Don't echo commands to the console 154 | e$flag <- FALSE # Do not flag pieces of code (not used yet!) 155 | e$multiline <- TRUE # Allow for multiline code 156 | e$bare <- TRUE # Always start in "bare" mode 157 | # Note: in bare mode, all other parameters are inactive! 158 | # Assign it to SciViews:TempEnv 159 | assign(sc, e, envir = temp_env()) 160 | } else { 161 | e <- get(sc, envir = temp_env(), mode = "environment") 162 | } 163 | # Change or add parameters if they are provided 164 | # There is no reason that server_port changes 165 | # but if a client disconnects and reconnects, the client_socket may be 166 | # different! But only change if it is sockXXX 167 | if (grepl("^sock[0-9]+$", client_socket)) 168 | e$client_socket <- client_socket 169 | args <- list(...) 170 | if (l <- length(args)) { 171 | change.par <- function(x, val, env) { 172 | if (is.null(x)) 173 | return(FALSE) # Do nothing without a valid name 174 | if (is.null(val)) { 175 | suppressWarnings(rm(list = x, envir = env)) # Remove it 176 | return(TRUE) 177 | } 178 | env[[x]] <- val # Add or change this variable in the environment 179 | return(TRUE) 180 | } 181 | n <- names(args) 182 | res <- rep(TRUE, l) 183 | for (i in seq_len(l)) 184 | res[i] <- change.par(n[i], args[[i]], e) 185 | if (any(!res)) 186 | warning("Non named arguments are ignored") 187 | } 188 | 189 | invisible(e) 190 | } 191 | 192 | # Old name of the function 193 | #' @export 194 | #' @rdname par_socket_server 195 | parSocket <- par_socket_server 196 | -------------------------------------------------------------------------------- /R/process_socket_server.R: -------------------------------------------------------------------------------- 1 | #' The function that processes a command coming from the socket 2 | #' 3 | #' This is the default R function called each time data is send by a client 4 | #' through a socket. It is possible to customize this function and to use 5 | #' customized versions for particular R socket servers. 6 | #' 7 | #' @param msg the message send by the client, to be processed. 8 | #' @param socket the client socket identifier, as in [get_socket_clients()]. 9 | #' This is passed by the calling function and can be used internally. 10 | #' @param server_port the port on which the server is running, this is passed by 11 | #' the calling function and can be used internally. 12 | #' @param ... anything you want to pass to [process_socket_server()], but it 13 | #' needs to rework [start_socket_server()] to use it). 14 | #' 15 | #' @return 16 | #' The results of processing `msg` in a character string vector. 17 | #' 18 | #' @details 19 | #' There are special code that one can send to R to easily turn the server 20 | #' (possibly temporarily) into a given configuration. First, if you want to 21 | #' persistently store parameters for your client in the R server and make sure 22 | #' you retrieve the same parameters the next time you reconnect, you should 23 | #' specify your own identifier. This is done by sending `<<>>` at the 24 | #' very beginning of each of your commands. Always remember that, if you do not 25 | #' specify an identifier, the name of your socket will be used. Since socket 26 | #' names can be reused, you should always reinitialize the configuration of your 27 | #' server the first time you connect to it. 28 | #' 29 | #' Then, sending `<<>>` breaks current multiline code submission and 30 | #' flushes the multiline buffer. 31 | #' 32 | #' The sequence `<<>>` at the beginning of a command indicates that the 33 | #' server wants to disconnect once the command is fully treated by R. Similarly, 34 | #' the sequence `<<>>` tells the server to disconnect the client before 35 | #' processing the command (no error message is returned to the client!). 36 | #' 37 | #' It is easy to turn the server to evaluate R code (including multiline code) 38 | #' and return the result and disconnect by using the `<<>>` sequence at the 39 | #' beginning of a command. Using `<<>>` or `<<>>` configures that server 40 | #' to process a (single-line code only) command silently and disconnect before 41 | #' (uppercase H) or after (lowercase h) processing that command. It is the less 42 | #' intrusive mode that is very useful for all commands that should be executed 43 | #' behind the scene between R and a R editor or IDE, like contextual help, 44 | #' calltips, completion lists, etc.). Note that using these modes in a server 45 | #' that is, otherwise, configured as a multi-line server does not break current 46 | #' multi-line buffer. 47 | #' 48 | #' The other sequences that can be used are: `<<>>` for a placeholder to 49 | #' configure the current server (with configuration parameters after it), and 50 | #' `<<>>` to indicate a newline in your code (submitting two lines of code 51 | #' as a single one; also works with servers configured as single-line 52 | #' evaluators). 53 | #' 54 | #' To debug the R socket server and inspect how commands send by a client are 55 | #' interpreted by this function, use `options(debug.Socket = TRUE)`. This 56 | #' function uses [svMisc::parse_text()] and [svMisc::capture_all()] in order to 57 | #' evaluate R code in character string almost exactly the same way as if it was 58 | #' typed at the command line of a R console. 59 | #' 60 | #' @export 61 | #' @seealso [start_socket_server()], [send_socket_clients()], 62 | #' [par_socket_server()], [svMisc::parse_text()], [svMisc::capture_all()] 63 | #' @keywords IO utilities 64 | #' @concept stateful socket server interprocess communication 65 | #' 66 | #' @examples 67 | #' \dontrun{ 68 | #' # A simple REPL (R eval/process loop) using basic features of processSocket() 69 | #' repl <- function() { 70 | #' pars <- par_socket_server("repl", "", bare = FALSE) # Parameterize the loop 71 | #' cat("Enter R code, hit or to exit\n> ") # First prompt 72 | #' repeat { 73 | #' entry <- readLines(n = 1) # Read a line of entry 74 | #' if (entry == "") entry <- "<<>>" # Exit from multiline mode 75 | #' cat(process_socket_server(entry, "repl", "")) # Process the entry 76 | #' } 77 | #' } 78 | #' repl() 79 | #' } 80 | process_socket_server <- function(msg, socket, server_port, ...) { 81 | # This is the default R function that processes a command send by a socket 82 | # client. 'msg' is assumed to be R code contained in a string 83 | 84 | # Strings are supposed to be send in UTF-8 format 85 | # Encoding(msg) <- "UTF-8" 86 | # msg <- enc2native(msg) 87 | 88 | # Special case of a HEAD HTTPrequest 89 | # TODO: rework this, return a header and force socket disconnection on the 90 | # server side 91 | # HEAD should return only the header with 92 | # HTTP/1.1 200 OK 93 | # Server: R socket server 94 | # Connection: close 95 | # Content-type: text/plain;charset=UTF-8 96 | # (followed by an empty line) 97 | # Get does the same, but process the R command and returns processed results 98 | # in the body, i.e, after the empty line 99 | # Respond immediately 100 | if (regexpr("^(HEAD|GET) /custom/SciViews\\?.* HTTP/.*$", msg) > 0 ) { 101 | # Get the code to execute 102 | code <- sub("^(HEAD|GET) /custom/SciViews\\?(.*) HTTP/.*$", "\\2", msg) 103 | code <- URLdecode(code) 104 | # Do we receive a <<>> sequence? 105 | if (regexpr("^<<>>", code) > 0) { 106 | # Get the identifier 107 | client <- sub("^<<>>.*$", "\\1", code) 108 | # ... and eliminate that sequence 109 | code <- sub("^<<>>", "", code) 110 | } else { 111 | # The client name is simply the socket name 112 | client <- socket 113 | } 114 | # Do some replacements 115 | # Replace <<>> by \n (for multiline code) 116 | code <- gsub("<<>>", "\n", code) 117 | # Replace <<>> by the corresponding client id and server port 118 | code <- gsub("<<>>", paste('"', client, '", ', server_port, sep = ""), 119 | code) 120 | # We ignore and eliminate the other <<>> sequences 121 | code <- gsub("<<<[^>]+>>>", "", code) 122 | 123 | # Parse and execute this code 124 | expr <- parse_text(code) 125 | results <- try(capture_all(expr, echo = FALSE, split = FALSE), 126 | silent = TRUE) 127 | 128 | # Make sure to return something different than "" (this is used to 129 | # sense if the R server is socket or HTTP) 130 | if (is.null(results) || is.na(results) || !length(results) || 131 | results == "") results <- " " 132 | 133 | # Return captured results 134 | # return(enc2utf8(results)) 135 | return(results) 136 | } 137 | 138 | # These are other key: value lines send by HTTP clients... just ignore 139 | if (regexpr("^[-a-zA-Z]+: ", msg) > 0) 140 | return("") 141 | 142 | # Do we receive a <<>> sequence? 143 | if (regexpr("^<<>>", msg) > 0) { 144 | # Get the identifier 145 | client <- sub("^<<>>.*$", "\\1", msg) 146 | # ... and eliminate that sequence 147 | msg <- sub("^<<>>", "", msg) 148 | } else { 149 | # The client name is simply the socket name 150 | client <- socket 151 | } 152 | 153 | # Do we receive <<>>? => break (currently, only break multiline mode) 154 | if (substr(msg, 1, 9) == "<<>>") { 155 | # Reset multiline code and update clientsocket 156 | pars <- par_socket_server(client, server_port, client_socket = socket, 157 | code = "") 158 | msg <- substr(msg, 10, 1000000) 159 | } 160 | 161 | # Replace <<>> by \n (for multiline code) 162 | msg <- gsub("<<>>", "\n", msg) 163 | 164 | # Replace <<>> by the corresponding client identifier and server port 165 | msg <- gsub("<<>>", paste('"', client, '", ', server_port, sep = ""), msg) 166 | 167 | hidden_mode <- FALSE 168 | return_results <- TRUE 169 | # If msg starts with <<>> or <<>>, then disconnect server before or 170 | # after evaluation of the command, respectively 171 | # If msg starts with <<>>, evaluate command in the console and disconnect 172 | # If msg starts with <<>> or <<>>, evaluate in hidden mode + disconnect 173 | startmsg <- substr(msg, 1, 7) 174 | if (startmsg == "<<>>") { 175 | msg <- substr(msg, 8, 1000000) 176 | # Indicate to the client that he can disconnect now 177 | close_socket_clients(sockets = socket, server_port = server_port) 178 | return_results <- FALSE 179 | } else if (startmsg == "<<>>") { 180 | msg <- substr(msg, 8, 1000000) 181 | # Remember to indicate disconnection at the end 182 | par_socket_server(client, server_port, client_socket = socket, 183 | last = "\n\f") 184 | } else if (startmsg == "<<>>") { 185 | msg <- substr(msg, 8, 1000000) 186 | # We just configure the server correctly 187 | par_socket_server(client, server_port, client_socket = socket, bare = FALSE, 188 | echo = TRUE, prompt = ":> ", continue = ":+ ", multiline = TRUE, 189 | last = "\n\f") 190 | # Add a command to the command history 191 | #timestamp("my R command", "", "", quiet = TRUE) 192 | } else if (startmsg == "<<>>") { 193 | msg <- substr(msg, 8, 1000000) 194 | # Do not echo command on the server (silent execution) 195 | hidden_mode <- TRUE 196 | par_socket_server(client, server_port, client_socket = socket, bare = TRUE, 197 | last = "\n\f") 198 | } else if (startmsg == "<<>>") { 199 | msg <- substr(msg, 8, 1000000) 200 | # Do not echo command on the server (silent execution with no return) 201 | close_socket_clients(sockets = socket, server_port = server_port) 202 | hidden_mode <- TRUE 203 | return_results <- FALSE 204 | par_socket_server(client, server_port, client_socket = socket, bare = TRUE) 205 | } else if (startmsg == "<<>>") { 206 | msg <- substr(msg, 8, 1000000) 207 | # Silent execution, nothing is returned to the client 208 | # (but still echoed to the server) 209 | hidden_mode <- FALSE 210 | return_results <- FALSE 211 | par_socket_server(client, server_port, client_socket = socket, bare = TRUE) 212 | } 213 | 214 | # Get parameters for the client 215 | pars <- par_socket_server(client, server_port) 216 | if (Bare <- pars$bare) { 217 | Prompt <- "" 218 | Continue <- "" 219 | Echo <- FALSE 220 | } else { 221 | Prompt <- pars$prompt 222 | Continue <- pars$continue 223 | Echo <- pars$echo 224 | } 225 | if (!hidden_mode) { 226 | if (Echo) { 227 | # Note: command lines are now echoed directly in captureAll() 228 | # => no need of this any more! 229 | if (pars$code == "") Pre <- Prompt else Pre <- Continue 230 | #cat(Pre, msg, "\n", sep = "") 231 | } 232 | # Add previous content if we were in multiline mode 233 | if (pars$code != "") 234 | msg <- paste(pars$code, msg, sep = "\n") 235 | pars$code <- "" # This changes the original data too! 236 | } 237 | 238 | # Parse the R code 239 | expr <- parse_text(msg) 240 | # Is it a wrong code? 241 | if (inherits(expr, "try-error")) { 242 | res <- paste(ngettext(1, "Error: ", "", domain = "R"), 243 | sub("^[^:]+: ([^\n]+)\n[0-9]+:(.*)$", "\\1\\2", expr), sep = "") 244 | if (Echo) 245 | cat(res) 246 | # return(enc2utf8(paste(res, pars$last, Prompt, sep = ""))) 247 | return(paste(res, pars$last, Prompt, sep = "")) 248 | } 249 | # Is it incomplete code? 250 | if (!is.expression(expr)) { 251 | # Is multiline mode allowed? 252 | if (!Bare && pars$multiline) { 253 | pars$code <- msg 254 | if (return_results) { 255 | # return(enc2utf8(paste(pars$last, Continue, sep = ""))) 256 | return(paste(pars$last, Continue, sep = "")) 257 | } else { 258 | return("") 259 | } 260 | } else {# Multimode not allowed 261 | res <- paste(gettext("Error: incomplete command in single line mode"), 262 | "\n", sep = "") 263 | if (Echo) 264 | cat(res) 265 | if (return_results) { 266 | # return(enc2utf8(paste(res, pars$last, Prompt, sep = ""))) 267 | return(paste(res, pars$last, Prompt, sep = "")) 268 | } else { 269 | return("") 270 | } 271 | } 272 | } 273 | # Freeze parameters (unlinks from the environment) 274 | pars <- as.list(pars) 275 | # Is it something to evaluate? 276 | if (length(expr) < 1) { 277 | # return(enc2utf8(paste(pars$last, Prompt, sep = ""))) 278 | return(paste(pars$last, Prompt, sep = "")) 279 | } 280 | # Correct code,... we evaluate it 281 | # Something like this should allow for real-time echo in client, 282 | # but it is too slow and it outputs all results at the end... 283 | #results <- capture_all(expr, split = Echo, 284 | # file = socket_client_connection(socket)) 285 | results <- capture_all(expr, echo = Echo, split = Echo) 286 | # Should we run taskCallbacks? 287 | # Note: these are installed in svKomodo package 288 | if (!hidden_mode) { 289 | h <- get_temp(".svTaskCallbackManager", default = NULL, mode = "list") 290 | if (!is.null(h)) 291 | h$evaluate() 292 | } 293 | # Collapse and add last and the prompt at the end 294 | results <- paste(results, collapse = "\n") 295 | #if (Echo) cat(results) 296 | if (!return_results) 297 | return("") 298 | Prompt <- if (pars$bare) "" else pars$prompt 299 | results <- paste(results, pars$last, Prompt, sep = "") 300 | # return(enc2utf8(results)) 301 | results 302 | } 303 | 304 | # Old name of the function 305 | #' @export 306 | #' @rdname process_socket_server 307 | processSocket <- process_socket_server 308 | -------------------------------------------------------------------------------- /R/send_socket_clients.R: -------------------------------------------------------------------------------- 1 | #' Send data to one or more clients through a socket 2 | #' 3 | #' The text is send to one or more clients of the R socket server currently 4 | #' connected. 5 | #' 6 | #' @param text the text to send to the client(s). 7 | #' @param sockets the Tcl name of the client(s) socket(s) currently connected 8 | #' (`sockXXX`), or `"all"` (by default) to send the same text to all connected 9 | #' clients. 10 | #' @param server_port the port of the server considered. 11 | #' 12 | #' @export 13 | #' @seealso [close_socket_clients()], [process_socket_server()] 14 | #' @keywords IO utilities 15 | #' @concept stateful socket server interprocess communication 16 | #' 17 | #' @examples 18 | #' \dontrun{ 19 | #' # Start an R process (R#1) and make it a server 20 | #' library(svSocket) 21 | #' server_port <- 8888 # Port 8888 by default, but you can change it 22 | #' start_socket_server(port = server_port) 23 | #' 24 | #' 25 | #' # Start a second R process (R#2) and run this code in it (the R client): 26 | #' library(svSocket) 27 | #' # Connect with the R socket server 28 | #' con <- socketConnection(host = "localhost", port = 8888, blocking = FALSE) 29 | #' 30 | #' 31 | #' # Now, go back to the server R#1 32 | #' get_socket_clients() # You should have one client registered 33 | #' # Send something to all clients from R#1 34 | #' send_socket_clients("Hi there!") 35 | #' 36 | #' 37 | #' # Switch back to client R#2 38 | #' # Since the connection is not blocking, you have to read lines actively 39 | #' readLines(con) 40 | #' # Note the final empty string indicating there is no more data 41 | #' close(con) # Once done... 42 | #' 43 | #' 44 | #' # Switch to the R#1 server and close the server 45 | #' stop_socket_server(port = server_port) 46 | #' } 47 | send_socket_clients <- function(text, sockets = "all", server_port = 8888) { 48 | # Note that 'real' clients should manage to print this BEFORE the current 49 | # command line, something that 'SimpleClient.Tcl' cannot do! 50 | 51 | # Make sure that the text ends with a carriage return 52 | # (same behavior as in Mac R.app but different from RGui!) 53 | if (regexpr("\n^", text) < 0) 54 | text <- paste(text, "\n", sep = "") 55 | 56 | # Send the given text to one or more clients through a socket 57 | if (sockets == "all") 58 | sockets <- get_socket_clients_names(port = server_port) 59 | if (!is.null(sockets) && length(sockets) > 0) 60 | for (i in 1:length(sockets)) 61 | tcl("puts", sockets[i], text) 62 | } 63 | 64 | # Old name of the function 65 | #' @export 66 | #' @rdname send_socket_clients 67 | sendSocketClients <- send_socket_clients 68 | -------------------------------------------------------------------------------- /R/socket_client_connection.R: -------------------------------------------------------------------------------- 1 | #' Open a connection to a SciViews socket client for write access 2 | #' 3 | #' A 'sockclientconn' object is created that opens a connection from R to a 4 | #' SciViews socket client (that must be currently connected). A timeout is 5 | #' defined by `options(timeout = XX)` where `XX` is a number of seconds. In R, 6 | #' its default value is 60 sec. 7 | #' 8 | #' @param client the client identification. By default, it is the socket 9 | #' identifier as it appears in [get_socket_clients()]. The client must be 10 | #' currently connected. 11 | #' @param server_port the port on which the server is running, 8888 by default. 12 | #' This server must be currently running. 13 | #' @param socket the Tcl socket name where the targeted client is connected. If 14 | #' not provided, it will be guessed from `client`, otherwise, `client` is 15 | #' ignored. 16 | #' @param blocking logical. Should the connection wait that the data is written 17 | #' before exiting? 18 | #' @param open character. How the connection is opened. Currently, only `"a"` 19 | #' for append (default) or `"w"` for write access are usable. 20 | #' @param encoding the name of the encoding to use. 21 | #' @param object A 'sockclientconn' object as returned by 22 | #' [socket_client_connection()]. 23 | #' @param ... further arguments passed to the method (not used for the moment). 24 | #' 25 | #' @return 26 | #' [socket_client_connection()] creates a 'sockclientconn' object redirects text 27 | #' send to it to the SciViews socket server client. It is inherits from a 28 | #' 'sockconn' object (see `socketConnection()`), and the only difference is that 29 | #' output is redirected to a Tcl socket corresponding to a given SciViews socket 30 | #' client currently connected. 31 | #' @export 32 | #' @seealso [socketConnection()], [send_socket_clients()] 33 | #' @keywords IO utilities 34 | #' @concept stateful socket server interprocess communication 35 | socket_client_connection <- function(client, server_port = 8888, socket, 36 | blocking = FALSE, open = "a", encoding = getOption("encoding")) { 37 | # Only accepts "a" or "w" modes currently 38 | if (!open %in% c("a", "w")) 39 | stop("Only modes \"a\" or \"w\" are currently supported") 40 | 41 | # Connect to a client of the svSocket server, serving on 'server_port' 42 | # First check that the server is running and is serving 'socket' 43 | if (is.null(server_port) || !is.numeric(server_port[1]) || server_port[1] < 1) 44 | stop("'server_port' must be a positive integer!") 45 | portnum <- round(server_port[1]) 46 | if (!portnum %in% get_socket_servers()) 47 | stop("There is no currently running socket server on port ", 48 | portnum, "\n Start one by using start_socket_server() first") 49 | # If socket is not provided, try to get it from client's infos 50 | if (missing(socket)) 51 | socket <- par_socket_server(client, server_port)$client_socket 52 | # Check that 'socket' is a currently opened Tcl socket and is a client 53 | res <- try(.Tcl(paste("fconfigure", socket, "-peername")), silent = TRUE) 54 | if (inherits(res, "try-error")) 55 | stop("This client or this socket is not currently connected") 56 | res <- as.character(res) 57 | redir <- paste("->", res[1], ":", res[length(res)], sep = "") 58 | # That's OK, we could proceed in opening a socketConnection and redirect it 59 | # to the client's socket... 60 | # curr_sockets <- get_socket_clients_names(portnum) 61 | # Note: timeout is taken from getOption("timeout"), 60 sec by default 62 | sck <- socketConnection(host = "127.0.0.1", port = portnum, server = FALSE, 63 | blocking = blocking, open = open, encoding = encoding) 64 | # We need to leave enough time in the background to Tcl to establish the 65 | # connection 66 | # i <- 0 67 | # mysock <- character(0) 68 | # while (length(mysock) < 1 && i < 10) { 69 | # i <- i + 1 70 | .Tcl("update idletasks") 71 | # Sys.sleep(0.05) 72 | # curr_sockets2 <- get_socket_clients_names(portnum) 73 | # mysock <- curr_sockets2[!curr_sockets2 %in% curr_sockets] 74 | # } 75 | # if (length(mysock) < 1) { 76 | # try(close(sck), silent = TRUE) 77 | # stop("Unable to connect to the client socket") 78 | # } 79 | # mysock <- mysock[1] # Just a precaution 80 | # Now, activate the redirection in Tcl 81 | # .Tcl(paste("fileevent", mysock, "readable [list sock_redirect", mysock, 82 | # socket, "]")) 83 | # ... and eliminate this client for the list 84 | # .Tcl(paste("unset Rserver_", portnum, "(", mysock, ")", sep = "")) 85 | 86 | # Instruct the socket server to redirect to socket 87 | cat(">>>>>>", socket, "\n", sep = "", file = sck) 88 | .Tcl("update idletasks") 89 | 90 | # Finalize the "sockclientconn" object 91 | # attr(sck, "conn_tclsocket") <- mySock 92 | attr(sck, "conn_redirsocket") <- socket 93 | attr(sck, "conn_redirection") <- redir 94 | class(sck) <- c("sockclientconn", class(sck)) 95 | sck 96 | } 97 | 98 | #' @export 99 | #' @rdname socket_client_connection 100 | #' @method summary sockclientconn 101 | summary.sockclientconn <- function(object, ...) { 102 | obj2 <- object 103 | class(obj2) <- "connection" 104 | res <- summary(obj2) 105 | # Change description and class 106 | res$description <- attr(object, "conn_redirection") 107 | res$class <- "sockclientconn" 108 | res 109 | } 110 | 111 | # Old name of the function 112 | #' @export 113 | #' @rdname socket_client_connection 114 | socketClientConnection <- socket_client_connection 115 | -------------------------------------------------------------------------------- /R/start_socket_server.R: -------------------------------------------------------------------------------- 1 | #' Start and stop a R socket server 2 | #' 3 | #' A R socket server is listening for command send by clients to a TCP port. 4 | #' This server is implemented in Tcl/Tk, using the powerful 'socket' command. 5 | #' Since it runs in the separate tcltk event loop, it is not blocking R, and it 6 | #' runs in the background; the user can still enter commands at the R prompt 7 | #' while one or several R socket servers are running and even, possibly, 8 | #' processing socket clients requests. 9 | #' 10 | #' @param port the TCP port of the R socket server. 11 | #' @param server_name the internal name of this server. 12 | #' @param procfun the function to use to process client's commands. By default, 13 | #' it is `process_socket_server()`. 14 | #' @param secure do we start a secure (TLS) server? (not implemented yet) 15 | #' @param local if `TRUE`, accept only connections from local clients, i.e., 16 | #' from clients with IP address 127.0.0.1. Set by default if the server is not 17 | #' secure. 18 | #' 19 | #' @details 20 | #' This server is currently synchronous in the processing of the command. 21 | #' However, neither R, nor the client are blocked during exchange of data 22 | #' (communication is asynchronous). 23 | #' 24 | #' Note also that socket numbers are reused, and corresponding configurations 25 | #' are not deleted from one connection to the other. So, it is possible for a 26 | #' client to connect/disconnect several times and continue to work with the same 27 | #' configuration (in particular, the multiline code submitted line by line) if 28 | #' every command starts with `<<>>` where `myID` is an alphanumeric 29 | #' (unique) identifier. This property is call a stateful server. Take care! The 30 | #' R server never checks uniqueness of this identifier. You are responsible to 31 | #' use one that would not interfere with other, concurrent, clients connected 32 | #' to the same server. 33 | #' 34 | #' For trials and basic testings of the R socket server, you can use the Tcl 35 | #' script `SimpleClient.Tcl`. See the `ReadMe.txt` file in the 36 | #' /etc/ subdirectory of the svSocket package folder. Also, in the source of the 37 | #' svSocket package you will find `testCLI.R`, a script to torture test CLI for 38 | #' R (console). 39 | #' 40 | #' @note 41 | #' Due to a change in R 4.3.x in its event loop, some Tcl socket events are not 42 | #' processes and this prevents the R socket server to work properly. This is 43 | #' corrected in R 4.4.0. The socket server also works well with R 4.0.x, R 4.1.x 44 | #' and R 4.2.x. 45 | #' 46 | #' One can write a different `procfun()` function than the default one for 47 | #' special servers. That function must accept one argument (a string with the 48 | #' command send by the client) and it must return a character string containing 49 | #' the result of the computation. 50 | #' 51 | #' @export 52 | #' @seealso [process_socket_server()], [send_socket_clients()] 53 | #' @keywords IO utilities 54 | #' @concept stateful socket server interprocess communication 55 | start_socket_server <- function(port = 8888, server_name = "Rserver", 56 | procfun = process_socket_server, secure = FALSE, local = !secure) { 57 | # OK, could be port = 80 to emulate a simple HTML server 58 | # This is the main function that starts the server 59 | # This function implements a basic R socket server on 'port' 60 | # socket_server_proc is the R workhorse function that do the computation 61 | # The server is written in Tcl. This way it is not blocking R command-line! 62 | # It is designed in a way that R can open simultaneously several ports and 63 | # accept connection from multiple clients to each of them. 64 | # Commands from each port can be processed differently 65 | 66 | # Secure server requires the tcl-tls package! 67 | if (isTRUE(secure)) { 68 | # TODO: On Mac with AquaTclTk installed, I need: 69 | # addTclPath("/System/Library/Tcl") 70 | res <- tclRequire("tls") 71 | if (!inherits(res, "tclObj")) 72 | stop("You must install the tcl-tls package for using a secure server!") 73 | } 74 | 75 | if (!is.function(procfun)) 76 | stop("'procfun' must be a function!") 77 | 78 | # Note: the data send by the client is in the Tcl $::sock_msg variable 79 | # Could a clash happen here if multiple clients send data at the 80 | # same time to the R socket server??? 81 | if (!is.numeric(port[1]) || port[1] < 1) 82 | stop("'port' must be a positive integer!") 83 | portnum <- round(port[1]) 84 | port <- as.character(portnum) 85 | 86 | if (!is.character(server_name)) 87 | stop("'server_name' must be a string!") 88 | server_name <- as.character(server_name)[1] 89 | 90 | # Check if the port is not open yet 91 | servers <- get_socket_servers() 92 | if (port %in% servers) 93 | return(TRUE) # This port is already open! 94 | 95 | # We need Tcl to be able to call an R function to process clients' requests 96 | tclProcExists <- function(proc) { 97 | proc <- as.character(proc[1]) 98 | length(as.character(tcl("info", "commands", proc))) == 1 99 | } 100 | 101 | if (!tclProcExists("socket_server_proc")) { 102 | # Create the callback when a client sends data 103 | socket_server_fun <- function() { 104 | # Note: I don't know how to pass arguments here. 105 | # So, I use Tcl global variables instead: 106 | # - the server port from $::sock_port, 107 | # - the socket client from $::sock_client, 108 | # - and the message from $::sock_msg 109 | tclGetValue_ <- function(name) { 110 | # Get the value stored in a plain Tcl variable 111 | if (!is.character(name)) 112 | stop("'name' must be a character!") 113 | 114 | # Create a temporary dual variable with tclVar() 115 | temp <- tclVar(init = "") 116 | 117 | # Copy the content of the var of interest to it 118 | .Tcl(paste0("catch {set ", as.character(temp), " $", name, "}")) 119 | 120 | # Get the content of the temporary variable 121 | tclvalue(temp) # temp is destroyed when function exists 122 | } 123 | 124 | temp_env_ <- function() { 125 | pos <- match("SciViews:TempEnv", search()) 126 | if (is.na(pos)) {# Must create it 127 | `SciViews:TempEnv` <- list() 128 | attach_ <- function(...) 129 | get("attach", mode = "function")(...) 130 | attach_(`SciViews:TempEnv`, pos = length(search()) - 1) 131 | rm(`SciViews:TempEnv`) 132 | pos <- match("SciViews:TempEnv", search()) 133 | } 134 | pos.to.env(pos) 135 | } 136 | 137 | get_temp_ <- function(x, default = NULL, mode = "any") { 138 | if (exists(x, envir = temp_env_(), mode = mode, inherits = FALSE)) { 139 | return(get(x, envir = temp_env_(), mode = mode, inherits = FALSE)) 140 | } else {# Variable not found, return the default value 141 | return(default) 142 | } 143 | } 144 | 145 | process <- function() { 146 | port <- tclGetValue_("::sock_port") 147 | if (port == "") 148 | return(FALSE) # The server is closed 149 | client <- tclGetValue_("::sock_client") 150 | if (client == "") 151 | return(FALSE) # The socket client is unknown! 152 | msg <- tclGetValue_("::sock_msg") 153 | if (msg == "") 154 | return(FALSE) # No message! 155 | 156 | # Make sure this message is not processed twice 157 | .Tcl("set ::sock_msg {}") 158 | 159 | # Do we have to debug socket transactions 160 | Debug <- isTRUE(getOption("debug.Socket")) 161 | if (Debug) 162 | cat(client, " > ", port, ": ", msg, "\n", sep = "") 163 | 164 | # Function to process the client request: socket_server_proc_ 165 | proc <- get_temp_(paste("socket_server_proc", port, sep = "_"), 166 | mode = "function") 167 | if (is.null(proc) || !is.function(proc)) 168 | return(FALSE) # The server should be closed 169 | # Call this function 170 | res <- proc(msg, client, port) 171 | # Return result to the client 172 | if (res != "") { 173 | if (isTRUE(Debug)) 174 | cat(port, " > ", client, ": ", res, "\n", sep = "") 175 | chk <- try(tcl("puts", client, res), silent = TRUE) 176 | if (inherits(chk, "try-error")) { 177 | warning("Impossible to return results to a disconnected client.") 178 | return(FALSE) 179 | } 180 | } 181 | return(TRUE) # The command is processed 182 | } 183 | return(process) # Create the closure function for .Tcl.callback() 184 | } 185 | assign_temp("socket_server_proc", socket_server_fun()) 186 | # Create a Tcl proc that calls this function back 187 | res <- .Tcl.callback(get_temp("socket_server_proc"), temp_env()) 188 | if (length(grep("R_call ", res) > 0)) { 189 | # Create a proc with the same name in Tcl 190 | .Tcl(paste("proc socket_server_proc {} {", res, "}", sep = "")) 191 | } else { 192 | stop("Cannot create the SciViews socket server callback function") 193 | } 194 | } 195 | 196 | # Copy procfun into SciViews:TempEnv as socket_server_proc_ 197 | assign(paste("socket_server_proc", port, sep = "_"), procfun, 198 | envir = temp_env()) 199 | 200 | # Create the Tcl function that retrieves data from the socket 201 | # (command send by the client), call the processing R function 202 | # and returns result to the client 203 | cmd <- paste(c(paste("proc sock_handler_", port, " {sock} {", sep = ""), 204 | paste("global Rserver_", port, sep = ""), 205 | "if {[eof $sock] == 1 || [catch {gets $sock line}]} {", 206 | " # end of file or abnormal connection drop", 207 | " fileevent $sock readable {}", 208 | " close $sock", 209 | paste(" #puts \"Close $Rserver_", port, "($sock)\"", sep = ""), 210 | paste(" unset Rserver_", port, "($sock)", sep = ""), 211 | "} else {", 212 | " # Do we have to redirect the connection?", 213 | " if {[string compare \">>>>>>sock\" [string range $line 0 9]] == 0} {", 214 | " set redir_sock [string range $line 6 12]", 215 | " fileevent $sock readable [list sock_redirect $sock $redir_sock]", 216 | paste(" unset Rserver_", port, "($sock)", sep = ""), 217 | " } else {", 218 | " global sock_port", 219 | " global sock_client", 220 | " global sock_msg", 221 | paste(" set ::sock_port", port), 222 | " set ::sock_client $sock", 223 | " set ::sock_msg $line", 224 | " socket_server_proc ;# process the command in R", 225 | "}\n}\n}"), 226 | collapse = "\n") 227 | # if {[gets $sock line] < 0} {return} # To handle incomplete lines! 228 | .Tcl(cmd) 229 | 230 | # Create the Tcl function that accepts input from a client 231 | # (a different one for each server port) 232 | # Code is slightly different if the server is only local or not 233 | if (isTRUE(local)) { 234 | cmd <- paste(c(paste("proc sock_accept_", port, " {sock addr port} {", 235 | sep = ""), 236 | paste("global Rserver_", port, sep = ""), 237 | "# Configure the socket", 238 | "fconfigure $sock -buffering line -blocking 0", 239 | "# Accept only local clients", 240 | "if {$addr != \"127.0.0.1\"} {", 241 | " # puts $sock \"Error: Only local clients allowed!\"", 242 | " close $sock", 243 | " return", 244 | "}", 245 | paste("set Rserver_", port, "($sock) [list $addr, $port]", sep = ""), 246 | paste("fileevent $sock readable [list sock_handler_", port, 247 | " $sock]; update idletasks }", sep = "")), 248 | collapse = "\n") 249 | } else { 250 | cmd <- paste(c(paste("proc sock_accept_", port, " {sock addr port} {", 251 | sep = ""), 252 | paste("global Rserver_", port, sep = ""), 253 | "# Configure the socket", 254 | "fconfigure $sock -buffering line -blocking 0", 255 | paste("set Rserver_", port, "($sock) [list $addr, $port]", sep = ""), 256 | paste("fileevent $sock readable [list sock_handler_", port, 257 | " $sock] }", sep = "")), 258 | collapse = "\n") 259 | } 260 | .Tcl(cmd) 261 | 262 | # Create a Tcl procedure to redirect output used in socket_client_connection() 263 | if (!tclProcExists("sock_redirect")) { 264 | cmd <- paste(c("proc sock_redirect {sock tosock} {", 265 | "if {[eof $sock] == 1 || [catch {gets $sock line}]} {", 266 | " # end of file or abnormal connection drop", 267 | " fileevent $sock readable {}", 268 | " close $sock", 269 | "} else {", 270 | " puts $tosock $line", 271 | "}\n}"), 272 | collapse = "\n") 273 | .Tcl(cmd) 274 | } 275 | 276 | # Create the socket server itself in Tcl (a different one for each port) 277 | # If we want a secure server, use the tls secured socket instead 278 | if (isTRUE(secure)) { 279 | .Tcl(paste("set Rserver_", port, "(main) [tls::socket -server sock_accept_", 280 | #port, " -require 1 -cafile caPublic.pem -certfile ~/serverR.pem ", 281 | port, " -certfile Rserver.pem -keyfile Rserver.pem -ssl2 1 -ssl3 1 -tls1 0 -require 0 -request 0 ", 282 | port, "]; update idletasks", sep = "")) 283 | # For client, use: 284 | # set chan [tls::socket -cafile caPublic.pem -certfile ~/clientR.pem server.site.net $port] 285 | # To generate the keys: 286 | # cd ~ 287 | # Copy /System/Library/OpenSSL/openssl.cnf on ~, and edit 288 | # openssl genrsa -out serverR.pem 1024 # use -des3 to secure with a password 289 | # openssl req -new -x509 -key serverR.pem -out clientR.pem -days 365 -config openssl.cnf 290 | # ... and answer to a couple of questions 291 | } else { 292 | .Tcl(paste("set Rserver_", port, "(main) [socket -server sock_accept_", 293 | port, " ", port, "]; update idletasks", sep = "")) 294 | } 295 | 296 | # Add this port in the variable 'Socket_servers' in SciViews:TempEnv 297 | socks <- get_socket_servers() 298 | namesocks <- names(socks) 299 | if (!(portnum %in% socks)) { 300 | socks <- c(socks, portnum) 301 | names(socks) <- c(namesocks, server_name) 302 | socks <- sort(socks) 303 | assign("socket_servers", socks, envir = temp_env()) 304 | } 305 | return(TRUE) # Humm! Only if it succeeds... 306 | } 307 | 308 | # Old name of the function 309 | #' @export 310 | #' @rdname start_socket_server 311 | startSocketServer <- start_socket_server 312 | -------------------------------------------------------------------------------- /R/stop_socket_server.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @rdname start_socket_server 3 | stop_socket_server <- function(port = 8888) { 4 | # Stop one or more running socket server(s) 5 | if (port == "all") { 6 | port <- get_socket_servers() 7 | servers <- port 8 | } else { 9 | servers <- get_socket_servers() 10 | } 11 | if (!is.numeric(port) || any(port < 1)) 12 | stop("'port' must be positive integers!") 13 | port <- round(port) 14 | any_closed <- FALSE 15 | for (i in 1:length(port)) { 16 | my_port <- port[i] 17 | if (my_port %in% servers) {# This port is open 18 | any_closed <- TRUE 19 | # First ask to all clients to nicely disconnect (note: if they don't 20 | # the server simply does not process them any more!) 21 | close_socket_clients(server_port = my_port) 22 | 23 | # Assign it back, with the corresponding port stripped out 24 | # But if I was the last one, delete the SocketServers variable 25 | servers <- servers[servers != my_port] 26 | if (length(servers) == 0) { 27 | if (exists("socket_servers", envir = temp_env(), inherits = FALSE)) 28 | rm("socket_servers", envir = temp_env()) 29 | } else { 30 | assign("socket_servers", servers[servers != my_port], 31 | envir = temp_env()) 32 | } 33 | 34 | # Eliminate the processing function from SciViews:TempEnv 35 | sock_proc <- paste("socket_server_proc", my_port, sep = "_") 36 | if (exists(sock_proc, envir = temp_env())) 37 | rm(list = sock_proc, envir = temp_env()) 38 | 39 | # Close the socket in order not to reject future client connections 40 | .Tcl(paste("close $Rserver_", my_port, "(main)", sep = "")) 41 | 42 | # Note: Tcl procs and variables are not eliminated yet 43 | # because there may be still clients connected! 44 | } 45 | } 46 | any_closed 47 | } 48 | 49 | # Old name of the function 50 | #' @export 51 | #' @rdname start_socket_server 52 | stopSocketServer <- stop_socket_server 53 | -------------------------------------------------------------------------------- /R/svSocket-Internal.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(lib, pkg) { 2 | # Create our SciViews task callback manager 3 | #PhG: now moved to svKomodo! 4 | #assign_temp(".svTaskCallbackManager", svTaskCallbackManager()) 5 | } 6 | 7 | .onUnload <- function(libpath) { 8 | #PhG: now moved to svKomodo! 9 | #removeTaskCallback("SV-taskCallbackManager") 10 | #rm_temp(".svTaskCallbackManager") 11 | 12 | #PhG: From .Last.lib(), now in .onUnload() 13 | # Make sure that all clients are disconnected 14 | # and all servers are closed 15 | servers <- get_socket_servers() 16 | if (is.null(servers) || length(servers) < 1) 17 | return() 18 | cat(ngettext(length(servers), "Stopping socket server\n", 19 | "Stopping socket servers\n")) 20 | stop_socket_server("all") 21 | # TODO: make sure to delete all client environments 22 | # (or do it in stop_socket_server()?) 23 | } 24 | -------------------------------------------------------------------------------- /R/svSocket-package.R: -------------------------------------------------------------------------------- 1 | #' @details 2 | #' The SciViews \{svSocket\} package provides a stateful, multi-client and 3 | #' preemptive socket server. Socket transaction are operational even when R is 4 | #' busy in its main event loop (calculation done at the prompt). This R socket 5 | #' server uses the excellent asynchronous socket ports management by Tcl, and 6 | #' thus, it needs a working version of Tcl/Tk (>= 8.4) and of the \{tcltk\} R 7 | #' package. 8 | #' 9 | #' A particular effort has been made to handle requests the same way as if they 10 | #' where introduced at the command prompt, including presentation of the output. 11 | #' However, the server sends results back to the client only at the end of the 12 | #' computations. It means that any interaction during computation (for instance, 13 | #' using [scan()], [browser()], or `par(ask = TRUE)` is not echoed in the client 14 | #' on due time. If you parameterize the socket server to echo commands in the R 15 | #' console, such interaction would be possible from there. Another option is to 16 | #' run R in non-interactive mode. 17 | #' 18 | #' Although initially designed to server GUI clients, the R socket server can 19 | #' also be used to exchange data between separate R processes. The 20 | #' [eval_socket_server()] function is particularly useful for this. Note, 21 | #' however, that R objects are serialized into a text (i.e., using [dump()]) 22 | #' format, currently. It means that the transfer of large object is not as 23 | #' efficient as, say \{Rserver\} (\{Rserver\} exchanges R objects in binary format, 24 | #' but \{Rserver\} is not stateful, clients do not share the same global workspace 25 | #' and it does not allow concurrent use of the command prompt). 26 | #' 27 | #' Due to a change in R 4.3.x in its event loop, some Tcl socket events are not 28 | #' processes and this prevents the R socket server to work properly. This is 29 | #' corrected in R 4.4.0. The socket server also works well with R 4.0.x, R 4.1.x 30 | #' and R 4.2.x. 31 | #' 32 | #' See [start_socket_server()] and [process_socket_server()] for further 33 | #' implementation details. 34 | #' @keywords internal 35 | "_PACKAGE" 36 | 37 | #' @importFrom tcltk tcl .Tcl tclRequire tclVar tclvalue .Tcl.callback 38 | #' @importFrom svMisc capture_all parse_text temp_env get_temp assign_temp rm_temp 39 | #' @importFrom utils URLdecode 40 | # The following block is used by usethis to automatically manage 41 | # roxygen namespace tags. Modify with care! 42 | ## usethis namespace: start 43 | ## usethis namespace: end 44 | NULL 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svSocket 2 | 3 | 4 | 5 | [![R-CMD-check](https://github.com/SciViews/svSocket/workflows/R-CMD-check/badge.svg)](https://github.com/SciViews/svSocket/actions) [![Win Build Status](https://ci.appveyor.com/api/projects/status/github/SciViews/svSocket?branch=master&svg=true)](https://ci.appveyor.com/project/phgrosjean/svSocket) [![Coverage Status](https://img.shields.io/codecov/c/github/SciViews/svSocket/master.svg)](https://codecov.io/github/SciViews/svSocket?branch=master) [![CRAN Status](https://www.r-pkg.org/badges/version/svSocket)](https://cran.r-project.org/package=svSocket) [![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://www.gnu.org/licenses/gpl-2.0.html) [![Life cycle stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://www.tidyverse.org/lifecycle/#stable) 6 | 7 | 8 | 9 | A socket server that allows for another process to connect to R and to interact with it as if it was at the command line directly. 10 | 11 | ## Installation 12 | 13 | The latest stable version of {svSocket} can simply be installed from [CRAN](http://cran.r-project.org): 14 | 15 | ``` r 16 | install.packages("svSocket") 17 | ``` 18 | 19 | You can also install the latest development version. Make sure you have the {remotes} R package installed: 20 | 21 | ``` r 22 | install.packages("remotes") 23 | ``` 24 | 25 | Use `install_github()` to install the {svSocket} package from Github (source from **master** branch will be recompiled on your machine): 26 | 27 | ``` r 28 | remotes::install_github("SciViews/svSocket") 29 | ``` 30 | 31 | R should install all required dependencies automatically, and then it should compile and install {svSocket}. 32 | 33 | Latest devel version of {svSocket} (source + Windows binaries for the latest stable version of R at the time of compilation) is also available from [appveyor](https://ci.appveyor.com/project/phgrosjean/svSocket/build/artifacts). 34 | 35 | ## Further explore {svSocket} 36 | 37 | You can get further help about this package this way: Make the {svSocket} package available in your R session: 38 | 39 | ``` r 40 | library("svSocket") 41 | ``` 42 | 43 | Get help about this package: 44 | 45 | ``` r 46 | library(help = "svSocket") 47 | help("svSocket-package") 48 | vignette("svSocket") # None is installed with install_github() 49 | ``` 50 | 51 | For further instructions, please, refer to these help pages at . 52 | 53 | ## Code of Conduct 54 | 55 | Please note that the {svSocket} project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/0/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. 56 | 57 | ## Note to developers 58 | 59 | This package used to be developed on R-Forge in the past. However, the latest [R-Forge version](https://r-forge.r-project.org/projects/sciviews/) was moved to this Github repository on 2016-03-18 (SVN version 569). **Please, do not use R-Forge anymore for SciViews development, use this Github repository instead.** 60 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # svSocket To Do list 2 | 3 | - Rework the mechanism to add history item to R from within Komodo. 4 | 5 | - Easy redefinition of `options(width = ...)` to adjust width of output to the GUI client R console. 6 | 7 | - Make use of a config file for the server + `par_socket_server()` to change (some!) parameters of the server. 8 | 9 | - Translation of all messages in the package. 10 | 11 | - Delete `socket_client_xxxx` on disconnection + make sure they are all deleted on server stopping and on package detaching (in `.Last.lib()`). 12 | 13 | - `source_part()` function sourcing only from line X to line Y in a file! 14 | 15 | - Send a command to the regular command line. 16 | 17 | - Silent mode (interesting with `echo = TRUE`). 18 | 19 | - Correct little glitches with multiline mode and empty lines. 20 | 21 | - A mode that flags various parts of output. 22 | 23 | - Unattended messages should be printed above command line. 24 | 25 | - Allow for remote connection + security? Use a tcl list: 26 | 27 | \% set xxxx_allow [list] % llength \$xxxx_allow % lappend xxxx_allow "194.127.34.1" % lindex \$xxxx_allow 0 hiking % lindex \$user_preferences 1 biking % lindex \$user_preferences 2 whale watching % lindex \$user_preferences 3 % lindex \$user_preferences 5 % \# Before we test: % lappend xxxx_allow "127.0.0.1" if { [lsearch -exact \$xxxx_allow \$addr] != -1 } { \# the address is the allowed list } 28 | 29 | - Implement a way to interrupt from the remote console + correct `<<>>`. 30 | 31 | - Manage buffered output with `flush.console()`! 32 | 33 | - Redirect `stdin()` so that `scan()`, etc. 34 | 35 | - For multiline commands, do number them. 36 | 37 | - `<<>>` with addition of the command in the R command history. 38 | 39 | - Check and solve possible clash when several clients submit commands at the same time. 40 | 41 | ## Differences between CLI and processSocket() 42 | 43 | Tested with R 2.6.1 and testCLIcmd.r v. 1.0-0 44 | 45 | - When successive commands are issued and the last one is incomplete like: 46 | 47 | > search(); log( 48 | 49 | `process_socket_server()` waits for a complete last command before processing everything while other consoles process all the commands and then wait for last one to be completed (should be considered as a feature). 50 | 51 | - Same problem with multiple instructions send at once when there is an error: the first few correct instructions before the error should be evaluated, while they are not with `process_socket_server()` (considered as a feature). Compare this: 52 | 53 | > search(); log) 54 | 55 | - For long calculations, `process_socket_server()` acts as a buffered console (like RGui) except that it does not understand (yet) `flush.console()`! 56 | 57 | - There are sometimes cosmetic differences in the way warnings are printed (essentially, truncation of call argument). For instance: 58 | 59 | > options(warn = 0) (function() {warning("Warn!"); 1:3})() 60 | 61 | - There are slight differences in the way the error message is presented when incorrect code is processed. Moreover, R.app (Macintosh) does not print a descriptive error message, but only "syntax error". This is an old behaviour that remains only in R.app (among all tested consoles). 62 | 63 | - R.app (Macintosh) always print prompts at the beginning of a line. So, `cat("text")` does not produce exactly the same result as in many other consoles including with `process_socket_server()` (should be considered wrong in R.app!). 64 | 65 | - R.app (Macintosh) incorrectly prints a continuation prompt after: 66 | 67 | > options(warn = 2) (function() {warning("Warn!"); 1:3})() 68 | 69 | - R.app (Macintosh) does not interpret `\\a` and `\\b` on the contrary to most other consoles. With `process_socket_server()`, it is the client that must decide how to interpret special characters. Most important ones are: `\\a` =\> sound a bip and print nothing, `\\b` = backspace, erase previous character, except if at the beginning of a line, `\\t` = tabulation (4 spaces), `\\n` = newline, `\\r` = same as `\\n` (but not interpreted on RGui), `\\r\n` = same as `\\n`, `\\r\\r` and `\\n\\r` are equivalent to `\\n\\n` (for portability) from one platform to the other. `\\f` and `\\v` are interpreted as `\\n` (but not on RGui under Windows), `\\u` and `\\x` are also recognized but interpreted differently from one to the other console, as well as the other exotic escape sequences. It is better to ignore them, or to print a question mark instead. 70 | 71 | - Regarding internationalization of error messages, there are two differences between messages at the CLI and issued by `process_socket_server()`. 72 | 73 | - Warning message(s): is not translated at CLI bu it is translated by `process_socket_server()` (bug?) in: 74 | 75 | > options(warn = 0) warning("Warn!") 76 | 77 | - On the contrary, `process_socket_server()` fails to translate 'Error in' for most error messages, and I don't understand this bug: 78 | 79 | > cos("a") 80 | 81 | - If you define `options(warning.expression)`, there will be a problem because `process_socket_server()` redefines it also, and thus, at best, you expression is ignored (must fix this!). 82 | 83 | - On the MacOS, there are many mismatches for string translation, both at the command line and using `process_socket_server()`, but they are not same mismatches! 84 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://www.sciviews.org/svSocket 2 | 3 | destination: docs 4 | 5 | development: 6 | mode: auto 7 | 8 | template: 9 | package: svPkgdown 10 | params: 11 | bootswatch: spacelab 12 | mathjax: true 13 | 14 | toc: 15 | depth: 3 16 | 17 | home: 18 | strip_header: true 19 | 20 | authors: 21 | Philippe Grosjean: 22 | href: https://phgrosjean.sciviews.org 23 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | build: 8 | verbosity: minimal 9 | 10 | platform: 11 | - x86 12 | 13 | shallow_clone: true 14 | 15 | # Can also add --no-build-vignettes or --no-tests 16 | environment: 17 | global: 18 | R_VERSION: "stable" 19 | R_CHECK_ARGS: "--no-multiarch --no-manual" 20 | _R_CHECK_DOC_SIZES_: "FALSE" 21 | USE_RTOOLS: true 22 | 23 | # DO NOT CHANGE the "init" and "install" sections below 24 | # Download script file from GitHub 25 | init: 26 | - ps: | 27 | $ErrorActionPreference = "Stop" 28 | Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1" 29 | Import-Module '..\appveyor-tool.ps1' 30 | 31 | install: 32 | - ps: Bootstrap 33 | 34 | # Adapt as necessary starting from here 35 | build_script: 36 | - travis-tool.sh install_deps 37 | 38 | test_script: 39 | - travis-tool.sh run_tests 40 | 41 | on_failure: 42 | - travis-tool.sh dump_logs 43 | 44 | artifacts: 45 | - path: '*.Rcheck\**\*.log' 46 | name: Logs 47 | 48 | - path: '*.Rcheck\**\*.out' 49 | name: Logs 50 | 51 | - path: '*.Rcheck\**\*.fail' 52 | name: Logs 53 | 54 | - path: '*.Rcheck\**\*.Rout' 55 | name: Logs 56 | 57 | - path: '\*_*.tar.gz' 58 | name: Bits 59 | 60 | - path: '\*_*.zip' 61 | name: Bits 62 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | appveyor 2 | calltip 3 | calltips 4 | CLI 5 | CMD 6 | config 7 | deserializing 8 | devtools 9 | esc 10 | evalServer 11 | Github 12 | http 13 | infos 14 | interprocess 15 | io 16 | ip 17 | Jiang 18 | Komodo 19 | MacOS 20 | ORCID 21 | Ph 22 | phgrosjean 23 | pkgdown 24 | ReadMe 25 | REPL 26 | Roxygen 27 | Rserver 28 | Ryczkowski 29 | sciviews 30 | SciViews 31 | serializable 32 | server_port 33 | SimpleClient 34 | sockclientconn 35 | sockconn 36 | sockXXX 37 | stateful 38 | svHttp 39 | svKomodo 40 | svMisc 41 | tclsh 42 | tcltk 43 | testCLI 44 | Tinn 45 | TLS 46 | unserializable 47 | usr 48 | Xiaoqian 49 | YYY 50 | -------------------------------------------------------------------------------- /inst/etc/ReadMe.txt: -------------------------------------------------------------------------------- 1 | SimpleClient.tcl is a Tcl script that implements a very simple client for R 2 | socket server. This was developed and tested with tclsh8.4. It may not work 3 | with higher Tcl version! 4 | 5 | Launch R and start a R socket server: 6 | 7 | > library(svSocket) 8 | Loading required package: tcltk 9 | Loading Tcl/Tk interface ... done 10 | > start_socket_server() 11 | [1] TRUE 12 | 13 | Then launch the client in a console window navigate to the /etc subdirectory 14 | of you svSocket R package installation (where you have found this ReadMe.txt 15 | file), and then, issue the following command: 16 | 17 | $ cd [your_path_here]/library/svSocket/etc/ 18 | $ tclsh SimpleClient.tcl 19 | 20 | Follow instructions: you can type R commands in the console of the client 21 | application and R returns the results of the calculation. You have no command 22 | line edition, and no command history, but you can test various R commands 23 | by typing them in the client or pasting them one by one. 24 | 25 | To get something closer to a real console, you should display the prompt and 26 | enable multiline mode. You achieve this by configuring the client with: 27 | 28 | par_socket_server(<<>>, bare = FALSE) 29 | 30 | Experiment various R code in this console. Also, you can turn echo on/off of 31 | your commands and the output to the regular R console with: 32 | 33 | par_socket_server(<<>>, echo = TRUE) 34 | 35 | You can connect simultaneously as many clients as you like, but you can only 36 | connect local clients for the moment. This is a restriction very easy to 37 | eliminate, but we need to install protection (password access, crypting of the 38 | data) before doing so (planned for the future). 39 | 40 | There is a prototype secure version too. Make sure the 'tls' Tcl package is 41 | installed on the server side. Then, in an R pocess, run: 42 | 43 | > library(svSocket) 44 | Loading required package: tcltk 45 | Loading Tcl/Tk interface ... done 46 | > start_socket_server(secure = TRUE) 47 | [1] TRUE 48 | 49 | Then, you can connect from a separate console to this secure server using the 50 | modified version: 51 | 52 | $ cd [your_path_here]/library/svSocket/etc/ 53 | $ tclsh SimpleClientSecure.tcl 54 | 55 | You have also another prototype client for Mozilla applications (Firefox, 56 | Thunderbird, Komodo, etc.). See ?ko_cmd in the svIDE package to learn how to 57 | install and use it. Once you have installed the 'SciViews x.y' toolbox folder in 58 | Komodo, go to 'SciViews x.y/Communication/Socket client'. If the R socket server 59 | is started on the same machine as explaned here above, double clicking on this 60 | macro triggers some code in R that asks for the syntax of the 'library()' 61 | function, and then, disconnect immediately. The result is printed in the 62 | 'Command Output' panel in Komodo (open the panel at the bottom if necessary). 63 | This is just a proof-of-concept for the moment. 64 | 65 | Enjoy! 66 | 67 | Note: tclsh is part of any Tcl distribution. However, it is not provided with 68 | the minimalist Tcl distribution installed by default with R under Windows. To 69 | get tclsh, install the free standard edition of ActiveTcl 70 | (http://www.activestate.com/Products/activetcl/) on this platform. The directory 71 | where tclsh.exe is located must be in the path (done automatically by ActiveTcl 72 | installer, but you probably need to restart windows after installation). 73 | -------------------------------------------------------------------------------- /inst/etc/SimpleClient.Tcl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/tclsh 2 | # Open a terminal and issue 3 | # $ tclsh SimpleClient.tcl 4 | # when the R socket server is running 5 | # (Under Windows, you need to install ActiveTcl8.4 first; 6 | # see http://www.activestate.com/Products/ActiveTcl/) 7 | 8 | # These are parameters of the R socket server 9 | set rsHost "localhost" ;# localhost only (take care of security otherwise!) 10 | set rsPort 8888 ;# port is arbitrarily fixed at 8888 for the R server 11 | 12 | # Read data from a channel (the R socket server) and put it into stdout 13 | # this implements receiving and handling (viewing) a server reply 14 | proc read_sock {sock} { 15 | if {[eof $sock] == 1 || [catch {gets $sock l}] || $l == "\f"} { 16 | fileevent $sock readable {} 17 | close $sock 18 | # puts "\nR socket server closed!" 19 | global eventLoop 20 | set eventLoop "done" 21 | } else { 22 | foreach {out in} [split $l "\n"] { 23 | if {$out == "> " || $out == "+ "} { 24 | puts -nonewline stdout $out 25 | flush stdout 26 | } else { 27 | ### TODO: a special command to insert a string before the command line! 28 | puts stdout "$out" 29 | } 30 | } 31 | } 32 | } 33 | 34 | # Read a line of text from stdin and send it to the R socket server, 35 | # on eof stdin closedown (ctrl-c) the R server client socket connection 36 | # this implements sending a message to the Server. 37 | proc read_stdin {wsock} { 38 | set l [gets stdin] 39 | if {[eof stdin] == 1} { 40 | fileevent $wsock readable {} 41 | close $wsock ;# close the socket client connection 42 | global eventLoop 43 | set eventLoop "done" ;# terminate the vwait (eventloop) 44 | } else { 45 | puts $wsock $l ;# send the data to the server 46 | } 47 | } 48 | 49 | # Open the connection to the R socket server... 50 | # this is a synchronous connection: 51 | # The command does not return until the server responds to the 52 | # connection request 53 | set rsSock [socket $rsHost $rsPort] 54 | 55 | if {[eof $rsSock] == 1} { 56 | close $rsSock ;# connection closed ... abort 57 | } else { 58 | 59 | # Setup monitoring on the socket so that when there is data to be 60 | # read the proc "read_sock" is called 61 | fileevent $rsSock readable [list read_sock $rsSock] 62 | 63 | # configure channel modes 64 | # ensure the socket is line buffered so we can get a line of text 65 | # at a time (because that's what the server expects)... 66 | # Depending on your needs you may also want this unbuffered so 67 | # you don't block in reading a chunk larger than has been fed 68 | # into the socket 69 | # i.e fconfigure $esvrSock -blocking off 70 | # but this requires some modifications in the R socket server! 71 | fconfigure $rsSock -buffering line 72 | 73 | # set up our keyboard read event handler: 74 | # Vector stdin data to the socket 75 | fileevent stdin readable [list read_stdin $rsSock] 76 | 77 | # message indicating connection accepted and we're ready to go 78 | puts "Connected to R socket server" 79 | puts "...what you type should be send to R." 80 | puts "Paste only one line of code at a time." 81 | puts " hit to close the connection." 82 | 83 | # wait for and handle either socket or stdin events... 84 | vwait eventLoop 85 | 86 | puts "\nConnection with R is closed!" 87 | } 88 | -------------------------------------------------------------------------------- /inst/etc/SimpleClientSecure.Tcl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/tclsh 2 | # Open a terminal and issue 3 | # $ tclsh SimpleClientSecure.tcl 4 | # when the R socket server is running 5 | # (Under Windows, you need to install ActiveTcl8.4 first; 6 | # see http://www.activestate.com/Products/ActiveTcl/) 7 | 8 | # We use a TLS secure layer to communicate with the server 9 | package require tls 10 | # Start R socket server with startSocketServer(secure = TRUE) 11 | # but you need to install the 'tls' package in the Tcl you use from R first 12 | 13 | # These are parameters of the R socket server 14 | set rsHost "localhost" ;# localhost for example, but could be distant machine 15 | set rsPort 8888 ;# port is arbitrarily fixed at 8888 for the R server 16 | 17 | # Read data from a channel (the R socket server) and put it into stdout 18 | # this implements receiving and handling (viewing) a server reply 19 | proc read_sock {sock} { 20 | if {[eof $sock] == 1 || [catch {gets $sock l}] || $l == "\f"} { 21 | fileevent $sock readable {} 22 | close $sock 23 | # puts "\nR socket server closed!" 24 | global eventLoop 25 | set eventLoop "done" 26 | } else { 27 | foreach {out in} [split $l "\n"] { 28 | if {$out == "> " || $out == "+ "} { 29 | puts -nonewline stdout $out 30 | flush stdout 31 | } else { 32 | ### TODO: a special command to insert a string before the command line! 33 | puts stdout "$out" 34 | } 35 | } 36 | } 37 | } 38 | 39 | # Read a line of text from stdin and send it to the R socket server, 40 | # on eof stdin closedown (ctrl-c) the R server client socket connection 41 | # this implements sending a message to the Server. 42 | proc read_stdin {wsock} { 43 | set l [gets stdin] 44 | if {[eof stdin] == 1} { 45 | fileevent $wsock readable {} 46 | close $wsock ;# close the socket client connection 47 | global eventLoop 48 | set eventLoop "done" ;# terminate the vwait (eventloop) 49 | } else { 50 | puts $wsock $l ;# send the data to the server 51 | } 52 | } 53 | 54 | # Open the connection to the R socket server... 55 | # this is a synchronous connection: 56 | # The command does not return until the server responds to the 57 | # connection request 58 | set rsSock [tls::socket $rsHost $rsPort] 59 | 60 | if {[eof $rsSock] == 1} { 61 | close $rsSock ;# connection closed ... abort 62 | } else { 63 | 64 | # Setup monitoring on the socket so that when there is data to be 65 | # read the proc "read_sock" is called 66 | fileevent $rsSock readable [list read_sock $rsSock] 67 | 68 | # configure channel modes 69 | # ensure the socket is line buffered so we can get a line of text 70 | # at a time (because that's what the server expects)... 71 | # Depending on your needs you may also want this unbuffered so 72 | # you don't block in reading a chunk larger than has been fed 73 | # into the socket 74 | # i.e fconfigure $esvrSock -blocking off 75 | # but this requires some modifications in the R socket server! 76 | fconfigure $rsSock -buffering line 77 | 78 | # set up our keyboard read event handler: 79 | # Vector stdin data to the socket 80 | fileevent stdin readable [list read_stdin $rsSock] 81 | 82 | # message indicating connection accepted and we're ready to go 83 | puts "Connected to R socket server" 84 | puts "...what you type should be send to R." 85 | puts "Paste only one line of code at a time." 86 | puts " hit to close the connection." 87 | 88 | # wait for and handle either socket or stdin events... 89 | vwait eventLoop 90 | 91 | puts "\nConnection with R is closed!" 92 | } 93 | -------------------------------------------------------------------------------- /inst/testCLI/testCLI.R: -------------------------------------------------------------------------------- 1 | # CLI torture test, copyright (c) Ph. Grosjean (phgrosjean@sciviews.org) 2 | # GNU GPL >= 2 license 3 | # A series of commands to check for R CLI (or console widget) 4 | # Note: test this in English, but also, in internationalized versions of R! 5 | 6 | # Copy all text in testCLIcmd.r and paste it in a console window. Copy all 7 | # the content of the console into a document (for instance testCLIres.r) 8 | # Then, run the following code that will do the same, but from a 9 | # processSocket() as if it is issued by a client 10 | # Then, compare both files 11 | 12 | # To test in French, $LANG=fr_FR.UTF8 R or 13 | Sys.setenv(LANG = "fr_FR.UTF8") 14 | 15 | # Open a clean R session, then, issue these commands and copy/paste the content 16 | # of testCLIcmd.r in it... Copy the results from the console into a *.save file 17 | library(svMisc) 18 | library(svSocket) 19 | temp_env() 20 | res <- outfile <- out <- i <- cmdfile <- cmd0 <- cmd <- "" 21 | options(width = 80) 22 | 23 | # Run this to generate output with process_socket_server() 24 | temp_dir <- temp_dir() 25 | setwd(temp_dir) 26 | # Copy this file in temp_dir first! 27 | cmdfile <- 'testCLIcmd.R' 28 | outfile <- 'testCLIcmd.out' 29 | options(width = 80) 30 | 31 | # Read the file with commands 32 | cmd <- readLines(cmdfile) 33 | # Run these commands 34 | out <- file(outfile, "w") 35 | cmd0 <- "" 36 | cat("> ", file = out) 37 | for (i in 1:length(cmd)) { 38 | cat(cmd[i], "\n", sep = "", file = out) 39 | if (cmd0 == "") cmd0 <- cmd[i] else 40 | cmd0 <- paste(cmd0, cmd[i], sep = "<<>>") 41 | res <- process_socket_server(cmd0, "", "") 42 | if (res != "+ ") cmd0 <- "" # Not a multiline command 43 | cat(res, file = out) 44 | } 45 | close(out) 46 | # Until here... 47 | 48 | 49 | # User interrupt 50 | # Here is a long calculation. Hit or in mother R app during 51 | # calculation. See how interrupt is managed 52 | ##for (i in 1:10000000) i 53 | # Here is a command that issues a very long output. Let it calculate 54 | # and when output starts, hit or in mother R app 55 | # See how interrupt is managed 56 | ##1:100000 # This is like if I have to use flush.console() 57 | # TODO: correct handling of flush.console() 58 | 59 | # Interaction at the command line 60 | #scan() # Read in numbers 61 | # Other tests to be implement 62 | #readline() 63 | #non graphical menu() 64 | #create graph (are there problems between the graph device and Tcl/Tk event loop?) 65 | #test ask = TRUE for graphs and demos 66 | #identify() 67 | #locator() 68 | #lattice graph 69 | #rgl graph 70 | #progress() from the svMisc package 71 | #assignment & ->> 72 | #test tcltk windows and widgets + messages (are they printed correctly?) 73 | #:, ::, ::: 74 | #addTaskCallback => do we run it after execution? 75 | #test history and the like 76 | #q("yes"), q("no"), q() 77 | #UTF-8 or other non ASCII characters (input and output) 78 | #break 79 | #debug 80 | #trace 81 | #browser 82 | #test connections 83 | #source 84 | #sink 85 | #capture.output 86 | #try & tryCatch 87 | #on.exit, sys.on.exit 88 | #Cstack_info 89 | #eval, evalq, eval.parent, local 90 | #exists 91 | #test size of console output and allow for automatic adjustment of width (like in Rgui) 92 | #flush + flush.console 93 | #shell & shell.exec 94 | #stderr, stdin, stdout 95 | #interactive() 96 | #utf8ToInt, intToUtf8 97 | #system, system.time 98 | #pause (sm package) 99 | #bringToTop 100 | #dev.interactive 101 | #graphics.off 102 | #stayOnTop under Windows 103 | #choose.dir 104 | #choose.files 105 | #chooseCRANmirror, install & update packages 106 | #Data.entry, dataentry, de, edit, fix, fixInNamespace 107 | #file.edit 108 | #history 109 | #page 110 | #recover 111 | #setWindowTitle under Windows 112 | #winDialog, winDialogString under Windows 113 | #winMenuXXXX under Windows 114 | #gettext, ngettext, bindtextdomain 115 | #readline functionnalities 116 | #test command history with pgup/pgdwn 117 | -------------------------------------------------------------------------------- /inst/testCLI/testCLIcmd.R: -------------------------------------------------------------------------------- 1 | ## CLI torture test, copyright (c) Ph. Grosjean (phgrosjean@sciviews.org) 2 | ## GNU GPL => 2 license 3 | ## A series of commands to check for R CLI (or console widget) 4 | ## Version 1.0-0 5 | 6 | ## Simple instructions 7 | 1+1 # Simple command with one line of output 8 | 1:100 # Simple command with several lines of output 9 | search() # Evaluation of a function 10 | ls() # Idem... look if this function is evaluated in .GlobalEnv! 11 | 12 | ## Multiple instructions on one line 13 | 1+1; 2+2 # Two successive prints 14 | 1+1; cat("test\n"); 2+2 # Print, cat, print 15 | 1+1; cat("test"); 2+2 # Idem, but cat without \n 16 | 17 | ## Visible/invisible output 18 | invisible(1) # Command with invisible result 19 | a <- 1:10 # Idem 20 | (a <- 1:10) # Idem, with visible output 21 | for (i in 1:3) print(i) # Output within a loop 22 | for (i in 1:3) i # Do not explicit use print(), so output nothing 23 | 24 | cat("test\n") # Simple text print with carriage return 25 | cat("test") # Idem, but without carriage return 26 | 27 | ## S4 objects 28 | setClass("track", representation(x = "numeric", y = "numeric")) 29 | setMethod("show", "track", function(object) {cat("track:\n"); print(object@x); print(object@y)}) 30 | tr <- new("track", x = 1:3, y = 4:6) # invisible 31 | (tr <- new("track", x = 1:3, y = 4:6)) # visible 32 | tr # show it 33 | show(tr) # idem 34 | print(tr) # This is the same! 35 | 36 | ## Special characters 37 | cat("\ttabs\n") # \t (tabulation) should indent the text by 4 characters 38 | cat("tesg\bt\n") # \b (backspace) erases previous character thus it print "test" 39 | alarm() # idem as cat("\a"), should sound a bip 40 | 41 | ## Code parsing and multiline commands 42 | log) # Generate and error 43 | 1+1; log) # Should run first instruction before generating the error 44 | ## This is a partial line without comments (should issue the continue prompt) 45 | log( 46 | 10) + 47 | 1 48 | 49 | log( # This is partial with comments 50 | 10) 51 | 52 | search(); log( 53 | 10) 54 | 55 | log( # Issuing an empty line between instructions 56 | 57 | 10) 58 | 59 | ## String on several lines 60 | text <- "some 61 | text" 62 | text # should print "some\ntext" 63 | 64 | ## Idem, but with single quote 65 | text <- 'some 66 | text' 67 | text # should print "some\ntext" 68 | 69 | ## A horrible code with a variable name on two lines (but correct syntax)! 70 | `var 71 | 1` <- 1 72 | `var\n1` 73 | 74 | ## Truncation of very long output 75 | options(max.print = 1000) # Put a lower limit 76 | 1:1100 # Should be truncated 77 | options(max.print = NULL) # Restore default value 78 | 79 | ## Errors messages 80 | nonExistingVar 81 | cos("a") 82 | cos(nonExisting) 83 | stop("Error!"); 1:3 # Error in .GlobalEnv (no call info) 84 | (function() stop("Error!"))(); 1:3 # Error; second command not evaluated 85 | 86 | ## Warnings handling (a little tricky!) 87 | options(warn = -1) # Do not display warnings 88 | {warning("Warn!"); 1:3} # Simple warning 89 | {warning("Warn!", immediate. = TRUE); 1:3} # Should issue the warning anyway 90 | 91 | options(warn = 0) # Delay warning display 92 | {warning("Warn!"); 1:3} # Simple delayed warning 93 | {warning("Warn!", immediate. = TRUE); 1:3} # Do not delay warning 94 | for (i in 1:3) {print(i); warning("test")} 95 | for (i in 1:4) {print(i); warning("test", immediate. = (i < 3))} 96 | 97 | options(warn = 1) # Display warnings immediatelly 98 | {warning("Warn!"); 1:3} 99 | 100 | options(warn = 2) # Every warning generates an error 101 | {warning("Warn!"); 1:3} # Warning turned into an error 102 | warnings() 103 | 104 | 105 | ## Warnings inside functions 106 | options(warn = -1) 107 | (function() {warning("Warn!"); 1:3})() 108 | options(warn = 0) 109 | (function() {warning("Warn!"); 1:3})() 110 | options(warn = 1) 111 | (function() {warning("Warn!"); 1:3})() 112 | options(warn = 2) 113 | (function() {warning("Warn!"); 1:3})() 114 | 115 | ## Multiple warnings and/or errors (warn = 0 + 9, 10, 11, 49, 50 & 60 warnings) 116 | options(warn = 0) 117 | for (i in 1:9) warning("Warn ", i) 118 | warnings() # Redisplay last warnings 119 | for (i in 1:10) warning("Warn ", i) 120 | warnings() 121 | for (i in 1:11) warning("Warn ", i) 122 | warnings() 123 | for (i in 1:49) warning("Warn ", i) 124 | warnings() 125 | for (i in 1:50) warning("Warn ", i) 126 | warnings() 127 | for (i in 1:60) warning("Warn ", i) 128 | warnings() 129 | 130 | ## warning() and then, error message with warn = 0 ("In addition: ...") 131 | options(warn = 0) 132 | (function() {warning("Warn!"); stop("Error!"); 1:3})() 133 | options(warn = 1) 134 | (function() {warning("Warn!"); stop("Error!"); 1:3})() 135 | 136 | ## Messages handling 137 | {message("A message"); 1:3} # Should issue the message 138 | simpleMessage("test") 139 | simpleMessage("test", call = "me") 140 | 141 | ## Multiline or very long message 142 | options(warning.length = 100) 143 | warning("A very long message for my warning that should be truncated or at least flowed on several lines, what does it gives here?") 144 | warning("A multiline warning\nSecond line,\nThird line") 145 | -------------------------------------------------------------------------------- /inst/testCLI/testCLIcmd_RMacTermUS.out: -------------------------------------------------------------------------------- 1 | > # CLI torture test, copyright (c) Ph. Grosjean (phgrosjean@sciviews.org) 2 | > # GNU GPL => 2 license 3 | > # A series of commands to check for R CLI (or console widget) 4 | > # Version 1.0-0 5 | > 6 | > ## Simple instructions 7 | > 1+1 # Simple command with one line of output 8 | [1] 2 9 | > 1:100 # Simple command with several lines of output 10 | [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 11 | [19] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 12 | [37] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 13 | [55] 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 14 | [73] 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 15 | [91] 91 92 93 94 95 96 97 98 99 100 16 | > search() # Evaluation of a function 17 | [1] ".GlobalEnv" "package:stats" "package:graphics" 18 | [4] "package:grDevices" "package:utils" "package:datasets" 19 | [7] "package:methods" "TempEnv" "Autoloads" 20 | [10] "package:base" 21 | > ls() # Idem... look if this function is evaluated in .GlobalEnv! 22 | [1] "Parse" "TempEnv_" "capture.all" 23 | [4] "closeSocketClients" "cmd" "cmd0" 24 | [7] "cmdfile" "getSocketClients" "getSocketClientsNames" 25 | [10] "getSocketServerName" "getSocketServers" "gettext_" 26 | [13] "gettextf_" "i" "out" 27 | [16] "outfile" "processSocket" "res" 28 | [19] "sendSocketClients" "startSocketServer" "stopSocketServer" 29 | > 30 | > ## Multiple instructions on one line 31 | > 1+1; 2+2 # Two successive prints 32 | [1] 2 33 | [1] 4 34 | > 1+1; cat("test\n"); 2+2 # Print, cat, print 35 | [1] 2 36 | test 37 | [1] 4 38 | > 1+1; cat("test"); 2+2 # Idem, but cat without \n 39 | [1] 2 40 | test[1] 4 41 | > 42 | > ## Visible/invisible output 43 | > invisible(1) # Command with invisible result 44 | > a <- 1:10 # Idem 45 | > (a <- 1:10) # Idem, with visible output 46 | [1] 1 2 3 4 5 6 7 8 9 10 47 | > for (i in 1:3) print(i) # Output within a loop 48 | [1] 1 49 | [1] 2 50 | [1] 3 51 | > for (i in 1:3) i # Do not explicit use print(), so output nothing 52 | > 53 | > cat("test\n") # Simple text print with carriage return 54 | test 55 | > cat("test") # Idem, but without carriage return 56 | test> 57 | > ## S4 objects 58 | > setClass("track", representation(x = "numeric", y = "numeric")) 59 | [1] "track" 60 | > setMethod("show", "track", function(object) {cat("track:\n"); print(object@x); print(object@y)}) 61 | [1] "show" 62 | > tr <- new("track", x = 1:3, y = 4:6) # invisible 63 | > (tr <- new("track", x = 1:3, y = 4:6)) # visible 64 | track: 65 | [1] 1 2 3 66 | [1] 4 5 6 67 | > tr # show it 68 | track: 69 | [1] 1 2 3 70 | [1] 4 5 6 71 | > show(tr) # idem 72 | track: 73 | [1] 1 2 3 74 | [1] 4 5 6 75 | > print(tr) # This is the same! 76 | track: 77 | [1] 1 2 3 78 | [1] 4 5 6 79 | > 80 | > ## Special characters 81 | > cat("\ttabs\n") # \t (tabulation) should indent the text by 4 characters 82 | tabs 83 | > cat("tesg\bt\n") # \b (backspace) erases previous character thus it print "test" 84 | tesgt 85 | > alarm() # idem as cat("\a"), should sound a bip 86 | > 87 | > ## Code parsing and multiline commands 88 | > log) # Generate and error 89 | Error: unexpected ')' at log) 90 | > 1+1; log) # Should run first instruction before generating the error 91 | Error: unexpected ')' at 1+1; log) 92 | > # This is a partial line without comments (should issue the continue prompt) 93 | > log( 94 | + 10) + 95 | + 1 96 | [1] 3.302585 97 | > 98 | > log( # This is partial with comments 99 | + 10) 100 | [1] 2.302585 101 | > 102 | > search(); log( 103 | + 10) 104 | [1] ".GlobalEnv" "package:stats" "package:graphics" 105 | [4] "package:grDevices" "package:utils" "package:datasets" 106 | [7] "package:methods" "TempEnv" "Autoloads" 107 | [10] "package:base" 108 | [1] 2.302585 109 | > 110 | > log( # Issuing an empty line between instructions 111 | + 112 | + 10) 113 | [1] 2.302585 114 | > 115 | > # String on several lines 116 | > text <- "some 117 | + text" 118 | > text # should print "some\ntext" 119 | [1] "some\ntext" 120 | > 121 | > # Idem, but with single quote 122 | > text <- 'some 123 | + text' 124 | > text # should print "some\ntext" 125 | [1] "some\ntext" 126 | > 127 | > # A horrible code with a variable name on two lines (but correct syntax)! 128 | > `var 129 | + 1` <- 1 130 | > `var\n1` 131 | [1] 1 132 | > 133 | > ## Truncation of very long output 134 | > options(max.print = 1000) # Put a lower limit 135 | > 1:1100 # Should be truncated 136 | [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 137 | [15] 15 16 17 18 19 20 21 22 23 24 25 26 27 28 138 | [29] 29 30 31 32 33 34 35 36 37 38 39 40 41 42 139 | [43] 43 44 45 46 47 48 49 50 51 52 53 54 55 56 140 | [57] 57 58 59 60 61 62 63 64 65 66 67 68 69 70 141 | [71] 71 72 73 74 75 76 77 78 79 80 81 82 83 84 142 | [85] 85 86 87 88 89 90 91 92 93 94 95 96 97 98 143 | [99] 99 100 101 102 103 104 105 106 107 108 109 110 111 112 144 | [113] 113 114 115 116 117 118 119 120 121 122 123 124 125 126 145 | [127] 127 128 129 130 131 132 133 134 135 136 137 138 139 140 146 | [141] 141 142 143 144 145 146 147 148 149 150 151 152 153 154 147 | [155] 155 156 157 158 159 160 161 162 163 164 165 166 167 168 148 | [169] 169 170 171 172 173 174 175 176 177 178 179 180 181 182 149 | [183] 183 184 185 186 187 188 189 190 191 192 193 194 195 196 150 | [197] 197 198 199 200 201 202 203 204 205 206 207 208 209 210 151 | [211] 211 212 213 214 215 216 217 218 219 220 221 222 223 224 152 | [225] 225 226 227 228 229 230 231 232 233 234 235 236 237 238 153 | [239] 239 240 241 242 243 244 245 246 247 248 249 250 251 252 154 | [253] 253 254 255 256 257 258 259 260 261 262 263 264 265 266 155 | [267] 267 268 269 270 271 272 273 274 275 276 277 278 279 280 156 | [281] 281 282 283 284 285 286 287 288 289 290 291 292 293 294 157 | [295] 295 296 297 298 299 300 301 302 303 304 305 306 307 308 158 | [309] 309 310 311 312 313 314 315 316 317 318 319 320 321 322 159 | [323] 323 324 325 326 327 328 329 330 331 332 333 334 335 336 160 | [337] 337 338 339 340 341 342 343 344 345 346 347 348 349 350 161 | [351] 351 352 353 354 355 356 357 358 359 360 361 362 363 364 162 | [365] 365 366 367 368 369 370 371 372 373 374 375 376 377 378 163 | [379] 379 380 381 382 383 384 385 386 387 388 389 390 391 392 164 | [393] 393 394 395 396 397 398 399 400 401 402 403 404 405 406 165 | [407] 407 408 409 410 411 412 413 414 415 416 417 418 419 420 166 | [421] 421 422 423 424 425 426 427 428 429 430 431 432 433 434 167 | [435] 435 436 437 438 439 440 441 442 443 444 445 446 447 448 168 | [449] 449 450 451 452 453 454 455 456 457 458 459 460 461 462 169 | [463] 463 464 465 466 467 468 469 470 471 472 473 474 475 476 170 | [477] 477 478 479 480 481 482 483 484 485 486 487 488 489 490 171 | [491] 491 492 493 494 495 496 497 498 499 500 501 502 503 504 172 | [505] 505 506 507 508 509 510 511 512 513 514 515 516 517 518 173 | [519] 519 520 521 522 523 524 525 526 527 528 529 530 531 532 174 | [533] 533 534 535 536 537 538 539 540 541 542 543 544 545 546 175 | [547] 547 548 549 550 551 552 553 554 555 556 557 558 559 560 176 | [561] 561 562 563 564 565 566 567 568 569 570 571 572 573 574 177 | [575] 575 576 577 578 579 580 581 582 583 584 585 586 587 588 178 | [589] 589 590 591 592 593 594 595 596 597 598 599 600 601 602 179 | [603] 603 604 605 606 607 608 609 610 611 612 613 614 615 616 180 | [617] 617 618 619 620 621 622 623 624 625 626 627 628 629 630 181 | [631] 631 632 633 634 635 636 637 638 639 640 641 642 643 644 182 | [645] 645 646 647 648 649 650 651 652 653 654 655 656 657 658 183 | [659] 659 660 661 662 663 664 665 666 667 668 669 670 671 672 184 | [673] 673 674 675 676 677 678 679 680 681 682 683 684 685 686 185 | [687] 687 688 689 690 691 692 693 694 695 696 697 698 699 700 186 | [701] 701 702 703 704 705 706 707 708 709 710 711 712 713 714 187 | [715] 715 716 717 718 719 720 721 722 723 724 725 726 727 728 188 | [729] 729 730 731 732 733 734 735 736 737 738 739 740 741 742 189 | [743] 743 744 745 746 747 748 749 750 751 752 753 754 755 756 190 | [757] 757 758 759 760 761 762 763 764 765 766 767 768 769 770 191 | [771] 771 772 773 774 775 776 777 778 779 780 781 782 783 784 192 | [785] 785 786 787 788 789 790 791 792 793 794 795 796 797 798 193 | [799] 799 800 801 802 803 804 805 806 807 808 809 810 811 812 194 | [813] 813 814 815 816 817 818 819 820 821 822 823 824 825 826 195 | [827] 827 828 829 830 831 832 833 834 835 836 837 838 839 840 196 | [841] 841 842 843 844 845 846 847 848 849 850 851 852 853 854 197 | [855] 855 856 857 858 859 860 861 862 863 864 865 866 867 868 198 | [869] 869 870 871 872 873 874 875 876 877 878 879 880 881 882 199 | [883] 883 884 885 886 887 888 889 890 891 892 893 894 895 896 200 | [897] 897 898 899 900 901 902 903 904 905 906 907 908 909 910 201 | [911] 911 912 913 914 915 916 917 918 919 920 921 922 923 924 202 | [925] 925 926 927 928 929 930 931 932 933 934 935 936 937 938 203 | [939] 939 940 941 942 943 944 945 946 947 948 949 950 951 952 204 | [953] 953 954 955 956 957 958 959 960 961 962 963 964 965 966 205 | [967] 967 968 969 970 971 972 973 974 975 976 977 978 979 980 206 | [981] 981 982 983 984 985 986 987 988 989 990 991 992 993 994 207 | [995] 995 996 997 998 999 1000 208 | [ reached getOption("max.print") -- omitted 100 entries ]] 209 | > options(max.print = NULL) # Restore default value 210 | > 211 | > ## Errors messages 212 | > nonExistingVar 213 | Error: object "nonExistingVar" not found 214 | > cos("a") 215 | Error in cos("a") : Non-numeric argument to mathematical function 216 | > cos(nonExisting) 217 | Error: object "nonExisting" not found 218 | > stop("Error!"); 1:3 # Error in .GlobalEnv (no call info) 219 | Error: Error! 220 | > (function() stop("Error!"))(); 1:3 # Error; second command not evaluated 221 | Error in (function() stop("Error!"))() : Error! 222 | > 223 | > ## Warnings handling (a little tricky!) 224 | > options(warn = -1) # Do not display warnings 225 | > {warning("Warn!"); 1:3} # Simple warning 226 | [1] 1 2 3 227 | > {warning("Warn!", immediate. = TRUE); 1:3} # Should issue the warning anyway 228 | Warning: Warn! 229 | [1] 1 2 3 230 | > 231 | > options(warn = 0) # Delay warning display 232 | > {warning("Warn!"); 1:3} # Simple delayed warning 233 | [1] 1 2 3 234 | Warning message: 235 | Warn! 236 | > {warning("Warn!", immediate. = TRUE); 1:3} # Do not delay warning 237 | Warning: Warn! 238 | [1] 1 2 3 239 | > for (i in 1:3) {print(i); warning("test")} 240 | [1] 1 241 | [1] 2 242 | [1] 3 243 | Warning messages: 244 | 1: test 245 | 2: test 246 | 3: test 247 | > for (i in 1:4) {print(i); warning("test", immediate. = (i < 3))} 248 | [1] 1 249 | Warning: test 250 | [1] 2 251 | Warning: test 252 | [1] 3 253 | [1] 4 254 | Warning messages: 255 | 1: test 256 | 2: test 257 | > 258 | > options(warn = 1) # Display warnings immediatelly 259 | > {warning("Warn!"); 1:3} 260 | Warning: Warn! 261 | [1] 1 2 3 262 | > 263 | > options(warn = 2) # Every warning generates an error 264 | > {warning("Warn!"); 1:3} # Warning turned into an error 265 | Error: (converted from warning) Warn! 266 | > warnings() 267 | Warning messages: 268 | 1: test 269 | 2: test 270 | > 271 | > 272 | > ## Warnings inside functions 273 | > options(warn = -1) 274 | > (function() {warning("Warn!"); 1:3})() 275 | [1] 1 2 3 276 | > options(warn = 0) 277 | > (function() {warning("Warn!"); 1:3})() 278 | [1] 1 2 3 279 | Warning message: 280 | In (function() { ... : Warn! 281 | > options(warn = 1) 282 | > (function() {warning("Warn!"); 1:3})() 283 | Warning in (function() { : Warn! 284 | [1] 1 2 3 285 | > options(warn = 2) 286 | > (function() {warning("Warn!"); 1:3})() 287 | Error in (function() { : (converted from warning) Warn! 288 | > 289 | > ## Multiple warnings and/or errors (warn = 0 + 9, 10, 11, 49, 50 & 60 warnings) 290 | > options(warn = 0) 291 | > for (i in 1:9) warning("Warn ", i) 292 | Warning messages: 293 | 1: Warn 1 294 | 2: Warn 2 295 | 3: Warn 3 296 | 4: Warn 4 297 | 5: Warn 5 298 | 6: Warn 6 299 | 7: Warn 7 300 | 8: Warn 8 301 | 9: Warn 9 302 | > warnings() # Redisplay last warnings 303 | Warning messages: 304 | 1: Warn 1 305 | 2: Warn 2 306 | 3: Warn 3 307 | 4: Warn 4 308 | 5: Warn 5 309 | 6: Warn 6 310 | 7: Warn 7 311 | 8: Warn 8 312 | 9: Warn 9 313 | > for (i in 1:10) warning("Warn ", i) 314 | Warning messages: 315 | 1: Warn 1 316 | 2: Warn 2 317 | 3: Warn 3 318 | 4: Warn 4 319 | 5: Warn 5 320 | 6: Warn 6 321 | 7: Warn 7 322 | 8: Warn 8 323 | 9: Warn 9 324 | 10: Warn 10 325 | > warnings() 326 | Warning messages: 327 | 1: Warn 1 328 | 2: Warn 2 329 | 3: Warn 3 330 | 4: Warn 4 331 | 5: Warn 5 332 | 6: Warn 6 333 | 7: Warn 7 334 | 8: Warn 8 335 | 9: Warn 9 336 | 10: Warn 10 337 | > for (i in 1:11) warning("Warn ", i) 338 | There were 11 warnings (use warnings() to see them) 339 | > warnings() 340 | Warning messages: 341 | 1: Warn 1 342 | 2: Warn 2 343 | 3: Warn 3 344 | 4: Warn 4 345 | 5: Warn 5 346 | 6: Warn 6 347 | 7: Warn 7 348 | 8: Warn 8 349 | 9: Warn 9 350 | 10: Warn 10 351 | 11: Warn 11 352 | > for (i in 1:49) warning("Warn ", i) 353 | There were 49 warnings (use warnings() to see them) 354 | > warnings() 355 | Warning messages: 356 | 1: Warn 1 357 | 2: Warn 2 358 | 3: Warn 3 359 | 4: Warn 4 360 | 5: Warn 5 361 | 6: Warn 6 362 | 7: Warn 7 363 | 8: Warn 8 364 | 9: Warn 9 365 | 10: Warn 10 366 | 11: Warn 11 367 | 12: Warn 12 368 | 13: Warn 13 369 | 14: Warn 14 370 | 15: Warn 15 371 | 16: Warn 16 372 | 17: Warn 17 373 | 18: Warn 18 374 | 19: Warn 19 375 | 20: Warn 20 376 | 21: Warn 21 377 | 22: Warn 22 378 | 23: Warn 23 379 | 24: Warn 24 380 | 25: Warn 25 381 | 26: Warn 26 382 | 27: Warn 27 383 | 28: Warn 28 384 | 29: Warn 29 385 | 30: Warn 30 386 | 31: Warn 31 387 | 32: Warn 32 388 | 33: Warn 33 389 | 34: Warn 34 390 | 35: Warn 35 391 | 36: Warn 36 392 | 37: Warn 37 393 | 38: Warn 38 394 | 39: Warn 39 395 | 40: Warn 40 396 | 41: Warn 41 397 | 42: Warn 42 398 | 43: Warn 43 399 | 44: Warn 44 400 | 45: Warn 45 401 | 46: Warn 46 402 | 47: Warn 47 403 | 48: Warn 48 404 | 49: Warn 49 405 | > for (i in 1:50) warning("Warn ", i) 406 | There were 50 or more warnings (use warnings() to see the first 50) 407 | > warnings() 408 | Warning messages: 409 | 1: Warn 1 410 | 2: Warn 2 411 | 3: Warn 3 412 | 4: Warn 4 413 | 5: Warn 5 414 | 6: Warn 6 415 | 7: Warn 7 416 | 8: Warn 8 417 | 9: Warn 9 418 | 10: Warn 10 419 | 11: Warn 11 420 | 12: Warn 12 421 | 13: Warn 13 422 | 14: Warn 14 423 | 15: Warn 15 424 | 16: Warn 16 425 | 17: Warn 17 426 | 18: Warn 18 427 | 19: Warn 19 428 | 20: Warn 20 429 | 21: Warn 21 430 | 22: Warn 22 431 | 23: Warn 23 432 | 24: Warn 24 433 | 25: Warn 25 434 | 26: Warn 26 435 | 27: Warn 27 436 | 28: Warn 28 437 | 29: Warn 29 438 | 30: Warn 30 439 | 31: Warn 31 440 | 32: Warn 32 441 | 33: Warn 33 442 | 34: Warn 34 443 | 35: Warn 35 444 | 36: Warn 36 445 | 37: Warn 37 446 | 38: Warn 38 447 | 39: Warn 39 448 | 40: Warn 40 449 | 41: Warn 41 450 | 42: Warn 42 451 | 43: Warn 43 452 | 44: Warn 44 453 | 45: Warn 45 454 | 46: Warn 46 455 | 47: Warn 47 456 | 48: Warn 48 457 | 49: Warn 49 458 | 50: Warn 50 459 | > for (i in 1:60) warning("Warn ", i) 460 | There were 50 or more warnings (use warnings() to see the first 50) 461 | > warnings() 462 | Warning messages: 463 | 1: Warn 1 464 | 2: Warn 2 465 | 3: Warn 3 466 | 4: Warn 4 467 | 5: Warn 5 468 | 6: Warn 6 469 | 7: Warn 7 470 | 8: Warn 8 471 | 9: Warn 9 472 | 10: Warn 10 473 | 11: Warn 11 474 | 12: Warn 12 475 | 13: Warn 13 476 | 14: Warn 14 477 | 15: Warn 15 478 | 16: Warn 16 479 | 17: Warn 17 480 | 18: Warn 18 481 | 19: Warn 19 482 | 20: Warn 20 483 | 21: Warn 21 484 | 22: Warn 22 485 | 23: Warn 23 486 | 24: Warn 24 487 | 25: Warn 25 488 | 26: Warn 26 489 | 27: Warn 27 490 | 28: Warn 28 491 | 29: Warn 29 492 | 30: Warn 30 493 | 31: Warn 31 494 | 32: Warn 32 495 | 33: Warn 33 496 | 34: Warn 34 497 | 35: Warn 35 498 | 36: Warn 36 499 | 37: Warn 37 500 | 38: Warn 38 501 | 39: Warn 39 502 | 40: Warn 40 503 | 41: Warn 41 504 | 42: Warn 42 505 | 43: Warn 43 506 | 44: Warn 44 507 | 45: Warn 45 508 | 46: Warn 46 509 | 47: Warn 47 510 | 48: Warn 48 511 | 49: Warn 49 512 | 50: Warn 50 513 | > 514 | > # warning and then, error message with warn = 0 ("In addition: ...") 515 | > options(warn = 0) 516 | > (function() {warning("Warn!"); stop("Error!"); 1:3})() 517 | Error in (function() { : Error! 518 | In addition: Warning message: 519 | In (function() { ... : Warn! 520 | > options(warn = 1) 521 | > (function() {warning("Warn!"); stop("Error!"); 1:3})() 522 | Warning in (function() { : Warn! 523 | Error in (function() { : Error! 524 | > 525 | > ## Messages handling 526 | > {message("A message"); 1:3} # Should issue the message 527 | A message 528 | [1] 1 2 3 529 | > simpleMessage("test") 530 | 531 | > simpleMessage("test", call = "me") 532 | 533 | > 534 | > ## Multiline or very long message 535 | > options(warning.length = 100) 536 | > warning("A very long message for my warning that should be truncated or at least flowed on several lines, what does it gives here?") 537 | Warning: A very long message for my warning that should be truncated or at least flowed on several lines, wha [... truncated] 538 | > warning("A multiline warning\nSecond line,\nThird line") 539 | Warning: A multiline warning 540 | Second line, 541 | Third line 542 | > -------------------------------------------------------------------------------- /inst/testCLI/testCLIcmd_RUbuntuTermUS.save: -------------------------------------------------------------------------------- 1 | > # CLI torture test, copyright (c) Ph. Grosjean (phgrosjean@sciviews.org) 2 | > # GNU GPL => 2 license 3 | > # A series of commands to check for R CLI (or console widget) 4 | > # Version 1.0-0 5 | > 6 | > ## Simple instructions 7 | > 1+1 # Simple command with one line of output 8 | [1] 2 9 | > 1:100 # Simple command with several lines of output 10 | [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 11 | [19] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 12 | [37] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 13 | [55] 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 14 | [73] 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 15 | [91] 91 92 93 94 95 96 97 98 99 100 16 | > search() # Evaluation of a function 17 | [1] ".GlobalEnv" "package:stats" "package:graphics" 18 | [4] "package:grDevices" "package:utils" "package:datasets" 19 | [7] "package:methods" "TempEnv" "Autoloads" 20 | [10] "package:base" 21 | > ls() # Idem... look if this function is evaluated in .GlobalEnv! 22 | [1] "capture.all" "closeSocketClients" "cmd" 23 | [4] "cmd0" "cmdfile" "getSocketClients" 24 | [7] "getSocketClientsNames" "getSocketServerName" "getSocketServers" 25 | [10] "gettext_" "gettextf_" "i" 26 | [13] "out" "outfile" "Parse" 27 | [16] "processSocket" "res" "sendSocketClients" 28 | [19] "startSocketServer" "stopSocketServer" "TempEnv_" 29 | > 30 | > ## Multiple instructions on one line 31 | > 1+1; 2+2 # Two successive prints 32 | [1] 2 33 | [1] 4 34 | > 1+1; cat("test\n"); 2+2 # Print, cat, print 35 | [1] 2 36 | test 37 | [1] 4 38 | > 1+1; cat("test"); 2+2 # Idem, but cat without \n 39 | [1] 2 40 | test[1] 4 41 | > 42 | > ## Visible/invisible output 43 | > invisible(1) # Command with invisible result 44 | > a <- 1:10 # Idem 45 | > (a <- 1:10) # Idem, with visible output 46 | [1] 1 2 3 4 5 6 7 8 9 10 47 | > for (i in 1:3) print(i) # Output within a loop 48 | [1] 1 49 | [1] 2 50 | [1] 3 51 | > for (i in 1:3) i # Do not explicit use print(), so output nothing 52 | > 53 | > cat("test\n") # Simple text print with carriage return 54 | test 55 | > cat("test") # Idem, but without carriage return 56 | test> 57 | > ## S4 objects 58 | > setClass("track", representation(x = "numeric", y = "numeric")) 59 | [1] "track" 60 | > setMethod("show", "track", function(object) {cat("track:\n"); print(object@x); print(object@y)}) 61 | [1] "show" 62 | > tr <- new("track", x = 1:3, y = 4:6) # invisible 63 | > (tr <- new("track", x = 1:3, y = 4:6)) # visible 64 | track: 65 | [1] 1 2 3 66 | [1] 4 5 6 67 | > tr # show it 68 | track: 69 | [1] 1 2 3 70 | [1] 4 5 6 71 | > show(tr) # idem 72 | track: 73 | [1] 1 2 3 74 | [1] 4 5 6 75 | > print(tr) # This is the same! 76 | track: 77 | [1] 1 2 3 78 | [1] 4 5 6 79 | > 80 | > ## Special characters 81 | > cat("\ttabs\n") # \t (tabulation) should indent the text by 4 characters 82 | tabs 83 | > cat("tesg\bt\n") # \b (backspace) erases previous character thus it print "test" 84 | test 85 | > alarm() # idem as cat("\a"), should sound a bip 86 | > 87 | > ## Code parsing and multiline commands 88 | > log) # Generate and error 89 | Error: unexpected ')' in "log)" 90 | > 1+1; log) # Should run first instruction before generating the error 91 | [1] 2 92 | Error: unexpected ')' in " log)" 93 | > # This is a partial line without comments (should issue the continue prompt) 94 | > log( 95 | + 10) + 96 | + 1 97 | [1] 3.302585 98 | > 99 | > log( # This is partial with comments 100 | + 10) 101 | [1] 2.302585 102 | > 103 | > search(); log( 104 | [1] ".GlobalEnv" "package:stats" "package:graphics" 105 | [4] "package:grDevices" "package:utils" "package:datasets" 106 | [7] "package:methods" "TempEnv" "Autoloads" 107 | [10] "package:base" 108 | + 10) 109 | [1] 2.302585 110 | > 111 | > log( # Issuing an empty line between instructions 112 | + 113 | + 10) 114 | [1] 2.302585 115 | > 116 | > # String on several lines 117 | > text <- "some 118 | + text" 119 | > text # should print "some\ntext" 120 | [1] "some\ntext" 121 | > 122 | > # Idem, but with single quote 123 | > text <- 'some 124 | + text' 125 | > text # should print "some\ntext" 126 | [1] "some\ntext" 127 | > 128 | > # A horrible code with a variable name on two lines (but correct syntax)! 129 | > `var 130 | + 1` <- 1 131 | > `var\n1` 132 | [1] 1 133 | > 134 | > ## Truncation of very long output 135 | > options(max.print = 1000) # Put a lower limit 136 | > 1:1100 # Should be truncated 137 | [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 138 | [15] 15 16 17 18 19 20 21 22 23 24 25 26 27 28 139 | [29] 29 30 31 32 33 34 35 36 37 38 39 40 41 42 140 | [43] 43 44 45 46 47 48 49 50 51 52 53 54 55 56 141 | [57] 57 58 59 60 61 62 63 64 65 66 67 68 69 70 142 | [71] 71 72 73 74 75 76 77 78 79 80 81 82 83 84 143 | [85] 85 86 87 88 89 90 91 92 93 94 95 96 97 98 144 | [99] 99 100 101 102 103 104 105 106 107 108 109 110 111 112 145 | [113] 113 114 115 116 117 118 119 120 121 122 123 124 125 126 146 | [127] 127 128 129 130 131 132 133 134 135 136 137 138 139 140 147 | [141] 141 142 143 144 145 146 147 148 149 150 151 152 153 154 148 | [155] 155 156 157 158 159 160 161 162 163 164 165 166 167 168 149 | [169] 169 170 171 172 173 174 175 176 177 178 179 180 181 182 150 | [183] 183 184 185 186 187 188 189 190 191 192 193 194 195 196 151 | [197] 197 198 199 200 201 202 203 204 205 206 207 208 209 210 152 | [211] 211 212 213 214 215 216 217 218 219 220 221 222 223 224 153 | [225] 225 226 227 228 229 230 231 232 233 234 235 236 237 238 154 | [239] 239 240 241 242 243 244 245 246 247 248 249 250 251 252 155 | [253] 253 254 255 256 257 258 259 260 261 262 263 264 265 266 156 | [267] 267 268 269 270 271 272 273 274 275 276 277 278 279 280 157 | [281] 281 282 283 284 285 286 287 288 289 290 291 292 293 294 158 | [295] 295 296 297 298 299 300 301 302 303 304 305 306 307 308 159 | [309] 309 310 311 312 313 314 315 316 317 318 319 320 321 322 160 | [323] 323 324 325 326 327 328 329 330 331 332 333 334 335 336 161 | [337] 337 338 339 340 341 342 343 344 345 346 347 348 349 350 162 | [351] 351 352 353 354 355 356 357 358 359 360 361 362 363 364 163 | [365] 365 366 367 368 369 370 371 372 373 374 375 376 377 378 164 | [379] 379 380 381 382 383 384 385 386 387 388 389 390 391 392 165 | [393] 393 394 395 396 397 398 399 400 401 402 403 404 405 406 166 | [407] 407 408 409 410 411 412 413 414 415 416 417 418 419 420 167 | [421] 421 422 423 424 425 426 427 428 429 430 431 432 433 434 168 | [435] 435 436 437 438 439 440 441 442 443 444 445 446 447 448 169 | [449] 449 450 451 452 453 454 455 456 457 458 459 460 461 462 170 | [463] 463 464 465 466 467 468 469 470 471 472 473 474 475 476 171 | [477] 477 478 479 480 481 482 483 484 485 486 487 488 489 490 172 | [491] 491 492 493 494 495 496 497 498 499 500 501 502 503 504 173 | [505] 505 506 507 508 509 510 511 512 513 514 515 516 517 518 174 | [519] 519 520 521 522 523 524 525 526 527 528 529 530 531 532 175 | [533] 533 534 535 536 537 538 539 540 541 542 543 544 545 546 176 | [547] 547 548 549 550 551 552 553 554 555 556 557 558 559 560 177 | [561] 561 562 563 564 565 566 567 568 569 570 571 572 573 574 178 | [575] 575 576 577 578 579 580 581 582 583 584 585 586 587 588 179 | [589] 589 590 591 592 593 594 595 596 597 598 599 600 601 602 180 | [603] 603 604 605 606 607 608 609 610 611 612 613 614 615 616 181 | [617] 617 618 619 620 621 622 623 624 625 626 627 628 629 630 182 | [631] 631 632 633 634 635 636 637 638 639 640 641 642 643 644 183 | [645] 645 646 647 648 649 650 651 652 653 654 655 656 657 658 184 | [659] 659 660 661 662 663 664 665 666 667 668 669 670 671 672 185 | [673] 673 674 675 676 677 678 679 680 681 682 683 684 685 686 186 | [687] 687 688 689 690 691 692 693 694 695 696 697 698 699 700 187 | [701] 701 702 703 704 705 706 707 708 709 710 711 712 713 714 188 | [715] 715 716 717 718 719 720 721 722 723 724 725 726 727 728 189 | [729] 729 730 731 732 733 734 735 736 737 738 739 740 741 742 190 | [743] 743 744 745 746 747 748 749 750 751 752 753 754 755 756 191 | [757] 757 758 759 760 761 762 763 764 765 766 767 768 769 770 192 | [771] 771 772 773 774 775 776 777 778 779 780 781 782 783 784 193 | [785] 785 786 787 788 789 790 791 792 793 794 795 796 797 798 194 | [799] 799 800 801 802 803 804 805 806 807 808 809 810 811 812 195 | [813] 813 814 815 816 817 818 819 820 821 822 823 824 825 826 196 | [827] 827 828 829 830 831 832 833 834 835 836 837 838 839 840 197 | [841] 841 842 843 844 845 846 847 848 849 850 851 852 853 854 198 | [855] 855 856 857 858 859 860 861 862 863 864 865 866 867 868 199 | [869] 869 870 871 872 873 874 875 876 877 878 879 880 881 882 200 | [883] 883 884 885 886 887 888 889 890 891 892 893 894 895 896 201 | [897] 897 898 899 900 901 902 903 904 905 906 907 908 909 910 202 | [911] 911 912 913 914 915 916 917 918 919 920 921 922 923 924 203 | [925] 925 926 927 928 929 930 931 932 933 934 935 936 937 938 204 | [939] 939 940 941 942 943 944 945 946 947 948 949 950 951 952 205 | [953] 953 954 955 956 957 958 959 960 961 962 963 964 965 966 206 | [967] 967 968 969 970 971 972 973 974 975 976 977 978 979 980 207 | [981] 981 982 983 984 985 986 987 988 989 990 991 992 993 994 208 | [995] 995 996 997 998 999 1000 209 | [ reached getOption("max.print") -- omitted 100 entries ]] 210 | > options(max.print = NULL) # Restore default value 211 | > 212 | > ## Errors messages 213 | > nonExistingVar 214 | Error: object "nonExistingVar" not found 215 | > cos("a") 216 | Error in cos("a") : Non-numeric argument to mathematical function 217 | > cos(nonExisting) 218 | Error: object "nonExisting" not found 219 | > stop("Error!"); 1:3 # Error in .GlobalEnv (no call info) 220 | Error: Error! 221 | > (function() stop("Error!"))(); 1:3 # Error; second command not evaluated 222 | Error in (function() stop("Error!"))() : Error! 223 | > 224 | > ## Warnings handling (a little tricky!) 225 | > options(warn = -1) # Do not display warnings 226 | > {warning("Warn!"); 1:3} # Simple warning 227 | [1] 1 2 3 228 | > {warning("Warn!", immediate. = TRUE); 1:3} # Should issue the warning anyway 229 | Warning: Warn! 230 | [1] 1 2 3 231 | > 232 | > options(warn = 0) # Delay warning display 233 | > {warning("Warn!"); 1:3} # Simple delayed warning 234 | [1] 1 2 3 235 | Warning message: 236 | Warn! 237 | > {warning("Warn!", immediate. = TRUE); 1:3} # Do not delay warning 238 | Warning: Warn! 239 | [1] 1 2 3 240 | > for (i in 1:3) {print(i); warning("test")} 241 | [1] 1 242 | [1] 2 243 | [1] 3 244 | Warning messages: 245 | 1: test 246 | 2: test 247 | 3: test 248 | > for (i in 1:4) {print(i); warning("test", immediate. = (i < 3))} 249 | [1] 1 250 | Warning: test 251 | [1] 2 252 | Warning: test 253 | [1] 3 254 | [1] 4 255 | Warning messages: 256 | 1: test 257 | 2: test 258 | > 259 | > options(warn = 1) # Display warnings immediatelly 260 | > {warning("Warn!"); 1:3} 261 | Warning: Warn! 262 | [1] 1 2 3 263 | > 264 | > options(warn = 2) # Every warning generates an error 265 | > {warning("Warn!"); 1:3} # Warning turned into an error 266 | Error: (converted from warning) Warn! 267 | > warnings() 268 | Warning messages: 269 | 1: test 270 | 2: test 271 | > 272 | > 273 | > ## Warnings inside functions 274 | > options(warn = -1) 275 | > (function() {warning("Warn!"); 1:3})() 276 | [1] 1 2 3 277 | > options(warn = 0) 278 | > (function() {warning("Warn!"); 1:3})() 279 | [1] 1 2 3 280 | Warning message: 281 | In (function() { : Warn! 282 | > options(warn = 1) 283 | > (function() {warning("Warn!"); 1:3})() 284 | Warning in (function() { : Warn! 285 | [1] 1 2 3 286 | > options(warn = 2) 287 | > (function() {warning("Warn!"); 1:3})() 288 | Error in (function() { : (converted from warning) Warn! 289 | > 290 | > ## Multiple warnings and/or errors (warn = 0 + 9, 10, 11, 49, 50 & 60 warnings) 291 | > options(warn = 0) 292 | > for (i in 1:9) warning("Warn ", i) 293 | Warning messages: 294 | 1: Warn 1 295 | 2: Warn 2 296 | 3: Warn 3 297 | 4: Warn 4 298 | 5: Warn 5 299 | 6: Warn 6 300 | 7: Warn 7 301 | 8: Warn 8 302 | 9: Warn 9 303 | > warnings() # Redisplay last warnings 304 | Warning messages: 305 | 1: Warn 1 306 | 2: Warn 2 307 | 3: Warn 3 308 | 4: Warn 4 309 | 5: Warn 5 310 | 6: Warn 6 311 | 7: Warn 7 312 | 8: Warn 8 313 | 9: Warn 9 314 | > for (i in 1:10) warning("Warn ", i) 315 | Warning messages: 316 | 1: Warn 1 317 | 2: Warn 2 318 | 3: Warn 3 319 | 4: Warn 4 320 | 5: Warn 5 321 | 6: Warn 6 322 | 7: Warn 7 323 | 8: Warn 8 324 | 9: Warn 9 325 | 10: Warn 10 326 | > warnings() 327 | Warning messages: 328 | 1: Warn 1 329 | 2: Warn 2 330 | 3: Warn 3 331 | 4: Warn 4 332 | 5: Warn 5 333 | 6: Warn 6 334 | 7: Warn 7 335 | 8: Warn 8 336 | 9: Warn 9 337 | 10: Warn 10 338 | > for (i in 1:11) warning("Warn ", i) 339 | There were 11 warnings (use warnings() to see them) 340 | > warnings() 341 | Warning messages: 342 | 1: Warn 1 343 | 2: Warn 2 344 | 3: Warn 3 345 | 4: Warn 4 346 | 5: Warn 5 347 | 6: Warn 6 348 | 7: Warn 7 349 | 8: Warn 8 350 | 9: Warn 9 351 | 10: Warn 10 352 | 11: Warn 11 353 | > for (i in 1:49) warning("Warn ", i) 354 | There were 49 warnings (use warnings() to see them) 355 | > warnings() 356 | Warning messages: 357 | 1: Warn 1 358 | 2: Warn 2 359 | 3: Warn 3 360 | 4: Warn 4 361 | 5: Warn 5 362 | 6: Warn 6 363 | 7: Warn 7 364 | 8: Warn 8 365 | 9: Warn 9 366 | 10: Warn 10 367 | 11: Warn 11 368 | 12: Warn 12 369 | 13: Warn 13 370 | 14: Warn 14 371 | 15: Warn 15 372 | 16: Warn 16 373 | 17: Warn 17 374 | 18: Warn 18 375 | 19: Warn 19 376 | 20: Warn 20 377 | 21: Warn 21 378 | 22: Warn 22 379 | 23: Warn 23 380 | 24: Warn 24 381 | 25: Warn 25 382 | 26: Warn 26 383 | 27: Warn 27 384 | 28: Warn 28 385 | 29: Warn 29 386 | 30: Warn 30 387 | 31: Warn 31 388 | 32: Warn 32 389 | 33: Warn 33 390 | 34: Warn 34 391 | 35: Warn 35 392 | 36: Warn 36 393 | 37: Warn 37 394 | 38: Warn 38 395 | 39: Warn 39 396 | 40: Warn 40 397 | 41: Warn 41 398 | 42: Warn 42 399 | 43: Warn 43 400 | 44: Warn 44 401 | 45: Warn 45 402 | 46: Warn 46 403 | 47: Warn 47 404 | 48: Warn 48 405 | 49: Warn 49 406 | > for (i in 1:50) warning("Warn ", i) 407 | There were 50 or more warnings (use warnings() to see the first 50) 408 | > warnings() 409 | Warning messages: 410 | 1: Warn 1 411 | 2: Warn 2 412 | 3: Warn 3 413 | 4: Warn 4 414 | 5: Warn 5 415 | 6: Warn 6 416 | 7: Warn 7 417 | 8: Warn 8 418 | 9: Warn 9 419 | 10: Warn 10 420 | 11: Warn 11 421 | 12: Warn 12 422 | 13: Warn 13 423 | 14: Warn 14 424 | 15: Warn 15 425 | 16: Warn 16 426 | 17: Warn 17 427 | 18: Warn 18 428 | 19: Warn 19 429 | 20: Warn 20 430 | 21: Warn 21 431 | 22: Warn 22 432 | 23: Warn 23 433 | 24: Warn 24 434 | 25: Warn 25 435 | 26: Warn 26 436 | 27: Warn 27 437 | 28: Warn 28 438 | 29: Warn 29 439 | 30: Warn 30 440 | 31: Warn 31 441 | 32: Warn 32 442 | 33: Warn 33 443 | 34: Warn 34 444 | 35: Warn 35 445 | 36: Warn 36 446 | 37: Warn 37 447 | 38: Warn 38 448 | 39: Warn 39 449 | 40: Warn 40 450 | 41: Warn 41 451 | 42: Warn 42 452 | 43: Warn 43 453 | 44: Warn 44 454 | 45: Warn 45 455 | 46: Warn 46 456 | 47: Warn 47 457 | 48: Warn 48 458 | 49: Warn 49 459 | 50: Warn 50 460 | > for (i in 1:60) warning("Warn ", i) 461 | There were 50 or more warnings (use warnings() to see the first 50) 462 | > warnings() 463 | Warning messages: 464 | 1: Warn 1 465 | 2: Warn 2 466 | 3: Warn 3 467 | 4: Warn 4 468 | 5: Warn 5 469 | 6: Warn 6 470 | 7: Warn 7 471 | 8: Warn 8 472 | 9: Warn 9 473 | 10: Warn 10 474 | 11: Warn 11 475 | 12: Warn 12 476 | 13: Warn 13 477 | 14: Warn 14 478 | 15: Warn 15 479 | 16: Warn 16 480 | 17: Warn 17 481 | 18: Warn 18 482 | 19: Warn 19 483 | 20: Warn 20 484 | 21: Warn 21 485 | 22: Warn 22 486 | 23: Warn 23 487 | 24: Warn 24 488 | 25: Warn 25 489 | 26: Warn 26 490 | 27: Warn 27 491 | 28: Warn 28 492 | 29: Warn 29 493 | 30: Warn 30 494 | 31: Warn 31 495 | 32: Warn 32 496 | 33: Warn 33 497 | 34: Warn 34 498 | 35: Warn 35 499 | 36: Warn 36 500 | 37: Warn 37 501 | 38: Warn 38 502 | 39: Warn 39 503 | 40: Warn 40 504 | 41: Warn 41 505 | 42: Warn 42 506 | 43: Warn 43 507 | 44: Warn 44 508 | 45: Warn 45 509 | 46: Warn 46 510 | 47: Warn 47 511 | 48: Warn 48 512 | 49: Warn 49 513 | 50: Warn 50 514 | > 515 | > # warning and then, error message with warn = 0 ("In addition: ...") 516 | > options(warn = 0) 517 | > (function() {warning("Warn!"); stop("Error!"); 1:3})() 518 | Error in (function() { : Error! 519 | In addition: Warning message: 520 | In (function() { : Warn! 521 | > options(warn = 1) 522 | > (function() {warning("Warn!"); stop("Error!"); 1:3})() 523 | Warning in (function() { : Warn! 524 | Error in (function() { : Error! 525 | > 526 | > ## Messages handling 527 | > {message("A message"); 1:3} # Should issue the message 528 | A message 529 | [1] 1 2 3 530 | > simpleMessage("test") 531 | 532 | > simpleMessage("test", call = "me") 533 | 534 | > 535 | > ## Multiline or very long message 536 | > options(warning.length = 100) 537 | > warning("A very long message for my warning that should be truncated or at least flowed on sev 538 | + warning("A multiline warning\nSecond line,\nThird line") 539 | Error: unexpected symbol in: 540 | "warning("A very long message for my warning that should be truncated 541 | > 542 | 543 | -------------------------------------------------------------------------------- /inst/testCLI/testCLIcmd_RappFR.save: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciViews/svSocket/1c6081bb64b3bdb08730defa4efa31f976ebb41f/inst/testCLI/testCLIcmd_RappFR.save -------------------------------------------------------------------------------- /inst/testCLI/testCLIcmd_RguiFR.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciViews/svSocket/1c6081bb64b3bdb08730defa4efa31f976ebb41f/inst/testCLI/testCLIcmd_RguiFR.out -------------------------------------------------------------------------------- /inst/testCLI/testCLIcmd_RguiFR.save: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciViews/svSocket/1c6081bb64b3bdb08730defa4efa31f976ebb41f/inst/testCLI/testCLIcmd_RguiFR.save -------------------------------------------------------------------------------- /man/close_socket_clients.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/close_socket_clients.R 3 | \name{close_socket_clients} 4 | \alias{close_socket_clients} 5 | \alias{closeSocketClients} 6 | \title{Close one or more clients currently connected} 7 | \usage{ 8 | close_socket_clients(sockets = "all", server_port = 8888) 9 | 10 | closeSocketClients(sockets = "all", server_port = 8888) 11 | } 12 | \arguments{ 13 | \item{sockets}{the list of socket client names (sockXXX) to close, or \code{"all"} 14 | (by default) to disconnect all currently connected clients.} 15 | 16 | \item{server_port}{the corresponding R socket server port.} 17 | } 18 | \description{ 19 | The socket servers asks to clients to nicely disconnect (possibly doing 20 | further process on their side). This function is used by 21 | \code{\link[=stop_socket_server]{stop_socket_server()}}, but it can also be invoked manually to ask for 22 | disconnection of a particular client. Note that, in this case, the client 23 | still can decide not to disconnect! The code send to ask for client 24 | disconnection is: \verb{\\\\f}. 25 | } 26 | \seealso{ 27 | \code{\link[=send_socket_clients]{send_socket_clients()}} 28 | } 29 | \concept{stateful socket server interprocess communication} 30 | \keyword{IO} 31 | -------------------------------------------------------------------------------- /man/eval_socket_server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eval_socket_server.R 3 | \name{eval_socket_server} 4 | \alias{eval_socket_server} 5 | \alias{evalServer} 6 | \title{Evaluate R code in a server process} 7 | \usage{ 8 | eval_socket_server(con, expr, send = NULL) 9 | 10 | evalServer(con, expr, send = NULL) 11 | } 12 | \arguments{ 13 | \item{con}{a socket connection with the server (see examples).} 14 | 15 | \item{expr}{an R expression to evaluate in the server.} 16 | 17 | \item{send}{optional data to send to the server.} 18 | } 19 | \value{ 20 | The object returned by the last evaluation in the server. 21 | } 22 | \description{ 23 | This function is designed to connect two R processes together using the 24 | socket server. It allows for piloting the server R process from a client R 25 | process, to evaluate R code in the server and return its results to the 26 | client. 27 | } 28 | \details{ 29 | The function serializes R objects using \code{\link[=dump]{dump()}} on the server, and it 30 | \code{\link[=source]{source()}}s the data on the client side. It has, thus, the same limitations 31 | as \code{\link[=dump]{dump()}}, (see \code{?dump}), and in particular, environments, external 32 | pointers, weak references and objects of type \code{S4} are not serializable with 33 | \code{\link[=dump]{dump()}} and will raise an error, or will produce unusable objects on the 34 | client side. Note also that lists or attributes of accepted objects may 35 | contain external pointers or environments, and thus, the whole object becomes 36 | unserializable. In that case, try to coerce your object, or extract a part of 37 | it on the server side to make sure you send just the part that is 38 | transferable between the two R processes. 39 | } 40 | \examples{ 41 | \dontrun{ 42 | # Start an R process and make it a server 43 | library(svSocket) 44 | start_socket_server() 45 | 46 | # Start a second R process and run this code in it (the R client): 47 | library(svSocket) 48 | 49 | # Connect with the R socket server 50 | con <- socketConnection(host = "localhost", port = 8888, blocking = FALSE) 51 | 52 | L <- 10:20 53 | L 54 | eval_socket_server(con, L) # L is not an the server, hence the error 55 | eval_socket_server(con, L, L) # Send it to the server 56 | eval_socket_server(con, L) # Now it is there 57 | eval_socket_server(con, L, L + 2) 58 | L 59 | eval_socket_server(con, L) 60 | 61 | # More examples 62 | eval_socket_server(con, "x <- 42") # Set x 63 | eval_socket_server(con, "y <- 10") # Set y 64 | eval_socket_server(con, x + y) # Quotes not needed 65 | eval_socket_server(con, "x + y") # but you can put quotes if you like 66 | eval_socket_server(con, x) # Same as get x 67 | eval_socket_server(con, "x + Y") # Return server side-error to the client 68 | eval_socket_server(con, x) # Keep working after an error 69 | eval_socket_server(con, "x <- 'a'") # Embedded quotes are OK 70 | 71 | # Examples of sending data 72 | eval_socket_server(con, X, -42) # Alternative way to assign to X 73 | eval_socket_server(con, Y, 1:10) 74 | eval_socket_server(con, X + Y) 75 | X # Generates an error, X is not here in the client, only on the server 76 | eval_socket_server(con, X) 77 | eval_socket_server(con, "Z <- X + 3") # Send an assignment to execute remotely 78 | eval_socket_server(con, X + Z) 79 | eval_socket_server(con, "Z <- X + 1:1000; NULL") # Same but do not return Z 80 | eval_socket_server(con, length(Z)) 81 | Z <- eval_socket_server(con, Z) # Bring it back to client 82 | Z 83 | 84 | # Close connection with the R socket server 85 | close(con) 86 | 87 | # Now, switch back to the R server process and check 88 | # that the created variables are there 89 | L 90 | x 91 | y 92 | X 93 | Y 94 | Z 95 | 96 | # Stop the socket server 97 | stop_socket_server() 98 | } 99 | } 100 | \seealso{ 101 | \code{\link[=send_socket_clients]{send_socket_clients()}} 102 | } 103 | \author{ 104 | Matthew Dowle 105 | } 106 | \concept{stateful socket server interprocess communication} 107 | \keyword{IO} 108 | \keyword{utilities} 109 | -------------------------------------------------------------------------------- /man/get_socket_clients.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_socket.R 3 | \name{get_socket_clients} 4 | \alias{get_socket_clients} 5 | \alias{getSocketClients} 6 | \alias{get_socket_clients_names} 7 | \alias{getSocketClientsNames} 8 | \alias{getSocketServerName} 9 | \title{Get infos about socket clients} 10 | \usage{ 11 | get_socket_clients(port = 8888) 12 | 13 | getSocketClients(port = 8888) 14 | 15 | get_socket_clients_names(port = 8888) 16 | 17 | getSocketClientsNames(port = 8888) 18 | 19 | getSocketServerName(port = 8888) 20 | } 21 | \arguments{ 22 | \item{port}{the port of the R socket server.} 23 | } 24 | \value{ 25 | \code{\link[=get_socket_clients]{get_socket_clients()}} returns a vector of character string with the address of 26 | clients in the form XXX.XXX.XXX.XXX:YYY where XXX.XXX.XXX.XXX is their ip 27 | address and YYY is their port. For security reasons, only localhost clients 28 | (on the same machine) can connect to the socket server. Thus, XXX.XXX.XXX.XXX 29 | is ALWAYS 127.0.0.1. However, the function returns the full IP address, just 30 | in case of further extensions in the future. The name of these items equals 31 | the corresponding Tcl socket name. 32 | 33 | \code{\link[=get_socket_clients_names]{get_socket_clients_names()}} returns only a list of the socket client names. 34 | } 35 | \description{ 36 | List all clients currently connected to a given R socket server, or their 37 | names (\code{sockXXX}). 38 | } 39 | \seealso{ 40 | \code{\link[=get_socket_servers]{get_socket_servers()}} 41 | } 42 | \concept{stateful socket server interprocess communication} 43 | \keyword{IO} 44 | \keyword{utilities} 45 | -------------------------------------------------------------------------------- /man/get_socket_server_name.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_socket.R 3 | \name{get_socket_server_name} 4 | \alias{get_socket_server_name} 5 | \title{Get the name of a R socket server} 6 | \usage{ 7 | get_socket_server_name(port = 8888) 8 | } 9 | \arguments{ 10 | \item{port}{the port of the R socket server.} 11 | } 12 | \value{ 13 | A string with the server name, or \code{NULL} if it does not exist. 14 | } 15 | \description{ 16 | Get the internal name given to a particular R socket server. 17 | } 18 | \seealso{ 19 | \code{\link[=get_socket_servers]{get_socket_servers()}} 20 | } 21 | \concept{stateful socket server interprocess communication} 22 | \keyword{IO} 23 | \keyword{utilities} 24 | -------------------------------------------------------------------------------- /man/get_socket_servers.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_socket.R 3 | \name{get_socket_servers} 4 | \alias{get_socket_servers} 5 | \alias{getSocketServers} 6 | \title{Get the ports of current R socket servers} 7 | \usage{ 8 | get_socket_servers() 9 | 10 | getSocketServers() 11 | } 12 | \value{ 13 | A character string vector, or \code{NULL} if no R socket server is currently 14 | running. 15 | } 16 | \description{ 17 | Returns a list with all the ports of currently running R socket servers. 18 | } 19 | \seealso{ 20 | \code{\link[=get_socket_clients]{get_socket_clients()}}, \code{\link[=get_socket_server_name]{get_socket_server_name()}}, \link{start_socket_server} 21 | } 22 | \concept{stateful socket server interprocess communication} 23 | \keyword{IO} 24 | \keyword{utilities} 25 | -------------------------------------------------------------------------------- /man/par_socket_server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/par_socket_server.R 3 | \name{par_socket_server} 4 | \alias{par_socket_server} 5 | \alias{parSocket} 6 | \title{Get or set parameters specific to SciViews socket clients} 7 | \usage{ 8 | par_socket_server(client, server_port = 8888, client_socket = client, ...) 9 | 10 | parSocket(client, server_port = 8888, client_socket = client, ...) 11 | } 12 | \arguments{ 13 | \item{client}{the client identification. By default, it is the socket 14 | identifier as it appears in \code{\link[=get_socket_clients]{get_socket_clients()}}. Since no attempt is made 15 | to check if the client really exists and is connected, you can create fake 16 | ones, outside of the socket server, to test your code for instance.} 17 | 18 | \item{server_port}{the port on which the server is running, 8888 by default. 19 | Not important for fake socket client configurations.} 20 | 21 | \item{client_socket}{the Tcl name of the socket where the client is connected. 22 | By default, it is the same as \code{client} name, but in case it was modified, do 23 | provide a correct \code{client_socket} string if you want to be able to activate a 24 | redirection to it (see \code{\link[=socket_client_connection]{socket_client_connection()}}).} 25 | 26 | \item{...}{the parameters you want to change as named arguments. Non named 27 | arguments are ignored with a warning. If you specify \code{arg = NULL}, the 28 | corresponding variable is deleted from the environment.} 29 | } 30 | \value{ 31 | Returns the environment where parameters and data for the client are stored. 32 | To access those data, see examples below. 33 | } 34 | \description{ 35 | This function manage to persistently store sensible parameters for 36 | configuring communication between the server and the client, as well as, any 37 | other persistent data you may need. Parameters remain set even if the client 38 | disconnects and then reconnects to R, as long R was not restarted. 39 | } 40 | \details{ 41 | You can assign the environment to a variable, and then, access its content 42 | like if it was a list (\code{e$var} or \code{e$var <- "new value"}). To get a list of 43 | the content, use \code{ls(par_socket_server(client, port))}, or 44 | \code{ls(par_socket_server(client, port), all.names = TRUE)}, but not 45 | \code{names(par_socket_server(client, port))}. As long as you keep a variable 46 | pointing on that environment alive, you have access to last values (i.e., 47 | changes done elsewhere are taken into account). If you want a frozen snapshot 48 | of the parameters, you should use 49 | \verb{myvar <- as.list(par_socket_server(client, port)}. 50 | 51 | There is a convenient placeholder for code send by the client to insert 52 | automatically the right socket and server_port in 53 | \code{par_socket_server()}: \verb{<<>>}. 54 | Hence, code that the client send to access or change its environment is just 55 | \verb{par_socket_server(<<>>, bare = FALSE)} or 56 | \verb{par_socket_server(<<>>)$bare} to set or get one parameter. Note that you 57 | can set or change many parameters at once. 58 | 59 | Currently, parameters are: 60 | \itemize{ 61 | \item \code{bare = TRUE|FALSE} for "bare" mode (no prompt, no echo, no multiline; by 62 | default, \code{bare = TRUE}), 63 | \item \code{multiline = TRUE|FALSE}: does the server accept code spread on multiple 64 | lines and send in several steps (by default, yes, but works only if 65 | \code{bare = FALSE}. 66 | \item \code{echo = TRUE|FALSE} is the command echoed to the regular R console (by 67 | default \code{echo = FALSE}). 68 | \item \code{last = ""} string to append to each output (for instance to indicate that 69 | processing is done), 70 | \item \code{prompt = "> "}, the prompt to use (if not in bare mode) and 71 | \item \code{continue = "+ "} the continuation prompt to use, when multiline mode is 72 | active. You can only cancel a multiline mode by completing the R code you are 73 | sending to the server, but you can break it too by sending \verb{<<>>} before 74 | the next instruction. You can indicate \verb{<<>>} or \verb{<<>>} at the very 75 | beginning of an instruction to tell R to disconnect the connection after the 76 | command is processed and result is returned (with \verb{<<>>}), or when the 77 | instructions are received but before they are processed (with \verb{<<>>}). 78 | This is useful for "one shot" clients (clients that connect, send code and 79 | want to disconnect immediately after that). The code send by the server to 80 | the client to tell him to disconnect gracefully (and do some housekeeping) is 81 | \verb{\\\\f} send at the beginning of one line. So, clients should detect this and 82 | perform the necessary actions to gracefully disconnect from the server as 83 | soon as possible, and he cannot send further instructions from this moment 84 | on. 85 | } 86 | 87 | For clients that repeatedly connect and disconnect, but want persistent data, 88 | the default client identifier (the socket name) cannot be used, because that 89 | socket name would change from connection to connection. The client must then 90 | provide its own identifier. This is done by sending \verb{<<>>} at the 91 | very beginning of a command. This must be done for all commands! \code{myID} must 92 | use only characters or digits. This code could be followed by \verb{<<>>}, 93 | \verb{<<>>} or \verb{<<>>}. These commands are intended for R editors/IDE. The 94 | first code \verb{<<>>} sets the server into a mode that is suitable to 95 | evaluate R code (including in a multi-line way). The other code temporarily 96 | configure the server to run the command (in single line mode only) in a 97 | hidden way. They can be used to execute R code without displaying it in the 98 | console (for instance, to start context help, to get a calltip, or a 99 | completion list, etc.). The differences between \verb{<<>>} and \verb{<<>>} is 100 | that the former waits for command completion and returns results of the 101 | command to the client before disconnecting, while the latter disconnects from 102 | the client before executing the command. 103 | 104 | There is a simple client (written in Tcl) available in the /etc subdirectory 105 | of this package installation. Please, read the 'ReadMe.txt' file in the same 106 | directory to learn how to use it. You can use this simple client to 107 | experiment with the communication using these sockets, but it does not 108 | provide advanced command line edition, no command history, and avoid pasting 109 | more than one line of code into it. 110 | } 111 | \examples{ 112 | # We use a fake socket client configuration environment 113 | e <- par_socket_server("fake") 114 | # Look at what it contains 115 | ls(e) 116 | # Get one data 117 | e$bare 118 | # ... or 119 | par_socket_server("fake")$bare 120 | 121 | # Change it 122 | par_socket_server("fake", bare = FALSE)$bare 123 | # Note it is changed too for e 124 | e$bare 125 | 126 | # You can change it too with 127 | e$bare <- TRUE 128 | e$bare 129 | par_socket_server("fake")$bare 130 | 131 | # Create a new entry 132 | e$foo <- "test" 133 | ls(e) 134 | par_socket_server("fake")$foo 135 | # Now delete it 136 | par_socket_server("fake", foo = NULL) 137 | ls(e) 138 | 139 | # Our fake socket config is in SciViews:TempEnv environment 140 | s <- search() 141 | l <- length(s) 142 | pos <- (1:l)[s == "SciViews:TempEnv"] 143 | ls(pos = pos) # It is named 'socket_client_fake' 144 | # Delete it 145 | rm(socket_client_fake, pos = pos) 146 | # Do some house keeping 147 | rm(list = c("s", "l", "pos")) 148 | } 149 | \seealso{ 150 | \code{\link[=start_socket_server]{start_socket_server()}}, \code{\link[=send_socket_clients]{send_socket_clients()}}, \code{\link[=get_socket_clients]{get_socket_clients()}}, 151 | \code{\link[=socket_client_connection]{socket_client_connection()}} 152 | } 153 | \concept{stateful socket server interprocess communication} 154 | \keyword{IO} 155 | \keyword{utilities} 156 | -------------------------------------------------------------------------------- /man/process_socket_server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/process_socket_server.R 3 | \name{process_socket_server} 4 | \alias{process_socket_server} 5 | \alias{processSocket} 6 | \title{The function that processes a command coming from the socket} 7 | \usage{ 8 | process_socket_server(msg, socket, server_port, ...) 9 | 10 | processSocket(msg, socket, server_port, ...) 11 | } 12 | \arguments{ 13 | \item{msg}{the message send by the client, to be processed.} 14 | 15 | \item{socket}{the client socket identifier, as in \code{\link[=get_socket_clients]{get_socket_clients()}}. 16 | This is passed by the calling function and can be used internally.} 17 | 18 | \item{server_port}{the port on which the server is running, this is passed by 19 | the calling function and can be used internally.} 20 | 21 | \item{...}{anything you want to pass to \code{\link[=process_socket_server]{process_socket_server()}}, but it 22 | needs to rework \code{\link[=start_socket_server]{start_socket_server()}} to use it).} 23 | } 24 | \value{ 25 | The results of processing \code{msg} in a character string vector. 26 | } 27 | \description{ 28 | This is the default R function called each time data is send by a client 29 | through a socket. It is possible to customize this function and to use 30 | customized versions for particular R socket servers. 31 | } 32 | \details{ 33 | There are special code that one can send to R to easily turn the server 34 | (possibly temporarily) into a given configuration. First, if you want to 35 | persistently store parameters for your client in the R server and make sure 36 | you retrieve the same parameters the next time you reconnect, you should 37 | specify your own identifier. This is done by sending \verb{<<>>} at the 38 | very beginning of each of your commands. Always remember that, if you do not 39 | specify an identifier, the name of your socket will be used. Since socket 40 | names can be reused, you should always reinitialize the configuration of your 41 | server the first time you connect to it. 42 | 43 | Then, sending \verb{<<>>} breaks current multiline code submission and 44 | flushes the multiline buffer. 45 | 46 | The sequence \verb{<<>>} at the beginning of a command indicates that the 47 | server wants to disconnect once the command is fully treated by R. Similarly, 48 | the sequence \verb{<<>>} tells the server to disconnect the client before 49 | processing the command (no error message is returned to the client!). 50 | 51 | It is easy to turn the server to evaluate R code (including multiline code) 52 | and return the result and disconnect by using the \verb{<<>>} sequence at the 53 | beginning of a command. Using \verb{<<>>} or \verb{<<>>} configures that server 54 | to process a (single-line code only) command silently and disconnect before 55 | (uppercase H) or after (lowercase h) processing that command. It is the less 56 | intrusive mode that is very useful for all commands that should be executed 57 | behind the scene between R and a R editor or IDE, like contextual help, 58 | calltips, completion lists, etc.). Note that using these modes in a server 59 | that is, otherwise, configured as a multi-line server does not break current 60 | multi-line buffer. 61 | 62 | The other sequences that can be used are: \verb{<<>>} for a placeholder to 63 | configure the current server (with configuration parameters after it), and 64 | \verb{<<>>} to indicate a newline in your code (submitting two lines of code 65 | as a single one; also works with servers configured as single-line 66 | evaluators). 67 | 68 | To debug the R socket server and inspect how commands send by a client are 69 | interpreted by this function, use \code{options(debug.Socket = TRUE)}. This 70 | function uses \code{\link[svMisc:parse_text]{svMisc::parse_text()}} and \code{\link[svMisc:capture_all]{svMisc::capture_all()}} in order to 71 | evaluate R code in character string almost exactly the same way as if it was 72 | typed at the command line of a R console. 73 | } 74 | \examples{ 75 | \dontrun{ 76 | # A simple REPL (R eval/process loop) using basic features of processSocket() 77 | repl <- function() { 78 | pars <- par_socket_server("repl", "", bare = FALSE) # Parameterize the loop 79 | cat("Enter R code, hit or to exit\n> ") # First prompt 80 | repeat { 81 | entry <- readLines(n = 1) # Read a line of entry 82 | if (entry == "") entry <- "<<>>" # Exit from multiline mode 83 | cat(process_socket_server(entry, "repl", "")) # Process the entry 84 | } 85 | } 86 | repl() 87 | } 88 | } 89 | \seealso{ 90 | \code{\link[=start_socket_server]{start_socket_server()}}, \code{\link[=send_socket_clients]{send_socket_clients()}}, 91 | \code{\link[=par_socket_server]{par_socket_server()}}, \code{\link[svMisc:parse_text]{svMisc::parse_text()}}, \code{\link[svMisc:capture_all]{svMisc::capture_all()}} 92 | } 93 | \concept{stateful socket server interprocess communication} 94 | \keyword{IO} 95 | \keyword{utilities} 96 | -------------------------------------------------------------------------------- /man/send_socket_clients.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/send_socket_clients.R 3 | \name{send_socket_clients} 4 | \alias{send_socket_clients} 5 | \alias{sendSocketClients} 6 | \title{Send data to one or more clients through a socket} 7 | \usage{ 8 | send_socket_clients(text, sockets = "all", server_port = 8888) 9 | 10 | sendSocketClients(text, sockets = "all", server_port = 8888) 11 | } 12 | \arguments{ 13 | \item{text}{the text to send to the client(s).} 14 | 15 | \item{sockets}{the Tcl name of the client(s) socket(s) currently connected 16 | (\code{sockXXX}), or \code{"all"} (by default) to send the same text to all connected 17 | clients.} 18 | 19 | \item{server_port}{the port of the server considered.} 20 | } 21 | \description{ 22 | The text is send to one or more clients of the R socket server currently 23 | connected. 24 | } 25 | \examples{ 26 | \dontrun{ 27 | # Start an R process (R#1) and make it a server 28 | library(svSocket) 29 | server_port <- 8888 # Port 8888 by default, but you can change it 30 | start_socket_server(port = server_port) 31 | 32 | 33 | # Start a second R process (R#2) and run this code in it (the R client): 34 | library(svSocket) 35 | # Connect with the R socket server 36 | con <- socketConnection(host = "localhost", port = 8888, blocking = FALSE) 37 | 38 | 39 | # Now, go back to the server R#1 40 | get_socket_clients() # You should have one client registered 41 | # Send something to all clients from R#1 42 | send_socket_clients("Hi there!") 43 | 44 | 45 | # Switch back to client R#2 46 | # Since the connection is not blocking, you have to read lines actively 47 | readLines(con) 48 | # Note the final empty string indicating there is no more data 49 | close(con) # Once done... 50 | 51 | 52 | # Switch to the R#1 server and close the server 53 | stop_socket_server(port = server_port) 54 | } 55 | } 56 | \seealso{ 57 | \code{\link[=close_socket_clients]{close_socket_clients()}}, \code{\link[=process_socket_server]{process_socket_server()}} 58 | } 59 | \concept{stateful socket server interprocess communication} 60 | \keyword{IO} 61 | \keyword{utilities} 62 | -------------------------------------------------------------------------------- /man/socket_client_connection.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/socket_client_connection.R 3 | \name{socket_client_connection} 4 | \alias{socket_client_connection} 5 | \alias{summary.sockclientconn} 6 | \alias{socketClientConnection} 7 | \title{Open a connection to a SciViews socket client for write access} 8 | \usage{ 9 | socket_client_connection( 10 | client, 11 | server_port = 8888, 12 | socket, 13 | blocking = FALSE, 14 | open = "a", 15 | encoding = getOption("encoding") 16 | ) 17 | 18 | \method{summary}{sockclientconn}(object, ...) 19 | 20 | socketClientConnection( 21 | client, 22 | server_port = 8888, 23 | socket, 24 | blocking = FALSE, 25 | open = "a", 26 | encoding = getOption("encoding") 27 | ) 28 | } 29 | \arguments{ 30 | \item{client}{the client identification. By default, it is the socket 31 | identifier as it appears in \code{\link[=get_socket_clients]{get_socket_clients()}}. The client must be 32 | currently connected.} 33 | 34 | \item{server_port}{the port on which the server is running, 8888 by default. 35 | This server must be currently running.} 36 | 37 | \item{socket}{the Tcl socket name where the targeted client is connected. If 38 | not provided, it will be guessed from \code{client}, otherwise, \code{client} is 39 | ignored.} 40 | 41 | \item{blocking}{logical. Should the connection wait that the data is written 42 | before exiting?} 43 | 44 | \item{open}{character. How the connection is opened. Currently, only \code{"a"} 45 | for append (default) or \code{"w"} for write access are usable.} 46 | 47 | \item{encoding}{the name of the encoding to use.} 48 | 49 | \item{object}{A 'sockclientconn' object as returned by 50 | \code{\link[=socket_client_connection]{socket_client_connection()}}.} 51 | 52 | \item{...}{further arguments passed to the method (not used for the moment).} 53 | } 54 | \value{ 55 | \code{\link[=socket_client_connection]{socket_client_connection()}} creates a 'sockclientconn' object redirects text 56 | send to it to the SciViews socket server client. It is inherits from a 57 | 'sockconn' object (see \code{socketConnection()}), and the only difference is that 58 | output is redirected to a Tcl socket corresponding to a given SciViews socket 59 | client currently connected. 60 | } 61 | \description{ 62 | A 'sockclientconn' object is created that opens a connection from R to a 63 | SciViews socket client (that must be currently connected). A timeout is 64 | defined by \code{options(timeout = XX)} where \code{XX} is a number of seconds. In R, 65 | its default value is 60 sec. 66 | } 67 | \seealso{ 68 | \code{\link[=socketConnection]{socketConnection()}}, \code{\link[=send_socket_clients]{send_socket_clients()}} 69 | } 70 | \concept{stateful socket server interprocess communication} 71 | \keyword{IO} 72 | \keyword{utilities} 73 | -------------------------------------------------------------------------------- /man/start_socket_server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/start_socket_server.R, R/stop_socket_server.R 3 | \name{start_socket_server} 4 | \alias{start_socket_server} 5 | \alias{startSocketServer} 6 | \alias{stop_socket_server} 7 | \alias{stopSocketServer} 8 | \title{Start and stop a R socket server} 9 | \usage{ 10 | start_socket_server( 11 | port = 8888, 12 | server_name = "Rserver", 13 | procfun = process_socket_server, 14 | secure = FALSE, 15 | local = !secure 16 | ) 17 | 18 | startSocketServer( 19 | port = 8888, 20 | server_name = "Rserver", 21 | procfun = process_socket_server, 22 | secure = FALSE, 23 | local = !secure 24 | ) 25 | 26 | stop_socket_server(port = 8888) 27 | 28 | stopSocketServer(port = 8888) 29 | } 30 | \arguments{ 31 | \item{port}{the TCP port of the R socket server.} 32 | 33 | \item{server_name}{the internal name of this server.} 34 | 35 | \item{procfun}{the function to use to process client's commands. By default, 36 | it is \code{process_socket_server()}.} 37 | 38 | \item{secure}{do we start a secure (TLS) server? (not implemented yet)} 39 | 40 | \item{local}{if \code{TRUE}, accept only connections from local clients, i.e., 41 | from clients with IP address 127.0.0.1. Set by default if the server is not 42 | secure.} 43 | } 44 | \description{ 45 | A R socket server is listening for command send by clients to a TCP port. 46 | This server is implemented in Tcl/Tk, using the powerful 'socket' command. 47 | Since it runs in the separate tcltk event loop, it is not blocking R, and it 48 | runs in the background; the user can still enter commands at the R prompt 49 | while one or several R socket servers are running and even, possibly, 50 | processing socket clients requests. 51 | } 52 | \details{ 53 | This server is currently synchronous in the processing of the command. 54 | However, neither R, nor the client are blocked during exchange of data 55 | (communication is asynchronous). 56 | 57 | Note also that socket numbers are reused, and corresponding configurations 58 | are not deleted from one connection to the other. So, it is possible for a 59 | client to connect/disconnect several times and continue to work with the same 60 | configuration (in particular, the multiline code submitted line by line) if 61 | every command starts with \verb{<<>>} where \code{myID} is an alphanumeric 62 | (unique) identifier. This property is call a stateful server. Take care! The 63 | R server never checks uniqueness of this identifier. You are responsible to 64 | use one that would not interfere with other, concurrent, clients connected 65 | to the same server. 66 | 67 | For trials and basic testings of the R socket server, you can use the Tcl 68 | script \code{SimpleClient.Tcl}. See the \code{ReadMe.txt} file in the 69 | /etc/ subdirectory of the svSocket package folder. Also, in the source of the 70 | svSocket package you will find \code{testCLI.R}, a script to torture test CLI for 71 | R (console). 72 | } 73 | \note{ 74 | Due to a change in R 4.3.x in its event loop, some Tcl socket events are not 75 | processes and this prevents the R socket server to work properly. This is 76 | corrected in R 4.4.0. The socket server also works well with R 4.0.x, R 4.1.x 77 | and R 4.2.x. 78 | 79 | One can write a different \code{procfun()} function than the default one for 80 | special servers. That function must accept one argument (a string with the 81 | command send by the client) and it must return a character string containing 82 | the result of the computation. 83 | } 84 | \seealso{ 85 | \code{\link[=process_socket_server]{process_socket_server()}}, \code{\link[=send_socket_clients]{send_socket_clients()}} 86 | } 87 | \concept{stateful socket server interprocess communication} 88 | \keyword{IO} 89 | \keyword{utilities} 90 | -------------------------------------------------------------------------------- /man/svSocket-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/svSocket-package.R 3 | \docType{package} 4 | \name{svSocket-package} 5 | \alias{svSocket} 6 | \alias{svSocket-package} 7 | \title{svSocket: 'SciViews' - Socket Server} 8 | \description{ 9 | A socket server allows to connect clients to R. 10 | } 11 | \details{ 12 | The SciViews \{svSocket\} package provides a stateful, multi-client and 13 | preemptive socket server. Socket transaction are operational even when R is 14 | busy in its main event loop (calculation done at the prompt). This R socket 15 | server uses the excellent asynchronous socket ports management by Tcl, and 16 | thus, it needs a working version of Tcl/Tk (>= 8.4) and of the \{tcltk\} R 17 | package. 18 | 19 | A particular effort has been made to handle requests the same way as if they 20 | where introduced at the command prompt, including presentation of the output. 21 | However, the server sends results back to the client only at the end of the 22 | computations. It means that any interaction during computation (for instance, 23 | using \code{\link[=scan]{scan()}}, \code{\link[=browser]{browser()}}, or \code{par(ask = TRUE)} is not echoed in the client 24 | on due time. If you parameterize the socket server to echo commands in the R 25 | console, such interaction would be possible from there. Another option is to 26 | run R in non-interactive mode. 27 | 28 | Although initially designed to server GUI clients, the R socket server can 29 | also be used to exchange data between separate R processes. The 30 | \code{\link[=eval_socket_server]{eval_socket_server()}} function is particularly useful for this. Note, 31 | however, that R objects are serialized into a text (i.e., using \code{\link[=dump]{dump()}}) 32 | format, currently. It means that the transfer of large object is not as 33 | efficient as, say \{Rserver\} (\{Rserver\} exchanges R objects in binary format, 34 | but \{Rserver\} is not stateful, clients do not share the same global workspace 35 | and it does not allow concurrent use of the command prompt). 36 | 37 | Due to a change in R 4.3.x in its event loop, some Tcl socket events are not 38 | processes and this prevents the R socket server to work properly. This is 39 | corrected in R 4.4.0. The socket server also works well with R 4.0.x, R 4.1.x 40 | and R 4.2.x. 41 | 42 | See \code{\link[=start_socket_server]{start_socket_server()}} and \code{\link[=process_socket_server]{process_socket_server()}} for further 43 | implementation details. 44 | } 45 | \seealso{ 46 | Useful links: 47 | \itemize{ 48 | \item \url{https://github.com/SciViews/svSocket} 49 | \item \url{https://www.sciviews.org/svSocket/} 50 | \item Report bugs at \url{https://github.com/SciViews/svSocket/issues} 51 | } 52 | 53 | } 54 | \author{ 55 | \strong{Maintainer}: Philippe Grosjean \email{phgrosjean@sciviews.org} (\href{https://orcid.org/0000-0002-2694-9471}{ORCID}) 56 | 57 | Other contributors: 58 | \itemize{ 59 | \item Matthew Dowle \email{mdowle@mdowle.plus.com} [contributor] 60 | } 61 | 62 | } 63 | \keyword{internal} 64 | -------------------------------------------------------------------------------- /revdep/.gitignore: -------------------------------------------------------------------------------- 1 | checks 2 | library 3 | checks.noindex 4 | library.noindex 5 | data.sqlite 6 | *.html 7 | -------------------------------------------------------------------------------- /revdep/README.md: -------------------------------------------------------------------------------- 1 | # Platform 2 | 3 | |field |value | 4 | |:--------|:----------------------------| 5 | |version |R version 3.6.3 (2020-02-29) | 6 | |os |macOS Catalina 10.15.4 | 7 | |system |x86_64, darwin15.6.0 | 8 | |ui |RStudio | 9 | |language |(EN) | 10 | |collate |en_US.UTF-8 | 11 | |ctype |en_US.UTF-8 | 12 | |tz |Europe/Brussels | 13 | |date |2020-05-10 | 14 | 15 | # Dependencies 16 | 17 | |package |old |new |Δ | 18 | |:--------|:------|:-----|:--| 19 | |svSocket |0.9-57 |1.0.0 |* | 20 | 21 | # Revdeps 22 | 23 | -------------------------------------------------------------------------------- /revdep/email.yml: -------------------------------------------------------------------------------- 1 | release_date: ??? 2 | rel_release_date: ??? 3 | my_news_url: ??? 4 | release_version: ??? 5 | release_details: ??? 6 | -------------------------------------------------------------------------------- /revdep/failures.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /revdep/problems.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /svSocket.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: XeLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageCheckArgs: --as-cran 22 | PackageRoxygenize: rd,collate,namespace,vignette 23 | 24 | SpellingDictionary: en_US 25 | -------------------------------------------------------------------------------- /tests/spelling.R: -------------------------------------------------------------------------------- 1 | if (requireNamespace('spelling', quietly = TRUE)) 2 | spelling::spell_check_test(vignettes = TRUE, error = FALSE, 3 | skip_on_cran = TRUE) 4 | -------------------------------------------------------------------------------- /vignettes/svSocket.R: -------------------------------------------------------------------------------- 1 | ## ----setup, include=FALSE----------------------------------------------------- 2 | knitr::opts_chunk$set( 3 | collapse = TRUE, 4 | comment = "#>" 5 | ) 6 | 7 | ## ----install, eval=FALSE------------------------------------------------------ 8 | # install.packages("svSocket") 9 | 10 | ## ----launch-server, eval=FALSE------------------------------------------------ 11 | # # Start a separate R process with a script that launch a socket server on 8889 12 | # # and wait for the variable `done` in `.GlobalEnv` to finish 13 | # rscript <- Sys.which("Rscript") 14 | # system2(rscript, "--vanilla -e 'svSocket::start_socket_server(8889); while (!exists(\"done\")) Sys.sleep(1)'", wait = FALSE) 15 | 16 | ## ----no_server_version, include=FALSE----------------------------------------- 17 | server_ready <- FALSE 18 | 19 | ## ----wait, include=FALSE, eval=FALSE------------------------------------------ 20 | # # Launch the server 21 | # rscript <- Sys.which("Rscript") 22 | # try(system2(rscript, "--vanilla -e 'svSocket::start_socket_server(8889); while (!exists(\"done\")) Sys.sleep(1)'", wait = FALSE), silent = TRUE) 23 | # 24 | # # Leave enough time for the server to get ready 25 | # Sys.sleep(5) 26 | # # and make sure we can connect to it 27 | # con <- try(socketConnection(host = "localhost", port = 8889, 28 | # blocking = FALSE, timeout = 30), 29 | # silent = TRUE) 30 | # server_ready <- !inherits(con, "try-error") 31 | 32 | ## ----connect, eval=FALSE------------------------------------------------------ 33 | # con <- socketConnection(host = "localhost", port = 8889, 34 | # blocking = FALSE, timeout = 30) 35 | 36 | ## ----eval1, eval=server_ready------------------------------------------------- 37 | # library(svSocket) 38 | # eval_socket_server(con, '1 + 1') 39 | 40 | ## ----evalx, eval=server_ready------------------------------------------------- 41 | # # Local x 42 | # x <- "local" 43 | # # x on the server 44 | # eval_socket_server(con, 'x <- "server"') 45 | 46 | ## ----evalx2, eval=server_ready------------------------------------------------ 47 | # eval_socket_server(con, 'ls()') 48 | # eval_socket_server(con, 'x') 49 | 50 | ## ----localx, eval=server_ready------------------------------------------------ 51 | # ls() 52 | # x 53 | 54 | ## ----iris2, eval=server_ready------------------------------------------------- 55 | # data(iris) 56 | # eval_socket_server(con, iris2, iris) 57 | # eval_socket_server(con, "ls()") # iris2 is there 58 | # eval_socket_server(con, "head(iris2)") # ... and its content is OK 59 | 60 | ## ----low-level, eval=server_ready--------------------------------------------- 61 | # # Send a command to the R server (low-level version) 62 | # cat('{Sys.sleep(2); "Done!"}\n', file = con) 63 | # # Wait for, and get response from the server 64 | # res <- NULL 65 | # while (!length(res)) { 66 | # Sys.sleep(0.01) 67 | # res <- readLines(con) 68 | # } 69 | # res 70 | 71 | ## ----cat, eval=server_ready--------------------------------------------------- 72 | # cat(res, "\n") 73 | 74 | ## ----runServer, eval=server_ready--------------------------------------------- 75 | # run_socket_server <- function(con, code) { 76 | # cat(code, "\n", file = con) 77 | # res <- NULL 78 | # while (!length(res)) { 79 | # Sys.sleep(0.01) 80 | # res <- readLines(con) 81 | # } 82 | # # Use this instruction to output results as if code was run at the prompt 83 | # #cat(res, "\n") 84 | # invisible(res) 85 | # } 86 | 87 | ## ----runServer2, eval=server_ready-------------------------------------------- 88 | # (run_socket_server(con, '{Sys.sleep(2); "Done!"}')) 89 | 90 | ## ----async, eval=server_ready------------------------------------------------- 91 | # (run_socket_server(con, '\n<<>>{Sys.sleep(2); "Done!"}')) 92 | 93 | ## ----pars1, eval=server_ready------------------------------------------------- 94 | # cat(run_socket_server(con, 'ls(envir = svSocket::par_socket_server(<<>>))'), sep = "\n") 95 | 96 | ## ----pars2, eval=server_ready------------------------------------------------- 97 | # cat(run_socket_server(con, 'svSocket::par_socket_server(<<>>)$bare'), sep = "\n") 98 | 99 | ## ----bare-false, eval=server_ready-------------------------------------------- 100 | # (run_socket_server(con, '\n<<>>svSocket::par_socket_server(<<>>, bare = FALSE)')) 101 | # (run_socket_server(con, '1 + 1')) 102 | 103 | ## ----close, eval=server_ready------------------------------------------------- 104 | # # Usually, the client does not stop the server, but it is possible here 105 | # eval_socket_server(con, 'done <- NULL') # The server will stop after this transaction 106 | # close(con) 107 | 108 | -------------------------------------------------------------------------------- /vignettes/svSocket.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SciViews socket server" 3 | author: "Philippe Grosjean (phgrosjean@sciviews.org)" 4 | date: "`r Sys.Date()`" 5 | output: 6 | rmarkdown::html_vignette: 7 | fig_caption: yes 8 | vignette: > 9 | %\VignetteIndexEntry{SciViews socket server} 10 | %\VignetteEngine{knitr::rmarkdown} 11 | %\VignetteEncoding{UTF-8} 12 | --- 13 | 14 | ```{r setup, include=FALSE} 15 | knitr::opts_chunk$set( 16 | collapse = TRUE, 17 | comment = "#>" 18 | ) 19 | ``` 20 | 21 | > The SciViews {svSocket} server implements an R server that is mainly designed to interact with the R prompt from a separate process. The {svSocket} clients and the R console share the same global environment (`.GlobalEnv`). So, everybody interacts with the same variables. Use cases are to build a separate R console, to monitor or query an R session, or even, to build a complete GUI or IDE on top of R. Examples of such GUIs are [Tinn-R](https://tinn-r.org/en/) and Komodo IDE with the [SciViews-K extension](https://github.com/SciViews/sciviewsk). 22 | 23 | ## Quick install and use 24 | 25 | The SciViews {svSocket} package provides a stateful, multi-client and preemptive socket server. Socket transactions are operational even when R is busy in its main event loop (calculation done at the prompt). This R socket server uses the excellent asynchronous socket ports management by Tcl, and thus, it needs a working version of Tcl/Tk (\>= 8.4), the {tcltk} R package, and R with `isTRUE(capabilities("tcltk"))`. 26 | 27 | Install the {svSocket} package as usual: 28 | 29 | ```{r install, eval=FALSE} 30 | install.packages("svSocket") 31 | ``` 32 | 33 | (For the development version, install first `devtools` or `remotes`, and then use something like `remotes::github_install("SciViews/svSocket")`) 34 | 35 | ```{r launch-server, eval=FALSE} 36 | # Start a separate R process with a script that launch a socket server on 8889 37 | # and wait for the variable `done` in `.GlobalEnv` to finish 38 | rscript <- Sys.which("Rscript") 39 | system2(rscript, "--vanilla -e 'svSocket::start_socket_server(8889); while (!exists(\"done\")) Sys.sleep(1)'", wait = FALSE) 40 | ``` 41 | 42 | Sometimes (on MacOS Catalina or more, for instance), you are prompted to allow for R to use a socket. Of course, you have to allow to get an operational server. 43 | 44 | What this code does is: 45 | 46 | - Get the path to `Rscript` using `Sys.which` -Start it with a command that launches the socket server on port 8889[^1] (`svSocket::start_socket_server(8889)`) 47 | - Wait that the variable `done` appears in `.GlobalEnv` as a simple mean to keep the server alive. 48 | - Since `system2()` is invoked with `wait = FALSE`, the command exits as soon as the server is created and you can interact at the R prompt. 49 | 50 | [^1]: Of course, this port must be free. If not, just use another value. 51 | 52 | As you can see, creating a socket server with {svSocket} is easy. Now, let's connect to it. It is also very simple: 53 | 54 | ```{r no_server_version, include=FALSE} 55 | server_ready <- FALSE 56 | ``` 57 | 58 | ```{r wait, include=FALSE, eval=FALSE} 59 | # Launch the server 60 | rscript <- Sys.which("Rscript") 61 | try(system2(rscript, "--vanilla -e 'svSocket::start_socket_server(8889); while (!exists(\"done\")) Sys.sleep(1)'", wait = FALSE), silent = TRUE) 62 | 63 | # Leave enough time for the server to get ready 64 | Sys.sleep(5) 65 | # and make sure we can connect to it 66 | con <- try(socketConnection(host = "localhost", port = 8889, 67 | blocking = FALSE, timeout = 30), 68 | silent = TRUE) 69 | server_ready <- !inherits(con, "try-error") 70 | ``` 71 | 72 | ```{r connect, eval=FALSE} 73 | con <- socketConnection(host = "localhost", port = 8889, 74 | blocking = FALSE, timeout = 30) 75 | ``` 76 | 77 | Here, we use the `socketConnection()` that comes with base R in non-blocking mode and a time out at 30 sec. So, {svSocket} is even not required to connect an R client to our server. The connection is not blocking. We have the control of the prompt immediately. 78 | 79 | Now, we can execute code in our R server process with `svSocket::eval_socket_server()`: 80 | 81 | ```{r eval1, eval=server_ready} 82 | library(svSocket) 83 | eval_socket_server(con, '1 + 1') 84 | ``` 85 | 86 | The instruction is indeed executed in the server, and the result is returned to the client transparently. You now master two separate R process: the original one where you interact at the R prompt (`>`), and the server process that you can interact with through `eval_socket_server()`. To understand this, we will create the variable `x` on both processes, but with different values: 87 | 88 | ```{r evalx, eval=server_ready} 89 | # Local x 90 | x <- "local" 91 | # x on the server 92 | eval_socket_server(con, 'x <- "server"') 93 | ``` 94 | 95 | Now, let's look what is available on the server side: 96 | 97 | ```{r evalx2, eval=server_ready} 98 | eval_socket_server(con, 'ls()') 99 | eval_socket_server(con, 'x') 100 | ``` 101 | 102 | Obviously, there is only one variable, `x`, with value `"server"`. All right ... and in our original process? 103 | 104 | ```{r localx, eval=server_ready} 105 | ls() 106 | x 107 | ``` 108 | 109 | We have `x`, but also `con` and `rsscript`. The value of `x` locally is `"local"`. 110 | 111 | As you can see, commands and results are rather similar. And since the processes are different, so are `x` in both processes. 112 | 113 | If you want to transfer data to the server, you can still use `eval_socket_server()`, with the name you want for the variable on the server instead of a string with R code, and as third argument, the name of the local variable whose the content will be copied to the server. `eval_socket_server()` manages to send the data to the server (by serializing the data on your side and deserializing it on the server). Here, we will transfer the `iris` data frame to the server under the `iris2` name: 114 | 115 | ```{r iris2, eval=server_ready} 116 | data(iris) 117 | eval_socket_server(con, iris2, iris) 118 | eval_socket_server(con, "ls()") # iris2 is there 119 | eval_socket_server(con, "head(iris2)") # ... and its content is OK 120 | ``` 121 | 122 | For more examples on using `eval_socket_server`, see its man page with `?eval_socket_server`. 123 | 124 | ## Lower-level interaction 125 | 126 | The {svSocket} server also allows, of course, for a lower-level interaction with the server, with a lot more options. Here, you send something to the server using `cat, file = con)` (make sure to end your command by a carriage return `\n`, or the server will wait indefinitely for the next part of the instruction!), and you read results using `readLines(con)`. Of course, you must wait that R processes the command before reading results back: 127 | 128 | ```{r low-level, eval=server_ready} 129 | # Send a command to the R server (low-level version) 130 | cat('{Sys.sleep(2); "Done!"}\n', file = con) 131 | # Wait for, and get response from the server 132 | res <- NULL 133 | while (!length(res)) { 134 | Sys.sleep(0.01) 135 | res <- readLines(con) 136 | } 137 | res 138 | ``` 139 | 140 | Here you got the results send back as strings. If you want to display them on the client's console as if it was output there (like `eval_socket_server()` does), you should use `cat( sep = "\n")`: 141 | 142 | ```{r cat, eval=server_ready} 143 | cat(res, "\n") 144 | ``` 145 | 146 | For convenience, we will wrap all this a function `run_socket_server()`: 147 | 148 | ```{r runServer, eval=server_ready} 149 | run_socket_server <- function(con, code) { 150 | cat(code, "\n", file = con) 151 | res <- NULL 152 | while (!length(res)) { 153 | Sys.sleep(0.01) 154 | res <- readLines(con) 155 | } 156 | # Use this instruction to output results as if code was run at the prompt 157 | #cat(res, "\n") 158 | invisible(res) 159 | } 160 | ``` 161 | 162 | The transaction is now much easier: 163 | 164 | ```{r runServer2, eval=server_ready} 165 | (run_socket_server(con, '{Sys.sleep(2); "Done!"}')) 166 | ``` 167 | 168 | ## Configuration and special instructions 169 | 170 | Now at the low-level (not within `eval_socket_server()` but within our `run_socket_server()` function), one can insert special code `<<>>` with `X` being a marker that the server will use on his side. The last instruction we send instructed the server to wait for 2 sec and then, to return `"Done!"`. We have send the instruction to the server and *wait* for it to finish processing and then, we got the results back. Thus, you original R process is locked down the time the server processes the code. Note that we had to lock it down on our side using the `while(!length(res))` construct. It means that communication between the server and the client is asynchronous, but process of the command must be made synchronous. If you want to return immediately in the calling R process *before* the instruction is processed in the server, you could just consider to drop the `while(...)` section. **This is not a good idea!** Indeed, R will send results back and you will read them on the next `readLines(con)` you issue, and mix it with, perhaps, the result of the next instruction. So, here, we really must tell the R server to send nothing back to us. This is done by inserting the special instruction `<<>>` on one line (surrounded by `\n`). This way, we still have to wait for the instruction to be processed on the server, but nothing is returned back to the client. Also, sending `<<>>` will result into an immediate finalization of the transaction by the server *before* the instruction is processed. 171 | 172 | ```{r async, eval=server_ready} 173 | (run_socket_server(con, '\n<<>>{Sys.sleep(2); "Done!"}')) 174 | ``` 175 | 176 | Here, we got the result immediately, but it is *not* the results of the code execution. Our R server simply indicates that he got our code, he parsed it and is about to process it on his side by returning an empty string `""`. 177 | 178 | There are several special instructions you can use. See `?par_socket_server` for further details. The server can be configured to behave in a given way, and that configuration is persistent from one connection to the other for the same client. The function `par_socket_server()` allows to change or query the configuration. Its first argument is the name of the client on the server-side... but from the client, you don't necessarily know which name the server gave to you (one can connect several different clients at the same time, and default name is `sock` followed by Tcl name of the client socket connection). Using `<<>>` as a placeholder for this name circumvents the problem. `par_socket_server()` returns an environment that contains configuration variables. Here is the list of configuration item for us: 179 | 180 | ```{r pars1, eval=server_ready} 181 | cat(run_socket_server(con, 'ls(envir = svSocket::par_socket_server(<<>>))'), sep = "\n") 182 | ``` 183 | 184 | The `bare` item indicates if the server sends bare results, or also returns a prompt, acting more like a terminal. By default, it returns the bare results. Here is the current value: 185 | 186 | ```{r pars2, eval=server_ready} 187 | cat(run_socket_server(con, 'svSocket::par_socket_server(<<>>)$bare'), sep = "\n") 188 | ``` 189 | 190 | And here is how you can change it: 191 | 192 | ```{r bare-false, eval=server_ready} 193 | (run_socket_server(con, '\n<<>>svSocket::par_socket_server(<<>>, bare = FALSE)')) 194 | (run_socket_server(con, '1 + 1')) 195 | ``` 196 | 197 | When `bare = FALSE` the server issues the formatted command with a prompt (`:>` by default) and the result. For more information about {svSocket} server configuration, see `?par_socket_server`. There are also functions to manipulate the pool of clients and their configurations from the server-side, and the server can also send unattended data to client, see `?send_socket_clients`. finally, the workhorse function on the server-side is `process_socket_server()`. See `?process_socket_server` to learn more about it. You can provide your own process function to the server if you need to. 198 | 199 | ## Disconnection 200 | 201 | Don't forget to close the connection, once you have done using `close(con)`, and you can also close the server from the server-side by using `stop_socket_server()`. But never use it from the client-side because you will obviously break in the middle of the transaction. If you want to close the server from the client, you have to install a mechanisms that will nicely shut down the server *after* the transaction is processed. Here, we have installed such a mechanism by detecting the presence of a variable named `done` on the server-side. So: 202 | 203 | ```{r close, eval=server_ready} 204 | # Usually, the client does not stop the server, but it is possible here 205 | eval_socket_server(con, 'done <- NULL') # The server will stop after this transaction 206 | close(con) 207 | ``` 208 | 209 | You can also access the {svSocket} server from another language. There is a very basic example written in Tcl in the `/etc` subdirectory of the {svSocket} package. See the `ReadMe.txt` file there. The Tcl script `SimpleClient.tcl` implements a Tcl client in a few dozens of code lines. For other examples, you can inspect the code of [SciViews-K](https://github.com/SciViews/sciviewsk), and the code of [Tinn-R](https://tinn-r.org/en/) for a Pascal version. Writing clients in C, Java, Python, etc. should not be difficult if you inspire you from these examples. Finally, there is another implementation of a similar R server through HTTP in the {svHttp} package. 210 | --------------------------------------------------------------------------------