├── .Rbuildignore ├── .gitignore ├── .travis.yml ├── API ├── DESCRIPTION ├── LICENSE ├── Makefile ├── NAMESPACE ├── NEWS.md ├── R ├── function-calls.R ├── function-defs.R ├── map.R ├── package.R ├── parse.R ├── prepare.R ├── print-data.R ├── print-fun.R ├── print-reexport.R ├── print.R ├── roclet.R ├── tick.R └── utils.R ├── README.Rmd ├── README.md ├── appveyor.yml ├── functionmap2.Rproj ├── man ├── api_roclet.Rd ├── extract_api.Rd ├── function_defs.Rd ├── map_package.Rd ├── parse_expressions.Rd └── pkgapi.Rd └── tests ├── testthat.R └── testthat └── test.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^Makefile$ 4 | ^README.Rmd$ 5 | ^.travis.yml$ 6 | ^appveyor.yml$ 7 | ^API$ 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: r 2 | sudo: false 3 | cache: packages 4 | 5 | r: 6 | - release 7 | - devel 8 | - oldrel 9 | 10 | after_success: 11 | - test $TRAVIS_R_VERSION_STRING = "release" && Rscript -e 'covr::codecov()' 12 | -------------------------------------------------------------------------------- /API: -------------------------------------------------------------------------------- 1 | # API for pkgapi package 2 | 3 | ## Exported functions 4 | 5 | api_roclet() 6 | extract_api(path = ".", targets = character()) 7 | map_package(path = ".") 8 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: pkgapi 2 | Title: Map Function Calls 3 | Version: 1.0.0 4 | Author: Gábor Csárdi 5 | Maintainer: Gábor Csárdi 6 | Description: Create the map of function calls in a package, including calls 7 | to imported packages. 8 | License: MIT + file LICENSE 9 | LazyData: true 10 | URL: https://github.com/r-lib/pkgapi#readme 11 | BugReports: https://github.com/r-lib/pkgapi/issues 12 | RoxygenNote: 5.0.1.9000 13 | Roxygen: list(markdown = TRUE, roclets = c("collate", "rd", "namespace", "pkgapi::api_roclet")) 14 | Imports: 15 | callr, 16 | pkgload, 17 | roxygen2 (>= 6.1.0), 18 | tools, 19 | usethis, 20 | withr 21 | Suggests: 22 | covr, 23 | testthat 24 | Encoding: UTF-8 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2016 2 | COPYRIGHT HOLDER: Gábor Csárdi 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: README.md 3 | 4 | README.md: README.Rmd 5 | Rscript -e "library(knitr); knit('$<', output = '$@', quiet = TRUE)" 6 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(format,pkgapi) 4 | S3method(print,pkgapi) 5 | S3method(roclet_clean,roclet_api) 6 | S3method(roclet_output,roclet_api) 7 | S3method(roclet_process,roclet_api) 8 | S3method(roclet_tags,roclet_api) 9 | export(api_roclet) 10 | export(extract_api) 11 | export(map_package) 12 | importFrom(callr,r_vanilla) 13 | importFrom(roxygen2,roclet) 14 | importFrom(roxygen2,roclet_clean) 15 | importFrom(roxygen2,roclet_output) 16 | importFrom(roxygen2,roclet_process) 17 | importFrom(roxygen2,roclet_tags) 18 | importFrom(tools,list_files_with_type) 19 | importFrom(utils,getParseData) 20 | importFrom(utils,getSrcFilename) 21 | importFrom(utils,getSrcLocation) 22 | importFrom(utils,getSrcref) 23 | importFrom(withr,with_collate) 24 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | 2 | # 1.0.0 3 | 4 | First public release. 5 | -------------------------------------------------------------------------------- /R/function-calls.R: -------------------------------------------------------------------------------- 1 | 2 | function_calls <- function(path) { 3 | 4 | exprs <- parse_expressions(path) 5 | calls <- lapply(exprs, find_calls) 6 | 7 | res <- do.call(rbind, calls) 8 | row.names(res) <- NULL 9 | res 10 | } 11 | 12 | find_calls <- function(expr) { 13 | pd <- getParseData(expr) 14 | fc <- which(pd$token == "SYMBOL_FUNCTION_CALL") 15 | 16 | res <- data.frame( 17 | stringsAsFactors = FALSE, 18 | file = if (length(fc)) paste0("R/", getSrcFilename(expr)) else character(), 19 | from = character(length(fc)), # to be filled later 20 | to = character(length(fc)), # to be filled later 21 | type = if (length(fc)) "call" else character(), 22 | line1 = pd$line1[fc], 23 | line2 = pd$line2[fc], 24 | col1 = pd$col1[fc], 25 | col2 = pd$col2[fc], 26 | str = pd$text[fc] 27 | ) 28 | 29 | ## We fill in explicit :: and ::: call targets here, the rest later 30 | ## Need to search for this: 31 | ## expr 32 | ## +- SYMBOL_PACKAGE 33 | ## +- NS_GET / NS_GET_INT 34 | ## +- SYMBOL_FUNCTION_CALL 35 | 36 | for (i in seq_along(fc)) { 37 | call_row <- fc[i] 38 | parent_id <- as.character(pd$parent[call_row]) 39 | if (parent_id != 0 && pd[parent_id,]$token == "expr") { 40 | sibling_ids <- as.character(pd$id[pd$parent == parent_id]) 41 | subpd <- pd[sibling_ids, , drop = FALSE] 42 | if (nrow(subpd) == 3) { 43 | subpd <- subpd[order(subpd$line1, subpd$col1), ] 44 | if (subpd$token[1] == "SYMBOL_PACKAGE" && 45 | subpd$token[2] %in% c("NS_GET", "NS_GET_INT") && 46 | subpd$token[3] == "SYMBOL_FUNCTION_CALL") { 47 | res$to[i] <- paste0(subpd$text[1], "::", subpd$text[3]) 48 | } 49 | } 50 | } 51 | } 52 | 53 | res 54 | } 55 | 56 | find_caller <- function(calls, idx, defs) { 57 | 58 | file <- calls$file[idx] 59 | line1 <- calls$line1[idx] 60 | line2 <- calls$line2[idx] 61 | col1 <- calls$col1[idx] 62 | col2 <- calls$col2[idx] 63 | 64 | w <- which( 65 | defs$file == file & 66 | (defs$line1 < line1 | (defs$line1 == line1 & defs$col1 <= col1)) & 67 | (defs$line2 > line2 | (defs$line2 == line2 & defs$col2 >= col2)) 68 | ) 69 | 70 | if (length(w) == 1) { 71 | defs$name[w] 72 | 73 | } else if (length(w) > 1) { 74 | ## This is possible if two symbols point to the same function, 75 | ## as the function's srcref will only point to a single location 76 | defs$name[w[1]] 77 | 78 | } else { 79 | ## Calling from outside a function 80 | "" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /R/function-defs.R: -------------------------------------------------------------------------------- 1 | 2 | #' Table of function definitions 3 | #' 4 | #' @param path path to package root 5 | #' @return Data frame with columns: `name`, `file`, `line`, `col`. 6 | #' 7 | #' @keywords internal 8 | 9 | function_defs <- function(funcs, exports) { 10 | 11 | locs <- lapply(funcs, find_function_location) 12 | 13 | res <- data.frame( 14 | stringsAsFactors = FALSE, 15 | name = names(funcs), 16 | file = vapply(locs, "[[", "", "file"), 17 | line1 = vapply(locs, "[[", 1L, "line1"), 18 | col1 = vapply(locs, "[[", 1L, "col1"), 19 | line2 = vapply(locs, "[[", 1L, "line2"), 20 | col2 = vapply(locs, "[[", 1L, "col2"), 21 | exported = names(funcs) %in% exports 22 | ) 23 | 24 | row.names(res) <- NULL 25 | 26 | res 27 | } 28 | 29 | #' @importFrom utils getSrcLocation getParseData getSrcFilename getSrcref 30 | 31 | find_function_location <- function(func) { 32 | 33 | ## This might happen in tricky cases, e.g. memoised functions via the 34 | ## memoise packag 35 | if (is.null(getSrcref(func))) { 36 | return(list(file = NA_character_, 37 | line1 = NA_integer_, col1 = NA_integer_, 38 | line2 = NA_integer_, col2 = NA_integer_)) 39 | } 40 | 41 | ## We need to parse the file again, because of an R bug, at 42 | ## least on macOS. First-time parse in an R session fails 43 | pd <- getParseData( 44 | parse(getSrcFilename(func, full.names = TRUE), keep.source = TRUE) 45 | ) 46 | 47 | line <- getSrcLocation(func, "parse") 48 | col <- getSrcLocation(func, "column") 49 | 50 | pdline <- which( 51 | pd$parent == 0 & 52 | (pd$line1 < line | (pd$line1 == line & pd$col1 <= col)) & 53 | (pd$line2 > line | (pd$line2 == line & pd$col2 >= col)) 54 | ) 55 | 56 | list( 57 | file = paste0("R/", getSrcFilename(func)), 58 | line1 = pd$line1[pdline], 59 | col1 = pd$col1 [pdline], 60 | line2 = pd$line2[pdline], 61 | col2 = pd$col2 [pdline] 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /R/map.R: -------------------------------------------------------------------------------- 1 | 2 | #' Create the function call map of an R package 3 | #' 4 | #' Find all functions and function calls in a package. This requires the 5 | #' loading of the package (in another R session, so the current R session 6 | #' is unaffected by this), which means that all depended and imported 7 | #' packages must be available as well. 8 | #' 9 | #' @param path path to an R package root 10 | #' @return List with elements: 11 | #' * `name`: package name, 12 | #' * `exports`: character vector of exported funtions and operators. 13 | #' * `defs`: data frame of function definitions. 14 | #' * `calls`: data frame of function calls. 15 | #' 16 | #' Columns in `defs`: 17 | #' * `name`: function name, 18 | #' * `file`: file path where it was defined, relative to the package root. 19 | #' * `line1`: line of start position of function definition. 20 | #' * `col1`: column of start position. 21 | #' * `line2`: line of end position. 22 | #' * `col2`: column of end position. 23 | #' * `exported`: whether the function is exported or not. 24 | #' 25 | #' Columns in `calls`: 26 | #' * `file`: file path of the call, relative to the package root. 27 | #' * `from`: name of calling function, or empty string if called from 28 | #' outside of a function. 29 | #' * `to`: called function, in the `package::function` form. 30 | #' * `type`: call type, regular functions calls are `call`. 31 | #' * `line1`: line of the beginning of the call. 32 | #' * `col1`: column of the beginning of the call. 33 | #' * `line1`: line of the end of the call. 34 | #' * `col2`: column of the beginning of the call. 35 | #' * `str`: the actual code, text of the function call, usually just the 36 | #' called function's name. 37 | #' 38 | #' @export 39 | 40 | map_package <- function(path = ".") { 41 | 42 | ## Need to parse expressions for calls first 43 | 44 | calls <- function_calls(path) 45 | 46 | ## Then we eval the code, to see which expression creates which function. 47 | ## This allows finding the function definitions. And while we are at it, 48 | ## we also find the targets of the calls, by simply evaluating the 49 | ## the names of the called functions, in the package environment. 50 | 51 | prep <- extract_api(path, calls$str) 52 | 53 | defs <- function_defs(prep$functions, prep$exports) 54 | calls$to <- ifelse( 55 | calls$to != "", 56 | calls$to, 57 | paste0(prep$targets, "::", calls$str) 58 | ) 59 | 60 | ## Now we need to find the caller functions as well 61 | for (i in seq_along(calls$from)) { 62 | calls$from[i] <- find_caller(calls, i, defs) 63 | } 64 | 65 | ## Ready 66 | list( 67 | name = prep$name, 68 | exports = prep$exports, 69 | defs = defs, 70 | calls = calls 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /R/package.R: -------------------------------------------------------------------------------- 1 | 2 | #' Map Function Calls 3 | #' 4 | #' Create the map function calls in a package, including calls to imported 5 | #' packages. 6 | #' 7 | #' @docType package 8 | #' @name pkgapi 9 | NULL 10 | -------------------------------------------------------------------------------- /R/parse.R: -------------------------------------------------------------------------------- 1 | 2 | #' Parse all files of a package 3 | #' 4 | #' This includes all (unevaluated) code of the package. We use it 5 | #' to find all function calls. 6 | #' 7 | #' @param path package root 8 | #' @return List of expressions, one for each file, names are full 9 | #' file names. 10 | #' 11 | #' @keywords internal 12 | #' 13 | #' @importFrom tools list_files_with_type 14 | #' @importFrom withr with_collate 15 | 16 | parse_expressions <- function(path) { 17 | 18 | path_r <- file.path(path, "R") 19 | r_files <- with_collate( 20 | "C", 21 | list_files_with_type(path_r, "code", full.names = TRUE) 22 | ) 23 | 24 | structure( 25 | lapply(r_files, parse, keep.source = TRUE), 26 | names = r_files 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /R/prepare.R: -------------------------------------------------------------------------------- 1 | 2 | #' Get package data 3 | #' 4 | #' Parse and evaluate code from a package, and store it in an 5 | #' environment, so that we can access it later without parsing it again. 6 | #' 7 | #' We need to evaluate code to get the functions, which might require 8 | #' loading some packages, so we perform all this in a separate R process, 9 | #' using the `callr` package. 10 | #' 11 | #' While we are at it, we also check the imports of the package, 12 | #' and return where each function was imported from. 13 | #' 14 | #' @param path package root 15 | #' @param targets character vector, function call targets to find 16 | #' @return Named list with components: 17 | #' - `name` is the package name. 18 | #' - `version` is the vesion of the package. 19 | #' - `targets` is a character vector, the names of the environments where 20 | #' each target was found. 21 | #' - `functions` is a list of functions, with source references. 22 | #' - `exports` is a character vector of exported objects. 23 | #' - `s3_methods` is a character vector of declared S3 methods. 24 | #' - `imports` is a named list of environment names, one for each import. 25 | #' 26 | #' @keywords internal 27 | #' @importFrom callr r_vanilla 28 | #' @export 29 | 30 | extract_api <- function(path = ".", targets = character()) { 31 | 32 | r_vanilla( 33 | function(path, targets, sort_c) { 34 | options(keep.source = TRUE) 35 | pkgload::load_all(path, export_all = FALSE, helpers = FALSE) 36 | name <- pkgload::pkg_name(path) 37 | env <- pkgload::ns_env(name) 38 | 39 | all_names <- ls(env, all.names = TRUE) 40 | objects <- mget(all_names, env) 41 | 42 | functions <- Filter(is.function, objects) 43 | data <- Filter(Negate(is.function), objects) 44 | 45 | exports <- ls(env$.__NAMESPACE__.$exports, all.names = TRUE) 46 | 47 | s3_methods <- env$.__NAMESPACE__.$S3methods[, 3] 48 | 49 | imports <- eapply( 50 | pkgload::imports_env(name), 51 | function(x) environmentName(environment(x)) 52 | ) 53 | 54 | target_funcs <- mget( 55 | targets, 56 | envir = env, 57 | mode = "function", 58 | inherits = TRUE, 59 | ifnotfound = NA_character_ 60 | ) 61 | 62 | target_envs <- lapply( 63 | target_funcs, 64 | function(x) { 65 | if (identical(x, NA_character_)) { 66 | x 67 | } else if (is.primitive(x)) { 68 | "base" 69 | } else { 70 | environmentName(environment(x)) 71 | } 72 | } 73 | ) 74 | 75 | structure( 76 | list( 77 | name = name, 78 | version = pkgload::pkg_version(path), 79 | targets = target_envs, 80 | functions = functions, 81 | data = data, 82 | exports = sort_c(exports), 83 | s3_methods = sort_c(s3_methods), 84 | imports = imports 85 | ), 86 | class = "pkgapi" 87 | ) 88 | }, 89 | libpath = .libPaths(), 90 | repos = getOption("repos"), 91 | args = list(path = path, targets = targets, sort_c = sort_c) 92 | ) 93 | } 94 | -------------------------------------------------------------------------------- /R/print-data.R: -------------------------------------------------------------------------------- 1 | format_exported_data <- function(x) { 2 | exports <- Filter(Negate(is.null), x$data[x$exports]) 3 | unlist(unname(Map(format_export_data, names(exports), exports))) 4 | } 5 | 6 | format_export_data <- function(name, data) { 7 | paste0(name, ": ", paste(class(data), collapse = ", "), " (", mode(data), "[", length(data), "])") 8 | } 9 | -------------------------------------------------------------------------------- /R/print-fun.R: -------------------------------------------------------------------------------- 1 | format_exported_functions <- function(x) { 2 | exports <- Filter(Negate(is.null), x$functions[x$exports]) 3 | unlist(unname(Map(format_export_fun, names(exports), exports))) 4 | } 5 | 6 | format_own_s3_methods <- function(x) { 7 | format_s3_methods(x, own = TRUE) 8 | } 9 | 10 | format_foreign_s3_methods <- function(x) { 11 | format_s3_methods(x, own = FALSE) 12 | } 13 | 14 | format_s3_methods <- function(x, own) { 15 | method_names <- gsub("^([^.]*)[.].*$", "\\1", x$s3_methods) 16 | method_is_own <- method_names %in% x$exports 17 | s3_methods <- x$functions[ x$s3_methods[method_is_own == own] ] 18 | s3_methods <- Filter(Negate(is.null), s3_methods) 19 | unlist(unname(Map(format_export_fun, names(s3_methods), s3_methods))) 20 | } 21 | 22 | format_export_fun <- function(name, fun) { 23 | paste0(name, "(", format_args(formals(fun)), ")") 24 | } 25 | 26 | format_args <- function(args) { 27 | if (length(args) == 0L) { 28 | "" 29 | } else { 30 | paste0(tick_if_needed(names(args)), vapply(args, format_default, character(1L)), collapse = ", ") 31 | } 32 | } 33 | 34 | format_default <- function(lang) { 35 | if (identical(as.character(lang), "")) { 36 | "" 37 | } else { 38 | paste0(" = ", paste(deparse(lang, width.cutoff = 500, backtick = TRUE), collapse = "")) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /R/print-reexport.R: -------------------------------------------------------------------------------- 1 | format_reexported_functions <- function(x) { 2 | reexports <- Filter(Negate(is.null), x$imports[x$exports]) 3 | unlist(unname(Map(format_reexport_fun, reexports, names(reexports)))) 4 | } 5 | 6 | format_reexport_fun <- function(name, fun) { 7 | paste0(name, "::", fun) 8 | } 9 | -------------------------------------------------------------------------------- /R/print.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | print.pkgapi <- function(x, ...) { 3 | cat(format(x), sep = "\n") 4 | } 5 | 6 | #' @export 7 | format.pkgapi <- function(x, ...) { 8 | c( 9 | format_caption("API for ", x$name, " package", level = 1), 10 | add_caption("Exported functions", format_exported_functions(x)), 11 | add_caption("Own S3 methods", format_own_s3_methods(x)), 12 | add_caption("Foreign S3 methods", format_foreign_s3_methods(x)), 13 | add_caption("Exported data", format_exported_data(x)), 14 | add_caption("Reexported objects", format_reexported_functions(x)), 15 | NULL 16 | ) 17 | } 18 | 19 | add_caption <- function(caption, x) { 20 | if (length(x) == 0L) { 21 | character() 22 | } else { 23 | c("", format_caption(caption), "", x) 24 | } 25 | } 26 | 27 | 28 | format_caption <- function(..., level = 2) { 29 | c(paste0(paste(rep("#", level), collapse = ""), " ", ...)) 30 | } 31 | -------------------------------------------------------------------------------- /R/roclet.R: -------------------------------------------------------------------------------- 1 | ns_tags <- c('api') 2 | 3 | #' Roclet: make API file. 4 | #' 5 | #' This roclet automates the production of an `API` file that describes 6 | #' the exported interface of a package. 7 | #' 8 | #' @export 9 | #' @importFrom roxygen2 roclet 10 | #' @api 11 | api_roclet <- function() { 12 | roclet("api") 13 | } 14 | 15 | #' @importFrom roxygen2 roclet_process 16 | #' @export 17 | roclet_process.roclet_api <- function(x, ...) { 18 | invisible() 19 | } 20 | 21 | #' @importFrom roxygen2 roclet_tags 22 | #' @export 23 | roclet_tags.roclet_api <- function(x) { 24 | list( 25 | api = roxygen2::tag_toggle 26 | ) 27 | } 28 | 29 | #' @importFrom roxygen2 roclet_output 30 | #' @export 31 | roclet_output.roclet_api <- function(x, results, base_path, ...) { 32 | output <- format(extract_api(base_path)) 33 | 34 | file_name <- "API" 35 | API <- file.path(base_path, file_name) 36 | 37 | # FIXME: Add marker that indicates if this is "our" file 38 | # FIXME: write_if_different() 39 | cat("Writing API\n") 40 | writeLines(output, API) 41 | 42 | withr::with_dir(base_path, usethis::use_build_ignore(file_name)) 43 | 44 | API 45 | } 46 | 47 | #' @importFrom roxygen2 roclet_clean 48 | #' @export 49 | roclet_clean.roclet_api <- function(x, base_path) { 50 | # FIXME: Check if this is "our" file 51 | API <- file.path(base_path, "API") 52 | unlink(API) 53 | } 54 | -------------------------------------------------------------------------------- /R/tick.R: -------------------------------------------------------------------------------- 1 | # FIXME: Also exists in pillar, do we need to export? 2 | tick <- function(x) { 3 | ifelse(is.na(x), "NA", encodeString(x, quote = "`")) 4 | } 5 | 6 | is_syntactic <- function(x) { 7 | ret <- make.names(x) == x 8 | ret[is.na(x)] <- FALSE 9 | ret 10 | } 11 | 12 | tick_if_needed <- function(x) { 13 | needs_ticks <- !is_syntactic(x) 14 | x[needs_ticks] <- tick(x[needs_ticks]) 15 | x 16 | } 17 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | sort_c <- function(x) { 2 | withr::with_collate("C", sort(x)) 3 | } 4 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | 2 | ```{r, setup, echo = FALSE, message = FALSE} 3 | knitr::opts_chunk$set( 4 | comment = "#>", 5 | tidy = FALSE, 6 | error = FALSE, 7 | fig.width = 8, 8 | fig.height = 8) 9 | ``` 10 | 11 | # pkgapi 12 | 13 | > Map Function Calls 14 | 15 | [![Linux Build Status](https://travis-ci.org/r-lib/pkgapi.svg?branch=master)](https://travis-ci.org/r-lib/pkgapi) 16 | [![Windows Build status](https://ci.appveyor.com/api/projects/status/github/r-lib/pkgapi?svg=true)](https://ci.appveyor.com/project/gaborcsardi/pkgapi) 17 | [![](http://www.r-pkg.org/badges/version/pkgapi)](http://www.r-pkg.org/pkg/pkgapi) 18 | [![CRAN RStudio mirror downloads](http://cranlogs.r-pkg.org/badges/pkgapi)](http://www.r-pkg.org/pkg/pkgapi) 19 | [![Coverage Status](https://img.shields.io/codecov/c/github/r-lib/pkgapi/master.svg)](https://codecov.io/github/r-lib/pkgapi?branch=master) 20 | 21 | Create the map of function calls in a package, including calls to imported 22 | packages. 23 | 24 | ## Installation 25 | 26 | Once on CRAN, install the package as usual: 27 | 28 | ```{r eval = FALSE} 29 | install.packages("pkgapi") 30 | ``` 31 | 32 | ## Usage 33 | 34 | ```{r} 35 | library(pkgapi) 36 | ``` 37 | 38 | ## License 39 | 40 | MIT © [Gábor Csárdi](https://github.com/gaborcsardi), 41 | [Kirill Müller](https://github.com/krlmlr) 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # pkgapi 5 | 6 | > Map Function Calls 7 | 8 | [![Linux Build Status](https://travis-ci.org/r-lib/pkgapi.svg?branch=master)](https://travis-ci.org/r-lib/pkgapi) 9 | [![Windows Build status](https://ci.appveyor.com/api/projects/status/github/r-lib/pkgapi?svg=true)](https://ci.appveyor.com/project/gaborcsardi/pkgapi) 10 | [![](http://www.r-pkg.org/badges/version/pkgapi)](http://www.r-pkg.org/pkg/pkgapi) 11 | [![CRAN RStudio mirror downloads](http://cranlogs.r-pkg.org/badges/pkgapi)](http://www.r-pkg.org/pkg/pkgapi) 12 | [![Coverage Status](https://img.shields.io/codecov/c/github/r-lib/pkgapi/master.svg)](https://codecov.io/github/r-lib/pkgapi?branch=master) 13 | 14 | Create the map of function calls in a package, including calls to imported 15 | packages. 16 | 17 | ## Installation 18 | 19 | Once on CRAN, install the package as usual: 20 | 21 | ```r 22 | install.packages("pkgapi") 23 | ``` 24 | 25 | ## Usage 26 | 27 | 28 | ```r 29 | library(pkgapi) 30 | ``` 31 | 32 | ## License 33 | 34 | MIT © [Gábor Csárdi](https://github.com/gaborcsardi), 35 | [Kirill Müller](https://github.com/krlmlr) 36 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # DO NOT CHANGE the "init" and "install" sections below 2 | 3 | # Download script file from GitHub 4 | init: 5 | ps: | 6 | $ErrorActionPreference = "Stop" 7 | Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1" 8 | Import-Module '..\appveyor-tool.ps1' 9 | 10 | install: 11 | ps: Bootstrap 12 | 13 | # Adapt as necessary starting from here 14 | 15 | # We need rtools for the dev version of roxygen2 16 | environment: 17 | NOT_CRAN: true 18 | USE_RTOOLS: true 19 | 20 | build_script: 21 | - travis-tool.sh install_deps 22 | 23 | test_script: 24 | - travis-tool.sh run_tests 25 | 26 | on_failure: 27 | - travis-tool.sh dump_logs 28 | 29 | artifacts: 30 | - path: '*.Rcheck\**\*.log' 31 | name: Logs 32 | 33 | - path: '*.Rcheck\**\*.out' 34 | name: Logs 35 | 36 | - path: '*.Rcheck\**\*.fail' 37 | name: Logs 38 | 39 | - path: '*.Rcheck\**\*.Rout' 40 | name: Logs 41 | 42 | - path: '\*_*.tar.gz' 43 | name: Bits 44 | 45 | - path: '\*_*.zip' 46 | name: Bits 47 | -------------------------------------------------------------------------------- /functionmap2.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /man/api_roclet.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roclet.R 3 | \name{api_roclet} 4 | \alias{api_roclet} 5 | \title{Roclet: make API file.} 6 | \usage{ 7 | api_roclet() 8 | } 9 | \description{ 10 | This roclet automates the production of an \code{API} file that describes 11 | the exported interface of a package. 12 | } 13 | -------------------------------------------------------------------------------- /man/extract_api.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/prepare.R 3 | \name{extract_api} 4 | \alias{extract_api} 5 | \title{Get package data} 6 | \usage{ 7 | extract_api(path = ".", targets = character()) 8 | } 9 | \arguments{ 10 | \item{path}{package root} 11 | 12 | \item{targets}{character vector, function call targets to find} 13 | } 14 | \value{ 15 | Named list with components: 16 | \itemize{ 17 | \item \code{name} is the package name. 18 | \item \code{version} is the vesion of the package. 19 | \item \code{targets} is a character vector, the names of the environments where 20 | each target was found. 21 | \item \code{functions} is a list of functions, with source references. 22 | \item \code{exports} is a character vector of exported objects. 23 | \item \code{s3_methods} is a character vector of declared S3 methods. 24 | \item \code{imports} is a named list of environment names, one for each import. 25 | } 26 | } 27 | \description{ 28 | Parse and evaluate code from a package, and store it in an 29 | environment, so that we can access it later without parsing it again. 30 | } 31 | \details{ 32 | We need to evaluate code to get the functions, which might require 33 | loading some packages, so we perform all this in a separate R process, 34 | using the \code{callr} package. 35 | 36 | While we are at it, we also check the imports of the package, 37 | and return where each function was imported from. 38 | } 39 | \keyword{internal} 40 | -------------------------------------------------------------------------------- /man/function_defs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/function-defs.R 3 | \name{function_defs} 4 | \alias{function_defs} 5 | \title{Table of function definitions} 6 | \usage{ 7 | function_defs(funcs, exports) 8 | } 9 | \arguments{ 10 | \item{path}{path to package root} 11 | } 12 | \value{ 13 | Data frame with columns: \code{name}, \code{file}, \code{line}, \code{col}. 14 | } 15 | \description{ 16 | Table of function definitions 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/map_package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/map.R 3 | \name{map_package} 4 | \alias{map_package} 5 | \title{Create the function call map of an R package} 6 | \usage{ 7 | map_package(path = ".") 8 | } 9 | \arguments{ 10 | \item{path}{path to an R package root} 11 | } 12 | \value{ 13 | List with elements: 14 | \itemize{ 15 | \item \code{name}: package name, 16 | \item \code{exports}: character vector of exported funtions and operators. 17 | \item \code{defs}: data frame of function definitions. 18 | \item \code{calls}: data frame of function calls. 19 | } 20 | 21 | Columns in \code{defs}: 22 | \itemize{ 23 | \item \code{name}: function name, 24 | \item \code{file}: file path where it was defined, relative to the package root. 25 | \item \code{line1}: line of start position of function definition. 26 | \item \code{col1}: column of start position. 27 | \item \code{line2}: line of end position. 28 | \item \code{col2}: column of end position. 29 | \item \code{exported}: whether the function is exported or not. 30 | } 31 | 32 | Columns in \code{calls}: 33 | \itemize{ 34 | \item \code{file}: file path of the call, relative to the package root. 35 | \item \code{from}: name of calling function, or empty string if called from 36 | outside of a function. 37 | \item \code{to}: called function, in the \code{package::function} form. 38 | \item \code{type}: call type, regular functions calls are \code{call}. 39 | \item \code{line1}: line of the beginning of the call. 40 | \item \code{col1}: column of the beginning of the call. 41 | \item \code{line1}: line of the end of the call. 42 | \item \code{col2}: column of the beginning of the call. 43 | \item \code{str}: the actual code, text of the function call, usually just the 44 | called function's name. 45 | } 46 | } 47 | \description{ 48 | Find all functions and function calls in a package. This requires the 49 | loading of the package (in another R session, so the current R session 50 | is unaffected by this), which means that all depended and imported 51 | packages must be available as well. 52 | } 53 | -------------------------------------------------------------------------------- /man/parse_expressions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/parse.R 3 | \name{parse_expressions} 4 | \alias{parse_expressions} 5 | \title{Parse all files of a package} 6 | \usage{ 7 | parse_expressions(path) 8 | } 9 | \arguments{ 10 | \item{path}{package root} 11 | } 12 | \value{ 13 | List of expressions, one for each file, names are full 14 | file names. 15 | } 16 | \description{ 17 | This includes all (unevaluated) code of the package. We use it 18 | to find all function calls. 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/pkgapi.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/package.R 3 | \docType{package} 4 | \name{pkgapi} 5 | \alias{pkgapi} 6 | \alias{pkgapi-package} 7 | \title{Map Function Calls} 8 | \description{ 9 | Create the map function calls in a package, including calls to imported 10 | packages. 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(pkgapi) 3 | 4 | test_check("pkgapi") 5 | -------------------------------------------------------------------------------- /tests/testthat/test.R: -------------------------------------------------------------------------------- 1 | 2 | context("pkgapi") 3 | 4 | test_that("pkgapi works", { 5 | 6 | expect_true(TRUE) 7 | 8 | }) 9 | --------------------------------------------------------------------------------