IRkernel/0000755000176200001440000000000014362573562012002 5ustar liggesusersIRkernel/NAMESPACE0000644000176200001440000000266314362570132013216 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(comm_manager) export(installspec) export(jupyter_option_defaults) export(log_debug) export(log_error) export(log_info) export(main) exportClasses(Comm) exportClasses(CommManager) import(digest) import(methods) import(uuid) importFrom(IRdisplay,prepare_mimebundle) importFrom(IRdisplay,publish_mimebundle) importFrom(crayon,blue) importFrom(crayon,green) importFrom(crayon,red) importFrom(evaluate,evaluate) importFrom(evaluate,flush_console) importFrom(evaluate,new_output_handler) importFrom(evaluate,parse_all) importFrom(grDevices,pdf) importFrom(grDevices,png) importFrom(jsonlite,fromJSON) importFrom(jsonlite,toJSON) importFrom(pbdZMQ,ZMQ.MC) importFrom(pbdZMQ,ZMQ.PO) importFrom(pbdZMQ,ZMQ.SO) importFrom(pbdZMQ,ZMQ.ST) importFrom(pbdZMQ,zmq.bind) importFrom(pbdZMQ,zmq.ctx.new) importFrom(pbdZMQ,zmq.getsockopt) importFrom(pbdZMQ,zmq.msg.recv) importFrom(pbdZMQ,zmq.msg.send) importFrom(pbdZMQ,zmq.poll) importFrom(pbdZMQ,zmq.poll.get.revents) importFrom(pbdZMQ,zmq.recv) importFrom(pbdZMQ,zmq.recv.multipart) importFrom(pbdZMQ,zmq.send) importFrom(pbdZMQ,zmq.send.multipart) importFrom(pbdZMQ,zmq.setsockopt) importFrom(pbdZMQ,zmq.socket) importFrom(repr,mime2repr) importFrom(repr,repr_option_defaults) importFrom(stats,setNames) importFrom(utils,capture.output) importFrom(utils,getFromNamespace) importFrom(utils,getS3method) importFrom(utils,head) importFrom(utils,str) importFrom(utils,tail) IRkernel/LICENSE0000644000176200001440000000006314362570132012774 0ustar liggesusersYEAR: 2014-2022 COPYRIGHT HOLDER: IRkernel authors IRkernel/README.md0000644000176200001440000000774614362570132013265 0ustar liggesusers# Native R kernel for Jupyter [![b-CI]][CI] [![b-CRAN]][CRAN] [b-CI]: https://github.com/IRkernel/IRkernel/actions/workflows/r.yml/badge.svg "Build status" [CI]: https://github.com/IRkernel/IRkernel/actions/workflows/r.yml [b-CRAN]: https://www.r-pkg.org/badges/version/IRkernel "Comprehensive R Archive Network" [CRAN]: https://cran.r-project.org/package=IRkernel For detailed requirements and install instructions see [irkernel.github.io](https://irkernel.github.io/) ## Requirements * [Jupyter](https://jupyter.org). * A current [R installation](https://www.R-project.org). ## Installation This package is available on CRAN: ```R install.packages('IRkernel') IRkernel::installspec() # to register the kernel in the current R installation jupyter labextension install @techrah/text-shortcuts # for RStudio’s shortcuts ``` Per default `IRkernel::installspec()` will install a kernel with the name “ir” and a display name of “R”. Multiple calls will overwrite the kernel with a kernel spec pointing to the last R interpreter you called that commands from. You can install kernels for multiple versions of R by supplying a `name` and `displayname` argument to the `installspec()` call (You still need to install these packages in all interpreters you want to run as a jupyter kernel!): ```r # in R 3.3 IRkernel::installspec(name = 'ir33', displayname = 'R 3.3') # in R 3.2 IRkernel::installspec(name = 'ir32', displayname = 'R 3.2') ``` By default, it installs the kernel per-user. To install system-wide, use `user = FALSE`. To install in the `sys.prefix` of the currently detected `jupyter` command line utility, use `sys_prefix = TRUE`. Now both R versions are available as an R kernel in the notebook. ### If you encounter problems during installation 1. Have a look at the [full installation instructions](https://irkernel.github.io/installation/)! 2. [Search the existing open and closed issues](https://github.com/IRkernel/IRkernel/issues?utf8=%E2%9C%93&q=is%3Aissue). 3. If you are sure that this is a new problem, [file an issue](https://github.com/IRkernel/IRkernel/issues/new). ## Running the notebook If you have Jupyter installed, you can create a notebook using IRkernel from the dropdown menu. You can also start other interfaces with an R kernel: ```bash # “ir” is the kernel name installed by the above `IRkernel::installspec()` # change if you used a different name! jupyter qtconsole --kernel=ir jupyter console --kernel=ir ``` ## Run a stable release in a Docker container Refer to the [jupyter/docker-stacks r-notebook](https://github.com/jupyter/docker-stacks/tree/master/r-notebook) repository If you have a Docker daemon running, e.g. reachable on localhost, start a container with: ```bash docker run -d -p 8888:8888 jupyter/r-notebook ``` Open localhost:8888 in your browser. All notebooks from your session will be saved in the current directory. On other platforms without docker, this can be started using `docker-machine` by replacing “localhost” with an IP from `docker-machine ip `. With the deprecated `boot2docker`, this IP will be `boot2docker ip`. ## Develop and run from source in a Docker container ```bash make docker_dev_image #builds dev image and installs IRkernel dependencies from github make docker_dev #mounts source, installs, and runs Jupyter notebook; docker_dev_image is a prerequisite make docker_test #builds the package from source then runs the tests via R CMD check; docker_dev_image is a prerequisite ``` ## How does it know where to install? The IRKernel does not have any Python dependencies whatsoever, and does not know anything about any other Jupyter/Python installations you may have. It only requires the `jupyter` command to be available on `$PATH`. To install the kernel, it prepares a kernelspec directory (containing `kernel.json` and so on), and passes it to the command line `jupyter kernelspec install [options] prepared_kernel_dir/`, where options such as `--name`, `--user`, `--prefix`, and `--sys-prefix` are given based on the options. IRkernel/man/0000755000176200001440000000000014362570132012543 5ustar liggesusersIRkernel/man/log.Rd0000644000176200001440000000110114362570132013604 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/logging.r \name{log} \alias{log} \alias{log_debug} \alias{log_info} \alias{log_error} \title{Kernel logging functions} \usage{ log_debug(...) log_info(...) log_error(...) } \arguments{ \item{...}{message to log} } \description{ A set of exported logging utilities that have the capability to be used in upstream projects. Log level and log file can be set via R package options e.g. \code{options(jupyter.log_level = 2L)} or from the environment variables JUPYTER_LOG_LEVEL and JUPYTER_LOGFILE. } IRkernel/man/installspec.Rd0000644000176200001440000000273314362570132015360 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/installspec.r \name{installspec} \alias{installspec} \title{Install the kernelspec to tell Jupyter about IRkernel.} \usage{ installspec( user = NULL, name = "ir", displayname = "R", rprofile = NULL, prefix = NULL, sys_prefix = NULL, verbose = getOption("verbose") ) } \arguments{ \item{user}{Install into user directory (\href{https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html}{\code{$XDG_DATA_HOME}}\code{/jupyter/kernels}) or globally? (default: NULL but treated as TRUE if "prefix" is not specified)} \item{name}{The name of the kernel (default "ir")} \item{displayname}{The name which is displayed in the notebook (default: "R")} \item{rprofile}{(optional) Path to kernel-specific Rprofile (defaults to system-level settings)} \item{prefix}{(optional) Path to alternate directory to install kernelspec into (default: NULL)} \item{sys_prefix}{(optional) Install kernelspec using the \code{--sys-prefix} option of the currently detected jupyter (default: NULL)} \item{verbose}{(optional) If \code{FALSE}, silence output of \code{install}} } \value{ Exit code of the \code{jupyter kernelspec install} call. } \description{ This can be called multiple times for different R interpreter, but you have to give a different name (and displayname to see a difference in the notebook UI). If the same name is give, it will overwrite older versions of the kernel spec with that name! } IRkernel/man/IRkernel-package.Rd0000644000176200001440000000356414362570132016146 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/options.r, R/help.r \docType{data} \name{jupyter_option_defaults} \alias{jupyter_option_defaults} \alias{IRkernel-package} \alias{IRkernel} \alias{IRkernel-options} \title{An R kernel for Jupyter.} \format{ An object of class \code{list} of length 7. } \usage{ jupyter_option_defaults } \description{ Jupyter speaks a JSON+ZMQ protocol to a 'kernel' which is responsible for executing code. This package is a kernel for the R language. } \section{Options}{ The following can be set/read via \code{options(opt.name = ...)} / \code{getOption('opt.name')} \describe{ \item{\code{jupyter.log_level}}{1L (errors), 2L (warnings), or 3L (debug). 1L is the default.} \item{\code{jupyter.pager_classes}}{Classes to use the pager for instead of displaying them inline. Default: help pages} \item{\code{jupyter.in_kernel}}{\code{TRUE} if this code is executed in a running kernel. Set to pretend being/not being in a kernel} \item{\code{jupyter.rich_display}}{Use more than just text display} \item{\code{jupyter.display_mimetypes}}{ The formats emitted when any return value is to be displayed (default: all mimetypes listed \href{http://ipython.org/ipython-doc/stable/api/generated/IPython.core.formatters.html#IPython.core.formatters.format_display_data}{here}) } \item{\code{jupyter.plot_mimetypes}}{ The plot formats emitted to the frontend when a plot is displayed. (default: image/png and application/pdf) } \item{\code{jupyter.plot_scale}}{ The ratio (notebook PPI / \code{repr.plot.res}). E.g.: With the defaults \code{repr.plot.res}=120 px/in (PPI) and \code{jupyter.plot_scale}=2, a 1in\eqn{\times}1in image will be displayed as a 0.5in\eqn{\times}0.5in, 240 PPI image. (default: 2, fit for retina displays) } } } \seealso{ \link{installspec} } \keyword{datasets} IRkernel/man/CommManager-class.Rd0000644000176200001440000000044214362570132016323 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/comm_manager.r \docType{class} \name{CommManager-class} \alias{CommManager-class} \alias{CommManager} \title{The CommManager} \description{ Has methods able to register comms/targets and process comm messages } IRkernel/man/main.Rd0000644000176200001440000000051614362570132013760 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/main.r \name{main} \alias{main} \title{Initialise and run the kernel} \usage{ main(connection_file = "") } \arguments{ \item{connection_file}{The path to the Jupyter connection file, written by the frontend} } \description{ Initialise and run the kernel } IRkernel/man/Comm-class.Rd0000644000176200001440000000037314362570132015033 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/comm_manager.r \docType{class} \name{Comm-class} \alias{Comm-class} \alias{Comm} \title{The Comm} \description{ Has methods able to register and handle message callbacks } IRkernel/man/comm_manager.Rd0000644000176200001440000000050614362570132015460 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/environment_runtime.r \name{comm_manager} \alias{comm_manager} \title{Get global CommManager instance} \usage{ comm_manager() } \value{ \link{CommManager} instance if a kernel is running, else NULL } \description{ Get global CommManager instance } IRkernel/DESCRIPTION0000644000176200001440000000326714362573562013520 0ustar liggesusersPackage: IRkernel Title: Native R Kernel for the 'Jupyter Notebook' Description: The R kernel for the 'Jupyter' environment executes R code which the front-end ('Jupyter Notebook' or other front-ends) submits to the kernel via the network. Version: 1.3.2 Authors@R: c( person('Thomas', 'Kluyver', role = c('aut', 'cph'), email = 'thomas@kluyver.me.uk'), person('Philipp', 'Angerer', role = c('aut', 'cph', 'cre'), email = 'phil.angerer@gmail.com', comment = c(ORCID = "0000-0002-0369-2888")), person('Jan', 'Schulz', role = c('aut', 'cph'), email = 'jasc@gmx.net'), person('Karthik', 'Ram', role = c('aut', 'cph'), email = 'karthik.ram@gmail.com')) URL: https://irkernel.github.io BugReports: https://github.com/IRkernel/IRkernel/issues/ Depends: R (>= 3.2.0) Suggests: testthat, roxygen2 SystemRequirements: jupyter, jupyter_kernel_test (Python package for testing) License: MIT + file LICENSE Encoding: UTF-8 Imports: repr (>= 0.4.99), methods, evaluate (>= 0.10), IRdisplay (>= 0.3.0.9999), pbdZMQ (>= 0.2-1), crayon, jsonlite (>= 0.9.6), uuid, digest Collate: 'class_unions.r' 'logging.r' 'comm_manager.r' 'compat.r' 'completion.r' 'environment_runtime.r' 'environment_shadow.r' 'options.r' 'execution.r' 'handlers.r' 'help.r' 'installspec.r' 'utils.r' 'kernel.r' 'main.r' 'onload.r' RoxygenNote: 7.2.3 NeedsCompilation: no Packaged: 2023-01-20 19:55:04 UTC; phil Author: Thomas Kluyver [aut, cph], Philipp Angerer [aut, cph, cre] (), Jan Schulz [aut, cph], Karthik Ram [aut, cph] Maintainer: Philipp Angerer Repository: CRAN Date/Publication: 2023-01-20 20:20:02 UTC IRkernel/tests/0000755000176200001440000000000014362570132013132 5ustar liggesusersIRkernel/tests/testthat/0000755000176200001440000000000014362573562015004 5ustar liggesusersIRkernel/tests/testthat/test-options.r0000644000176200001440000000105314362570132017624 0ustar liggesuserscontext('default options') test_that('default options are set', { expect_true(getOption('jupyter.rich_display')) expect_equal(getOption('jupyter.log_level'), 1L) expect_equal(getOption('jupyter.logfile'), NA) expect_equal(getOption('jupyter.pager_classes'), c( 'packageIQR', 'help_files_with_topic')) expect_equal(getOption('jupyter.plot_mimetypes'), c( 'text/plain', 'image/png')) # this is not a kernel itself, only the loaded package! expect_equal(getOption('jupyter.in_kernel'), FALSE) }) IRkernel/tests/testthat/njr/0000755000176200001440000000000014362570630015566 5ustar liggesusersIRkernel/tests/testthat/njr/ndjson_testrunner.py0000644000176200001440000000632414362570133021727 0ustar liggesusersimport sys import io import json import unittest import warnings from collections import OrderedDict from typing import Optional, Dict, Any from contextlib import contextmanager __all__ = ['JSONTestResult', 'JSONTestRunner'] class JSONTestResult(unittest.TestResult): def __init__(self, stream, failfast, buffer, tb_locals): super().__init__(stream) self.stream = stream self.failfast = failfast self.buffer = buffer self.tb_locals = tb_locals def test_run(self, test): self.startTestRun() try: test(self) finally: self.stopTestRun() def result_to_dict(self, type_: str, test: unittest.TestCase, err: Optional[Any]=None) -> Dict[str, Optional[str]]: msg = None if isinstance(err, tuple) and len(err) == 3: msg = self._exc_info_to_string(err, test) elif err: msg = str(err) return OrderedDict([ ('type', type_), ('id', test.id()), ('desc', test.shortDescription()), # May be None ('msg', msg), ]) def write_result(self, typ_: str, test: unittest.TestCase, err: Optional[Any]=None): json.dump(self.result_to_dict(typ_, test, err), self.stream, separators=(',', ':')) self.stream.write('\n') def addSuccess(self, test): super().addSuccess(test) self.write_result('success', test) def addExpectedFailure(self, test, err): super().addExpectedFailure(test, err) self.write_result('expected_failure', test, err) def addFailure(self, test, err): super().addFailure(test, err) self.write_result('failure', test, err) def addError(self, test, err): super().addError(test, err) self.write_result('error', test, err) def addUnexpectedSuccess(self, test): super().addUnexpectedSuccess(test) self.write_result('unexpected_success', test) def addSkip(self, test, reason): super().addSkip(test, reason) self.write_result('skip', test, reason) def addSubTest(self, test, subtest, err): super().addSubTest(test, subtest, err) if err is None: self.write_result('success', subtest) elif issubclass(err[0], test.failureException): self.write_result('failure', subtest, err) else: self.write_result('error', subtest, err) class JSONTestRunner: """TODO""" def __init__( self, stream: io.TextIOBase=None, failfast: bool=False, buffer: bool=False, warnings: Optional[str]=None, *, tb_locals: bool=False ): """Construct a JSONTestRunner.""" self.stream = sys.stdout if stream is None else stream self.failfast = failfast self.buffer = buffer self.tb_locals = tb_locals self.warnings = warnings @contextmanager def filter_warnings(self): """Install own warnings filter for the context""" with warnings.catch_warnings(): if self.warnings: warnings.simplefilter(self.warnings) if self.warnings in ['default', 'always']: warnings.filterwarnings('module', category=DeprecationWarning, message=r'Please use assert\w+ instead.') yield def run(self, test): """Run the given test case or test suite.""" result = JSONTestResult(self.stream, self.failfast, self.buffer, self.tb_locals) unittest.registerResult(result) with self.filter_warnings(): result.test_run(test) return result if __name__ == '__main__': module = sys.argv[1] sys.argv[1:] = sys.argv[2:] unittest.main(module, testRunner=JSONTestRunner) IRkernel/tests/testthat/test_ir.py0000644000176200001440000003316614362570132017026 0ustar liggesusers# Test manually via # IR_KERNEL_NAME=ir python3 -m test_ir -k some_test import os import sys from pathlib import Path HERE = Path(__file__).parent sys.path.insert(0, str(HERE / 'jkt')) import unittest import jupyter_kernel_test as jkt from jupyter_client.manager import start_new_kernel from jupyter_kernel_test import validate_message without_rich_display = '''\ options(jupyter.rich_display = FALSE) {} options(jupyter.rich_display = TRUE) ''' #this will not work! #withr::with_options(list(jupyter.rich_display = FALSE), {}) TIMEOUT = 15 class IRkernelTests(jkt.KernelTests): kernel_name = os.environ.get('IR_KERNEL_NAME', 'testir') language_name = 'R' def _execute_code(self, code, tests=True, silent=False, store_history=True): self.flush_channels() reply, output_msgs = self.execute_helper(code, silent=silent, store_history=store_history) self.assertEqual(reply['content']['status'], 'ok', '{0}: {0}'.format(reply['content'].get('ename'), reply['content'].get('evalue'))) if tests: self.assertGreaterEqual(len(output_msgs), 1) # the irkernel only sends display_data, not execute_results self.assertEqual(output_msgs[0]['msg_type'], 'display_data') return reply, output_msgs code_hello_world = 'print("hello, world")' completion_samples = [ {'text': 'zi', 'matches': {'zip'}}, {'text': 'seq_len(', 'matches': {'length.out = '}}, {'text': 'base::transform(', 'matches': {'`_data` = ', '...'}}, {'text': 'foo(R_system', 'matches': {'R_system_version'}}, {'text': 'version$plat', 'matches': {'version$platform'}}, {'text': 'stats4::AIC@def', 'matches': {'stats4::AIC@default'}}, {'text': 'stats4::AIC@default@ta', 'matches': {'stats4::AIC@default@target'}}, {'text': 'grDevice', 'matches': {'grDevices::'}}, {'text': 'base::abbrev', 'matches': {'base::abbreviate'}}, {'text': 'base::.rowNamesD', 'matches': {'base::`.rowNamesDF<-`'}}, {'text': 'repr:::repr_png.def', 'matches': {'repr:::repr_png.default'}}, {'text': 'repr::format2repr$mark', 'matches': {'repr::format2repr$markdown'}}, {'text': 'load("test_i', 'matches': {'test_ir.py'}}, {'text': 'load("./test_', 'matches': {'./test_utils.r', './test_kernel.r', './test_ir.py'}}, {'text': '.Last.v', 'matches': {'.Last.value'}}, ] complete_code_samples = ['1', 'print("hello, world")', 'f <- function(x) {\n x*2\n}'] incomplete_code_samples = ['print("hello', 'f <- function(x) {\n x*2'] code_display_data = [ {'code': '"a"', 'mime': 'text/plain'}, {'code': '"a"', 'mime': 'text/html'}, {'code': '"a"', 'mime': 'text/latex'}, {'code': '1:3', 'mime': 'text/plain'}, {'code': '1:3', 'mime': 'text/html'}, {'code': '1:3', 'mime': 'text/latex'}, {'code': without_rich_display.format('"a"'), 'mime': 'text/plain'}, {'code': without_rich_display.format('1:3'), 'mime': 'text/plain'}, ] def test_display_vector(self): """display of vectors""" code = '1:3' reply, output_msgs = self._execute_code(code) # we currently send those formats: text/plain, text/html, text/latex, and text/markdown data = output_msgs[0]['content']['data'] self.assertEqual(len(data), 4, data.keys()) self.assertEqual(data['text/plain'], '[1] 1 2 3') self.assertIn('text/html', data) # this should not be a test of the repr functionality... self.assertIn('', output_msgs[0]['content']['data']['text/html']) self.assertIn('text/latex', output_msgs[0]['content']['data']) self.assertIn('text/markdown', output_msgs[0]['content']['data']) def test_display_vector_only_plaintext(self): """display of plain text vectors""" code = without_rich_display.format('1:3') reply, output_msgs = self._execute_code(code) data = output_msgs[0]['content']['data'] self.assertEqual(len(data), 1, data.keys()) self.assertEqual(data['text/plain'], '[1] 1 2 3') def test_irkernel_plots(self): """plotting""" code = 'plot(1:3)' reply, output_msgs = self._execute_code(code) # we currently send two formats: png, and text/plain data = output_msgs[0]['content']['data'] self.assertEqual(len(data), 2, data.keys()) self.assertEqual(data['text/plain'], 'plot without title') self.assertIn('image/png', data) # we send image dimensions metadata = output_msgs[0]['content']['metadata'] self.assertEqual(len(metadata), 1, metadata.keys()) self.assertIn('image/png', metadata) self.assertEqual(metadata['image/png'], dict(width=420, height=420), metadata['image/png']) def test_irkernel_plots_only_PNG(self): """plotting PNG""" # the reset needs to happen in another execute because plots are sent after either # the next plot is opened or everything is executed, not at the time when plot # command is actually happening. # (we have a similar problem with width/... but we worked around it by setting the # appropriate options to the recorderdplot object) code = '''\ old_options <- options(jupyter.plot_mimetypes = c('image/png')) plot(1:3) ''' reply, output_msgs = self._execute_code(code) # Only png, no svg or plain/text data = output_msgs[0]['content']['data'] self.assertEqual(len(data), 1, data.keys()) self.assertIn('image/png', data) # nothing in metadata metadata = output_msgs[0]['content']['metadata'] self.assertEqual(len(metadata), 1, metadata.keys()) self.assertIn('image/png', metadata) self.assertEqual(metadata['image/png'], dict(width=420, height=420), metadata['image/png']) # And reset code = 'options(old_options)' reply, output_msgs = self._execute_code(code, tests=False) def test_irkernel_plots_only_SVG(self): # again the reset dance (see PNG) code = '''\ old_options <- options(jupyter.plot_mimetypes = c('image/svg+xml')) plot(1:3) ''' reply, output_msgs = self._execute_code(code) # Only svg, no png or plain/text data = output_msgs[0]['content']['data'] self.assertEqual(len(data), 1, data.keys()) self.assertIn('image/svg+xml', data) # svg output is currently isolated metadata = output_msgs[0]['content']['metadata'] self.assertEqual(len(metadata), 1, metadata.keys()) self.assertEqual(len(metadata['image/svg+xml']), 3) self.assertEqual(metadata['image/svg+xml'], dict(width=420, height=420, isolated=True), metadata['image/svg+xml']) # And reset code = 'options(old_options)' reply, output_msgs = self._execute_code(code, tests=False) def test_irkernel_plots_without_rich_display(self): code = '''\ options(jupyter.rich_display = FALSE) plot(1:3) ''' reply, output_msgs = self._execute_code(code) # Even with rich output as false, we send plots data = output_msgs[0]['content']['data'] self.assertEqual(len(data), 2, data.keys()) self.assertEqual(data['text/plain'], 'plot without title') self.assertIn('image/png', data) # And reset code = 'options(jupyter.rich_display = TRUE)' reply, output_msgs = self._execute_code(code, tests=False) def test_irkernel_df_default_rich_output(self): """data.frame rich representation""" code = 'data.frame(x = 1:3)' reply, output_msgs = self._execute_code(code) # we currently send three formats: text/plain, html, and latex data = output_msgs[0]['content']['data'] self.assertEqual(len(data), 4, data.keys()) def test_irkernel_df_no_rich_output(self): """data.frame plain representation""" code = ''' options(jupyter.rich_display = FALSE) data.frame(x = 1:3) options(jupyter.rich_display = TRUE) ''' reply, output_msgs = self._execute_code(code) # only text/plain data = output_msgs[0]['content']['data'] self.assertEqual(len(data), 1, data.keys()) def test_html_isolated(self): """HTML isolation""" code = ''' repr_html.full_page <- function(obj) sprintf('%s', obj) structure(0, class = 'full_page') ''' reply, output_msgs = self._execute_code(code) data = output_msgs[0]['content']['data'] self.assertEqual(len(data), 2, data.keys()) self.assertEqual(data['text/html'], '0') metadata = output_msgs[0]['content']['metadata'] self.assertEqual(len(metadata), 1, metadata.keys()) self.assertEqual(len(metadata['text/html']), 1, metadata['text/html'].keys()) self.assertEqual(metadata['text/html']['isolated'], True) def test_in_kernel_set(self): """jupyter.in_kernel option""" reply, output_msgs = self._execute_code('getOption("jupyter.in_kernel")') data = output_msgs[0]['content']['data'] self.assertGreaterEqual(len(data), 1, data.keys()) self.assertEqual(data['text/plain'], '[1] TRUE', data.keys()) def test_warning_message(self): self.flush_channels() reply, output_msgs = self.execute_helper('options(warn=1); warning(simpleWarning("wmsg"))') self.assertEqual(output_msgs[0]['msg_type'], 'stream') self.assertEqual(output_msgs[0]['content']['name'], 'stderr') self.assertEqual(output_msgs[0]['content']['text'].strip(), 'Warning message:\n“wmsg”') self.flush_channels() reply, output_msgs = self.execute_helper('options(warn=1); f <- function() warning("wmsg"); f()') self.assertEqual(output_msgs[0]['msg_type'], 'stream') self.assertEqual(output_msgs[0]['content']['name'], 'stderr') self.assertEqual(output_msgs[0]['content']['text'].strip(), 'Warning message in f():\n“wmsg”') def test_should_increment_history(self): """properly increments execution history""" code = 'data.frame(x = 1:3)' reply, output_msgs = self._execute_code(code) reply2, output_msgs2 = self._execute_code(code) execution_count_1 = reply['content']['execution_count'] execution_count_2 = reply2['content']['execution_count'] self.assertEqual(execution_count_1 + 1, execution_count_2) def test_should_not_increment_history(self): """Does not increment history if silent is true or store_history is false""" code = 'data.frame(x = 1:3)' reply, output_msgs = self._execute_code(code, store_history=False) reply2, output_msgs2 = self._execute_code(code, store_history=False) reply3, output_msgs3 = self._execute_code(code, tests=False, silent=True) execution_count_1 = reply['content']['execution_count'] execution_count_2 = reply2['content']['execution_count'] execution_count_3 = reply3['content']['execution_count'] self.assertEqual(execution_count_1, execution_count_2) self.assertEqual(execution_count_1, execution_count_3) def test_irkernel_inspects(self): """Test if object inspection works.""" self.flush_channels() def test_token_is_ok(token, preprocess=None, postprocess=None): """Check if inspect_request for the `token` returns a reply. Run code in `preprocess` before requesting if it's given, and `proprocess` after requesting. Currently just test if the kernel replys without an error and not care about its content. Because the contents of inspections are still so arguable. When the requirements for the contents are decided, fix the tests beow and check the contents. """ if preprocess: self._execute_code(preprocess, tests=False) msg_id = self.kc.inspect(token) reply = self.kc.get_shell_msg(timeout=TIMEOUT) validate_message(reply, 'inspect_reply', msg_id) self.assertEqual(reply['content']['status'], 'ok') self.assertTrue(reply['content']['found']) self.assertGreaterEqual(len(reply['content']['data']), 1) if postprocess: self._execute_code(postprocess, tests=False) # Numeric constant test_token_is_ok('1') # Reserved word # test_token_is_ok('NULL') # null is not an object anymore? # Dataset with a help document test_token_is_ok('iris') # Function with a help document test_token_is_ok('c') # Function name with namespace test_token_is_ok('base::c') # Function not exported from namespace test_token_is_ok('tools:::.Rd2pdf') # User-defined variable test_token_is_ok( 'x', preprocess='x <- 1', postprocess='rm("x")' ) # User-defined function test_token_is_ok( 'f', preprocess='f <- function (x) x + x', postprocess='rm("f")' ) # Object which masks other object in workspace test_token_is_ok( 'c', preprocess='c <- function (x) x + x', postprocess='rm("c")' ) if __name__ == '__main__': unittest.main(verbosity=2) IRkernel/tests/testthat/test_utils.r0000644000176200001440000000120114362570132017346 0ustar liggesuserscontext('utils') library(evaluate) test_that('skip_repeated works', { stack <- c('foo()', 'f()', 'f()', 'f()', 'f()', 'bar()') expect_equal(skip_repeated(stack), c('foo()', 'f()', ellip_h, 'f()', 'bar()')) }) test_that('skip_repeated does not skip three or less consecutive items', { stack <- c('foo()', 'f()', 'f()', 'f()', 'bar()') expect_equal(skip_repeated(stack), stack) }) test_that('skip_repeated works on tracebacks', { err <- try_capture_stack(quote({ f <- function(x) stop(x) f(1) }), new.env()) skipped_stack <- skip_repeated(err$calls) expect_is(skipped_stack[[1]], 'call') }) IRkernel/tests/testthat/jkt/0000755000176200001440000000000014362570630015565 5ustar liggesusersIRkernel/tests/testthat/jkt/pyproject.toml0000644000176200001440000000072214362570133020500 0ustar liggesusers[build-system] requires = ["setuptools", "jupyter_client", "jsonschema"] build-backend = "setuptools.build_meta" [tool.jupyter-releaser] skip = ["check-links"] [tool.tbump.version] current = "0.4.3" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? ''' [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" [[tool.tbump.file]] src = "jupyter_kernel_test/__init__.py" IRkernel/tests/testthat/jkt/jupyter_kernel_test/0000755000176200001440000000000014362570133021664 5ustar liggesusersIRkernel/tests/testthat/jkt/jupyter_kernel_test/__init__.py0000644000176200001440000003302014362570133023773 0ustar liggesusers"""Machinery for testing Jupyter kernels via the messaging protocol. """ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from time import time from unittest import TestCase, SkipTest from queue import Empty from jupyter_client.manager import start_new_kernel from jupyter_client.utils import run_sync from .msgspec_v5 import validate_message TIMEOUT = 15 __version__ = '0.4.3' class KernelTests(TestCase): kernel_name = "python3" @classmethod def setUpClass(cls): cls.km, cls.kc = start_new_kernel(kernel_name=cls.kernel_name) @classmethod def tearDownClass(cls): cls.kc.stop_channels() cls.km.shutdown_kernel() def flush_channels(self): for channel in (self.kc.shell_channel, self.kc.iopub_channel): while True: try: msg = run_sync(channel.get_msg)(timeout=0.1) except Empty: break else: validate_message(msg) language_name = "" file_extension = "" def test_kernel_info(self): self.flush_channels() msg_id = self.kc.kernel_info() reply = self.kc.get_shell_msg(timeout=TIMEOUT) validate_message(reply, 'kernel_info_reply', msg_id) if self.language_name: self.assertEqual(reply['content']['language_info']['name'], self.language_name) if self.file_extension: self.assertEqual(reply['content']['language_info']['file_extension'], self.file_extension) self.assertTrue(reply['content']['language_info']['file_extension'].startswith(".")) def execute_helper(self, code, timeout=TIMEOUT, silent=False, store_history=True, stop_on_error=True): msg_id = self.kc.execute(code=code, silent=silent, store_history=store_history, stop_on_error=stop_on_error) reply = self.get_non_kernel_info_reply(timeout=timeout) validate_message(reply, 'execute_reply', msg_id) busy_msg = run_sync(self.kc.iopub_channel.get_msg)(timeout=1) validate_message(busy_msg, 'status', msg_id) self.assertEqual(busy_msg['content']['execution_state'], 'busy') output_msgs = [] while True: msg = run_sync(self.kc.iopub_channel.get_msg)(timeout=0.1) validate_message(msg, msg['msg_type'], msg_id) if msg['msg_type'] == 'status': self.assertEqual(msg['content']['execution_state'], 'idle') break elif msg['msg_type'] == 'execute_input': self.assertEqual(msg['content']['code'], code) continue output_msgs.append(msg) return reply, output_msgs code_hello_world = "" def test_execute_stdout(self): if not self.code_hello_world: raise SkipTest self.flush_channels() reply, output_msgs = self.execute_helper(code=self.code_hello_world) self.assertEqual(reply['content']['status'], 'ok') self.assertGreaterEqual(len(output_msgs), 1) for msg in output_msgs: if (msg['msg_type'] == 'stream') and (msg['content']['name'] == 'stdout'): self.assertIn('hello, world', msg['content']['text']) break else: self.assertTrue(False, "Expected one output message of type 'stream' and 'content.name'='stdout'") code_stderr = "" def test_execute_stderr(self): if not self.code_stderr: raise SkipTest self.flush_channels() reply, output_msgs = self.execute_helper(code=self.code_stderr) self.assertEqual(reply['content']['status'], 'ok') self.assertGreaterEqual(len(output_msgs), 1) for msg in output_msgs: if (msg['msg_type'] == 'stream') and (msg['content']['name'] == 'stderr'): break else: self.assertTrue(False, "Expected one output message of type 'stream' and 'content.name'='stderr'") completion_samples = [] def get_non_kernel_info_reply(self, timeout=None): while True: reply = self.kc.get_shell_msg(timeout=timeout) if reply['header']['msg_type'] != 'kernel_info_reply': return reply def test_completion(self): if not self.completion_samples: raise SkipTest for sample in self.completion_samples: with self.subTest(text=sample['text']): msg_id = self.kc.complete(sample['text']) reply = self.get_non_kernel_info_reply() validate_message(reply, 'complete_reply', msg_id) if 'matches' in sample: self.assertEqual(set(reply['content']['matches']), set(sample['matches'])) complete_code_samples = [] incomplete_code_samples = [] invalid_code_samples = [] def check_is_complete(self, sample, status): msg_id = self.kc.is_complete(sample) reply = self.get_non_kernel_info_reply() validate_message(reply, 'is_complete_reply', msg_id) if reply['content']['status'] != status: msg = "For code sample\n {!r}\nExpected {!r}, got {!r}." raise AssertionError(msg.format(sample, status, reply['content']['status'])) def test_is_complete(self): if not (self.complete_code_samples or self.incomplete_code_samples or self.invalid_code_samples): raise SkipTest self.flush_channels() with self.subTest(status="complete"): for sample in self.complete_code_samples: self.check_is_complete(sample, 'complete') with self.subTest(status="incomplete"): for sample in self.incomplete_code_samples: self.check_is_complete(sample, 'incomplete') with self.subTest(status="invalid"): for sample in self.invalid_code_samples: self.check_is_complete(sample, 'invalid') code_page_something = "" def test_pager(self): if not self.code_page_something: raise SkipTest self.flush_channels() reply, output_msgs = self.execute_helper(self.code_page_something) self.assertEqual(reply['content']['status'], 'ok') payloads = reply['content']['payload'] self.assertEqual(len(payloads), 1) self.assertEqual(payloads[0]['source'], 'page') mimebundle = payloads[0]['data'] self.assertIn('text/plain', mimebundle) code_generate_error = "" def test_error(self): if not self.code_generate_error: raise SkipTest self.flush_channels() reply, output_msgs = self.execute_helper(self.code_generate_error) self.assertEqual(reply['content']['status'], 'error') self.assertEqual(len(output_msgs), 1) self.assertEqual(output_msgs[0]['msg_type'], 'error') code_execute_result = [] def test_execute_result(self): if not self.code_execute_result: raise SkipTest for sample in self.code_execute_result: with self.subTest(code=sample['code']): self.flush_channels() reply, output_msgs = self.execute_helper(sample['code']) self.assertEqual(reply['content']['status'], 'ok') self.assertGreaterEqual(len(output_msgs), 1) found = False for msg in output_msgs: if msg['msg_type'] == 'execute_result': found = True else: continue self.assertIn('text/plain', msg['content']['data']) self.assertEqual(msg['content']['data']['text/plain'], sample['result']) assert found, 'execute_result message not found' code_display_data = [] def test_display_data(self): if not self.code_display_data: raise SkipTest for sample in self.code_display_data: with self.subTest(code=sample['code']): self.flush_channels() reply, output_msgs = self.execute_helper(sample['code']) self.assertEqual(reply['content']['status'], 'ok') self.assertGreaterEqual(len(output_msgs), 1) found = False for msg in output_msgs: if msg['msg_type'] == 'display_data': found = True else: continue self.assertIn(sample['mime'], msg['content']['data']) assert found, 'display_data message not found' # this should match one of the values in code_execute_result code_history_pattern = "" supported_history_operations = () def history_helper(self, execute_first, timeout=TIMEOUT, **histargs): self.flush_channels() for code in execute_first: reply, output_msgs = self.execute_helper(code) self.flush_channels() msg_id = self.kc.history(**histargs) reply = self.get_non_kernel_info_reply(timeout=timeout) validate_message(reply, 'history_reply', msg_id) return reply def test_history(self): if not self.code_execute_result: raise SkipTest codes = [s['code'] for s in self.code_execute_result] results = [s['result'] for s in self.code_execute_result] n = len(codes) session = start = None with self.subTest(hist_access_type="tail"): if 'tail' not in self.supported_history_operations: raise SkipTest reply = self.history_helper(codes, output=False, raw=True, hist_access_type="tail", n=n) self.assertEqual(len(reply['content']['history']), n) self.assertEqual(len(reply['content']['history'][0]), 3) self.assertEqual(codes, [h[2] for h in reply['content']['history']]) session, start = reply['content']['history'][0][0:2] with self.subTest(output=True): reply = self.history_helper(codes, output=True, raw=True, hist_access_type="tail", n=n) self.assertEqual(len(reply['content']['history'][0][2]), 2) with self.subTest(hist_access_type="range"): if 'range' not in self.supported_history_operations: raise SkipTest if session is None: raise SkipTest reply = self.history_helper(codes, output=False, raw=True, hist_access_type="range", session=session, start=start, stop=start+1) self.assertEqual(len(reply['content']['history']), 1) self.assertEqual(reply['content']['history'][0][0], session) self.assertEqual(reply['content']['history'][0][1], start) with self.subTest(hist_access_type="search"): if not self.code_history_pattern: raise SkipTest if 'search' not in self.supported_history_operations: raise SkipTest with self.subTest(subsearch="normal"): reply = self.history_helper(codes, output=False, raw=True, hist_access_type="search", pattern=self.code_history_pattern) self.assertGreaterEqual(len(reply['content']['history']), 1) with self.subTest(subsearch="unique"): reply = self.history_helper(codes, output=False, raw=True, hist_access_type="search", pattern=self.code_history_pattern, unique=True) self.assertEqual(len(reply['content']['history']), 1) with self.subTest(subsearch="n"): reply = self.history_helper(codes, output=False, raw=True, hist_access_type="search", pattern=self.code_history_pattern, n=3) self.assertEqual(len(reply['content']['history']), 3) code_inspect_sample = "" def test_inspect(self): if not self.code_inspect_sample: raise SkipTest self.flush_channels() msg_id = self.kc.inspect(self.code_inspect_sample) reply = self.get_non_kernel_info_reply(timeout=TIMEOUT) validate_message(reply, 'inspect_reply', msg_id) self.assertEqual(reply['content']['status'], 'ok') self.assertTrue(reply['content']['found']) self.assertGreaterEqual(len(reply['content']['data']), 1) code_clear_output = "" def test_clear_output(self): if not self.code_clear_output: raise SkipTest self.flush_channels() reply, output_msgs = self.execute_helper(code=self.code_clear_output) self.assertEqual(reply['content']['status'], 'ok') self.assertGreaterEqual(len(output_msgs), 1) found = False for msg in output_msgs: if msg['msg_type'] == 'clear_output': found = True else: continue assert found, 'clear_output message not found' IRkernel/tests/testthat/jkt/jupyter_kernel_test/msgspec_v5.py0000644000176200001440000002647514362570133024327 0ustar liggesusers"""Message schemas for message spec version 5""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from jsonschema import Draft4Validator, ValidationError import re protocol_version = (5, 1) # These fragments will be wrapped in the boilerplate for a valid JSON schema. # We also add a default 'required' containing all keys. schema_fragments = {} def get_msg_content_validator(msg_type, version_minor): frag = schema_fragments[msg_type] schema = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "{} message contents schema".format(msg_type), "type": "object", "properties": {}, "additionalProperties": version_minor > protocol_version[1], } schema.update(frag) if "required" not in schema: # Require all keys by default schema["required"] = sorted(schema["properties"].keys()) return Draft4Validator(schema) header_part = {"type": "object", "properties": { "msg_id": {"type": "string"}, "username": {"type": "string"}, "session": {"type": "string"}, # TODO - this is parsed to a datetime before we get it: "date": {}, #{"type": "string"}, "msg_type": {"type": "string"}, "version": {"type": "string"}, }, "required": ["msg_id", "username", "session", "date", "msg_type", "version"]} msg_schema = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Jupyter message structure schema", "type": "object", "properties": { "header": header_part, "parent_header": {"type": "object"}, "metadata": {"type": "object"}, "content": {"type": "object"}, # Checked separately "buffers": {"type": "array"} }, "required": ["header", "parent_header", "metadata", "content"], } msg_structure_validator = Draft4Validator(msg_schema) def get_error_reply_validator(version_minor): return Draft4Validator({ "$schema": "http://json-schema.org/draft-04/schema#", "description": "Jupyter 'error' reply schema", "type": "object", "properties": { "status": {"const": "error"}, "ename": {"type": "string"}, "evalue": {"type": "string"}, "traceback": {"type": "array", "items": {"type": "string"}}, }, "required": ["status", "ename", "evalue", "traceback"], "additionalProperties": version_minor > protocol_version[1] }) def get_abort_reply_validator(version_minor): return Draft4Validator({ "$schema": "http://json-schema.org/draft-04/schema#", "description": "Jupyter 'abort' reply schema", "type": "object", "properties": { "status": {"const": "error"}, "ename": {"type": "string"}, "evalue": {"type": "string"}, "traceback": {"type": "list", "items": {"type": "string"}}, }, "required": ["status", "ename", "evalue", "traceback"], "additionalProperties": version_minor > protocol_version[1] }) reply_msgs_using_status = { 'execute_reply', 'inspect_reply', 'complete_reply', 'history_reply', 'connect_reply', 'comm_info_reply', 'kernel_info_reply', 'shutdown_reply', 'interrupt_reply', } def validate_message(msg, msg_type=None, parent_id=None): msg_structure_validator.validate(msg) msg_version_s = msg['header']['version'] m = re.match(r'(\d+)\.(\d+)', msg_version_s) if not m: raise ValidationError("Version {} not like 'x.y'") version_minor = int(m.group(2)) if msg_type is not None: if msg['header']['msg_type'] != msg_type: raise ValidationError("Message type {!r} != {!r}".format( msg['header']['msg_type'], msg_type )) else: msg_type = msg['header']['msg_type'] # Check for unexpected fields, unless it's a newer protocol version if version_minor <= protocol_version[1]: unx_top = set(msg) - set(msg_schema['properties']) if unx_top: raise ValidationError("Unexpected keys: {}".format(unx_top)) unx_header = set(msg['header']) - set(header_part['properties']) if unx_header: raise ValidationError("Unexpected keys in header: {}".format(unx_header)) # Check the parent id if 'reply' in msg_type and parent_id and msg['parent_header']['msg_id'] != parent_id: raise ValidationError("Parent header does not match expected") if msg_type in reply_msgs_using_status: # Most _reply messages have common 'error' and 'abort' structures try: status = msg['content']['status'] except KeyError as e: raise ValidationError(str(e)) if status == 'error': content_vdor = get_error_reply_validator(version_minor) elif status == 'abort': content_vdor = get_abort_reply_validator(version_minor) elif status == 'ok': content_vdor = get_msg_content_validator(msg_type, version_minor) else: raise ValidationError( "status {!r} should be ok/error/abort".format(status)) else: content_vdor = get_msg_content_validator(msg_type, version_minor) content_vdor.validate(msg['content']) # Shell messages ---------------------------------------------- schema_fragments['execute_request'] = {"properties": { "code": {"type": "string"}, "silent": {"type": "boolean"}, "store_history": {"type": "boolean"}, "user_expressions": {"type": "object"}, "allow_stdin": {"type": "boolean"}, "stop_on_error": {"type": "boolean"} }} schema_fragments['execute_reply'] = {"properties": { # statuses 'error' and 'abort' change the structure, so check separately "status": {"const": "ok"}, "execution_count": {"type": "number"}, "payload": {"type": "array", "items": { "type": "object", "properties": {"source": {"type": "string"}}, "additionalProperties": True, }}, "user_expressions": {"type": "object"}, }, "required": ["status", "execution_count"]} schema_fragments['inspect_request'] = {"properties": { "code": {"type": "string"}, "cursor_pos": {"type": "number"}, "detail_level": {"enum": [0, 1]}, }} schema_fragments['inspect_reply'] = {"properties": { # statuses 'error' and 'abort' change the structure, so check separately "status": {"const": "ok"}, "found": {"type": "boolean"}, "data": {"type": "object"}, "metadata": {"type": "object"}, }} schema_fragments['complete_request'] = {"properties": { "code": {"type": "string"}, "cursor_pos": {"type": "number"}, }} schema_fragments['complete_reply'] = {"properties": { # statuses 'error' and 'abort' change the structure, so check separately "status": {"const": "ok"}, "matches": {"type": "array", "items": {"type": "string"}}, "cursor_start": {"type": "number"}, "cursor_end": {"type": "number"}, "metadata": {"type": "object"}, }} schema_fragments['history_request'] = {"properties": { 'output' : {"type": "boolean"}, 'raw' : {"type": "boolean"}, 'hist_access_type' : {"enum": ["range", "tail", "search"]}, 'session' : {"type": "number"}, 'start' : {"type": "number"}, 'stop' : {"type": "number"}, 'n' : {"type": "number"}, 'pattern' : {"type": "string"}, 'unique' : {"type": "boolean"}, }, "required": ["output", "raw", "hist_access_type"]} schema_fragments['history_reply'] = {"properties": { "status": {"const": "ok"}, "history": {"type": "array", "items": { "minItems": 3, "maxItems": 3 }} }} schema_fragments['is_complete_request'] = {"properties": { "code": {"type": "string"}, }} schema_fragments['is_complete_reply'] = {"properties": { "status": {"enum": ["complete", "incomplete", "invalid", "unknown"]}, "indent": {"type": "string"} }, "required": ["status"]} # NB connect_request is deprecated schema_fragments["connect_request"] = {"properties": {}} schema_fragments["connect_reply"] = {"properties": { "shell_port": {"type": "number"}, "iopub_port": {"type": "number"}, "stdin_port": {"type": "number"}, "hb_port": {"type": "number"}, "control_port": {"type": "number"}, }} schema_fragments["comm_info_request"] = {"properties": { "target_name": {"type": "string"}, }, "required": []} schema_fragments["comm_info_reply"] = {"properties": { # statuses 'error' and 'abort' change the structure, so check separately "status": {"const": "ok"}, "comms": {"type": "object"}, }} schema_fragments["kernel_info_request"] = {"properties": {}} schema_fragments["kernel_info_reply"] = {"properties": { # statuses 'error' and 'abort' change the structure, so check separately "status": {"const": "ok"}, "protocol_version": {"type": "string"}, "implementation": {"type": "string"}, "implementation_version": {"type": "string"}, "language_info": {"type": "object"}, "banner": {"type": "string"}, "debugger": {"type": "boolean"}, "help_links": {"type": "array", "items": {"type": "object", "properties": { "text": {"type": "string"}, "url": {"type": "string"} }}} }, "required": ["status", "protocol_version", "implementation", "implementation_version", "language_info", "banner"]} schema_fragments['shutdown_request'] = {"properties": { "restart": {"type": "boolean"}, }} schema_fragments['shutdown_reply'] = {"properties": { # statuses 'error' and 'abort' change the structure, so check separately "status": {"const": "ok"}, "restart": {"type": "boolean"}, }} schema_fragments["interrupt_request"] = {"properties": {}} schema_fragments["interrupt_reply"] = {"properties": { # statuses 'error' and 'abort' change the structure, so check separately "status": {"const": "ok"}, }} # IOPub messages ---------------------------------------------- mime_data = { "type":"object", "patternProperties": {r'^[\w\-\+\.]+/[\w\-\+\.]+$': {}}, "additionalProperties": False, } schema_fragments['stream'] = {"properties": { "name": {"enum": ["stdout", "stderr"]}, "text": {"type": "string"}, }} schema_fragments['display_data'] = {"properties": { "data": mime_data, "metadata": {"type": "object"}, "transient": {"type": "object"}, }, "required": ["data", "metadata"]} schema_fragments['update_display_data'] = {"properties": { "data": mime_data, "metadata": {"type": "object"}, "transient": {"type": "object"}, }} schema_fragments['execute_result'] = {"properties": { "execution_count": {"type": "number"}, "data": mime_data, "metadata": {"type": "object"}, "transient": {"type": "object"}, }, "required": ["execution_count", "data", "metadata"]} schema_fragments['clear_output'] = {"properties": { "wait": {"type": "boolean"}, }} schema_fragments['execute_input'] = {"properties": { "code": {"type": "string"}, "execution_count": {"type": "number"}, }} schema_fragments['error'] = {"properties": { "ename": {"type": "string"}, "evalue": {"type": "string"}, "traceback": {"type": "array", "items": {"type": "string"}}, }} schema_fragments['status'] = {"properties": { "execution_state": {"enum": ["busy", "idle", "starting"]}, }} # Stdin messages --------------------------------------------- schema_fragments["input_request"] = {"properties": { "prompt": {"type": "string"}, "password": {"type": "number"}, }} schema_fragments["input_reply"] = {"properties": { "value": {"type": "string"}, }} IRkernel/tests/testthat/test_kernel.r0000644000176200001440000000274014362570132017477 0ustar liggesuserscontext('kernel') get_desc <- function(result) { if (!is.na(result$desc)) result$desc else sub('_', ' ', result$id) } a_test_ran <- FALSE result_to_test <- function(result) { a_test_ran <<- TRUE emit_result <- switch(result$type, success = succeed, expected_failure = succeed, failure = fail, error = stop, unexpected_success = fail, skip = skip, stop('Unknown test result type: ', result$type)) msg <- if (!is.na(result$msg)) result$msg else '' test_that(get_desc(result), emit_result(msg)) } spec_add_status <- tryCatch(installspec(name = 'testir', displayname = 'testir'), error = function(e) 666L) test_that('test kernel installed', { skip_on_cran() expect_equal(spec_add_status, 0L) }) test_that('kernel tests pass', { skip_on_cran() expect_true(file.exists('test_ir.py'), 'test_ir.py exists') Sys.setenv(PYTHONPATH = 'njr', PYTHONUNBUFFERED = '1') con <- pipe('python3 -W ignore::DeprecationWarning -m ndjson_testrunner test_ir', 'rt') on.exit(expect_equal(close(con), 0L)) jsonlite::stream_in(con, result_to_test, pagesize = 1L, verbose = FALSE) expect_true(a_test_ran, 'at least one python test ran') }) spec_rm_status <- system2('jupyter', c('kernelspec', 'remove', '-f', 'testir')) test_that('test kernel removed', { skip_on_cran() expect_equal(spec_rm_status, 0) }) IRkernel/tests/testthat.R0000644000176200001440000000021214362570132015110 0ustar liggesusers# see https://github.com/hadley/testthat/issues/144 Sys.setenv(R_TESTS = '') library(testthat) library(IRkernel) test_check('IRkernel') IRkernel/R/0000755000176200001440000000000014362570132012171 5ustar liggesusersIRkernel/R/onload.r0000644000176200001440000000014114362570132013624 0ustar liggesusers.onLoad <- function(libname = NULL, pkgname = NULL) { init_options() init_backup_env() } IRkernel/R/environment_runtime.r0000644000176200001440000000044414362570132016465 0ustar liggesusers# This file contains an environment to store runtime variables independent from the kernel runtime_env <- new.env() #' Get global CommManager instance #' #' @return \link{CommManager} instance if a kernel is running, else NULL #' @export comm_manager <- function() runtime_env$comm_manager IRkernel/R/class_unions.r0000644000176200001440000000030314362570132015050 0ustar liggesuserssetClassUnion('functionOrNULL', members = c('function', 'NULL')) setClassUnion('recordedplotOrNULL', members = c('recordedplot', 'NULL')) setClassUnion('listOrNULL', members = c('list', 'NULL')) IRkernel/R/logging.r0000644000176200001440000000426614362570132014012 0ustar liggesusers# The primitive jupyter logging system... # Per default, only error messages are shown # levels: 3 = DEBUG, 2 = INFO/MSG, 1 = ERROR #' Kernel logging functions #' #' A set of exported logging utilities that have the capability to be used in upstream projects. #' Log level and log file can be set via R package options e.g. \code{options(jupyter.log_level = 2L)} #' or from the environment variables JUPYTER_LOG_LEVEL and JUPYTER_LOGFILE. #' #' @param ... message to log #' @name log NULL #' @rdname log #' @export log_debug <- function(...) { if (isTRUE(getOption('jupyter.log_level') >= 3L)) { log_msg('DEBUG', sprintf(...)) } } #' @rdname log #' @export log_info <- function(...) { if (isTRUE(getOption('jupyter.log_level') >= 2L)) { log_msg('INFO', sprintf(...)) } } #' @rdname log #' @export log_error <- function(...) { if (isTRUE(getOption('jupyter.log_level') >= 1L)) { log_msg('ERROR', sprintf(...)) } } log_msg <- function(lvl, msg) { log_msg_stderror(lvl, msg) log_msg_logfile(lvl, msg) } # Handle for stderr to even log to the console when the stderr() points to a # sink'ed connection... .stderror <- stderr() log_msg_stderror <- function(lvl, msg) { cat(sprintf('%s: %s\n', log_color(lvl), msg) , file = .stderror) } #' @importFrom crayon blue #' @importFrom crayon green #' @importFrom crayon red log_color <- function(lvl) { color <- switch(lvl, DEBUG = green, INFO = blue, ERROR = red, stop('unknown level: ', lvl)) color(lvl) } .is_changed_logfile <- local({ old_logfile <- '' function(logfile) { if (old_logfile != logfile) { old_logfile <<- logfile TRUE } else { FALSE } } }) log_msg_logfile <- function(lvl, msg) { cur_logfile <- getOption('jupyter.logfile') if (!is.na(cur_logfile)) { if (.is_changed_logfile(cur_logfile)) { log_msg_stderror('INFO', sprintf('Logging to %s', cur_logfile)) } log_con <- file(cur_logfile, open = 'ab') writeBin(charToRaw(sprintf('%s %s: %s\n', format(Sys.time()), lvl, msg)), log_con, endian = 'little') close(log_con) } } IRkernel/R/utils.r0000644000176200001440000000232614362570132013517 0ustar liggesusersellip_h <- repr:::.char_fallback('\u22EF', '...') #' @importFrom utils head tail skip_repeated <- function(vec) { if (length(vec) == 0L) return(vec) if (is.language(vec[[1]])) { # rle does not work on language items ctb <- as.character(vec) enc <- rle(ctb) enc$values <- match(enc$values, ctb) } else { enc <- rle(vec) } i <- which.max(enc$lengths) l <- enc$lengths[[i]] if (l <= 3) { vec } else { v <- enc$values[[i]] enc$lengths <- c(head(enc$lengths, i - 1), 1, 1, 1, tail(enc$lengths, -i)) enc$values <- c(head(enc$values, i - 1), v, ellip_h, v, tail(enc$values, -i)) inverse.rle(enc) } } fromRawJSON <- function(r) { s <- rawToChar(r) Encoding(s) <- 'UTF-8' fromJSON(s) } set_last_value <- function(obj) { # access via namespace so R CMD check does not complain .BaseNamespaceEnv$unlockBinding(".Last.value", .BaseNamespaceEnv) assign(".Last.value", obj, .BaseNamespaceEnv) lockBinding(".Last.value", .BaseNamespaceEnv) } get_os <- function() switch(.Platform$OS.type, windows = 'win', unix = if (identical(Sys.info()[['sysname']], 'Darwin')) 'osx' else 'unix') IRkernel/R/main.r0000644000176200001440000000114314362570132013277 0ustar liggesusers#' Initialise and run the kernel #' #' @param connection_file The path to the Jupyter connection file, written by the frontend #' #' @export main <- function(connection_file = '') { if (connection_file == '') { # On Windows, passing the connection file in as a string literal fails, # because the \U in C:\Users looks like a unicode escape. So, we have to # pass it as a separate command line argument. connection_file <- commandArgs(TRUE)[[1]] } log_debug('Starting the R kernel...') kernel <- Kernel$new(connection_file = connection_file) kernel$run() } IRkernel/R/compat.r0000644000176200001440000000173614362570132013646 0ustar liggesusersUNICODE_WARNING <- 'Your code contains a unicode char which cannot be displayed in your current locale and R will silently convert it to an escaped form when the R kernel executes this code. This can lead to subtle errors if you use such chars to do comparisons. For more information, please see https://github.com/IRkernel/repr/wiki/Problems-with-unicode-on-windows' #' @importFrom utils capture.output warn_unicode_on_windows <- function(code, warn) { # Workaround to warn user when code contains potential problematic code # https://github.com/IRkernel/repr/issues/28#issuecomment-208810772 # See https://github.com/hadley/evaluate/issues/66 if (.Platform$OS.type == 'windows') { # strip whitespace, because trailing newlines would trip the test... code <- gsub('^\\s+|\\s+$', '', code) real_len <- nchar(code) r_len <- nchar(paste(capture.output(cat(code)), collapse = '\n')) if (real_len != r_len) warn(UNICODE_WARNING) } } IRkernel/R/help.r0000644000176200001440000000435714362570132013315 0ustar liggesusers#' An R kernel for Jupyter. #' #' Jupyter speaks a JSON+ZMQ protocol to a 'kernel' which is responsible for executing code. #' This package is a kernel for the R language. #' #' @section Options: #' #' The following can be set/read via \code{options(opt.name = ...)} / \code{getOption('opt.name')} #' #' \describe{ #' \item{\code{jupyter.log_level}}{1L (errors), 2L (warnings), or 3L (debug). 1L is the default.} #' \item{\code{jupyter.pager_classes}}{Classes to use the pager for instead of displaying them inline. Default: help pages} #' \item{\code{jupyter.in_kernel}}{\code{TRUE} if this code is executed in a running kernel. Set to pretend being/not being in a kernel} #' \item{\code{jupyter.rich_display}}{Use more than just text display} #' \item{\code{jupyter.display_mimetypes}}{ #' The formats emitted when any return value is to be displayed #' (default: all mimetypes listed \href{http://ipython.org/ipython-doc/stable/api/generated/IPython.core.formatters.html#IPython.core.formatters.format_display_data}{here}) #' } #' \item{\code{jupyter.plot_mimetypes}}{ #' The plot formats emitted to the frontend when a plot is displayed. #' (default: image/png and application/pdf) #' } #' \item{\code{jupyter.plot_scale}}{ #' The ratio (notebook PPI / \code{repr.plot.res}). #' E.g.: With the defaults \code{repr.plot.res}=120 px/in (PPI) and \code{jupyter.plot_scale}=2, #' a 1in\eqn{\times}1in image will be displayed as a 0.5in\eqn{\times}0.5in, 240 PPI image. #' (default: 2, fit for retina displays) #' } #' } #' #' @export main #' #' @import methods #' @import uuid #' @import digest #' @importFrom pbdZMQ zmq.ctx.new zmq.socket zmq.bind zmq.getsockopt zmq.setsockopt #' @importFrom pbdZMQ zmq.send zmq.recv zmq.msg.send zmq.msg.recv zmq.send.multipart zmq.recv.multipart #' @importFrom pbdZMQ zmq.poll zmq.poll.get.revents ZMQ.MC ZMQ.PO ZMQ.ST ZMQ.SO #' @importFrom evaluate evaluate new_output_handler parse_all #' @importFrom jsonlite fromJSON toJSON #' @importFrom IRdisplay publish_mimebundle prepare_mimebundle #' @importFrom repr mime2repr repr_option_defaults #' #' @docType package #' @seealso \link{installspec} #' @name IRkernel-package #' @aliases IRkernel IRkernel-package IRkernel-options NULL IRkernel/R/handlers.r0000644000176200001440000000144014362570132014153 0ustar liggesusersprepare_mimebundle_kernel <- function(obj, handle_display_error = log_error) { # we always send text/plain, even if the user removed that from the option! text_bundle <- prepare_mimebundle(obj, 'text/plain', error_handler = handle_display_error) text_repr <- text_bundle$data[['text/plain']] # if the text/plain repr returns nothing, we also do if (is.null(text_repr) || nchar(text_repr) == 0L) return(list(data = NULL, metadata = NULL)) if (getOption('jupyter.rich_display')) { mimetypes <- setdiff(getOption('jupyter.display_mimetypes'), 'text/plain') bundle <- prepare_mimebundle(obj, mimetypes, error_handler = handle_display_error) bundle$data[['text/plain']] <- text_repr bundle } else { text_bundle } } IRkernel/R/environment_shadow.r0000644000176200001440000001160314362570132016266 0ustar liggesusers# Everthing related to the environment which takes functions which shadow base R functions. # This is needed to build in our own needs, like properly shutting down the kernel # when `quit()` is called. add_to_user_searchpath <- function(name, FN, pkg = NULL) { pkg_avail <- !is.null(pkg) && requireNamespace(pkg, quietly = TRUE) if (pkg_avail) { replace_in_package(pkg, name, FN) } else { assign(name, FN, 'jupyter:irkernel') } } replace_in_package <- function(pkg, name, FN) { env_name <- paste0('package:', pkg) if (env_name %in% search()) replace_in_env(name, FN, as.environment(env_name)) replace_in_env(name, FN, getNamespace(pkg)) } replace_in_env <- function(name, FN, env) { .BaseNamespaceEnv$unlockBinding(name, env) assign(name, FN, env) .BaseNamespaceEnv$lockBinding(name, env) } get_shadowenv <- function() { as.environment('jupyter:irkernel') } # save functions that are later replaced (called in .onLoad) backup_env <- new.env() # Circumvent windows build bug, see issue #530 backup_env$utils_flush_console <- function(...) {} # Circumvent devtools bug backup_env$base_flush_connection <- function(...) {} init_backup_env <- function() { if (!identical(environment(utils::flush.console), environment(utils::read.delim))) { tb <- .traceback(2) warning( 'init_backup_env called a second time after init_shadowenv:\n', paste(capture.output(traceback(tb)), collapse = '\n') ) return() } backup_env$base_flush_connection <- base::flush.connection backup_env$utils_flush_console <- utils::flush.console backup_env$base_quit <- base::quit } # Adds functions which do not need any access to the executer into the users searchpath #' @importFrom utils getFromNamespace getS3method #' @importFrom evaluate flush_console init_shadowenv <- function() { # add the accessors to the shadow env itself, so they are actually accessable # from everywhere... add_to_user_searchpath('.irk.get_shadowenv', get_shadowenv) add_to_user_searchpath('.irk.add_to_user_searchpath', add_to_user_searchpath) # For the rest of the functions, please explain why the workaround is needed # (=the problem) and link to the issue describing the problem. # workaround for problems with vignette(xxx) not bringing up the vignette # content in the browser: https://github.com/IRkernel/IRkernel/issues/267 add_to_user_searchpath('print.vignette', function(x, ...) { # R CMD check does not like us using ::: getS3method('print', 'vignette')(x, ...) # returning immediately will run into trouble with zmq and its polling # preventing the vignette server to startup. So wait a little to let # it startup... # 0.1 is too little, so add some margin... Sys.sleep(0.5) }) add_to_user_searchpath('View', function(x, title) { if (!missing(title)) IRdisplay::display_text(title) IRdisplay::display(x) invisible(x) # the manpage says it returns NULL, but this is useful for piping }) # we simply have currently no way to edit dfs: # https://github.com/IRkernel/IRkernel/issues/280 add_to_user_searchpath('edit', function(...) { stop(sQuote('edit()'), ' not yet supported in the Jupyter R kernel') }) # stream output in loops: # https://github.com/IRkernel/IRkernel/issues/3 replace_in_package('base', 'flush.connection', function(con) { backup_env$base_flush_connection(con) flush_console() }) replace_in_package('utils', 'flush.console', function() { backup_env$utils_flush_console() flush_console() }) } init_cran_repo <- function() { r <- getOption('repos') is_unuseable_mirror <- identical(r, c(CRAN = '@CRAN@')) if (is_unuseable_mirror) { # the default repo according to https://cran.R-project.org/mirrors.html # uses geo-redirects r[['CRAN']] <- 'https://cran.r-project.org' # attribute indicating the repos was set by us... attr(r, 'irkernel') <- TRUE options(repos = r) } } init_session <- function() { init_cran_repo() # We support color even if isatty(stdout()) is FALSE options(crayon.enabled = TRUE) } #' @importFrom grDevices pdf png init_null_device <- function() { # if possible, use a device that # 1. prints no warnings for unicode (unlike pdf/postscript) # 2. can handle /dev/null (unlike OSX devices) # since there is nothing like that on OSX AFAIK, use pdf there (accepting warnings). os <- get_os() ok_device <- switch(os, win = png, osx = pdf, unix = png) null_filename <- switch(os, win = 'NUL', osx = NULL, unix = '/dev/null') null_device <- function(filename = null_filename, ...) ok_device(filename, ...) if (identical(getOption('device'), pdf)) { options(device = null_device) } } IRkernel/R/options.r0000644000176200001440000000263714362570132014057 0ustar liggesusers#' @usage #' jupyter_option_defaults #' #' @rdname IRkernel-package #' @export jupyter_option_defaults <- list( jupyter.rich_display = TRUE, # moved from IRdisplay jupyter.log_level = 1L, jupyter.logfile = NA, jupyter.pager_classes = c( 'packageIQR', 'help_files_with_topic'), jupyter.plot_mimetypes = c( 'text/plain', 'image/png'), jupyter.plot_scale = 2, jupyter.in_kernel = FALSE) from_env <- list( JUPYTER_LOG_LEVEL = as.integer, JUPYTER_LOGFILE = function(f) if (nchar(f) == 0) NA else f) # converts e.g. jupyter.log_level to JUPYTER_LOG_LEVEL opt_to_env <- function(nms) gsub('.', '_', toupper(nms), fixed = TRUE) # called in .onLoad init_options <- function() { for (opt_name in names(jupyter_option_defaults)) { # skip option if it is already set, e.g. in the Rprofile if (is.null(getOption(opt_name))) { # prepare `options` call from the default call_arg <- jupyter_option_defaults[opt_name] # single [] preserve names # if an env var is set, get value from it. env_name <- opt_to_env(opt_name) convert <- from_env[[env_name]] env_val <- Sys.getenv(env_name, unset = NA) if (!is.null(convert) && !is.na(env_val)) call_arg[[opt_name]] <- convert(env_val) do.call(options, call_arg) } } } IRkernel/R/completion.r0000644000176200001440000000505614362570132014533 0ustar liggesuserscompletions <- function(code, cursor_pos = nchar(code), fixup = TRUE) { # Find which line we're on and position within that line lines <- strsplit(code, '\n', fixed = TRUE)[[1]] chars_before_line <- 0L for (line in lines) { new_cursor_pos <- cursor_pos - nchar(line) - 1L # -1 for the newline if (new_cursor_pos < 0L) { break } cursor_pos <- new_cursor_pos chars_before_line <- chars_before_line + nchar(line) + 1L } # guard from errors when completion is invoked in empty cells if (is.null(line)) { line <- '' } # the completion docs say: # > they are unexported because they are not meant to be called directly by users # And we are no users, so we just have to trick the overeager R CMD check by not using ::: utils_ns <- asNamespace('utils') get('.assignLinebuffer', utils_ns)(line) get('.assignEnd', utils_ns)(cursor_pos) # .guessTokenFromLine, like most other functions here usually sets variables in .CompletionEnv. # When specifying update = FALSE, it instead returns a list(token = ..., start = ...) c.info <- get('.guessTokenFromLine', utils_ns)(update = FALSE) get('.guessTokenFromLine', utils_ns)() get('.completeToken', utils_ns)() start_position <- chars_before_line + c.info$start in_string <- substr(code, start_position, start_position) %in% c("'", '"') comps <- get('.retrieveCompletions', utils_ns)() if (fixup && !in_string) comps <- fixup_comps(comps) list( comps = comps, start = start_position, end = start_position + nchar(c.info$token) ) } fixup_comps <- function(comps) { # TODO: only do this if we are not in a string or so re_trail <- '=|::' re_lead <- '[\\w\\d._]+(?:\\$|@|:::?)' # TODO: allow foo$`_bar`$baz # split off leading and trailing parts trailing <- gsub(sprintf('^.*?(%s)?$', re_trail), '\\1', comps, perl = TRUE) comps <- gsub(sprintf('(%s)$', re_trail), '', comps, perl = TRUE) leading <- gsub(sprintf('^((%s)*).*?$', re_lead), '\\1', comps, perl = TRUE) comps <- gsub(sprintf('^(%s)+', re_lead), '', comps, perl = TRUE) # wrap non-identifiers with `` # https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Identifiers comps <- gsub('^(_.*?|[.]{2,}.*?|.*?[^\\w\\d._].*?|.*?[.]\\d)$', '`\\1`', comps, perl = TRUE) # good coding style for completions trailing <- gsub('=', ' = ', trailing) comps <- paste0(leading, comps, trailing) gsub('^`[.][.][.]` = $', '...', comps) } IRkernel/R/kernel.r0000644000176200001440000003635314362570132013646 0ustar liggesusers#' @include execution.r help.r comm_manager.r logging.r utils.r PROTOCOL_VER = '5.3' Kernel <- setRefClass( 'Kernel', fields = list( connection_info = 'list', zmqctx = 'externalptr', sockets = 'list', executor = 'Executor', comm_manager = 'CommManager'), methods = list( hb_reply = function() { data <- zmq.msg.recv(sockets$hb, unserialize = FALSE) zmq.msg.send(data, sockets$hb, serialize = FALSE) }, sign_msg = function(msg_lst) { "Sign messages" concat <- unlist(msg_lst) hmac(connection_info$key, concat, 'sha256') }, wire_to_msg = function(parts) { "Deserialize a message" i <- 1 while (!identical(parts[[i]], charToRaw(''))) { i <- i + 1 } if (!identical(connection_info$key, '')) { signature <- rawToChar(parts[[i + 1]]) expected_signature <- sign_msg(parts[(i + 2):(i + 5)]) stopifnot(identical(signature, expected_signature)) } # Convert the four key parts of the message to strings and parse the JSON header <- fromRawJSON(parts[[i + 2]]) parent_header <- fromRawJSON(parts[[i + 3]]) metadata <- fromRawJSON(parts[[i + 4]]) content <- fromRawJSON(parts[[i + 5]]) # ZMQ routing bits if (i > 1) { identities <- parts[1:(i - 1)] } else { identities <- NULL } list( header = header, parent_header = parent_header, metadata = metadata, content = content, identities = identities) }, msg_to_wire = function(msg) { "Serialize a message" bodyparts <- list( charToRaw(toJSON(msg$header, auto_unbox = TRUE)), charToRaw(toJSON(msg$parent_header, auto_unbox = TRUE)), charToRaw(toJSON(msg$metadata, auto_unbox = TRUE)), charToRaw(toJSON(msg$content, auto_unbox = TRUE))) signature <- sign_msg(bodyparts) c( msg$identities, list(charToRaw('')), list(charToRaw(signature)), bodyparts) }, new_reply = function(msg_type, parent_msg) { "Prepare a reply" header <- list( msg_id = UUIDgenerate(), session = parent_msg$header$session, username = parent_msg$header$username, # ISO 8601, 6 is the maximum number decimal digits supported by R date = strftime(as.POSIXlt(Sys.time(), 'UTC'), '%Y-%m-%dT%H:%M:%OS6Z'), msg_type = msg_type, version = PROTOCOL_VER) list( header = header, parent_header = parent_msg$header, identities = parent_msg$identities, # Ensure this is {} in JSON, not [] metadata = namedlist()) }, send_response = function(msg_type, parent_msg, socket_name, content) { "Send a response" msg <- new_reply(msg_type, parent_msg) if (grepl('_reply$', msg_type) && is.null(content$status)) { content$status <- 'ok' } msg$content <- content socket <- sockets[[socket_name]] zmq.send.multipart(socket, msg_to_wire(msg), serialize = FALSE) log_debug('Sending msg %s.%s', socket_name, msg$header$msg_type) }, handle_shell = function() { "React to a shell message coming in" parts <- zmq.recv.multipart(sockets$shell, unserialize = FALSE) msg <- wire_to_msg(parts) # protocol 5.0: send busy/idle around all of these send_response('status', msg, 'iopub', list( execution_state = 'busy')) switch( msg$header$msg_type, comm_info_request = comm_manager$on_comm_info_request(msg), comm_open = comm_manager$on_comm_open(msg), comm_msg = comm_manager$on_comm_msg(msg), comm_close = comm_manager$on_comm_close(msg), execute_request = executor$execute(msg), kernel_info_request = kernel_info(msg), history_request = history(msg), complete_request = complete(msg), is_complete_request = is_complete(msg), inspect_request = inspect(msg), shutdown_request = shutdown(msg), log_debug(c('Got unhandled msg_type:', msg$header$msg_type))) send_response('status', msg, 'iopub', list( execution_state = 'idle')) }, abort_shell_msg = function() { "Send an abort message for an incoming shell request" # See https://github.com/ipython/ipykernel/blob/1d97cb2a04149387a0d2dbea1b3d0af691d8df6c/ipykernel/kernelbase.py#L623 parts <- zmq.recv.multipart(sockets$shell, unserialize = FALSE) msg <- wire_to_msg(parts) log_debug('Aborting msg of type %s', msg$header$msg_type) reply_type <- paste0(unlist(strsplit(msg$header$msg_type, '_'))[1], '_reply') reply_content <- list(status = 'aborted') send_response(reply_type, msg, 'shell', reply_content) log_debug('Aborted msg') }, abort_queued_messages = function() { "Abort all already queued shell messages after an error" log_debug('abort loop: aborted all outstanding msg') while (TRUE) { log_debug('abort loop: before poll') ret <- zmq.poll( c(sockets$shell), # only shell channel c(ZMQ.PO()$POLLIN), # type 0) # zero timeout, only what's already there log_debug('abort loop: after poll') if (bitwAnd(zmq.poll.get.revents(1), ZMQ.PO()$POLLIN)) { log_debug('abort loop: found msg') abort_shell_msg() } else { # no more messages... log_debug('abort loop: breaking') break } } log_debug('abort loop: end') }, handle_stdin = function() { "React to a stdin message coming in" # wait for 'input_reply' response message while (TRUE) { log_debug('stdin loop: beginning') zmq.poll(c(sockets$stdin), # only stdin channel c(ZMQ.PO()$POLLIN)) # type if (bitwAnd(zmq.poll.get.revents(1), ZMQ.PO()$POLLIN)) { log_debug('stdin loop: found msg') parts <- zmq.recv.multipart(sockets$stdin, unserialize = FALSE) msg <- wire_to_msg(parts) return(msg$content$value) } else { # else shouldn't be possible log_error('stdin loop: zmq.poll returned but no message found?') } } }, is_complete = function(request) { "Checks whether the code in the rest is complete" code <- request$content$code message <- tryCatch({ parse_all(code) # the code compiles, so we are complete (either no code at all / only # comments or syntactically correct code) 'complete' }, error = function(e) e$message) # One of 'complete', 'incomplete', 'invalid', 'unknown' status <- if (message == 'complete') { # syntactical complete code 'complete' } else if (grepl(gettext('unexpected end of input', domain = 'R'), message, fixed = TRUE)) { # missing closing parenthesis 'incomplete' } else if (grepl(gettextf('unexpected %s', 'INCOMPLETE_STRING', domain = 'R'), message, fixed = TRUE)) { # missing closing quotes 'incomplete' } else { # all else 'invalid' } content <- list(status = status) if (status == 'incomplete') { # we don't try to guess the indention level and just return zero indention # That's fine because R has braces... :-) # TODO: do some guessing? content <- c(content, indent = '') } send_response('is_complete_reply', request, 'shell', content) }, complete = function(request) { # 5.0 protocol: code <- request$content$code cursor_pos <- request$content$cursor_pos comps <- completions(code, cursor_pos) send_response('complete_reply', request, 'shell', list( matches = as.list(comps$comps), # make single strings not explode into letters metadata = namedlist(), cursor_start = comps$start, cursor_end = comps$end)) }, inspect = function(request) { # 5.0 protocol: code <- request$content$code cursor_pos <- request$content$cursor_pos title_templates <- list( 'text/plain' = '# %s:\n', 'text/html' = '

%s:

\n') # Function to add a section to content. add_new_section <- function(data, section_name, new_data) { for (mime in names(title_templates)) { new_content <- new_data[[mime]] if (is.null(new_content)) next title <- sprintf(title_templates[[mime]], section_name) # use paste0 since sprintf cannot deal with format strings > 8192 bytes data[[mime]] <- paste0(data[[mime]], title, new_content, '\n', sep = '\n') } return(data) } # Get token under the `cursor_pos`. # Since `.guessTokenFromLine()` does not check the characters after `cursor_pos` # check them by a loop. Use get since R CMD check does not like ::: token <- '' for (i in seq(cursor_pos, nchar(code))) { token_candidate <- get('.guessTokenFromLine', asNamespace('utils'))(code, i) if (nchar(token_candidate) == 0) break token <- token_candidate } data <- namedlist() if (nchar(token) != 0) { # In many cases `get(token)` works, but it does not # in the cases such as `token` is a numeric constant or a reserved word. # Therefore `eval()` is used here. obj <- tryCatch(eval(parse(text = token), envir = .GlobalEnv), error = function(e) NULL) class_data <- if (!is.null(obj)) IRdisplay::prepare_mimebundle(class(obj))$data print_data <- if (!is.null(obj)) IRdisplay::prepare_mimebundle(obj)$data # `help(token)` is not used here because it does not works # in the cases `token` is in `pkg::topic`or `pkg:::topic` form. help_data <- tryCatch({ help_obj <- eval(parse(text = paste0('?', token))) IRdisplay::prepare_mimebundle(help_obj)$data }, error = function(e) NULL) # only show help if we have a function if ('function' %in% class(obj) && !is.null(help_data)) { data <- help_data } else {# any of those that are NULL are automatically skipped data <- add_new_section(data, 'Class attribute', class_data) data <- add_new_section(data, 'Printed form', print_data) data <- add_new_section(data, 'Help document', help_data) } } found <- length(data) != 0 send_response('inspect_reply', request, 'shell', list( found = found, data = data, metadata = namedlist())) }, history = function(request) { send_response('history_reply', request, 'shell', list(history = list())) }, kernel_info = function(request) { rversion <- paste0(version$major, '.', version$minor) send_response('kernel_info_reply', request, 'shell', list( protocol_version = PROTOCOL_VER, implementation = 'IRkernel', implementation_version = as.character(packageVersion('IRkernel')), language_info = list( name = 'R', codemirror_mode = 'r', pygments_lexer = 'r', mimetype = 'text/x-r-source', file_extension = '.r', version = rversion), banner = version$version.string)) }, handle_control = function() { log_debug('Control: beginning') parts <- zmq.recv.multipart(sockets$control, unserialize = FALSE) msg <- wire_to_msg(parts) log_debug('Control: recv msg of type %s', msg$header$msg_type) if (msg$header$msg_type == 'shutdown_request') { log_debug('Control: shutdown...') shutdown(msg) } else { log_debug(paste('Unhandled control message, msg_type:', msg$header$msg_type)) } }, shutdown = function(request) { send_response('shutdown_reply', request, 'control', list( restart = request$content$restart)) # Always call the base quit() during shutdown since execution shadows it. backup_env$base_quit('no') # bound during startup in .onLoad }, initialize = function(connection_file) { if (is.character(connection_file)) connection_file <- file(connection_file) connection_info <<- fromJSON(connection_file) stopifnot(connection_info$transport %in% c('tcp', 'ipc')) url <- paste0(connection_info$transport, '://', connection_info$ip) url_with_port <- function(port_name) { sep <- switch(connection_info$transport, tcp = ':', ipc = '-') paste0(url, sep, connection_info[[port_name]]) } # ZMQ Socket setup zmqctx <<- zmq.ctx.new() sockets <<- list( hb = zmq.socket(zmqctx, ZMQ.ST()$REP), iopub = zmq.socket(zmqctx, ZMQ.ST()$PUB), control = zmq.socket(zmqctx, ZMQ.ST()$ROUTER), stdin = zmq.socket(zmqctx, ZMQ.ST()$ROUTER), shell = zmq.socket(zmqctx, ZMQ.ST()$ROUTER)) # Enable handover: https://github.com/IRkernel/IRkernel/issues/508 for (router in sockets[c('control', 'stdin', 'shell')]) { zmq.setsockopt(router, ZMQ.SO()$ROUTER_HANDOVER, 1L) } zmq.bind(sockets$hb, url_with_port('hb_port')) zmq.bind(sockets$iopub, url_with_port('iopub_port')) zmq.bind(sockets$control, url_with_port('control_port')) zmq.bind(sockets$stdin, url_with_port('stdin_port')) zmq.bind(sockets$shell, url_with_port('shell_port')) executor <<- Executor$new( send_response = .self$send_response, handle_stdin = .self$handle_stdin, abort_queued_messages = .self$abort_queued_messages) comm_manager <<- CommManager$new(send_response = .self$send_response) runtime_env$comm_manager <- comm_manager }, run = function() { options(jupyter.in_kernel = TRUE) while (TRUE) { log_debug('main loop: beginning') r <- tryCatch( zmq.poll( c(sockets$hb, sockets$shell, sockets$control), rep(ZMQ.PO()$POLLIN, 3), MC = ZMQ.MC(check.eintr = TRUE)), interrupt = function(e) list(0L, 'SIGINT')) log_debug('main loop: after poll. ZMQ code: %s; Errno: %s', r[[1L]], r[[2L]]) if (identical(r[[2L]], 'SIGINT')) { log_info('main loop: keyboard interrupt caught') next } # It's important that these messages are handled one by one in each # look. The problem is that during the handler, a new zmq.poll could be # done (and is done in case of errors in a execution request) and this # invalidates the zmq.poll.get.revents call leading to "funny" results # with found control message even if there are no control messages. So # the easiest seems to be to handle this in a big if .. else if .. else # clause... # https://github.com/IRkernel/IRkernel/pull/266 if (bitwAnd(zmq.poll.get.revents(1), ZMQ.PO()$POLLIN)) { log_debug('main loop: hb') hb_reply() } else if (bitwAnd(zmq.poll.get.revents(2), ZMQ.PO()$POLLIN)) { log_debug('main loop: shell') handle_shell() } else if (bitwAnd(zmq.poll.get.revents(3), ZMQ.PO()$POLLIN)) { log_debug('main loop: control') handle_control() } else { # else shouldn't be possible log_debug('main loop: zmq.poll returned but no message found?') } } log_debug('main loop: end') }) ) IRkernel/R/execution.r0000644000176200001440000003035114362570132014361 0ustar liggesusers#' @include options.r class_unions.r NULL # Create an empty named list #' @importFrom stats setNames namedlist <- function() setNames(list(), character(0)) # Converts something to a string no matter what #' @importFrom utils str resilient_to_str <- function(v) tryCatch(toString(v), error = function(e) capture.output(str(v))) plot_builds_upon <- function(prev, current) { if (is.null(prev)) { return(TRUE) } lprev <- length(prev[[1]]) lcurrent <- length(current[[1]]) lcurrent >= lprev && identical(current[[1]][1:lprev], prev[[1]][1:lprev]) } ask <- function(prompt = '') { answer <- NA while (is.na(answer)) { answer <- switch(readline(prompt), y = , Y = TRUE, n = , N = FALSE, c = NULL, NA) } answer } format_stack <- function(calls) { line_refs <- rep('', length(calls)) tb <- lapply(seq_along(calls), function(cl) { call <- calls[[cl]] # first_line, first_byte, last_line, last_byte, first_column, last_column, first_parsed, last_parsed ref <- attr(call, 'srcref') filename <- attr(ref, 'srcfile')$filename if (!is.null(ref)) { f <- ref[[1]] l <- ref[[3]] lines <- if (f == l) f else paste0(f, '-', l) line_refs[[cl]] <<- paste0(' # at line ', lines, ' of file ', filename) } white <- paste(rep(' ', nchar(format(cl))), collapse = '') f.call <- format(call) line.prefix <- c(cl, rep(white, length(f.call) - 1)) paste(paste0(line.prefix, '. ', f.call), collapse = '\n') }) paste0(tb, line_refs) } #' @importFrom utils capture.output Executor <- setRefClass( Class = 'Executor', fields = list( send_response = 'function', handle_stdin = 'function', abort_queued_messages = 'function', execution_count = 'integer', payload = 'list', err = 'list', interrupted = 'logical', last_recorded_plot = 'recordedplotOrNULL', current_request = 'listOrNULL', nframe = 'integer'), methods = list( is_silent = function() { current_request$content$silent }, should_store_history = function() { sh <- current_request$content$store_history !is.null(sh) && sh }, send_error_msg = function(msg) { if (is_silent()) return() send_response('stream', current_request, 'iopub', list(name = 'stderr', text = msg)) }, display_data = function(data, metadata = NULL) { if (is.null(metadata)) { metadata <- namedlist() } send_response('display_data', current_request, 'iopub', list( data = data, metadata = metadata)) invisible(TRUE) }, clear_output = function(wait = TRUE) { send_response('clear_output', current_request, 'iopub', list(wait = wait)) }, page = function(mimebundle) { payload <<- c(payload, list(c(source = 'page', mimebundle))) }, # .Last doesn’t seem to work, so replicating behavior quit = function(save = 'default', status = 0, runLast = TRUE) { save <- switch(save, default = , yes = TRUE, no = FALSE, ask = ask('Save workspace image? [y/n/c]: '), stop('unknown `save` value')) if (is.null(save)) return() # cancel if (runLast) { if (!is.null(.GlobalEnv$.Last)) .GlobalEnv$.Last() if (!is.null(.GlobalEnv$.Last.sys)) .GlobalEnv$.Last.sys() } if (save) NULL # TODO: actually save history payload <<- c(.self$payload, list(list(source = 'ask_exit', keepkernel = FALSE))) }, # noninteractive readline = function(prompt = '') { log_debug('entering custom readline') send_response('input_request', current_request, 'stdin', list(prompt = prompt, password = FALSE)) # wait for 'input_reply' response message input <- handle_stdin() }, # noninteractive 5.0 protocol: get_pass = function(prompt = '') { log_debug('entering custom get_pass') send_response('input_request', current_request, 'stdin', list(prompt = prompt, password = TRUE)) # wait for 'input_reply' response message log_debug('exiting custom get_pass') input <- handle_stdin() }, handle_error = function(e) { estr <- resilient_to_str(e) tryCatch({ log_debug('Error output: %s', estr) calls <- head(sys.calls()[-seq_len(nframe + 1L)], -3) calls <- skip_repeated(calls) msg <- paste0(estr, 'Traceback:\n') stack_info <- format_stack(calls) err <<- list(ename = 'ERROR', evalue = estr, traceback = as.list(c(msg, stack_info))) if (!is_silent()) { send_response('error', current_request, 'iopub', err) } }, error = function(e2) { log_error('Error in handle_error! %s', resilient_to_str(e2)) log_error('Caused when handling %s', estr) }) }, send_plot = function(plotobj) { log_debug('Sending plot...') formats <- namedlist() metadata <- namedlist() for (mime in getOption('jupyter.plot_mimetypes')) { w <- attr(plotobj, '.irkernel_width') h <- attr(plotobj, '.irkernel_height') ppi <- attr(plotobj, '.irkernel_ppi') tryCatch({ formats[[mime]] <- mime2repr[[mime]](plotobj, w, h) }, error = handle_error) if (!identical(mime, 'text/plain')) { metadata[[mime]] <- list( width = w * ppi, height = h * ppi ) } # Isolating SVGs (putting them in an iframe) avoids strange # interactions with CSS on the page. if (identical(mime, 'image/svg+xml')) { metadata[[mime]]$isolated <- TRUE } } publish_mimebundle(formats, metadata) }, handle_display_error = function(e) { # This is used with withCallingHandler and only has two additional # calls at the end instead of the 3 for tryCatch... (-2 at the end) # we also remove the tryCatch and mime2repr stuff at the head of the callstack (+7) calls <- head(sys.calls()[-seq_len(nframe + 7L)], -2) stack_info <- format_stack(calls) msg <- sprintf('ERROR while rich displaying an object: %s\nTraceback:\n%s\n', toString(e), paste(stack_info, collapse = '\n')) log_debug(msg) send_error_msg(msg) }, handle_value = function(obj, visible) { log_debug('Value output...') set_last_value(obj) if (!visible) return() mimebundle <- prepare_mimebundle_kernel(obj, .self$handle_display_error) if (is.null(mimebundle$data)) return() if (length(intersect(class(obj), getOption('jupyter.pager_classes'))) > 0) { log_debug('Showing pager: %s', paste(capture.output(str(mimebundle$data)), collapse = '\n')) page(mimebundle) } else { log_debug('Sending display_data: %s', paste(capture.output(str(mimebundle$data)), collapse = '\n')) send_response('display_data', current_request, 'iopub', mimebundle) } }, stream = function(output, streamname) { log_debug('Stream output: %s', output) send_response('stream', current_request, 'iopub', list( name = streamname, text = paste(output, collapse = '\n'))) }, handle_graphics = function(plotobj) { log_debug('Graphics output...') if (!plot_builds_upon(last_recorded_plot, plotobj)) { send_plot(last_recorded_plot) } # need to be set here to capture the size and have it available when the plot is sent attr(plotobj, '.irkernel_width') <- getOption('repr.plot.width', repr_option_defaults$repr.plot.width) attr(plotobj, '.irkernel_height') <- getOption('repr.plot.height', repr_option_defaults$repr.plot.height) attr(plotobj, '.irkernel_ppi') <- getOption('repr.plot.res', repr_option_defaults$repr.plot.res) / getOption('jupyter.plot_scale', jupyter_option_defaults$jupyter.plot_scale) last_recorded_plot <<- plotobj }, handle_message = function(o) { log_debug('Message output: %s', o) stream(paste(c(conditionMessage(o), '\n'), collapse = ''), 'stderr') }, handle_warning = function(o) { call <- conditionCall(o) call <- if (is.null(call)) '' else sprintf(' in %s', deparse(call)[[1]]) msg <- sprintf('Warning message%s:\n%s\n', call, dQuote(conditionMessage(o))) log_debug('Warning output: %s', msg) stream(msg, 'stderr') }, execute = function(request) { send_response('execute_input', request, 'iopub', list( code = request$content$code, execution_count = execution_count)) # Make the current request available to other functions current_request <<- request # reset ... payload <<- list() err <<- list() # shade base::readline replace_in_package('base', 'readline', .self$readline) # shade getPass::getPass add_to_user_searchpath('getPass', .self$get_pass, 'getPass') # shade base::quit replace_in_package('base', 'quit', .self$quit) replace_in_package('base', 'q', .self$quit) # find out stack depth in notebook cell # TODO: maybe replace with a single call on first execute and rest reuse the value? tryCatch(evaluate( 'stop()', stop_on_error = 1L, output_handler = new_output_handler(error = function(e) nframe <<- sys.nframe()))) oh <- if (is_silent()) { new_output_handler( text = identity, graphics = identity, message = identity, warning = identity, error = identity, value = identity) } else { new_output_handler( text = function(o) stream(o, 'stdout'), graphics = .self$handle_graphics, message = .self$handle_message, warning = .self$handle_warning, error = .self$handle_error, value = .self$handle_value) } interrupted <<- FALSE last_recorded_plot <<- NULL log_debug('Executing code: %s', request$content$code) warn_unicode_on_windows(request$content$code, .self$send_error_msg) tryCatch( evaluate( request$content$code, envir = .GlobalEnv, output_handler = oh, stop_on_error = 1L), interrupt = function(cond) { log_debug('Interrupt during execution') interrupted <<- TRUE }, error = .self$handle_error) # evaluate does not catch errors in parsing if (!is_silent() && !is.null(last_recorded_plot)) { send_plot(last_recorded_plot) } status <- if (interrupted) 'abort' else if (!is.null(err$ename)) 'error' else 'ok' log_debug('Code execution result: %s', status) reply_content <- c( list( status = status, execution_count = execution_count), switch(status, ok = list(payload = payload, user_expressions = namedlist()), error = err)) send_response('execute_reply', request, 'shell', reply_content) if (interrupted || !is.null(err$ename)) { # errors or interrupts should interrupt all currently queued messages, # not only the currently running one... abort_queued_messages() } if (!is_silent() && should_store_history()) { execution_count <<- execution_count + 1L } }, initialize = function(...) { execution_count <<- 1L err <<- list() options( pager = function(files, header, title, delete.file) { text <- title for (path in files) { text <- c(text, header, readLines(path)) } if (delete.file) file.remove(files) data <- list('text/plain' = paste(text, collapse = '\n')) page(list(data=data, metadata=namedlist())) }, jupyter.base_display_func = .self$display_data, jupyter.clear_output_func = .self$clear_output ) # Create the shadow env here and detach it finalize # so it's available for the whole lifetime of the kernel. .BaseNamespaceEnv$attach(NULL, name = 'jupyter:irkernel') # Add stuff to the user environment and configure a few options # in the current session init_shadowenv() init_session() init_null_device() callSuper(...) }, finalize = function() { detach('jupyter:irkernel') }) ) IRkernel/R/installspec.r0000644000176200001440000000602314362570132014676 0ustar liggesusers#' Install the kernelspec to tell Jupyter about IRkernel. #' #' This can be called multiple times for different R interpreter, but you have to give a #' different name (and displayname to see a difference in the notebook UI). If the same #' name is give, it will overwrite older versions of the kernel spec with that name! #' #' @param user Install into user directory (\href{https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html}{\code{$XDG_DATA_HOME}}\code{/jupyter/kernels}) or globally? (default: NULL but treated as TRUE if "prefix" is not specified) #' @param name The name of the kernel (default "ir") #' @param displayname The name which is displayed in the notebook (default: "R") #' @param rprofile (optional) Path to kernel-specific Rprofile (defaults to system-level settings) #' @param prefix (optional) Path to alternate directory to install kernelspec into (default: NULL) #' @param sys_prefix (optional) Install kernelspec using the \code{--sys-prefix} option of the currently detected jupyter (default: NULL) #' @param verbose (optional) If \code{FALSE}, silence output of \code{install} #' #' @return Exit code of the \code{jupyter kernelspec install} call. #' #' @export installspec <- function( user = NULL, name = 'ir', displayname = 'R', rprofile = NULL, prefix = NULL, sys_prefix = NULL, verbose = getOption('verbose') ) { exit_code <- system2('jupyter', c('kernelspec', '--version'), FALSE, FALSE) if (exit_code != 0) stop('jupyter-client has to be installed but ', dQuote('jupyter kernelspec --version'), ' exited with code ', exit_code, '.\n') # default to 'user' install if neither 'user' or 'prefix' is specified if (is.null(user)) user <- (is.null(prefix) && is.null(sys_prefix)) if (sum(user, !is.null(prefix), !is.null(sys_prefix)) > 1) stop('"user", "prefix", "sys_prefix" are mutually exclusive') # make a kernelspec with the current interpreter's absolute path srcdir <- system.file('kernelspec', package = 'IRkernel') tmp_name <- tempfile() dir.create(tmp_name) file.copy(srcdir, tmp_name, recursive = TRUE) spec_path <- file.path(tmp_name, 'kernelspec', 'kernel.json') spec <- fromJSON(spec_path) spec$argv[[1]] <- file.path(R.home('bin'), 'R') spec$display_name <- displayname if (!is.null(rprofile)) { spec$env <- list(R_PROFILE_USER = rprofile) } write(toJSON(spec, pretty = TRUE, auto_unbox = TRUE), file = spec_path) user_flag <- if (user) '--user' else character(0) prefix_flag <- if (!is.null(prefix)) c('--prefix', prefix) else character(0) sys_prefix_flag <- if (!is.null(sys_prefix)) c('--sys-prefix', prefix) else character(0) quiet_flag <- if (!verbose) '--log-level=WARN' else character(0) args <- c('kernelspec', 'install', '--replace', '--name', name, user_flag, prefix_flag, sys_prefix_flag, quiet_flag, file.path(tmp_name, 'kernelspec')) exit_code <- system2('jupyter', args) unlink(tmp_name, recursive = TRUE) invisible(exit_code) } IRkernel/R/comm_manager.r0000644000176200001440000001705114362570132015005 0ustar liggesusers#' The CommManager #' #' Has methods able to register comms/targets and process comm messages #' #' @include logging.r class_unions.r #' @export CommManager <- setRefClass( 'CommManager', fields = list( send_response = 'function', target_to_handler_map = 'list', commid_to_comm = 'list', parent_request = 'list' ), methods = list( new_comm = function(target_name, comm_id = UUIDgenerate()) { Comm$new(id = comm_id, target_name = target_name, comm_manager = .self) }, register_target = function(target_name, handler_func) target_to_handler_map[[target_name]] <<- handler_func, unregister_target = function(target_name, handler_func) target_to_handler_map[[target_name]] <<- NULL, register_comm = function(comm) commid_to_comm[[comm$id]] <<- comm, unregister_comm = function(comm) commid_to_comm[[comm$id]] <<- NULL, is_comm_registered = function(comm) !is.null(commid_to_comm[[comm$id]]), send_open = function(comm_id, target_name, data, metadata = list()) { send_response('comm_open', parent_request, 'iopub', list( metadata = metadata, comm_id = comm_id, target_name = target_name, data = data )) }, send_msg = function(comm_id, target_name, data, metadata = list()) { send_response('comm_msg', parent_request, 'iopub', list( metadata = metadata, comm_id = comm_id, target_name = target_name, data = data )) }, send_close = function(comm_id, target_name, data, metadata = list()) { send_response('comm_close', parent_request, 'iopub', list( metadata = metadata, comm_id = comm_id, target_name = target_name, data = data )) }, make_comm_list = function(comm_list) { all_comms <- list() for (the_comm in comm_list) { all_comms[[the_comm$id]] <- list(target_name = the_comm$target_name) } all_comms }, #response: #content = { # # A dictionary of the comms, indexed by uuids. # 'comms': { # comm_id_1: { # 'target_name': str, # }, # comm_id_2: { # 'target_name': str, # }, # }, #} on_comm_info_request = function(request) { #If request$content$target_name is provided return all commids registered with that target_name #Else target_name not in the request return all commids accross all targets reply_msg <- list() comms <- list() if ('target_name' %in% names(request$content)) { #reply with comms only for the specified target_name target_name_requested <- request$content$target_name filtered_comms <- Filter(function(x) x$target_name == target_name_requested, commid_to_comm) comms[['comms']] <- make_comm_list(filtered_comms) } else { comms[['comms']] <- make_comm_list(commid_to_comm) } reply_msg[['content']] <- comms send_response('comm_info_reply', request, 'shell', reply_msg) }, #{ # 'comm_id' : 'u-u-i-d', # 'target_name' : 'my_comm', # 'data' : {} # } on_comm_open = function(request) { parent_request <<- request target_name <- request$content$target_name comm_id <- request$content$comm_id if (target_name %in% names(target_to_handler_map)) { # create a comm object comm <- new_comm(target_name, comm_id) register_comm(comm) data <- request$content$data #invoke target handler tryCatch({ target_to_handler_map[[target_name]](comm, data) }, error = function(e) { log_debug('error invoking the handler for target: %s', e) }) } else { log_debug('target_name not found in comm_open') #reply with a comm_close message as target_name not found send_close(comm_id, target_name, list()) } }, #{ # 'comm_id' : 'u-u-i-d', # 'data' : {} #} on_comm_msg = function(request) { parent_request <<- request comm_id <- request$content$comm_id if (comm_id %in% names(commid_to_comm)) { comm <- commid_to_comm[[comm_id]] data <- request$content$data tryCatch({ comm$handle_msg(data) }, error = function(e) { log_debug('error invoking comm handle msg: %s', e) }) } else { log_debug('comm_id not found in comm_msg') } }, #{ # 'comm_id' : 'u-u-i-d', # 'data' : {} #} on_comm_close = function(request) { parent_request <<- request comm_id <- request$content$comm_id if (comm_id %in% names(commid_to_comm)) { comm <- commid_to_comm[[comm_id]] tryCatch({ comm$handle_close() }, error = function(e) { log_debug('error invoking comm handle close: %s', e) }) unregister_comm(comm) } else { log_debug('comm_id not found in comm_msg') } }, initialize = function(...) { callSuper(...) } ) ) #' The Comm #' #' Has methods able to register and handle message callbacks #' #' @export Comm <- setRefClass( 'Comm', fields = list( id = 'character', target_name = 'character', comm_manager = 'CommManager', msg_callback = 'functionOrNULL', close_callback = 'functionOrNULL' ), methods = list( open = function(msg = list()) { if (!comm_manager$is_comm_registered(.self)) { comm_manager$register_comm(.self) comm_manager$send_open(id, target_name, msg) } else { log_debug('Comm already opened!') } }, send = function(msg = list()) { if (comm_manager$is_comm_registered(.self)) { comm_manager$send_msg(id, target_name, msg) } else { log_debug('Comm is not opened. Cannot send!') } }, close = function(msg = list()) { if (comm_manager$is_comm_registered(.self)) { comm_manager$send_close(id, target_name, msg) comm_manager$unregister_comm(.self) } else { log_debug('Comm is already closed!') } }, on_msg = function(a_msg_callback) { msg_callback <<- a_msg_callback }, on_close = function(a_close_callback) { close_callback <<- a_close_callback }, handle_msg = function(msg) { if (!is.null(msg_callback)) { msg_callback(msg) } }, handle_close = function() { if (!is.null(close_callback)) { close_callback() } } ) ) IRkernel/MD50000644000176200001440000000417714362573562012323 0ustar liggesusers5b8498832c4a51a59fc89fcdbc4e3e67 *DESCRIPTION 3fe186c89f530bf2944b4d33064d7f79 *LICENSE 94a4209948a4af7cdb331e4a4a9dcbac *NAMESPACE b1c4f4c93173b0a9ceb834ce9d78bd99 *R/class_unions.r 87f62d7255f43134bdf4f2306748b2f2 *R/comm_manager.r 9dc12cb752cf14ad77d7d85f3cfe5861 *R/compat.r bbcb3d51057d8f79167a3e9e5b76a7aa *R/completion.r 1867fc3e83d2498e396eec07d1fa5847 *R/environment_runtime.r d417c7ed38d20a243617f5c624496916 *R/environment_shadow.r f9d5d490e56393c47b544ff995584739 *R/execution.r b325c17c1e9cb8eaac03bfd489975690 *R/handlers.r 5f8bc5021a6b86a6c19769916c044f39 *R/help.r bf711e4ac4a7ac5273a389efcd2279d9 *R/installspec.r 1429e6484aaee143dd2a9c475a2b7386 *R/kernel.r 16623bf1086219b98537e3a8a9f7aa41 *R/logging.r f9fe9f2c0288a1ce30dbce72a0774569 *R/main.r f445441f1ee8d3a8e7bcdc9375654b4b *R/onload.r daae9e89cf9362aec06e6138100ec044 *R/options.r 54a51eff21cfcbf0bde2f8c270e08300 *R/utils.r 4d851d53eba1ed14ec3083b0bcdc9d90 *README.md 53dfb0e6c19b3a09d9856e5fbfe2bf9c *inst/kernelspec/kernel.js f209c9fba50a620fdc8cfabecf3b3d4e *inst/kernelspec/kernel.json 20c1346c7adfcfa2f6ccdc20703fa84d *inst/kernelspec/logo-64x64.png 0d57c2c2f2b9e19e1a221b6c7ef0dedb *inst/kernelspec/logo-svg.svg dfb7be9510d7ecfd3c0d0d674addaeb5 *man/Comm-class.Rd 246fffe4a7ce7bb73e3dc5be38780628 *man/CommManager-class.Rd 48a1912f0dceeeec78e7b8c02f2afb86 *man/IRkernel-package.Rd b55bfa5a99e1a14a2f4660e322c359e3 *man/comm_manager.Rd 541cbcb26042a38db46d3c5ea36fc4ae *man/installspec.Rd b24db16df7be50d38e8c5382d4afea72 *man/log.Rd ce78f7bdbe1ef4c47a5a3f1b9e02c647 *man/main.Rd 6db253d573f087960e03ca43aee1e2ff *tests/testthat.R 7480bf1ee3d1729d19b12d4b649a60de *tests/testthat/jkt/jupyter_kernel_test/__init__.py f63bb983690ac3a7fc799ea54209d4e0 *tests/testthat/jkt/jupyter_kernel_test/msgspec_v5.py 61008cd43cde360e237de872ed821b83 *tests/testthat/jkt/pyproject.toml dccf98a0c409e7dab02f22fc3cd962d0 *tests/testthat/njr/ndjson_testrunner.py be4255ca30b664bee0f14d3e52d12b00 *tests/testthat/test-options.r 67bb81111e0f0d9c1ce15080c2dd0cd7 *tests/testthat/test_ir.py d4a1a4c9e5bded79fc63cd2b11fe811e *tests/testthat/test_kernel.r bbf735641a644711091fbc909432ef97 *tests/testthat/test_utils.r IRkernel/inst/0000755000176200001440000000000014362570132012745 5ustar liggesusersIRkernel/inst/kernelspec/0000755000176200001440000000000014362570132015100 5ustar liggesusersIRkernel/inst/kernelspec/kernel.js0000644000176200001440000000405414362570132016721 0ustar liggesusersconst cmd_key = /Mac/.test(navigator.platform) ? 'Cmd' : 'Ctrl' const edit_actions = [ { name: 'R Assign', shortcut: 'Alt--', icon: 'fa-long-arrow-left', help: 'R: Inserts the left-assign operator (<-)', handler(cm) { cm.replaceSelection(' <- ') }, }, { name: 'R Pipe', shortcut: `Shift-${cmd_key}-M`, icon: 'fa-angle-right', help: 'R: Inserts the magrittr pipe operator (%>%)', handler(cm) { cm.replaceSelection(' %>% ') }, }, { name: 'R Help', shortcut: 'F1', icon: 'fa-book', help: 'R: Shows the manpage for the item under the cursor', handler(cm, cell) { const {anchor, head} = cm.findWordAt(cm.getCursor()) const word = cm.getRange(anchor, head) const callbacks = cell.get_callbacks() const options = {silent: false, store_history: false, stop_on_error: true} cell.last_msg_id = cell.notebook.kernel.execute(`help(\`${word}\`)`, callbacks, options) }, }, ] const prefix = 'irkernel' function add_edit_shortcut(notebook, actions, keyboard_manager, edit_action) { const {name, shortcut, icon, help, handler} = edit_action const action = { icon, help, help_index : 'zz', handler: () => { const cell = notebook.get_selected_cell() handler(cell.code_mirror, cell) }, } const full_name = actions.register(action, name, prefix) Jupyter.keyboard_manager.edit_shortcuts.add_shortcut(shortcut, full_name) } function render_math(pager, html) { if (!html) return const $container = pager.pager_element.find('#pager-container') $container.find('p[style="text-align: center;"]').map((i, e) => e.outerHTML = `\\[${e.querySelector('i').innerHTML}\\]`) $container.find('i').map((i, e) => e.outerHTML = `\\(${e.innerHTML}\\)`) MathJax.Hub.Queue(['Typeset', MathJax.Hub, $container[0]]) } define(['base/js/namespace'], ({ notebook, actions, keyboard_manager, pager, }) => ({ onload() { edit_actions.forEach(a => add_edit_shortcut(notebook, actions, keyboard_manager, a)) pager.events.on('open_with_text.Pager', (event, {data: {'text/html': html}}) => render_math(pager, html)) }, })) IRkernel/inst/kernelspec/logo-64x64.png0000644000176200001440000002045414362570132017344 0ustar liggesusersPNG  IHDR@@iq AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ pHYs99`!IDATxZyUŕ{7IE1ƍK04.J@Q>J 2/Lޠf4Nf1Y[i1 uׯFtu{rꜯNs7"Ћ@/"Ћ_*"xErƐ!C RZ{啦C Gf^nP! è< CG! RmR]ap0 >ZѦo}}Һ[ہ):Hh46;2)aښƓ,GI13#" #"R_-a>cr/!$/%,$10A %1@Xfw )^DUՔU(R΄F53)_0@ܘo5sU7YUC#AP'g:C9p[ܵ+tՍmjrc*ަdLP@m q 7\ے onn兢 K`뺴[ /yWFVEE沍RwDi)u#-p7w n+v›|4@Ʒ.Ѹ /r'cuwrr rT=&``S풙f3yRp҂ěIh+!gπVV"bSxHhVx]FY aG_pj>= ?,L\>v\mcL"S݄SJپnߞ~JfH SG0H֒ -z\ԋt+j+?u;W[{ w>9ضG\>B-XDx5#- Zk}ј1cܘDnz9i6d nx/>brŃmzM#͘cmUEm|y 1H2pㄉP DP%+^JGVEe0>o:ڷ%r5uAWf)*%N!ۨ:e_X=Lx̌U';s9tB+p 1RQ)sP50#e}Ʉo|K츔 9IK܀x }Kzi [c/?Y MXhqA 8(1:6_PcxLCOF+lЧBE~4sW&WNcܝŪD@ya #(O9QCdhbyױ)ZfQYsp"$T[Bє;C- yI!LA}G7mrdsI\=G'i@p؁MAb+!!GJoEb.Ʈ Ч4Aq͐|T*7D;-Ƨ5 $6Bx 9)60煼.O U\~L ?oݚ9$ l(j /t/5JHP"9}lӴ]I7g/ʻ^RZ*i汣L׾ڧ[q);_ AէIPyz iDޫ,*x橶e]V}woyghX-$ږXx΋'<@&yf1nQ.gz R> G\DG{;*a?+,`|[l W;}f[*?&@O%7['g6$ n" l?c=l4@΂7'ҙ4bᄑ'in ",_x8FS {t?P9aX @$C{cpPyKRQvAm:<%ւssy)ZL"rڒ*5 A (G/\,($(U籱@%&4]f)f|'ؖ_Qj`-3GQ{1ɡ} V(wVn$S! }W3KqI\QYSo5|Bc$(yIR1I.99^ 2L\Dl@`(I:fYw|;d d'Y hi ѡIbBνƣQBC ~^`>8D_ֱߔ5@Sª ل&sԳAq^u A%C mF0LesQl{F߽7{ #76k6AtOq͒O-8*>p]E~m~ ? lHppgP૤3OUGIkAI#UAIh bO ݡ "o0X4P wǺ IRkernel/inst/kernelspec/kernel.json0000644000176200001440000000017314362570132017254 0ustar liggesusers{"argv": ["R", "--slave", "-e", "IRkernel::main()", "--args", "{connection_file}"], "display_name":"R", "language":"R" }