File: cran.R

package info (click to toggle)
r-cran-xfun 0.37%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 792 kB
  • sloc: ansic: 242; sh: 22; makefile: 2
file content (152 lines) | stat: -rw-r--r-- 5,929 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# retrieve the release dates of packages
cran_pkg_dates = function(full = FALSE, maintainer = 'Yihui Xie') {
  info = tools::CRAN_package_db()
  pkgs = info[grep(maintainer, info$Maintainer), 'Package']
  info = setNames(vector('list', length(pkgs)), pkgs)
  for (p in pkgs) {
    message('Processing ', p)
    x = readLines(u <- sprintf('https://cran.rstudio.com/web/packages/%s/', p))
    i = which(x == '<td>Published:</td>')
    if (length(i) == 0) stop('Cannot find the publishing date from ', u)
    d = as.Date(gsub('</?td>', '', x[i[1] + 1]))
    x = try_silent(suppressWarnings(readLines(
      u <- sprintf('https://cran.r-project.org/src/contrib/Archive/%s/', p)
    )))
    if (inherits(x, 'try-error')) {
      info[[p]] = d; next
    }
    r = '.+</td><td align="right">(\\d{4,}-\\d{2}-\\d{2}) .+'
    d = c(d, as.Date(gsub(r, '\\1', grep(r, x, value = TRUE))))
    info[[p]] = sort(d, decreasing = TRUE)
  }
  if (full) info else sort(do.call(c, lapply(info, `[`, 1)), decreasing = TRUE)
}

# return packages that haven't been updated for X days, and can be updated on CRAN
cran_updatable = function(days = 90, maintainer = 'Yihui Xie') {
  info = cran_pkg_dates(TRUE, maintainer)
  flag = unlist(lapply(info, function(d) {
    sum(d > Sys.Date() - 180) < 6 && d[1] < Sys.Date() - days
  }))
  if (length(pkgs <- names(which(flag))) == 0) return(pkgs)
  # look into DESCRIPTION in Github repos and see if new version has been pushed
  info = tools::CRAN_package_db()
  info = info[info$Package %in% pkgs, , drop = FALSE]
  pkgs = info$Package
  for (i in seq_len(nrow(info))) {
    b = grep_sub('^(https://github.com/[^/]+/[^/]+)/issues$', '\\1', info$BugReports[i])
    if (length(b) != 1) next
    f = tempfile()
    u = paste0(b, '/raw/HEAD/DESCRIPTION')
    if (is.null(tryCatch(download.file(u, f, quiet = TRUE), error = function(e) NULL))) next
    d = read.dcf(f)
    file.remove(f)
    if (!'Version' %in% colnames(d)) next
    if (as.numeric_version(d[, 'Version']) <= paste0(info$Version[i], '.1')) {
      pkgs = setdiff(pkgs, info$Package[i])
      message('Skipped package ', info$Package[i], ' ', d[, 'Version'], ' (no new version).')
    } else {
      message('Package can be updated: ', b)
    }
  }
  pkgs
}


#' Some utility functions for checking packages
#'
#' Miscellaneous utility functions to obtain information about the package
#' checking environment.
#' @export
#' @keywords internal
is_R_CMD_check = function() {
  !is.na(check_package_name()) || tolower(Sys.getenv('_R_CHECK_LICENSE_')) == 'true'
}

#' @rdname is_R_CMD_check
#' @export
is_CRAN_incoming = function() {
  isTRUE(as.logical(Sys.getenv('_R_CHECK_CRAN_INCOMING_REMOTE_')))
}

#' @rdname is_R_CMD_check
#' @export
check_package_name = function() {
  Sys.getenv('_R_CHECK_PACKAGE_NAME_', NA)
}

# is R CMD check running on a package that has a version lower or equal to `version`?
#' @rdname is_R_CMD_check
#' @export
check_old_package = function(name, version) {
  if (is.na(pkg <- check_package_name()) || pkg != name) return(FALSE)
  tryCatch(packageVersion(name) <= version, error = function(e) FALSE)
}

# return package maintainers (with email addresses)
pkg_maintainers = function(pkgs) {
  info = tools::CRAN_package_db()
  info = info[match(pkgs, info$Package), c('Package', 'Maintainer')]
  setNames(info$Maintainer, info$Package)
}

#' Submit a source package to CRAN
#'
#' Build a source package and submit it to CRAN with the \pkg{curl} package.
#' @param file The path to the source package tarball. By default, the current
#'   working directory is treated as the package root directory, and
#'   automatically built into a tarball, which is deleted after submission. This
#'   means you should run \code{xfun::submit_cran()} in the root directory of a
#'   package project, unless you want to pass a path explicitly to the
#'   \code{file} argument.
#' @param comment Submission comments for CRAN. By default, if a file
#'   \file{cran-comments.md} exists, its content will be read and used as the
#'   comment.
#' @seealso \code{devtools::submit_cran()} does the same job, with a few more
#'   dependencies in addition to \pkg{curl} (such as \pkg{cli});
#'   \code{xfun::submit_cran()} only depends on \pkg{curl}.
#' @export
submit_cran = function(file = pkg_build(), comment = '') {
  # if the tarball is automatically created, delete it after submission
  if (missing(file)) on.exit(file.remove(file), add = TRUE)

  # read the maintainer's name/email
  dir_create(d <- tempfile())
  on.exit(unlink(d, recursive = TRUE), add = TRUE)
  desc = file.path(gsub('_.*', '', basename(file)), 'DESCRIPTION')
  untar(file, desc, exdir = d)
  info = read.dcf(file.path(d, desc), fields = 'Maintainer')[1, 1]
  info = unlist(strsplit(info, '( <|>)'))

  # read submission comments from cran-comments.md if exists
  if (missing(comment) && file_exists(f <- 'cran-comments.md')) {
    comment = file_string(f)
  }
  params = list(
    uploaded_file = curl::form_file(file), name = info[1], email = info[2],
    upload = 'Upload package'
  )
  params$comment = if (length(comment)) comment
  server = 'https://xmpalantir.wu.ac.at/cransubmit/index2.php'

  # submit the form
  h = curl::new_handle()
  curl::handle_setform(h, .list = params)
  res = curl::curl_fetch_memory(server, h)

  # find the pkg_id from the response page
  id = grep_sub(
    '(.*<input name="pkg_id" type="hidden" value=")([^"]+)(".*)', '\\2',
    rawToChar(res$content)
  )
  if (length(id) != 1) stop('Failed to submit ', file, ' to CRAN')

  # skip the review and submit directly
  h = curl::new_handle()
  curl::handle_setform(h, .list = list(pkg_id = id, submit = 'Submit package'))
  res = curl::curl_fetch_memory(server, h)
  if (grepl('>Step 3<', rawToChar(res$content))) message(
    'The package has been submitted. Please confirm the submission in email: ',
    params$email
  ) else message('The submission may be unsuccessful.')
}