pax_global_header00006660000000000000000000000064146520054270014517gustar00rootroot0000000000000052 comment=058c6fdd77dd27d82594d7a66fa03c467d44b137 r-cran-irkernel-1.3.2+git20240429.124f234/000077500000000000000000000000001465200542700170275ustar00rootroot00000000000000r-cran-irkernel-1.3.2+git20240429.124f234/.Rbuildignore000066400000000000000000000004031465200542700214520ustar00rootroot00000000000000.gitignore .github Makefile .ipynb_checkpoints .*.ipynb ^.*\.Rproj$ ^\.Rproj\.user$ .idea/.*$ [.]pdf$ example-notebooks/.*$ ^\.travis\.(sh|yml)$ ^tests/testthat/jkt/[^j][^u][^p].*$ ^tests/testthat/njr/[^n][^d][^j].*$ Dockerfile cran-comments.md ^LICENSE\.md$ r-cran-irkernel-1.3.2+git20240429.124f234/.github/000077500000000000000000000000001465200542700203675ustar00rootroot00000000000000r-cran-irkernel-1.3.2+git20240429.124f234/.github/issue_template.md000066400000000000000000000023051465200542700237340ustar00rootroot00000000000000 - [ ] I followed the [official installation instructions](https://irkernel.github.io/installation/) - [ ] I reproduced the error with the newest versions of `IRkernel`, `IRdisplay`, and `repr` - [ ] I included a [minimal reproducible example](https://stackoverflow.com/a/5963610/247482) - [ ] This the right repository: I think the issue is with IRkernel, not [IRkernel/repr](https://github.com/IRkernel/repr/issues/new) or a third party repository r-cran-irkernel-1.3.2+git20240429.124f234/.github/workflows/000077500000000000000000000000001465200542700224245ustar00rootroot00000000000000r-cran-irkernel-1.3.2+git20240429.124f234/.github/workflows/r.yml000066400000000000000000000020361465200542700234110ustar00rootroot00000000000000name: R on: push: branches: [master] pull_request: branches: [master] merge_group: jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: r-version: ['oldrel-1', 'release', 'devel'] os: [ubuntu-latest, macos-latest] env: R_KEEP_PKG_SOURCE: yes steps: - uses: actions/checkout@v3 with: submodules: yes # R - name: Set up R ${{ matrix.r-version }} uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.r-version }} - uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: any::rcmdcheck needs: check - if: runner.os == 'macOS' run: brew install --cask xquartz # Python & Jupyter - uses: actions/setup-python@v4 with: python-version: '3.x' cache: 'pip' cache-dependency-path: 'tests/requirements.txt' - run: pip install -r tests/requirements.txt # Checks - uses: r-lib/actions/check-r-package@v2 r-cran-irkernel-1.3.2+git20240429.124f234/.gitignore000066400000000000000000000001241465200542700210140ustar00rootroot00000000000000*.ipynb_checkpoints __pycache__ .directory .idea .Rproj.user .RData .Rhistory *.pdf r-cran-irkernel-1.3.2+git20240429.124f234/DESCRIPTION000066400000000000000000000026611465200542700205420ustar00rootroot00000000000000Package: 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.9000 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 r-cran-irkernel-1.3.2+git20240429.124f234/Dockerfile.dev000066400000000000000000000007371465200542700216050ustar00rootroot00000000000000# Installs Jupyter Notebook and IRkernel dependencies into a r-notebook-dev image FROM jupyter/r-notebook RUN Rscript -e "install.packages(c(\"devtools\", \"testthat\", \"roxygen2\"), repos = c(\"http://irkernel.github.io/\", \"http://cran.rstudio.com\"))" RUN Rscript -e "library(\"devtools\")" -e "install_github(\"IRkernel/repr\")" -e "install_github(\"IRkernel/IRdisplay\")" RUN pip install jupyter_kernel_test ndjson-testrunner RUN unlink /opt/conda/lib/libstdc++.so.6 r-cran-irkernel-1.3.2+git20240429.124f234/IRkernel.Rproj000066400000000000000000000006131465200542700215600ustar00rootroot00000000000000Version: 1.0 RestoreWorkspace: Default SaveWorkspace: Default AlwaysSaveHistory: Default EnableCodeIndexing: Yes UseSpacesForTab: Yes NumSpacesForTab: 4 Encoding: UTF-8 RnwWeave: Sweave LaTeX: pdfLaTeX AutoAppendNewline: Yes BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source PackageCheckArgs: --as-cran PackageRoxygenize: rd,collate,namespace r-cran-irkernel-1.3.2+git20240429.124f234/LICENSE000066400000000000000000000000631465200542700200330ustar00rootroot00000000000000YEAR: 2014-2022 COPYRIGHT HOLDER: IRkernel authors r-cran-irkernel-1.3.2+git20240429.124f234/LICENSE.md000066400000000000000000000020701465200542700204320ustar00rootroot00000000000000# MIT License Copyright (c) 2014-2022 IRkernel authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. r-cran-irkernel-1.3.2+git20240429.124f234/Makefile000066400000000000000000000016621465200542700204740ustar00rootroot00000000000000.PHONY: IRkernel.pdf docs check test docker_dev_image docker_dev docker_test DEV_IMAGE:=jupyter/r-notebook-dev IRkernel.pdf: man/*.Rd RD2PDF_INPUTENC=inputenx R_RD4PDF=ae,hyper R CMD Rd2pdf --force --batch --no-preview --encoding=UTF-8 --output=$@ . docs: Rscript -e "library(devtools); document('.'); check_doc()" check: Rscript -e "library(devtools); check()" test: Rscript -e "library(testthat); test()" docker_dev_image: @docker build -f Dockerfile.dev -t $(DEV_IMAGE) . docker_dev: docker_dev_image @docker run -it --rm \ -p 8888:8888 \ -v `pwd`:/src_irkernel \ $(DEV_IMAGE) bash -c 'R CMD INSTALL -l /opt/conda/lib/R/library /src_irkernel && \ jupyter notebook --no-browser --port 8888 --ip='*'' docker_test: docker_dev_image @echo 'Running IRkernel tests' @docker run -it --rm \ -v `pwd`:/src_irkernel \ $(DEV_IMAGE) bash -c 'R CMD build /src_irkernel && \ R CMD check IRkernel*.tar.gz' r-cran-irkernel-1.3.2+git20240429.124f234/NAMESPACE000066400000000000000000000026631465200542700202550ustar00rootroot00000000000000# 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) r-cran-irkernel-1.3.2+git20240429.124f234/R/000077500000000000000000000000001465200542700172305ustar00rootroot00000000000000r-cran-irkernel-1.3.2+git20240429.124f234/R/class_unions.r000066400000000000000000000003031465200542700221070ustar00rootroot00000000000000setClassUnion('functionOrNULL', members = c('function', 'NULL')) setClassUnion('recordedplotOrNULL', members = c('recordedplot', 'NULL')) setClassUnion('listOrNULL', members = c('list', 'NULL')) r-cran-irkernel-1.3.2+git20240429.124f234/R/comm_manager.r000066400000000000000000000170511465200542700220440ustar00rootroot00000000000000#' 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() } } ) ) r-cran-irkernel-1.3.2+git20240429.124f234/R/compat.r000066400000000000000000000017361465200542700207050ustar00rootroot00000000000000UNICODE_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) } } r-cran-irkernel-1.3.2+git20240429.124f234/R/completion.r000066400000000000000000000063351465200542700215730ustar00rootroot00000000000000completions <- 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 && length(comps)) 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 <- '=|::' # 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) # split off everything before the last special operator (a la utils:::specialOpLocs) # NB: use look-behind so that we can use the output directly without worrying about # match.length. Separate look-behind conditions because each one must have a # fixed length. lead_matches <- gregexpr("(?<=[$@])|(?<=[^:]::)|(?<=:::)", comps, perl = TRUE) last_match <- vapply(lead_matches, tail, n = 1L, integer(1L)) has_match <- last_match > 0L leading <- rep("", length(comps)) comps_with_leading <- comps[has_match] leading[has_match] <- substr(comps_with_leading, 1L, last_match - 1L) comps[has_match] <- substr(comps_with_leading, last_match, nchar(comps_with_leading)) # wrap non-identifiers with ``; h/t the related r-devel thread: # https://stat.ethz.ch/pipermail/r-devel/2023-March/082388.html non_empty <- nzchar(comps) comps[non_empty] <- vapply( comps[non_empty], # TODO(R>=4.0.0) use deparse1() for brevity function(nm) paste(deparse(as.name(nm), backtick = TRUE), collapse = " "), character(1L) ) # good coding style for completions trailing <- gsub('=', ' = ', trailing, fixed = TRUE) comps <- paste0(leading, comps, trailing) comps[comps == "... = "] <- "..." comps } r-cran-irkernel-1.3.2+git20240429.124f234/R/environment_runtime.r000066400000000000000000000004441465200542700235240ustar00rootroot00000000000000# 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 r-cran-irkernel-1.3.2+git20240429.124f234/R/environment_shadow.r000066400000000000000000000116031465200542700233250ustar00rootroot00000000000000# 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) } } r-cran-irkernel-1.3.2+git20240429.124f234/R/execution.r000066400000000000000000000303511465200542700214200ustar00rootroot00000000000000#' @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') }) ) r-cran-irkernel-1.3.2+git20240429.124f234/R/handlers.r000066400000000000000000000014401465200542700212120ustar00rootroot00000000000000prepare_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 } } r-cran-irkernel-1.3.2+git20240429.124f234/R/help.r000066400000000000000000000043571465200542700203540ustar00rootroot00000000000000#' 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 r-cran-irkernel-1.3.2+git20240429.124f234/R/installspec.r000066400000000000000000000064331465200542700217420ustar00rootroot00000000000000#' 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} #' @param env (optional) Named list of environment variables to set in the kernel (default: NULL) #' #' @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'), env = NULL ) { 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 spec$env <- if (!is.null(env)) env else namedlist() if (!is.list(spec$env) || is.null(names(spec$env))) stop('`env` needs to be a named list') if (!is.null(rprofile)) spec$env$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) } r-cran-irkernel-1.3.2+git20240429.124f234/R/kernel.r000066400000000000000000000363531465200542700207050ustar00rootroot00000000000000#' @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') }) ) r-cran-irkernel-1.3.2+git20240429.124f234/R/logging.r000066400000000000000000000042661465200542700210510ustar00rootroot00000000000000# 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) } } r-cran-irkernel-1.3.2+git20240429.124f234/R/main.r000066400000000000000000000011431465200542700203360ustar00rootroot00000000000000#' 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() } r-cran-irkernel-1.3.2+git20240429.124f234/R/onload.r000066400000000000000000000001411465200542700206630ustar00rootroot00000000000000.onLoad <- function(libname = NULL, pkgname = NULL) { init_options() init_backup_env() } r-cran-irkernel-1.3.2+git20240429.124f234/R/options.r000066400000000000000000000026371465200542700211160ustar00rootroot00000000000000#' @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) } } } r-cran-irkernel-1.3.2+git20240429.124f234/R/utils.r000066400000000000000000000023261465200542700205560ustar00rootroot00000000000000ellip_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') r-cran-irkernel-1.3.2+git20240429.124f234/README.md000066400000000000000000000077461465200542700203240ustar00rootroot00000000000000# 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. r-cran-irkernel-1.3.2+git20240429.124f234/cran-comments.md000066400000000000000000000021361465200542700221210ustar00rootroot00000000000000## Release summary This package is new on CRAN. It contains the R kernel for the Jupyter ecosystem. The kernel executes R code, which the frontend (Jupyter Notebook or other frontends) submits to the kernel via the network. ## Test environments * local Win7 64bit install, R 3.2.5, R 3.3.0 and r-devel (3.4) * Ubuntu 12.04 (on travis-ci), oldrelease, release, and r-devel ## R CMD check results ```r Found the following (possibly) invalid URLs: URL: http://localhost:8888/ From: README.md Status: Error Message: libcurl Fehlercode 7 \tFailed to connect to localhost port 8888: Verbindungsaufbau abgelehnt Status: 1 NOTE ``` This arises from the fact that a common usage of this package involves starting a server on localhost. As expected this URL is only reachable after starting it. ## Downstream dependencies It is assumed that there won't be any code dependencies on this package, as it implements an application without any API apart from the startup function and the implemented Jupyter Messaging spec. It's usually started as `R --slave -e IRkernel::main() --args {connection_file}`. r-cran-irkernel-1.3.2+git20240429.124f234/example-notebooks/000077500000000000000000000000001465200542700224635ustar00rootroot00000000000000r-cran-irkernel-1.3.2+git20240429.124f234/example-notebooks/Comm_Demo.ipynb000066400000000000000000000070151465200542700253700ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "library(IRdisplay)\n", "library(IRkernel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "target_name_in_kernel <- 'kernel_target'\n", "target_name_in_browser <- 'browser_target'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Open handler in the kernel" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "target_handler <- function(comm, data) {\n", " cat('Got open call\\n')\n", " print(data)\n", " \n", " comm$on_msg(function(msg) {\n", " cat('Got message\\n')\n", " print(msg)\n", " })\n", " \n", " comm$on_close(function() {\n", " cat('Got close\\n')\n", " })\n", "}\n", "\n", "comm_manager()$register_target(target_name_in_kernel, target_handler)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Test in Javascript console" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "display_javascript(sprintf('\n", "const comm = IPython.notebook.kernel.comm_manager.new_comm(\"%s\", {list: [1,2,3,4,5,6,7,8,9]})\n", "\n", "comm.send({foo: \"bar\"})\n", "\n", "comm.close()\n", "', target_name_in_kernel))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Open handler in browser" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Register target in browser" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "display_javascript(sprintf('\n", "IPython.notebook.kernel.comm_manager.register_target(\"%s\", () =>\n", " console.log(\"Got open message\", arguments))\n", "', target_name_in_browser))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "comm <- comm_manager()$new_comm(target_name_in_browser)\n", "comm$open()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Need to get a handle on the comm object in the browser. \n", "Put a breakpoint in comm.js in CommManager.prototype.comm_open and run `window.testcomm = comm`\n", "\n", "Register on_msg and on_close:\n", "* `window.testcomm.on_msg(function(data){console.log(\"Got data\", data)})`\n", "* `window.testcomm.on_close(function(data){console.log(\"Got close\", data)})`" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "comm$send()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "comm$close()" ] } ], "metadata": { "kernelspec": { "display_name": "R", "language": "R", "name": "ir" }, "language_info": { "codemirror_mode": "r", "file_extension": ".r", "mimetype": "text/x-r-source", "name": "R", "pygments_lexer": "r", "version": "3.2.3" } }, "nbformat": 4, "nbformat_minor": 0 } r-cran-irkernel-1.3.2+git20240429.124f234/example-notebooks/Demo.ipynb000066400000000000000000001465101465200542700244210ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Code without visible output:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "a <- 8" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "b <- 4:59" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With visible output:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/html": [ "\n", "
  1. 12
  2. 13
  3. 14
  4. 15
  5. 16
  6. 17
  7. 18
  8. 19
  9. 20
  10. 21
  11. 22
  12. 23
  13. 24
  14. 25
  15. 26
  16. 27
  17. 28
  18. 29
  19. 30
  20. 31
  21. 32
  22. 33
  23. 34
  24. 35
  25. 36
  26. 37
  27. 38
  28. 39
  29. 40
  30. 41
  31. 42
  32. 43
  33. 44
  34. 45
  35. 46
  36. 47
  37. 48
  38. 49
  39. 50
  40. 51
  41. 52
  42. 53
  43. 54
  44. 55
  45. 56
  46. 57
  47. 58
  48. 59
  49. 60
  50. 61
  51. 62
  52. 63
  53. 64
  54. 65
  55. 66
  56. 67
\n" ], "text/latex": [ "\\begin{enumerate*}\n", "\\item 12\n", "\\item 13\n", "\\item 14\n", "\\item 15\n", "\\item 16\n", "\\item 17\n", "\\item 18\n", "\\item 19\n", "\\item 20\n", "\\item 21\n", "\\item 22\n", "\\item 23\n", "\\item 24\n", "\\item 25\n", "\\item 26\n", "\\item 27\n", "\\item 28\n", "\\item 29\n", "\\item 30\n", "\\item 31\n", "\\item 32\n", "\\item 33\n", "\\item 34\n", "\\item 35\n", "\\item 36\n", "\\item 37\n", "\\item 38\n", "\\item 39\n", "\\item 40\n", "\\item 41\n", "\\item 42\n", "\\item 43\n", "\\item 44\n", "\\item 45\n", "\\item 46\n", "\\item 47\n", "\\item 48\n", "\\item 49\n", "\\item 50\n", "\\item 51\n", "\\item 52\n", "\\item 53\n", "\\item 54\n", "\\item 55\n", "\\item 56\n", "\\item 57\n", "\\item 58\n", "\\item 59\n", "\\item 60\n", "\\item 61\n", "\\item 62\n", "\\item 63\n", "\\item 64\n", "\\item 65\n", "\\item 66\n", "\\item 67\n", "\\end{enumerate*}\n" ], "text/markdown": [ "1. 12\n", "2. 13\n", "3. 14\n", "4. 15\n", "5. 16\n", "6. 17\n", "7. 18\n", "8. 19\n", "9. 20\n", "10. 21\n", "11. 22\n", "12. 23\n", "13. 24\n", "14. 25\n", "15. 26\n", "16. 27\n", "17. 28\n", "18. 29\n", "19. 30\n", "20. 31\n", "21. 32\n", "22. 33\n", "23. 34\n", "24. 35\n", "25. 36\n", "26. 37\n", "27. 38\n", "28. 39\n", "29. 40\n", "30. 41\n", "31. 42\n", "32. 43\n", "33. 44\n", "34. 45\n", "35. 46\n", "36. 47\n", "37. 48\n", "38. 49\n", "39. 50\n", "40. 51\n", "41. 52\n", "42. 53\n", "43. 54\n", "44. 55\n", "45. 56\n", "46. 57\n", "47. 58\n", "48. 59\n", "49. 60\n", "50. 61\n", "51. 62\n", "52. 63\n", "53. 64\n", "54. 65\n", "55. 66\n", "56. 67\n", "\n", "\n" ], "text/plain": [ " [1] 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36\n", "[26] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61\n", "[51] 62 63 64 65 66 67" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "a + b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Printing is captured and sent to the frontend:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1] \"Hello world! Love, R in Jupyter.\"\n" ] } ], "source": [ "print('Hello world! Love, R in Jupyter.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So are errors:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "f2 <- function() stop('deep error')\n", "throw <- function() f2()" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/html": [ "'this line is run / displayed'" ], "text/latex": [ "'this line is run / displayed'" ], "text/markdown": [ "'this line is run / displayed'" ], "text/plain": [ "[1] \"this line is run / displayed\"" ] }, "metadata": {}, "output_type": "display_data" }, { "ename": "ERROR", "evalue": "Error in f2(): deep error\n", "output_type": "error", "traceback": [ "Error in f2(): deep error\nTraceback:\n", "1. throw()", "2. f2() # at line 2 of file ", "3. stop(\"deep error\") # at line 1 of file " ] } ], "source": [ "'this line is run / displayed'\n", "throw()\n", "'this line is not run / displayed'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plotting works too:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "image/png": "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\n2djYvL+nWbNmEydO9Pf3Dw0NbdWqlbZ+MgCAzMmt2KWnPg0MDBTZLnaHDx9u06ZNdnZu2rSp\nYcOG/yUb9MKlS5d8fHwKFCgQHBzcuHFjzeLjx48HDBiwevXqwoULT58+PSEhQa1WW1tbZ/kd\nNOtxcXFCiBMnTtStW9fFxSXzts6dO/v7+x8/fpxiBwDIKXIrdoYmJWbOnJn9/Y0aNQoODv70\nid2SJUuOHDni7Oz8n9NBD0ydOlWlUu3du7dSpUoZi4UKFdqxY0ft2rVnz549cuRIa2trExOT\nu3fvZvkdNOtOTk7p6emvX792cnLKcptm/eXLl+8vJiQkHD58+N69e0ZGRpUqVapduzY3TwEA\nZJ/cip3SuMiECRO+YL9S6eXl9ek9e/fuFULw+poXJCUlhYaGNmvW7P1Wp5EvX74RI0b07Nkz\nNDS0S5cuTZo0OXDgwJ07d0qUKPHBzlWrVgkhmjZtqlQqraysnjx5kuWfFRMTI4Sws7PTfKlS\nqebMmfPTTz+9efMmY0/p0qWXLFnSpEmTnPoBAQDyRlkB/ufJkyfJycnly5fPclqhQgUhxP37\n94UQ3377bVpaWtu2bd8/t1OpVD/++OOmTZu8vb01m93d3U+ePBkdHZ35u23dulUIUa9ePc2X\nQ4YMGT9+vKOj48KFC48fPx4aGjpx4sQnT540b958165dOf2DAgDkSW4ndsB/oblBSUpKSpbT\n5OTkjD1169adP3/+yJEjy5Ur16JFi3LlysXHx+/bt+/u3buVK1fWHNoJIUaMGLF7924fH5/g\n4OD3P5MXFhY2c+bMcuXKNWvWTAgRGhr622+/NW/efOfOnRkXZDRt2rRPnz7u7u59+/Zt0KDB\nxz7SBwBABk7sgP9xcHCws7M7duxYllPNesZ53rBhw44cOdKgQYO9e/fOmjVryZIlKSkp/v7+\np0+ftrW11ezx8PAYP3788ePHy5UrN2nSpE2bNq1YsaJDhw7Nmzc3MjLavHlzvnz5hBC//fab\noaHhqlWrPrjMtmTJkj///POLFy+CgoJy8ccGAMgFJ3bIWxISEtavX3/gwIGYmBgbG5s6der4\n+fkVLlxYM1UqlT4+PgEBAb///nuvXr3e/40PHz6cM2dO4cKF37842t3dPTQ0NDExMSYmJn/+\n/I6Ojpn/xFmzZrm5uU2dOjXjsh6FQtGyZcsFCxaULFlSs3Lu3Llq1aplxHif5jOg586d69u3\n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\nAAAgMTkUO4uqnl2qSh0CAABAalyKBQAAkAmKHQAAgExQ7AAAAGRCDvfYacbVq1dNTEze8Ivk\n5+dv3LjRyclJT49K/VIKCwuvX79erVo1zthL4oy9Ks7Yq+KMvSrO2KsqLCy8devW0KFDDQ0N\npc5SsqtXr0od4Zkodi+m/os1fPhwqYMAAFBaBAYGSh3hBbSzd1LsXmzAgAEFBQXZ2dlv/qXO\nnTu3ZcuWFi1aODk5vflXKw1u3bp19OhRztjL44y9Ks7Yq+KMvSrO2KtSn7H+/fvXq1dP6izP\nZGpqOmDAAKlTlEQFDdq2bZsQYtu2bVIH0RmcsVfFGXtVnLFXxRl7VZyxV8UZexNc7wcAAJAJ\nih0AAIBMUOwAAABkgmIHAAAgExQ7AAAAmaDYAQAAyATFDgAAQCYodgAAADJBsQMAAJAJip1G\nmZqaFv0XL4Mz9qo4Y6+KM/aqOGOvijP2qjhjb0KhUqmkzlCKKJXKAwcOeHt76+vrS51FN3DG\nXhVn7FVxxl4VZ+xVccZeFWfsTVDsAAAAZIJLsQAAADJBsQMAAJAJih0AAIBMUOwAAABkgmIH\nAAAgExQ7AAAAmaDYAQAAyATFDgAAQCYodgAAADJBsQMAAJAJih0AAIBMUOwAAABkgmIHAAAg\nExQ7AAAAmaDYAQAAyATFDgAAQCYodgAAADJhIHWAUqQwP3nzimW7/4xOzTOoWqfZkE/HNnUo\nI3UoXaDK7d3uI9clIfNrWUsdRdslxexdtem3s1dv56iMnd0b9Rk2prV7ealDaS9VQVro+sCd\nB04mpGSalXNs0rrTx8O7lDPg192Xkpd+unu3qQ6DAtcMqSZ1Fi1VmJ/s3bbP0+PVh/7ISXuO\nh+f2rdyw7UzsbaMKLg1bdvt8eDsjhdSZdIsKGpGfealDVUshhJGlbWU7SyGEvrHjyr8fSJ1L\nB8TvGyWE+OhEotRBtN2ppcMM9RRCCCs7JycbcyGEQs94yHdHpM6lpQpybveqU04IYWhmW8Ot\nqqWRvhDCqkbHi5n5UkfTBYU5ExpWEEI09D8rdRTtlXFvbYk/djlpz3F65XBTPYVCoW9XqbKl\nsb4Qwva9YYl5Sqlz6RJ+N9WQn3v67rnxuO3skNTUxNuJjy7sXmRacO8zrx7pSpXU0bSWMjnu\n0o61X3t2LvkfRzwp52FYi4kbhYnbpqPxaYlxccnpNw8HNbIQm75otfT6I6nTaaOjn7ULuZDS\n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", "text/plain": [ "plot without title" ] }, "metadata": { "image/png": { "height": 420, "width": 420 } }, "output_type": "display_data" } ], "source": [ "x <- seq(0, 2*pi, length.out=50)\n", "plot(x, sin(x))" ] } ], "metadata": { "kernelspec": { "display_name": "R", "language": "R", "name": "ir" }, "language_info": { "codemirror_mode": "r", "file_extension": ".r", "mimetype": "text/x-r-source", "name": "R", "pygments_lexer": "r", "version": "4.0.3" } }, "nbformat": 4, "nbformat_minor": 4 } r-cran-irkernel-1.3.2+git20240429.124f234/example-notebooks/Display.ipynb000066400000000000000000000373071465200542700251450ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "library(IRdisplay)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The IRkernel already displays things automatically, but the [`IRdisplay` package](http://irkernel.github.io/docs/IRdisplay/0.4.4/) allows to programmatically access this machinery.\n", "\n", "You can display all standard formats of an object via [`display`](http://irkernel.github.io/docs/IRdisplay/0.4.4/display.html). This uses [`repr_*`](http://irkernel.github.io/docs/repr/0.8/repr-generics.html) and the [`jupyter.display_mimetypes` option](http://irkernel.github.io/docs/IRdisplay/0.4.4/IRdisplay-options.html):" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/html": [ "$jupyter.display_mimetypes = \n", "
  1. 'text/plain'
  2. 'text/html'
  3. 'text/markdown'
  4. 'text/latex'
  5. 'application/json'
  6. 'application/javascript'
  7. 'application/geo+json'
  8. 'application/vdom.v1+json'
  9. 'application/vnd.plotly.v1+json'
  10. 'application/vnd.vegalite.v2+json'
  11. 'application/vnd.vega.v4+json'
  12. 'application/pdf'
  13. 'image/png'
  14. 'image/jpeg'
  15. 'image/svg+xml'
\n" ], "text/latex": [ "\\textbf{\\$jupyter.display\\_mimetypes} = \\begin{enumerate*}\n", "\\item 'text/plain'\n", "\\item 'text/html'\n", "\\item 'text/markdown'\n", "\\item 'text/latex'\n", "\\item 'application/json'\n", "\\item 'application/javascript'\n", "\\item 'application/geo+json'\n", "\\item 'application/vdom.v1+json'\n", "\\item 'application/vnd.plotly.v1+json'\n", "\\item 'application/vnd.vegalite.v2+json'\n", "\\item 'application/vnd.vega.v4+json'\n", "\\item 'application/pdf'\n", "\\item 'image/png'\n", "\\item 'image/jpeg'\n", "\\item 'image/svg+xml'\n", "\\end{enumerate*}\n" ], "text/markdown": [ "**$jupyter.display_mimetypes** = 1. 'text/plain'\n", "2. 'text/html'\n", "3. 'text/markdown'\n", "4. 'text/latex'\n", "5. 'application/json'\n", "6. 'application/javascript'\n", "7. 'application/geo+json'\n", "8. 'application/vdom.v1+json'\n", "9. 'application/vnd.plotly.v1+json'\n", "10. 'application/vnd.vegalite.v2+json'\n", "11. 'application/vnd.vega.v4+json'\n", "12. 'application/pdf'\n", "13. 'image/png'\n", "14. 'image/jpeg'\n", "15. 'image/svg+xml'\n", "\n", "\n" ], "text/plain": [ "$jupyter.display_mimetypes\n", " [1] \"text/plain\" \"text/html\" \n", " [3] \"text/markdown\" \"text/latex\" \n", " [5] \"application/json\" \"application/javascript\" \n", " [7] \"application/geo+json\" \"application/vdom.v1+json\" \n", " [9] \"application/vnd.plotly.v1+json\" \"application/vnd.vegalite.v2+json\"\n", "[11] \"application/vnd.vega.v4+json\" \"application/pdf\" \n", "[13] \"image/png\" \"image/jpeg\" \n", "[15] \"image/svg+xml\" \n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "options('jupyter.display_mimetypes')" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "scrolled": true }, "outputs": [ { "data": { "text/html": [ "\n", "
display {IRdisplay}R Documentation
\n", "\n", "

Create and use multiple available reprs

\n", "\n", "

Description

\n", "\n", "

Both functions create a mimebundle for multiple reprs.\n", "display proceeds to publish it using publish_mimebundle.\n", "prepare_mimebundle returns it (see Value for details)\n", "

\n", "\n", "\n", "

Usage

\n", "\n", "
\n",
       "display(obj, ..., mimetypes = getOption(\"jupyter.display_mimetypes\"),\n",
       "  error_handler = stop)\n",
       "\n",
       "prepare_mimebundle(obj,\n",
       "  mimetypes = getOption(\"jupyter.display_mimetypes\"), metadata = NULL,\n",
       "  error_handler = stop)\n",
       "
\n", "\n", "\n", "

Arguments

\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
obj\n", "

The object to create representations for

\n", "
mimetypes\n", "

Mimetypes to create reprs for. The defaults are defined by the option jupyter.display_mimetypes. (see: IRdisplay-options)

\n", "
error_handler\n", "

Function used when errors in individual reprs occur

\n", "
metadata, ...\n", "

Metadata to attach to the result (can be expanded by additional metadata)

\n", "
\n", "\n", "\n", "

Value

\n", "\n", "

prepare_mimebundle returns a list with items corresponding to the parameters of publish_mimebundle (data and metadata)\n", "

\n", "\n", "\n", "

See Also

\n", "\n", "

publish_mimebundle\n", "

\n", "\n", "\n", "

Examples

\n", "\n", "
\n",
       "bundle <- prepare_mimebundle(diag(3))\n",
       "\n",
       "## Not run: ## (Run inside of an IRkernel)\n",
       "display(help(display))\n",
       "## End(Not run)\n",
       "\n",
       "
\n", "\n", "
[Package IRdisplay version 0.7.0 ]
" ], "text/latex": [ "\\inputencoding{utf8}\n", "\\HeaderA{display}{Create and use multiple available reprs}{display}\n", "\\aliasA{prepare\\_mimebundle}{display}{prepare.Rul.mimebundle}\n", "%\n", "\\begin{Description}\\relax\n", "Both functions create a mimebundle for multiple reprs.\n", "\\code{display} proceeds to publish it using \\code{\\LinkA{publish\\_mimebundle}{publish.Rul.mimebundle}}.\n", "\\code{prepare\\_mimebundle} returns it (see \\emph{Value} for details)\n", "\\end{Description}\n", "%\n", "\\begin{Usage}\n", "\\begin{verbatim}\n", "display(obj, ..., mimetypes = getOption(\"jupyter.display_mimetypes\"),\n", " error_handler = stop)\n", "\n", "prepare_mimebundle(obj,\n", " mimetypes = getOption(\"jupyter.display_mimetypes\"), metadata = NULL,\n", " error_handler = stop)\n", "\\end{verbatim}\n", "\\end{Usage}\n", "%\n", "\\begin{Arguments}\n", "\\begin{ldescription}\n", "\\item[\\code{obj}] The object to create representations for\n", "\n", "\\item[\\code{mimetypes}] Mimetypes to create reprs for. The defaults are defined by the option \\code{jupyter.display\\_mimetypes}. (see: \\LinkA{IRdisplay-options}{IRdisplay.Rdash.options})\n", "\n", "\\item[\\code{error\\_handler}] Function used when errors in individual reprs occur\n", "\n", "\\item[\\code{metadata, ...}] Metadata to attach to the result (can be expanded by additional metadata)\n", "\\end{ldescription}\n", "\\end{Arguments}\n", "%\n", "\\begin{Value}\n", "\\code{prepare\\_mimebundle} returns a list with items corresponding to the parameters of \\code{\\LinkA{publish\\_mimebundle}{publish.Rul.mimebundle}} (\\code{data} and \\code{metadata})\n", "\\end{Value}\n", "%\n", "\\begin{SeeAlso}\\relax\n", "\\code{\\LinkA{publish\\_mimebundle}{publish.Rul.mimebundle}}\n", "\\end{SeeAlso}\n", "%\n", "\\begin{Examples}\n", "\\begin{ExampleCode}\n", "bundle <- prepare_mimebundle(diag(3))\n", "\n", "## Not run: ## (Run inside of an IRkernel)\n", "display(help(display))\n", "## End(Not run)\n", "\n", "\\end{ExampleCode}\n", "\\end{Examples}" ], "text/plain": [ "display package:IRdisplay R Documentation\n", "\n", "_\bC_\br_\be_\ba_\bt_\be _\ba_\bn_\bd _\bu_\bs_\be _\bm_\bu_\bl_\bt_\bi_\bp_\bl_\be _\ba_\bv_\ba_\bi_\bl_\ba_\bb_\bl_\be _\br_\be_\bp_\br_\bs\n", "\n", "_\bD_\be_\bs_\bc_\br_\bi_\bp_\bt_\bi_\bo_\bn:\n", "\n", " Both functions create a mimebundle for multiple reprs. ‘display’\n", " proceeds to publish it using ‘publish_mimebundle’.\n", " ‘prepare_mimebundle’ returns it (see _Value_ for details)\n", "\n", "_\bU_\bs_\ba_\bg_\be:\n", "\n", " display(obj, ..., mimetypes = getOption(\"jupyter.display_mimetypes\"),\n", " error_handler = stop)\n", " \n", " prepare_mimebundle(obj,\n", " mimetypes = getOption(\"jupyter.display_mimetypes\"), metadata = NULL,\n", " error_handler = stop)\n", " \n", "_\bA_\br_\bg_\bu_\bm_\be_\bn_\bt_\bs:\n", "\n", " obj: The object to create representations for\n", "\n", "mimetypes: Mimetypes to create reprs for. The defaults are defined by\n", " the option ‘jupyter.display_mimetypes’. (see:\n", " IRdisplay-options)\n", "\n", "error_handler: Function used when errors in individual reprs occur\n", "\n", "metadata, ...: Metadata to attach to the result (can be expanded by\n", " additional metadata)\n", "\n", "_\bV_\ba_\bl_\bu_\be:\n", "\n", " ‘prepare_mimebundle’ returns a list with items corresponding to\n", " the parameters of ‘publish_mimebundle’ (‘data’ and ‘metadata’)\n", "\n", "_\bS_\be_\be _\bA_\bl_\bs_\bo:\n", "\n", " ‘publish_mimebundle’\n", "\n", "_\bE_\bx_\ba_\bm_\bp_\bl_\be_\bs:\n", "\n", " bundle <- prepare_mimebundle(diag(3))\n", " \n", " ## Not run:\n", " ## (Run inside of an IRkernel)\n", " display(help(display))\n", " ## End(Not run)\n", " " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display(help(display)) #without display(), help appears in a pager" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To display specific formats, you can use the [`display_`](http://irkernel.github.io/docs/IRdisplay/0.4.4/display_-less-than-image-greater-than.html) and [`display_text`](http://irkernel.github.io/docs/IRdisplay/0.4.4/display_-less-than-text-greater-than.html) functions (their only difference is that images support convenience `width` and `height` arguments):" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/html": [ "

HTML output

" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display_html('

HTML output

')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plots are also displayed automatically by the IRkernel, but if you want to display e. g. a PNG from another source, this is how to do it:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "image/png": "R0lGODdhAQABAIAAAPxqbAAAACwAAAAAAQABAAACAkQBADsK" }, "metadata": { "height": 64, "width": 64 }, "output_type": "display_data" } ], "source": [ "raw_png_data <- base64enc::base64decode('R0lGODdhAQABAIAAAPxqbAAAACwAAAAAAQABAAACAkQBADsK')\n", "display_png(raw_png_data, width = 64, height = 64)\n", "#display_png(file = 'a-file.png')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Displaying multiple formats (implicitly or explicitly) allows to choose the frontend to choose the best representation. E.g. when converting to PDF, you’ll want to make sure that `jupyter.plot_mimetypes` contains `image/svg+xml` and/or `application/pdf`." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/html": [ "$jupyter.plot_mimetypes = \n", "
  1. 'text/plain'
  2. 'image/png'
\n" ], "text/latex": [ "\\textbf{\\$jupyter.plot\\_mimetypes} = \\begin{enumerate*}\n", "\\item 'text/plain'\n", "\\item 'image/png'\n", "\\end{enumerate*}\n" ], "text/markdown": [ "**$jupyter.plot_mimetypes** = 1. 'text/plain'\n", "2. 'image/png'\n", "\n", "\n" ], "text/plain": [ "$jupyter.plot_mimetypes\n", "[1] \"text/plain\" \"image/png\" \n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "options('jupyter.plot_mimetypes')" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "A circle" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "publish_mimebundle(list(\n", " 'image/svg+xml' = '',\n", " 'text/plain' = 'A circle'))" ] } ], "metadata": { "kernelspec": { "display_name": "R", "language": "R", "name": "ir" }, "language_info": { "codemirror_mode": "r", "file_extension": ".r", "mimetype": "text/x-r-source", "name": "R", "pygments_lexer": "r", "version": "4.0.3" } }, "nbformat": 4, "nbformat_minor": 4 } r-cran-irkernel-1.3.2+git20240429.124f234/inst/000077500000000000000000000000001465200542700200045ustar00rootroot00000000000000r-cran-irkernel-1.3.2+git20240429.124f234/inst/kernelspec/000077500000000000000000000000001465200542700221375ustar00rootroot00000000000000r-cran-irkernel-1.3.2+git20240429.124f234/inst/kernelspec/kernel.js000066400000000000000000000040541465200542700237600ustar00rootroot00000000000000const 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)) }, })) r-cran-irkernel-1.3.2+git20240429.124f234/inst/kernelspec/kernel.json000066400000000000000000000001731465200542700243130ustar00rootroot00000000000000{"argv": ["R", "--slave", "-e", "IRkernel::main()", "--args", "{connection_file}"], "display_name":"R", "language":"R" } r-cran-irkernel-1.3.2+git20240429.124f234/inst/kernelspec/logo-64x64.png000066400000000000000000000204541465200542700244030ustar00rootroot00000000000000PNG  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Ǻ r-cran-irkernel-1.3.2+git20240429.124f234/man/000077500000000000000000000000001465200542700176025ustar00rootroot00000000000000r-cran-irkernel-1.3.2+git20240429.124f234/man/Comm-class.Rd000066400000000000000000000003731465200542700220720ustar00rootroot00000000000000% 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 } r-cran-irkernel-1.3.2+git20240429.124f234/man/CommManager-class.Rd000066400000000000000000000004421465200542700233620ustar00rootroot00000000000000% 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 } r-cran-irkernel-1.3.2+git20240429.124f234/man/IRkernel-package.Rd000066400000000000000000000035641465200542700232050ustar00rootroot00000000000000% 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} r-cran-irkernel-1.3.2+git20240429.124f234/man/comm_manager.Rd000066400000000000000000000005061465200542700225170ustar00rootroot00000000000000% 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 } r-cran-irkernel-1.3.2+git20240429.124f234/man/installspec.Rd000066400000000000000000000031121465200542700224070ustar00rootroot00000000000000% 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"), env = NULL ) } \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}} \item{env}{(optional) Named list of environment variables to set in the kernel (default: NULL)} } \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! } r-cran-irkernel-1.3.2+git20240429.124f234/man/log.Rd000066400000000000000000000011011465200542700206430ustar00rootroot00000000000000% 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. } r-cran-irkernel-1.3.2+git20240429.124f234/man/main.Rd000066400000000000000000000005161465200542700210170ustar00rootroot00000000000000% 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 } r-cran-irkernel-1.3.2+git20240429.124f234/tests/000077500000000000000000000000001465200542700201715ustar00rootroot00000000000000r-cran-irkernel-1.3.2+git20240429.124f234/tests/requirements.txt000066400000000000000000000001131465200542700234500ustar00rootroot00000000000000jupyter jupyter-client ndjson-testrunner>=1.1.3 jupyter-kernel-test>=0.5.0 r-cran-irkernel-1.3.2+git20240429.124f234/tests/testthat.R000066400000000000000000000002121465200542700221470ustar00rootroot00000000000000# see https://github.com/hadley/testthat/issues/144 Sys.setenv(R_TESTS = '') library(testthat) library(IRkernel) test_check('IRkernel') r-cran-irkernel-1.3.2+git20240429.124f234/tests/testthat/000077500000000000000000000000001465200542700220315ustar00rootroot00000000000000r-cran-irkernel-1.3.2+git20240429.124f234/tests/testthat/test-options.r000066400000000000000000000010531465200542700246630ustar00rootroot00000000000000context('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) }) r-cran-irkernel-1.3.2+git20240429.124f234/tests/testthat/test_ir.py000066400000000000000000000372351465200542700240660ustar00rootroot00000000000000# Test manually via # IR_KERNEL_NAME=ir python3 -m test_ir -k some_test import os import sys from pathlib import Path import unittest from jupyter_client.manager import start_new_kernel from jupyter_kernel_test import validate_message, KernelTests 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(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) irkernel_inspects = [ dict(name='Numeric constant', token='1'), # dict(name='Reserved word', token='NULL'), # null is not an object anymore? dict(name='Dataset with a help document', token='iris'), dict(name='Function with a help document', token='c'), dict(name='Function name with namespace', token='base::c'), dict(name='Function not exported from namespace', token='tools:::.Rd2pdf'), dict(name='User-defined variable', token='x', vars=dict(x='1')), dict(name='User-defined function', token='f', vars=dict(f='function (x) x + x')), dict(name='Object which masks other object in workspace', token='c', vars=dict(c='function (x) x + x')), ] def test_irkernel_inspects(self): """Test if object inspection works. Checks if inspect_request for rach `token` returns a reply. 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 and check the contents. """ self.flush_channels() for sample in self.irkernel_inspects: with self.subTest(text=sample["name"]): for var_name, var_code in sample.get('vars', {}).items(): self._execute_code(f'{var_name} <- {var_code}', tests=False) msg_id = self.kc.inspect(sample["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) for var_name in sample.get('vars', {}): self._execute_code(f'rm("{var_name}")', tests=False) non_syntactic_completion_samples = [ dict(text='xx$host[[1]]$h', matches=['xx$host[[1]]$h1', 'xx$host[[1]]$h2'], vars=dict(xx='list(host = list(list(h1 = 1, h2 = 2), list(h3 = 3, h4 = 4)))')), dict(text='odd_named_list$a', matches=['odd_named_list$a'], vars=dict(odd_named_list=r'list(a = 1, b = 2, `b c` = 3, `\`\\\`` = 4, 5)')), dict(text='odd_named_list$b', matches=['odd_named_list$b', 'odd_named_list$`b c`'], vars=dict(odd_named_list=r'list(a = 1, b = 2, `b c` = 3, `\`\\\`` = 4, 5)')), dict(text='odd_named_list$', matches=['odd_named_list$a', 'odd_named_list$b', 'odd_named_list$`b c`', r'odd_named_list$`\`\\\``', 'odd_named_list$'], vars=dict(odd_named_list=r'list(a = 1, b = 2, `b c` = 3, `\`\\\`` = 4, 5)')), dict(text='arith_named_env$a', matches=['arith_named_env$`abc+def`', 'arith_named_env$`abc-def`'], vars=dict(arith_named_env='list2env(list(`abc+def` = 1, `def-abc` = 2, `abc-def` = 3, defabc = 4))')), dict(text='arith_named_env$def', matches=['arith_named_env$`def-abc`', 'arith_named_env$defabc'], vars=dict(arith_named_env='list2env(list(`abc+def` = 1, `def-abc` = 2, `abc-def` = 3, defabc = 4))')), dict(text='arith_named_env$', matches=['arith_named_env$`abc+def`', 'arith_named_env$`def-abc`', 'arith_named_env$`abc-def`', 'arith_named_env$defabc'], vars=dict(arith_named_env='list2env(list(`abc+def` = 1, `def-abc` = 2, `abc-def` = 3, defabc = 4))')), ] def test_non_syntactic_completions(self): """Test tab-completion for non-syntactic names which require setup/teardown""" for sample in self.non_syntactic_completion_samples: with self.subTest(text=sample['text']): for var_name, var_value in sample['vars'].items(): self._execute_code(f'{var_name} <- {var_value}', tests=False) msg_id = self.kc.complete(sample['text']) reply = self.get_non_kernel_info_reply() validate_message(reply, 'complete_reply', msg_id) self.assertEqual(set(reply['content']['matches']), set(sample['matches'])) for var_name in sample['vars']: self._execute_code(f'rm({var_name})', tests=False) if __name__ == '__main__': unittest.main(verbosity=2) r-cran-irkernel-1.3.2+git20240429.124f234/tests/testthat/test_kernel.r000066400000000000000000000027401465200542700245360ustar00rootroot00000000000000context('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) }) r-cran-irkernel-1.3.2+git20240429.124f234/tests/testthat/test_utils.r000066400000000000000000000012011465200542700244050ustar00rootroot00000000000000context('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') })