readNEWS <- function(file = file.path(R.home(), "NEWS"), trace = FALSE, chop = c("first", "1", "par1", "keepAll")) { ## Purpose: read R's NEWS file - or a file similarly organized ## ---------------------------------------------------------------------- ## Arguments: trace: is used in "inner functions" ## ---------------------------------------------------------------------- ## Author: Martin Maechler, Date: 26 Jun 2006, 15:34 p0 <- function(...) paste(..., sep='') rmIniTABs <- function(ch) sub("^\t+", '', ch) rmTABs <- function(ch) gsub("\t+", '', ch) collapseWSpace <- function(ch) gsub("[\t ]+", " ", ch) "%nIN%" <- function(x,table) is.na(match(x, table)) chop1st <- function(cvec) { ## The first non-empty line n <- length(cvec); if(n <= 1) return(cvec) ## else n >= 2 empty <- grep("^[\t ]*$", cvec) cvec[if(any(!empty)) which(!empty)[1] else 1] } chopPara <- function(cvec) { n <- length(cvec); if(n <= 1) return(cvec) ## else n >= 2 empty <- grep("^[\t ]*$", cvec) ## the first non-empty ``from the right'' nm <- 1:n %nIN% (n + 1 - rev(empty)) if(any(nm)) cvec[1:(n - (which(nm)[1] - 1))] else ## all are empty; return just one cvec[1] } ### FIXME: default for 'chop' should be (something like) "dropEmptyTrail" cl <- match.call() E.prefix <- "^ o[\t ]+" chop <- match.arg(chop) chopFun <- switch(chop, "first" = chop1st, "1" = function(x) x[1], "par1" = chopPara, "keepAll" = function(x)x) parseEntry <- function(ll) { ## Purpose: parse a single NEWS entry ## Arguments: ll: lines of text (character vector) nl <- length(ll) ll[1] <- sub(E.prefix, '', ll[1]) ##cat(" entry with",nl, " lines of text") chv <- collapseWSpace(ll) chopFun(chv) } parseSection <- function(ll, kind) { ## Purpose: parse one section (e.g., "BUG FIXES") of NEWS ## Arguments: ll: lines of text (character vector) nl <- length(ll) if(trace) cat(" section '", kind,"' : ", nl, " lines", sep='') ## if(trace) cat(head(ll, min(3, nl)), if(nl > 5) ".............", '', sep="\n") ## if (nl > 3) if(trace) cat(tail(ll, min(2, nl-3)), '', sep="\n") iS <- grep(E.prefix, ll) if(trace) cat(" with ", length(iS), "entries\n") entries <- as.list(iS) ## entries have no labels ! iS <- c(iS, nl+1:1) for(i in seq(along = entries)) { entries[[i]] <- parseEntry(ll[iS[i] : (iS[i+1] - 1:1)]) } entries } parseVersion <- function(ll, ver) { ## Purpose: parse NEWS of one "x.y.z" R version ## Arguments: ll: lines of text (character vector) ## ver: version number of version, e.g., '2.2.1 patched' if(trace) cat(" parseVersion(*, ver =", ver,"),") s.pre <- "^[A-Z]+" iC <- grep(s.pre, ll) if(trace) cat(" with ", length(iC), "sections\n") sections <- as.list(iC) names(sections) <- ll[iC] iC <- c(iC, length(ll)+1:1) # such that iC[i] : (iC[i+1]-1) makes sense for(i in seq(along = sections)) { sections[[i]] <- parseSection(ll[(iC[i]+ 1:1) : (iC[i+1] - 1:1)], kind = names(sections)[i]) } sections } parseSeries <- function(ll, ver) { ## Purpose: parse NEWS of full (half year) "x.y" series of R version ## Arguments: ll: lines of text (character vector) ## ver: version number of series, e.g., '2.3' if(trace) cat("\nparseSeries(*, ver =", ver,"):\n") s.pre <- "^[\t ]*CHANGES IN R VERSION " iC <- grep(s.pre, ll) versions <- as.list(iC) names(versions) <- sub(s.pre, '', ll[iC]) iC <- c(iC, length(ll)+1:1) # such that iC[i] : (iC[i+1]-1) makes sense for(i in seq(along = versions)) { versions[[i]] <- parseVersion(ll[(iC[i]+ 1:1) : (iC[i+1] - 1:1)], ver = names(versions)[i]) } versions } if(is.character(file)) { file <- file(file, "r") on.exit(close(file)) } if(!inherits(file, "connection")) stop("'file' must be a character string or connection") if(!isOpen(file)) { open(file, "r") on.exit(close(file)) } ## We could read in parts ... ll <- readLines(file) nl <- length(ll) if(trace) cat("successfully read", nl, "lines from", file,"\n") s.pre <- "^\t*\\*[\t ]+ " ## REGEXP prefix used to identify series begin s.post <- " SERIES NEWS" iS <- grep(p0(s.pre, "[1-9]\.[0-9]", s.post), ll) series <- as.list(iS) names(series) <- sub(p0(s.post,"[\t ]*\\*$"), '', sub(s.pre, '', ll[iS])) if(trace) { cat(s.post, ":\n") print(unlist(series)) cat("Now parsing each: ...\n") } iS <- c(iS, nl+1:1) # such that iS[i] : (iS[i+1]-1) makes sense for(i in seq(along = series)) { series[[i]] <- parseSeries(ll[(iS[i]+ 1:1) : (iS[i+1] - 1:1)], ver = names(series)[i]) } attr(series, "call") <- cl class(series) <- "newsTree" series }