mirror of https://git.sr.ht/~hrbrmstr/vershist
boB Rudis
6 years ago
commit
d607d2c6be
35 changed files with 1810 additions and 0 deletions
@ -0,0 +1,11 @@ |
|||
^.*\.Rproj$ |
|||
^\.Rproj\.user$ |
|||
^\.travis\.yml$ |
|||
^README\.*Rmd$ |
|||
^README\.*html$ |
|||
^NOTES\.*Rmd$ |
|||
^NOTES\.*html$ |
|||
^\.codecov\.yml$ |
|||
^README_files$ |
|||
^doc$ |
|||
^tmp$ |
@ -0,0 +1 @@ |
|||
comment: false |
@ -0,0 +1,8 @@ |
|||
.DS_Store |
|||
.Rproj.user |
|||
.Rhistory |
|||
.RData |
|||
.Rproj |
|||
src/*.o |
|||
src/*.so |
|||
src/*.dll |
@ -0,0 +1,6 @@ |
|||
language: R |
|||
sudo: false |
|||
cache: packages |
|||
|
|||
after_success: |
|||
- Rscript -e 'covr::codecov()' |
@ -0,0 +1,36 @@ |
|||
Package: vershist |
|||
Type: Package |
|||
Title: Collect Version Histories For Vendor Products |
|||
Version: 0.1.0 |
|||
Date: 2018-03-20 |
|||
Authors@R: c( |
|||
person("Bob", "Rudis", email = "bob@rud.is", role = c("aut", "cre"), |
|||
comment = c(ORCID = "0000-0001-5670-2640")) |
|||
) |
|||
Maintainer: Bob Rudis <bob@rud.is> |
|||
Description: Provides a set of functions to gather version histories of products |
|||
(mainly software products) from their sources (generally websites). |
|||
URL: https://github.com/hrbrmstr/vershist |
|||
BugReports: https://github.com/hrbrmstr/vershist/issues |
|||
SystemRequirements: C++11 |
|||
Encoding: UTF-8 |
|||
License: AGPL |
|||
Suggests: |
|||
testthat, |
|||
covr |
|||
Depends: |
|||
R (>= 3.2.0) |
|||
Imports: |
|||
purrr, |
|||
rvest, |
|||
readr, |
|||
dplyr, |
|||
stringi, |
|||
semver, |
|||
lubridate, |
|||
utils, |
|||
xml2, |
|||
curl, |
|||
Rcpp |
|||
RoxygenNote: 6.0.1.9000 |
|||
LinkingTo: Rcpp |
@ -0,0 +1,42 @@ |
|||
# Generated by roxygen2: do not edit by hand |
|||
|
|||
export(apache_httpd_version_history) |
|||
export(is_valid) |
|||
export(lighttpd_version_history) |
|||
export(mongodb_version_history) |
|||
export(nginx_version_history) |
|||
export(sendmail_version_history) |
|||
import(semver) |
|||
importFrom(Rcpp,sourceCpp) |
|||
importFrom(curl,curl) |
|||
importFrom(dplyr,arrange) |
|||
importFrom(dplyr,as_data_frame) |
|||
importFrom(dplyr,bind_cols) |
|||
importFrom(dplyr,left_join) |
|||
importFrom(dplyr,mutate) |
|||
importFrom(dplyr,rename) |
|||
importFrom(dplyr,select) |
|||
importFrom(lubridate,mdy) |
|||
importFrom(lubridate,mdy_hms) |
|||
importFrom(lubridate,year) |
|||
importFrom(purrr,"%>%") |
|||
importFrom(purrr,discard) |
|||
importFrom(purrr,keep) |
|||
importFrom(purrr,map) |
|||
importFrom(purrr,map_df) |
|||
importFrom(readr,read_lines) |
|||
importFrom(rvest,html_nodes) |
|||
importFrom(rvest,html_text) |
|||
importFrom(rvest,xml_nodes) |
|||
importFrom(stringi,stri_detect_fixed) |
|||
importFrom(stringi,stri_detect_regex) |
|||
importFrom(stringi,stri_extract_first_regex) |
|||
importFrom(stringi,stri_match_first_regex) |
|||
importFrom(stringi,stri_replace_all_regex) |
|||
importFrom(stringi,stri_replace_first_fixed) |
|||
importFrom(stringi,stri_sub) |
|||
importFrom(utils,globalVariables) |
|||
importFrom(xml2,read_html) |
|||
importFrom(xml2,read_xml) |
|||
importFrom(xml2,xml_attr) |
|||
useDynLib(vershist) |
@ -0,0 +1,3 @@ |
|||
0.1.0 |
|||
* Initial release |
|||
* Support for Apache httpd, nginx, sendmail, lighttpd and mongodb |
@ -0,0 +1,11 @@ |
|||
# Generated by using Rcpp::compileAttributes() -> do not edit by hand |
|||
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 |
|||
|
|||
#' Test if semantic version strings are valid |
|||
#' |
|||
#' @param v character verctor of version strings |
|||
#' @export |
|||
is_valid <- function(v) { |
|||
.Call('_vershist_is_valid', PACKAGE = 'vershist', v) |
|||
} |
|||
|
@ -0,0 +1,6 @@ |
|||
utils::globalVariables( |
|||
c( |
|||
".", "vers", "major", "minor", "patch", "product", "rls_date", "rls_year", |
|||
"V1", "V2", "V3", "ts" |
|||
) |
|||
) |
@ -0,0 +1,29 @@ |
|||
#' Retrive Apache httpd Version Release History |
|||
#' |
|||
#' Reads <https://archive.apache.org/dist/httpd/> to build a data frame of |
|||
#' Apache `httpd` version release numbers and dates with semantic version |
|||
#' strings parsed and separate fields added. The data frame is also arranged in |
|||
#' order from lowest version to latest version and the `vers` column is an |
|||
#' ordered factor. |
|||
#' |
|||
#' @md |
|||
#' @export |
|||
apache_httpd_version_history <- function() { |
|||
|
|||
readr::read_lines("https://archive.apache.org/dist/httpd/") %>% |
|||
purrr::keep(stri_detect_regex, 'apache_.*gz"') %>% |
|||
stri_replace_first_fixed("<img src=\"/icons/compressed.gif\" alt=\"[ ]\"> ", "") %>% |
|||
stri_match_first_regex('href="apache_([[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+)\\.tar\\.gz".*([[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2})') %>% |
|||
dplyr::as_data_frame() %>% |
|||
dplyr::select(-V1) %>% |
|||
dplyr::rename(vers = V2, rls_date = V3) %>% |
|||
dplyr::mutate(rls_date = as.Date(rls_date)) %>% |
|||
dplyr::mutate(rls_year = lubridate::year(rls_date)) %>% |
|||
dplyr::bind_cols( |
|||
semver::parse_version(.$vers) %>% |
|||
as_data_frame() |
|||
) %>% |
|||
dplyr::arrange(major, minor, patch) %>% |
|||
dplyr::mutate(vers = factor(vers, levels = vers)) |
|||
|
|||
} |
@ -0,0 +1,35 @@ |
|||
#' Retrive lighttpd Version Release History |
|||
#' |
|||
#' Reads from the `lighttpd` releases and snapshot downloads to build a |
|||
#' data frame of version release numbers and dates. The caller is responsible |
|||
#' for extracting out the version components due to the non-standard |
|||
#' semantic versioning used. The [is_valid()] function can be used to test the |
|||
#' validity of version strings. |
|||
#' |
|||
#' @md |
|||
#' @export |
|||
lighttpd_version_history <- function() { |
|||
|
|||
c("https://download.lighttpd.net/lighttpd/releases-1.4.x/", |
|||
"https://download.lighttpd.net/lighttpd/snapshots-1.5/", |
|||
"https://download.lighttpd.net/lighttpd/snapshots-1.4.x/", |
|||
"https://download.lighttpd.net/lighttpd/snapshots-2.0.x/" |
|||
) %>% |
|||
purrr::map_df(~{ |
|||
pg <- read_html(.x) |
|||
dplyr::data_frame( |
|||
vers = rvest::html_nodes(pg, xpath=".//tr/td[1]") %>% |
|||
rvest::html_text(), |
|||
ts = rvest::html_nodes(pg, xpath=".//tr/td[2]") %>% |
|||
rvest::html_text() |
|||
) |
|||
}) %>% |
|||
dplyr::filter(stri_detect_regex(vers, "\\.tar\\.gz$")) %>% |
|||
dplyr::mutate(vers = stri_replace_all_regex(vers, "(^lighttpd-|\\.tar\\.gz$)", "")) %>% |
|||
dplyr::mutate( |
|||
ts = lubridate::ymd_hms(ts), |
|||
year = lubridate::year(ts) |
|||
) %>% |
|||
dplyr::rename(rls_date = ts, rls_year = year) |
|||
|
|||
} |
@ -0,0 +1,44 @@ |
|||
#' Retrive MongoDB Version Release History |
|||
#' |
|||
#' Reads <https://www.mongodb.org/dl/linux"> to build a data frame of |
|||
#' MongoDB version release numbers and dates with semantic version |
|||
#' strings parsed and separate fields added. The data frame is also arranged in |
|||
#' order from lowest version to latest version and the `vers` column is an |
|||
#' ordered factor. |
|||
#' |
|||
#' @md |
|||
#' @note Release candidates are not included and release history is only |
|||
#' supported for linux releases. |
|||
#' @export |
|||
mongodb_version_history <- function() { |
|||
|
|||
pg <- xml2::read_html("https://www.mongodb.org/dl/linux") |
|||
|
|||
dplyr::data_frame( |
|||
vers = rvest::html_nodes(pg, xpath=".//tr/td[1]") %>% |
|||
rvest::html_text(), |
|||
ts = rvest::html_nodes(pg, xpath=".//tr/td[2]") %>% |
|||
rvest::html_text() |
|||
) %>% |
|||
dplyr::filter( |
|||
!stri_detect_regex(vers, "(ubuntu|rhel|suse|debian|amazon|debug|latest)") |
|||
) %>% |
|||
dplyr::mutate( |
|||
vers = stri_extract_first_regex( |
|||
vers, |
|||
"[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]" |
|||
), |
|||
ts = stri_sub(ts, 1, 10) |
|||
) %>% |
|||
dplyr::distinct(vers, .keep_all=TRUE) %>% |
|||
dplyr::filter(!is.na(vers)) %>% |
|||
dplyr::mutate(year = lubridate::year(ts)) %>% |
|||
dplyr::bind_cols( |
|||
semver::parse_version(.$vers) %>% |
|||
dplyr::as_data_frame() |
|||
) %>% |
|||
dplyr::arrange(major, minor, patch) %>% |
|||
dplyr::mutate(vers = factor(vers, levels=vers)) %>% |
|||
dplyr::rename(rls_date = ts, rls_year = year) |
|||
|
|||
} |
@ -0,0 +1,34 @@ |
|||
#' Retrive nginx Version Release History |
|||
#' |
|||
#' Reads <https://raw.githubusercontent.com/nginx/nginx/master/docs/xml/nginx/changes.xml> |
|||
#' to build a data frame of nginx version release numbers and dates with semantic version |
|||
#' strings parsed and separate fields added. The data frame is also arranged in |
|||
#' order from lowest version to latest version and the `vers` column is an |
|||
#' ordered factor. |
|||
#' |
|||
#' @md |
|||
#' @export |
|||
nginx_version_history <- function() { |
|||
|
|||
nginx_changes_url <- |
|||
"https://raw.githubusercontent.com/nginx/nginx/master/docs/xml/nginx/changes.xml" |
|||
|
|||
doc <- suppressWarnings(xml2::read_xml(nginx_changes_url)) |
|||
|
|||
dplyr::data_frame( |
|||
vers = rvest::xml_nodes(doc, xpath="//changes") %>% |
|||
xml2::xml_attr("ver"), |
|||
ts = rvest::xml_nodes(doc, xpath="//changes") %>% |
|||
xml2::xml_attr("date") %>% |
|||
as.Date(), |
|||
year = lubridate::year(ts) |
|||
) %>% |
|||
dplyr::bind_cols( |
|||
semver::parse_version(.$vers) %>% |
|||
dplyr::as_data_frame() |
|||
) %>% |
|||
dplyr::arrange(major, minor, patch) %>% |
|||
dplyr::mutate(vers = factor(vers, levels=vers)) %>% |
|||
dplyr::rename(rls_date = ts, rls_year = year) |
|||
|
|||
} |
@ -0,0 +1,36 @@ |
|||
#' Retrive sendmail Version Release History |
|||
#' |
|||
#' Reads <ftp://ftp.sendmail.org/pub/sendmail/"> to build a data frame of |
|||
#' `sendmail` version release numbers and dates with semantic version |
|||
#' strings parsed and separate fields added. The data frame is also arranged in |
|||
#' order from lowest version to latest version and the `vers` column is an |
|||
#' ordered factor. |
|||
#' |
|||
#' @md |
|||
#' @export |
|||
sendmail_version_history <- function() { |
|||
|
|||
con <- curl::curl("ftp://ftp.sendmail.org/pub/sendmail/") |
|||
res <- readLines(con) |
|||
close(con) |
|||
|
|||
stri_match_first_regex(res, "([[:alpha:]]{3} [[:digit:]]{2} [[:digit:]]{4}) (.*)") %>% |
|||
dplyr::as_data_frame() %>% |
|||
dplyr::select(-V1) %>% |
|||
dplyr::rename(vers = V3, ts = V2) %>% |
|||
dplyr::select(vers, ts) %>% |
|||
dplyr::filter(stri_detect_regex(vers, "^sendmail.*\\.tar\\.gz$")) %>% |
|||
dplyr::filter(!stri_detect_fixed(vers, "->")) %>% |
|||
dplyr::mutate(vers = stri_replace_all_regex(vers, "(^sendmail\\.|\\.tar\\.gz$)", "")) %>% |
|||
dplyr::mutate(ts = stri_replace_all_regex(ts, "\ +", " ")) %>% |
|||
dplyr::mutate(ts = lubridate::mdy(ts)) %>% |
|||
dplyr::mutate(year = lubridate::year(ts)) %>% |
|||
dplyr::rename(rls_date = ts, rls_year = year) %>% |
|||
dplyr::bind_cols( |
|||
semver::parse_version(.$vers) %>% |
|||
dplyr::as_data_frame() |
|||
) %>% |
|||
dplyr::arrange(major, minor, patch) %>% |
|||
dplyr::mutate(vers = factor(vers, levels=vers)) |
|||
|
|||
} |
@ -0,0 +1,25 @@ |
|||
#' Collect Version Histories For Vendor Products |
|||
#' |
|||
#' Provides a set of functions to gather version histories of products |
|||
#' (mainly software products) from their sources (generally websites). |
|||
#' |
|||
#' @md |
|||
#' @name vershist |
|||
#' @docType package |
|||
#' @author Bob Rudis (bob@@rud.is) |
|||
#' @import semver |
|||
#' @importFrom purrr keep discard map map_df %>% |
|||
#' @importFrom dplyr mutate rename select as_data_frame left_join bind_cols arrange |
|||
#' @importFrom dplyr rename |
|||
#' @importFrom stringi stri_match_first_regex stri_detect_fixed stri_detect_regex |
|||
#' @importFrom stringi stri_replace_all_regex stri_replace_first_fixed |
|||
#' @importFrom stringi stri_extract_first_regex stri_sub |
|||
#' @importFrom lubridate year mdy mdy_hms |
|||
#' @importFrom readr read_lines |
|||
#' @importFrom utils globalVariables |
|||
#' @importFrom xml2 read_html read_xml xml_attr |
|||
#' @importFrom rvest html_nodes html_text xml_nodes |
|||
#' @importFrom curl curl |
|||
#' @useDynLib vershist |
|||
#' @importFrom Rcpp sourceCpp |
|||
NULL |
@ -0,0 +1,77 @@ |
|||
--- |
|||
output: rmarkdown::github_document |
|||
--- |
|||
|
|||
# vershist |
|||
|
|||
Collect Version Histories For Vendor Products |
|||
|
|||
## Description |
|||
|
|||
Provides a set of functions to gather version histories of products |
|||
(mainly software products) from their sources (generally websites). |
|||
|
|||
## What's Inside The Tin |
|||
|
|||
The following functions are implemented: |
|||
|
|||
Core: |
|||
|
|||
- `apache_httpd_version_history`: Retrive Apache httpd Version Release History |
|||
- `lighttpd_version_history`: Retrive lighttpd Version Release History |
|||
- `mongodb_version_history`: Retrive MongoDB Version Release History |
|||
- `nginx_version_history`: Retrive nginx Version Release History |
|||
- `sendmail_version_history`: Retrive sendmail Version Release History |
|||
|
|||
Utility: |
|||
|
|||
- `is_valid`: Test if semantic version strings are valid |
|||
|
|||
## Installation |
|||
|
|||
```{r eval=FALSE} |
|||
devtools::install_github("hrbrmstr/vershist") |
|||
``` |
|||
|
|||
```{r message=FALSE, warning=FALSE, error=FALSE, include=FALSE} |
|||
options(width=120) |
|||
``` |
|||
|
|||
## Usage |
|||
|
|||
```{r message=FALSE, warning=FALSE, error=FALSE} |
|||
library(vershist) |
|||
|
|||
# current verison |
|||
packageVersion("vershist") |
|||
``` |
|||
|
|||
Apache |
|||
|
|||
```{r} |
|||
apache_httpd_version_history() |
|||
``` |
|||
|
|||
lighttpd |
|||
|
|||
```{r} |
|||
lighttpd_version_history() |
|||
``` |
|||
|
|||
mongodb |
|||
|
|||
```{r} |
|||
mongodb_version_history() |
|||
``` |
|||
|
|||
nginx |
|||
|
|||
```{r} |
|||
nginx_version_history() |
|||
``` |
|||
|
|||
sendmail |
|||
|
|||
```{r} |
|||
sendmail_version_history() |
|||
``` |
@ -0,0 +1,153 @@ |
|||
|
|||
# vershist |
|||
|
|||
Collect Version Histories For Vendor Products |
|||
|
|||
## Description |
|||
|
|||
Provides a set of functions to gather version histories of products |
|||
(mainly software products) from their sources (generally websites). |
|||
|
|||
## What’s Inside The Tin |
|||
|
|||
The following functions are implemented: |
|||
|
|||
Core: |
|||
|
|||
- `apache_httpd_version_history`: Retrive Apache httpd Version Release |
|||
History |
|||
- `lighttpd_version_history`: Retrive lighttpd Version Release History |
|||
- `mongodb_version_history`: Retrive MongoDB Version Release History |
|||
- `nginx_version_history`: Retrive nginx Version Release History |
|||
- `sendmail_version_history`: Retrive sendmail Version Release History |
|||
|
|||
Utility: |
|||
|
|||
- `is_valid`: Test if semantic version strings are valid |
|||
|
|||
## Installation |
|||
|
|||
``` r |
|||
devtools::install_github("hrbrmstr/vershist") |
|||
``` |
|||
|
|||
## Usage |
|||
|
|||
``` r |
|||
library(vershist) |
|||
|
|||
# current verison |
|||
packageVersion("vershist") |
|||
``` |
|||
|
|||
## [1] '0.1.0' |
|||
|
|||
Apache |
|||
|
|||
``` r |
|||
apache_httpd_version_history() |
|||
``` |
|||
|
|||
## # A tibble: 29 x 8 |
|||
## vers rls_date rls_year major minor patch prerelease build |
|||
## <fct> <date> <dbl> <int> <int> <int> <chr> <chr> |
|||
## 1 1.3.0 1998-06-05 1998 1 3 0 "" "" |
|||
## 2 1.3.1 1998-07-22 1998 1 3 1 "" "" |
|||
## 3 1.3.2 1998-09-21 1998 1 3 2 "" "" |
|||
## 4 1.3.3 1998-10-09 1998 1 3 3 "" "" |
|||
## 5 1.3.4 1999-01-10 1999 1 3 4 "" "" |
|||
## 6 1.3.6 1999-03-23 1999 1 3 6 "" "" |
|||
## 7 1.3.9 1999-08-19 1999 1 3 9 "" "" |
|||
## 8 1.3.11 2000-01-22 2000 1 3 11 "" "" |
|||
## 9 1.3.12 2000-02-25 2000 1 3 12 "" "" |
|||
## 10 1.3.14 2000-10-10 2000 1 3 14 "" "" |
|||
## # ... with 19 more rows |
|||
|
|||
lighttpd |
|||
|
|||
``` r |
|||
lighttpd_version_history() |
|||
``` |
|||
|
|||
## # A tibble: 97 x 3 |
|||
## vers rls_date rls_year |
|||
## <chr> <dttm> <dbl> |
|||
## 1 1.4.20 2008-09-29 23:27:45 2008 |
|||
## 2 1.4.21-r2389 2009-02-05 12:43:18 2009 |
|||
## 3 1.4.13 2007-01-29 00:07:24 2007 |
|||
## 4 1.4.41 2016-07-31 12:51:39 2016 |
|||
## 5 1.4.27 2010-08-13 09:32:03 2010 |
|||
## 6 1.4.46 2017-10-21 19:54:46 2017 |
|||
## 7 1.4.39 2016-01-02 12:57:37 2016 |
|||
## 8 1.4.40 2016-07-16 10:28:52 2016 |
|||
## 9 1.4.32 2012-11-21 09:26:14 2012 |
|||
## 10 1.4.30 2011-12-18 15:23:06 2011 |
|||
## # ... with 87 more rows |
|||
|
|||
mongodb |
|||
|
|||
``` r |
|||
mongodb_version_history() |
|||
``` |
|||
|
|||
## # A tibble: 194 x 8 |
|||
## vers rls_date rls_year major minor patch prerelease build |
|||
## <fct> <chr> <dbl> <int> <int> <int> <chr> <chr> |
|||
## 1 0.8.0 2009-02-11 2009 0 8 0 "" "" |
|||
## 2 0.9.0 2009-03-27 2009 0 9 0 "" "" |
|||
## 3 0.9.1 2009-08-24 2009 0 9 1 "" "" |
|||
## 4 0.9.2 2009-05-22 2009 0 9 2 "" "" |
|||
## 5 0.9.3 2009-05-29 2009 0 9 3 "" "" |
|||
## 6 0.9.4 2009-06-09 2009 0 9 4 "" "" |
|||
## 7 0.9.5 2009-06-23 2009 0 9 5 "" "" |
|||
## 8 0.9.6 2009-07-08 2009 0 9 6 "" "" |
|||
## 9 0.9.7 2009-07-29 2009 0 9 7 "" "" |
|||
## 10 0.9.8 2009-08-14 2009 0 9 8 "" "" |
|||
## # ... with 184 more rows |
|||
|
|||
nginx |
|||
|
|||
``` r |
|||
nginx_version_history() |
|||
``` |
|||
|
|||
## # A tibble: 423 x 8 |
|||
## vers rls_date rls_year major minor patch prerelease build |
|||
## <fct> <date> <dbl> <int> <int> <int> <chr> <chr> |
|||
## 1 0.1.0 2004-10-04 2004 0 1 0 "" "" |
|||
## 2 0.1.1 2004-10-11 2004 0 1 1 "" "" |
|||
## 3 0.1.2 2004-10-21 2004 0 1 2 "" "" |
|||
## 4 0.1.3 2004-10-25 2004 0 1 3 "" "" |
|||
## 5 0.1.4 2004-10-26 2004 0 1 4 "" "" |
|||
## 6 0.1.5 2004-11-11 2004 0 1 5 "" "" |
|||
## 7 0.1.6 2004-11-11 2004 0 1 6 "" "" |
|||
## 8 0.1.7 2004-11-12 2004 0 1 7 "" "" |
|||
## 9 0.1.8 2004-11-20 2004 0 1 8 "" "" |
|||
## 10 0.1.9 2004-11-25 2004 0 1 9 "" "" |
|||
## # ... with 413 more rows |
|||
|
|||
sendmail |
|||
|
|||
``` r |
|||
sendmail_version_history() |
|||
``` |
|||
|
|||
## # A tibble: 16 x 8 |
|||
## vers rls_date rls_year major minor patch prerelease build |
|||
## <fct> <date> <dbl> <int> <int> <int> <chr> <chr> |
|||
## 1 8.12.11 2004-01-18 2004 8 12 11 "" "" |
|||
## 2 8.13.6 2006-03-22 2006 8 13 6 "" "" |
|||
## 3 8.13.7 2006-06-05 2006 8 13 7 "" "" |
|||
## 4 8.13.8 2006-08-09 2006 8 13 8 "" "" |
|||
## 5 8.14.0 2007-02-01 2007 8 14 0 "" "" |
|||
## 6 8.14.1 2007-04-04 2007 8 14 1 "" "" |
|||
## 7 8.14.2 2007-11-01 2007 8 14 2 "" "" |
|||
## 8 8.14.3 2008-05-03 2008 8 14 3 "" "" |
|||
## 9 8.14.4 2009-12-29 2009 8 14 4 "" "" |
|||
## 10 8.14.5 2011-05-17 2011 8 14 5 "" "" |
|||
## 11 8.14.6 2012-12-23 2012 8 14 6 "" "" |
|||
## 12 8.14.7 2013-04-21 2013 8 14 7 "" "" |
|||
## 13 8.14.8 2014-01-26 2014 8 14 8 "" "" |
|||
## 14 8.14.9 2014-05-21 2014 8 14 9 "" "" |
|||
## 15 8.15.1 2014-12-06 2014 8 15 1 "" "" |
|||
## 16 8.15.2 2015-07-03 2015 8 15 2 "" "" |
@ -0,0 +1,15 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/apache-httpd.R |
|||
\name{apache_httpd_version_history} |
|||
\alias{apache_httpd_version_history} |
|||
\title{Retrive Apache httpd Version Release History} |
|||
\usage{ |
|||
apache_httpd_version_history() |
|||
} |
|||
\description{ |
|||
Reads \url{https://archive.apache.org/dist/httpd/} to build a data frame of |
|||
Apache \code{httpd} version release numbers and dates with semantic version |
|||
strings parsed and separate fields added. The data frame is also arranged in |
|||
order from lowest version to latest version and the \code{vers} column is an |
|||
ordered factor. |
|||
} |
@ -0,0 +1,14 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/RcppExports.R |
|||
\name{is_valid} |
|||
\alias{is_valid} |
|||
\title{Test if semantic version strings are valid} |
|||
\usage{ |
|||
is_valid(v) |
|||
} |
|||
\arguments{ |
|||
\item{v}{character verctor of version strings} |
|||
} |
|||
\description{ |
|||
Test if semantic version strings are valid |
|||
} |
@ -0,0 +1,15 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/lighttpd.R |
|||
\name{lighttpd_version_history} |
|||
\alias{lighttpd_version_history} |
|||
\title{Retrive lighttpd Version Release History} |
|||
\usage{ |
|||
lighttpd_version_history() |
|||
} |
|||
\description{ |
|||
Reads from the \code{lighttpd} releases and snapshot downloads to build a |
|||
data frame of version release numbers and dates. The caller is responsible |
|||
for extracting out the version components due to the non-standard |
|||
semantic versioning used. The \code{\link[=is_valid]{is_valid()}} function can be used to test the |
|||
validity of version strings. |
|||
} |
@ -0,0 +1,19 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/mongodb.R |
|||
\name{mongodb_version_history} |
|||
\alias{mongodb_version_history} |
|||
\title{Retrive MongoDB Version Release History} |
|||
\usage{ |
|||
mongodb_version_history() |
|||
} |
|||
\description{ |
|||
Reads \url{https://www.mongodb.org/dl/linux"} to build a data frame of |
|||
MongoDB version release numbers and dates with semantic version |
|||
strings parsed and separate fields added. The data frame is also arranged in |
|||
order from lowest version to latest version and the \code{vers} column is an |
|||
ordered factor. |
|||
} |
|||
\note{ |
|||
Release candidates are not included and release history is only |
|||
supported for linux releases. |
|||
} |
@ -0,0 +1,15 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/nginx.R |
|||
\name{nginx_version_history} |
|||
\alias{nginx_version_history} |
|||
\title{Retrive nginx Version Release History} |
|||
\usage{ |
|||
nginx_version_history() |
|||
} |
|||
\description{ |
|||
Reads \url{https://raw.githubusercontent.com/nginx/nginx/master/docs/xml/nginx/changes.xml} |
|||
to build a data frame of nginx version release numbers and dates with semantic version |
|||
strings parsed and separate fields added. The data frame is also arranged in |
|||
order from lowest version to latest version and the \code{vers} column is an |
|||
ordered factor. |
|||
} |
@ -0,0 +1,15 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/sendmail.R |
|||
\name{sendmail_version_history} |
|||
\alias{sendmail_version_history} |
|||
\title{Retrive sendmail Version Release History} |
|||
\usage{ |
|||
sendmail_version_history() |
|||
} |
|||
\description{ |
|||
Reads \url{ftp://ftp.sendmail.org/pub/sendmail/"} to build a data frame of |
|||
\code{sendmail} version release numbers and dates with semantic version |
|||
strings parsed and separate fields added. The data frame is also arranged in |
|||
order from lowest version to latest version and the \code{vers} column is an |
|||
ordered factor. |
|||
} |
@ -0,0 +1,14 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/vershist-package.R |
|||
\docType{package} |
|||
\name{vershist} |
|||
\alias{vershist} |
|||
\alias{vershist-package} |
|||
\title{Collect Version Histories For Vendor Products} |
|||
\description{ |
|||
Provides a set of functions to gather version histories of products |
|||
(mainly software products) from their sources (generally websites). |
|||
} |
|||
\author{ |
|||
Bob Rudis (bob@rud.is) |
|||
} |
@ -0,0 +1,3 @@ |
|||
*.o |
|||
*.so |
|||
*.dll |
@ -0,0 +1,2 @@ |
|||
CXX_STD = CXX11 |
|||
PKG_CXXFLAGS = |
@ -0,0 +1,28 @@ |
|||
// Generated by using Rcpp::compileAttributes() -> do not edit by hand
|
|||
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
|
|||
|
|||
#include <Rcpp.h> |
|||
|
|||
using namespace Rcpp; |
|||
|
|||
// is_valid
|
|||
std::vector < bool > is_valid(std::vector < std::string > v); |
|||
RcppExport SEXP _vershist_is_valid(SEXP vSEXP) { |
|||
BEGIN_RCPP |
|||
Rcpp::RObject rcpp_result_gen; |
|||
Rcpp::RNGScope rcpp_rngScope_gen; |
|||
Rcpp::traits::input_parameter< std::vector < std::string > >::type v(vSEXP); |
|||
rcpp_result_gen = Rcpp::wrap(is_valid(v)); |
|||
return rcpp_result_gen; |
|||
END_RCPP |
|||
} |
|||
|
|||
static const R_CallMethodDef CallEntries[] = { |
|||
{"_vershist_is_valid", (DL_FUNC) &_vershist_is_valid, 1}, |
|||
{NULL, NULL, 0} |
|||
}; |
|||
|
|||
RcppExport void R_init_vershist(DllInfo *dll) { |
|||
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); |
|||
R_useDynamicSymbols(dll, FALSE); |
|||
} |
@ -0,0 +1,218 @@ |
|||
#ifndef CPP_SEMVER_TYPE_HPP |
|||
#define CPP_SEMVER_TYPE_HPP |
|||
|
|||
#include <string> |
|||
#include <vector> |
|||
|
|||
namespace semver |
|||
{ |
|||
// ------------ exception types ------------------------------------ //
|
|||
|
|||
struct semver_error : public std::runtime_error |
|||
{ |
|||
semver_error(const std::string& msg) : std::runtime_error(msg) {} |
|||
}; |
|||
|
|||
// ------------ syntax types ------------------------------------ //
|
|||
|
|||
struct syntax |
|||
{ |
|||
/*
|
|||
* original NPM semver syntax grammar: https://docs.npmjs.com/misc/Syntax#range-grammar
|
|||
* |
|||
* range-set ::= range ( logical-or range ) * |
|||
* logical-or ::= ( ' ' ) * '||' ( ' ' ) * |
|||
* range ::= hyphen | simple ( ' ' simple ) * | '' |
|||
* hyphen ::= partial ' - ' partial |
|||
* simple ::= primitive | partial | tilde | caret |
|||
* primitive ::= ( '<' | '>' | '>=' | '<=' | '=' | ) partial |
|||
* partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? |
|||
* xr ::= 'x' | 'X' | '*' | nr |
|||
* nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * |
|||
* tilde ::= '~' partial |
|||
* caret ::= '^' partial |
|||
* qualifier ::= ( '-' pre )? ( '+' build )? |
|||
* pre ::= parts |
|||
* build ::= parts |
|||
* parts ::= part ( '.' part ) * |
|||
* part ::= nr | [-0-9A-Za-z]+ |
|||
*/ |
|||
|
|||
enum class comparator |
|||
{ |
|||
eq, lt, lte, gt, gte, tilde, caret |
|||
}; |
|||
|
|||
/// default represents '*'
|
|||
struct xnumber |
|||
{ |
|||
bool is_wildcard = true; |
|||
int value = 0; |
|||
}; |
|||
|
|||
/// represents any type of 'simple', 'primitive', 'partial', 'tilde' or 'caret' from the grammar.
|
|||
/// The default value represents *.*.*
|
|||
struct simple |
|||
{ |
|||
xnumber major; |
|||
xnumber minor; |
|||
xnumber patch; |
|||
|
|||
std::string pre = ""; |
|||
std::string build = ""; |
|||
|
|||
comparator cmp = comparator::eq; |
|||
}; |
|||
|
|||
/// intersection set (i.e. AND conjunction )
|
|||
typedef std::vector< simple > range; |
|||
|
|||
/// union set (i.e. OR conjunction )
|
|||
typedef std::vector< range > range_set; |
|||
}; |
|||
|
|||
// ------------ semantic types ------------------------------------ //
|
|||
|
|||
struct semantic |
|||
{ |
|||
struct boundary |
|||
{ |
|||
int major = 0; |
|||
int minor = 0; |
|||
int patch = 0; |
|||
std::string pre = ""; |
|||
bool is_max = false; |
|||
|
|||
/// Represent a minimal version boundary
|
|||
static boundary min() |
|||
{ |
|||
return {}; |
|||
} |
|||
|
|||
/// Represent a maximal version boundary
|
|||
static boundary max() |
|||
{ |
|||
boundary b; |
|||
b.is_max = true; |
|||
return b; |
|||
} |
|||
}; |
|||
|
|||
/// default interval is *.*.* := [min, max]
|
|||
struct interval |
|||
{ |
|||
bool from_inclusive = true; |
|||
bool to_inclusive = true; |
|||
boundary from = boundary::min(); |
|||
boundary to = boundary::max(); |
|||
}; |
|||
|
|||
/// interval set (i.e. OR conjunction )
|
|||
typedef std::vector< interval > interval_set; |
|||
}; |
|||
|
|||
bool operator ==(const semantic::boundary& lhs, const semantic::boundary& rhs) |
|||
{ |
|||
return (lhs.major == rhs.major) && |
|||
(lhs.minor == rhs.minor) && |
|||
(lhs.patch == rhs.patch) && |
|||
(lhs.pre == rhs.pre) && |
|||
(lhs.is_max == rhs.is_max); |
|||
} |
|||
|
|||
bool operator !=(const semantic::boundary& lhs, const semantic::boundary& rhs) |
|||
{ |
|||
return !(lhs == rhs); |
|||
} |
|||
|
|||
bool operator <(const semantic::boundary& lhs, const semantic::boundary& rhs) |
|||
{ |
|||
// TODO: improve the performance of comparison
|
|||
|
|||
// XXX: is_max case
|
|||
if (lhs.is_max || rhs.is_max) |
|||
return !lhs.is_max && rhs.is_max; // 1.2.3 < max
|
|||
|
|||
if (lhs.major < rhs.major) // 1.* < 2.*
|
|||
return true; |
|||
|
|||
if ((lhs.major == rhs.major) && // 1.2.* < 1.3.*
|
|||
(lhs.minor < rhs.minor)) |
|||
return true; |
|||
|
|||
if ((lhs.major == rhs.major) && // 1.2.3 < 1.2.4
|
|||
(lhs.minor == rhs.minor) && |
|||
(lhs.patch < rhs.patch)) |
|||
return true; |
|||
|
|||
// XXX: pre-release case
|
|||
if ((lhs.major == rhs.major) && // 1.2.3-alpha < 1.2.3
|
|||
(lhs.minor == rhs.minor) && |
|||
(lhs.patch == rhs.patch) && |
|||
(!lhs.pre.empty() && rhs.pre.empty())) |
|||
return true; |
|||
|
|||
// XXX: pre-release case
|
|||
if ((lhs.major == rhs.major) && // 1.2.3-alpha < 1.2.3-beta
|
|||
(lhs.minor == rhs.minor) && |
|||
(lhs.patch == rhs.patch) && |
|||
(!lhs.pre.empty() && !rhs.pre.empty()) && |
|||
(lhs.pre < rhs.pre)) |
|||
return true; |
|||
|
|||
return false; |
|||
} |
|||
|
|||
bool operator >(const semantic::boundary& lhs, const semantic::boundary& rhs) |
|||
{ |
|||
return (lhs != rhs) && !(lhs < rhs); |
|||
} |
|||
|
|||
bool operator <=(const semantic::boundary& lhs, const semantic::boundary& rhs) |
|||
{ |
|||
return (lhs < rhs) || (lhs == rhs); |
|||
} |
|||
|
|||
bool operator >=(const semantic::boundary& lhs, const semantic::boundary& rhs) |
|||
{ |
|||
return (lhs > rhs) || (lhs == rhs); |
|||
} |
|||
|
|||
bool operator ==(const semantic::interval& lhs, const semantic::interval& rhs) |
|||
{ |
|||
return (lhs.from_inclusive == rhs.from_inclusive) && |
|||
(lhs.to_inclusive == rhs.to_inclusive) && |
|||
(lhs.from == rhs.from) && |
|||
(lhs.to == rhs.to); |
|||
} |
|||
|
|||
bool operator !=(const semantic::interval& lhs, const semantic::interval& rhs) |
|||
{ |
|||
return !(lhs == rhs); |
|||
} |
|||
|
|||
bool operator <(const semantic::interval& lhs, const semantic::interval& rhs) |
|||
{ |
|||
if (lhs.to < rhs.from || |
|||
(lhs.to == rhs.from && (!lhs.to_inclusive || !rhs.from_inclusive))) |
|||
return true; |
|||
return false; |
|||
} |
|||
|
|||
bool operator <=(const semantic::interval& lhs, const semantic::interval& rhs) |
|||
{ |
|||
return (lhs < rhs) || (lhs == rhs); |
|||
} |
|||
|
|||
bool operator >(const semantic::interval& lhs, const semantic::interval& rhs) |
|||
{ |
|||
return (lhs != rhs) && !(lhs < rhs); |
|||
} |
|||
|
|||
bool operator >=(const semantic::interval& lhs, const semantic::interval& rhs) |
|||
{ |
|||
return (lhs > rhs) || (lhs == rhs); |
|||
} |
|||
} |
|||
|
|||
#endif |
@ -0,0 +1,69 @@ |
|||
#ifndef CPP_SEMVER_UTIL_HPP |
|||
#define CPP_SEMVER_UTIL_HPP |
|||
|
|||
#include <string> |
|||
#include <vector> |
|||
|
|||
namespace semver |
|||
{ |
|||
const std::string any_space = " \n\r\t\v\f"; |
|||
const std::string any_number = "0123456789"; |
|||
const std::string any_alphabat = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; |
|||
|
|||
// [ x y z ] -> [ x y z ]
|
|||
std::string reduce_space(const std::string& str) |
|||
{ |
|||
std::string new_string; |
|||
bool pre_space = false; |
|||
for (const char& c : str) |
|||
{ |
|||
const bool has_space = (any_space.find(c) != std::string::npos); |
|||
if (pre_space && has_space) |
|||
continue; |
|||
|
|||
new_string.push_back(c); |
|||
pre_space = has_space; |
|||
} |
|||
return new_string; |
|||
} |
|||
|
|||
// [ x y z ] -> [x y z]
|
|||
std::string trim_string(const std::string& str) |
|||
{ |
|||
size_t start = str.find_first_not_of(any_space); |
|||
if (start == std::string::npos) |
|||
return {}; |
|||
|
|||
size_t end = str.find_last_not_of(any_space); |
|||
if (end == std::string::npos) |
|||
return str.substr(start); |
|||
|
|||
return str.substr(start, end - start + 1); |
|||
} |
|||
|
|||
std::vector<std::string> split(const std::string& input, const std::string& delimiter, bool trim = false) |
|||
{ |
|||
std::vector<std::string> result; |
|||
|
|||
size_t search_pos = 0; |
|||
while (1) |
|||
{ |
|||
size_t found = input.find(delimiter, search_pos); |
|||
if (found == std::string::npos) |
|||
{ |
|||
result.emplace_back(input.substr(search_pos)); |
|||
break; |
|||
} |
|||
result.emplace_back(input.substr(search_pos, found - search_pos)); |
|||
search_pos = found + delimiter.length(); |
|||
} |
|||
|
|||
if (trim) |
|||
for (std::string& s : result) |
|||
s = trim_string(s); |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
|
|||
#endif |
@ -0,0 +1,534 @@ |
|||
#ifndef CPP_SEMVER_HPP |
|||
#define CPP_SEMVER_HPP |
|||
|
|||
#include "base/type.h" |
|||
#include "base/util.h" |
|||
|
|||
#ifdef USE_PEGTL |
|||
#include "parser/peg.hpp" // PETGTL parser |
|||
#define PARSER peg::parser |
|||
#else |
|||
#include "parser/parser.h" |
|||
#define PARSER semver::parser |
|||
#endif |
|||
|
|||
#include <string> |
|||
#include <vector> |
|||
#include <memory> |
|||
|
|||
namespace semver |
|||
{ |
|||
namespace detail |
|||
{ |
|||
/* parse version range in string into syntactical representation @c syntax::range_set */ |
|||
syntax::range_set parse(const std::string& input) |
|||
{ |
|||
return PARSER(input); |
|||
} |
|||
|
|||
/* parse syntactical representation @c syntax::simple and convert it to sementical representation @c semantic::interval */ |
|||
semantic::interval parse(const syntax::simple& input) |
|||
{ |
|||
// default result * := [min, max]
|
|||
semantic::interval result; |
|||
|
|||
semantic::boundary b; // associate boundary from input x.y.z-pre
|
|||
// replace * with 0
|
|||
b.major = (!input.major.is_wildcard ? input.major.value : 0); |
|||
b.minor = (!input.minor.is_wildcard ? input.minor.value : 0); |
|||
b.patch = (!input.patch.is_wildcard ? input.patch.value : 0); |
|||
b.pre = input.pre; |
|||
|
|||
switch (input.cmp) |
|||
{ |
|||
case syntax::comparator::lt: // <x.y.z-pre := [min, x.y.z-pre)
|
|||
|
|||
result.to_inclusive = false; |
|||
result.to = b; |
|||
|
|||
if (input.major.is_wildcard || |
|||
input.minor.is_wildcard || |
|||
input.patch.is_wildcard) // <x.*-pre := [min, x.0.0)
|
|||
result.to.pre = ""; // <x.y.*-pre := [min, x,y.0)
|
|||
|
|||
break; |
|||
|
|||
case syntax::comparator::lte: // <=x.y.z.pre := [min, x.y.z-pre]
|
|||
|
|||
result.to = b; |
|||
|
|||
if (input.major.is_wildcard || |
|||
input.minor.is_wildcard || |
|||
input.patch.is_wildcard) // <=x.*-pre := [min, 'x+1'.0.0)
|
|||
{ // <=x.y.*-pre := [min, x,'y+1'.0)
|
|||
result.to_inclusive = false; |
|||
result.to.pre = ""; |
|||
} |
|||
|
|||
if (input.major.is_wildcard) // <=* := [min, max]
|
|||
{ |
|||
result = semantic::interval(); |
|||
} |
|||
else if (input.minor.is_wildcard) // <=x.*-pre := [min, 'x+1'.0.0)
|
|||
{ |
|||
result.to.major += 1; |
|||
result.to.patch = 0; |
|||
} |
|||
else if (input.patch.is_wildcard) // <=x.y.*-pre := [min, x,'y+1'.0)
|
|||
{ |
|||
result.to.minor += 1; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case syntax::comparator::gt: // >x.y.z-pre := (x.y.z-pre, max]
|
|||
result.from_inclusive = false; |
|||
result.from = b; |
|||
|
|||
if (input.major.is_wildcard || |
|||
input.minor.is_wildcard || |
|||
input.patch.is_wildcard) // >x.*-pre := ['x+1', max]
|
|||
{ // >x.y.*-pre := [x.'y+1', max]
|
|||
result.from_inclusive = true; |
|||
result.from.pre = ""; |
|||
} |
|||
|
|||
if (input.major.is_wildcard) |
|||
{ |
|||
result.from = semantic::boundary::max(); // invalid set
|
|||
} |
|||
else if (input.minor.is_wildcard) // >x.*-pre := ['x+1'.0.0, max]
|
|||
{ |
|||
result.from.major += 1; |
|||
result.from.patch = 0; |
|||
} |
|||
else if (input.patch.is_wildcard) // >x.y.*-pre := [x,'y+1'.0, max]
|
|||
{ |
|||
result.from.minor += 1; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case syntax::comparator::gte: // >=x.y.z-pre := [x.y.z-pre, max]
|
|||
|
|||
result.from = b; |
|||
|
|||
if (input.major.is_wildcard || |
|||
input.minor.is_wildcard || |
|||
input.patch.is_wildcard) // >=x.*-pre := [x.0.0, max]
|
|||
result.from.pre = ""; // >=x.y.*-pre := [x.y.0, max]
|
|||
|
|||
break; |
|||
|
|||
case syntax::comparator::caret: |
|||
|
|||
result.from = b; |
|||
result.to = b; |
|||
|
|||
if (input.major.is_wildcard || |
|||
input.minor.is_wildcard || |
|||
input.patch.is_wildcard) |
|||
result.from.pre = ""; |
|||
|
|||
result.to_inclusive = false; |
|||
result.to.pre = ""; |
|||
|
|||
if (input.major.is_wildcard) // ^* := [min, max]
|
|||
{ |
|||
result = semantic::interval(); |
|||
} |
|||
else if (input.major.value != 0 || // ^x.y.z-pre := [x.y.z-pre, 'x+1'.0.0)
|
|||
input.minor.is_wildcard) // ^x.y := [x.y.0, 'x+1'.0.0)
|
|||
{ // ^x := [x.0.0, 'x+1'.0.0)
|
|||
result.to.major += 1; // ^0 := [0.0.0, 1.0.0)
|
|||
result.to.minor = 0; |
|||
result.to.patch = 0; |
|||
} |
|||
else if (input.minor.value != 0 || // ^0.y.z := [0.y.z, 0.'y+1'.z)
|
|||
input.patch.is_wildcard) // ^0.y := [0.y.0, 0.'y+1'.0)
|
|||
{ |
|||
result.to.minor += 1; |
|||
result.to.patch = 0; |
|||
} |
|||
else if (input.patch.value != 0) |
|||
{ |
|||
result.to.patch += 1; // ^0.0.z := [0.0.z, 0.0.'z+1')
|
|||
} |
|||
|
|||
break; |
|||
|
|||
case syntax::comparator::tilde: |
|||
|
|||
result.from = b; |
|||
result.to = b; |
|||
|
|||
if (input.major.is_wildcard || |
|||
input.minor.is_wildcard || |
|||
input.patch.is_wildcard) |
|||
result.from.pre = ""; |
|||
|
|||
result.to_inclusive = false; |
|||
result.to.pre = ""; |
|||
|
|||
if (input.major.is_wildcard) // ~* := [min, max]
|
|||
{ |
|||
result = semantic::interval(); |
|||
} |
|||
else if (input.minor.is_wildcard) // ~x-pre := [x.0.0, 'x+1'.0.0)
|
|||
{ |
|||
result.from.pre = ""; |
|||
|
|||
result.to.major += 1; |
|||
result.to.minor = 0; |
|||
result.to.patch = 0; |
|||
} |
|||
else // ~x.y.z-pre := [x.y.z-pre, x.'y+1'.0)
|
|||
{ |
|||
result.to.minor += 1; |
|||
result.to.patch = 0; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case syntax::comparator::eq: |
|||
default: |
|||
|
|||
result.from = b; // x.y.z := [x.y.z, x.y.z]
|
|||
result.to = b; |
|||
|
|||
if (input.major.is_wildcard || |
|||
input.minor.is_wildcard || |
|||
input.patch.is_wildcard) // =x.*-pre := [x.0.0, 'x+1'.0.0)
|
|||
{ // =x.y.*-pre := [x.y.0, x.'y+1'.0)
|
|||
result.from.pre = ""; |
|||
|
|||
result.to_inclusive = false; |
|||
result.to.pre = ""; |
|||
} |
|||
|
|||
if (input.major.is_wildcard) // * := [min, max]
|
|||
{ |
|||
result = semantic::interval(); |
|||
} |
|||
else if (input.minor.is_wildcard) // x := [x.0.0, 'x+1'.0.0)
|
|||
{ |
|||
result.from.patch = 0; |
|||
|
|||
result.to.major += 1; |
|||
result.to.patch = 0; |
|||
} |
|||
else if (input.patch.is_wildcard) // x.y := [x.y.0, x.'y+1'.0)
|
|||
{ |
|||
result.to.minor += 1; |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
/* calculate AND-conjunction from a list of @c semantic::interval */ |
|||
std::unique_ptr<semantic::interval> and_conj(const std::vector< semantic::interval >& input) |
|||
{ |
|||
const size_t size = input.size(); |
|||
|
|||
if (size == 0) |
|||
return nullptr; |
|||
|
|||
semantic::interval result = input.at(0); |
|||
|
|||
for (size_t i = 1; i < size; i++) |
|||
{ |
|||
const semantic::interval& span = input.at(i); |
|||
|
|||
// prerelease tag case
|
|||
{ |
|||
const bool result_is_range = (result.from != result.to || result.from_inclusive != result.to_inclusive); |
|||
const bool span_is_range = (span.from != span.to || span.from_inclusive != span.to_inclusive); |
|||
|
|||
if (result_is_range != span_is_range) |
|||
{ |
|||
const semantic::interval& range = (result_is_range ? result : span); |
|||
const semantic::interval& target = (result_is_range ? span : result); |
|||
const bool range_from_has_pre = !range.from.pre.empty(); |
|||
const bool range_to_has_pre = !range.to.pre.empty(); |
|||
const bool target_has_pre = !target.from.pre.empty(); |
|||
|
|||
if (target_has_pre) |
|||
{ |
|||
// 1-rc not in '0 - 10'
|
|||
if (!range_from_has_pre && !range_to_has_pre) |
|||
return nullptr; |
|||
|
|||
// 1-rc in '1-beta - 10'
|
|||
if (range_from_has_pre && !range_to_has_pre) |
|||
if (range.from.major != target.from.major || |
|||
range.from.minor != target.from.minor || |
|||
range.from.patch != target.from.patch) |
|||
return nullptr; |
|||
|
|||
// 10-beta in '1 - 10-rc'
|
|||
if (!range_from_has_pre && range_to_has_pre) |
|||
if (range.to.major != target.to.major || |
|||
range.to.minor != target.to.minor || |
|||
range.to.patch != target.to.patch) |
|||
return nullptr; |
|||
|
|||
// 1-rc or 10-beta in '1-beta - 10-rc'
|
|||
if (range_from_has_pre && range_to_has_pre) |
|||
if (!((range.from.major == target.from.major && |
|||
range.from.minor == target.from.minor && |
|||
range.from.patch == target.from.patch) || |
|||
(range.to.major == target.to.major && |
|||
range.to.minor == target.to.minor && |
|||
range.to.patch == target.to.patch))) |
|||
return nullptr; |
|||
} |
|||
} |
|||
} // prerelease tag case
|
|||
|
|||
if ((span.from > result.from) || |
|||
((span.from == result.from) && !span.from_inclusive)) |
|||
{ |
|||
result.from = span.from; |
|||
result.from_inclusive = span.from_inclusive; |
|||
} |
|||
|
|||
if ((span.to < result.to) || |
|||
((span.to == result.to) && !span.to_inclusive)) |
|||
{ |
|||
result.to = span.to; |
|||
result.to_inclusive = span.to_inclusive; |
|||
} |
|||
} |
|||
|
|||
if ((result.from > result.to) || |
|||
((result.from == result.to) && (result.from_inclusive != result.to_inclusive))) |
|||
return nullptr; |
|||
|
|||
return std::unique_ptr<semantic::interval>(new semantic::interval(result)); |
|||
} |
|||
|
|||
/* parse syntactical representation @c syntax::range_set and convert it to sementical representation @c semantic::interval_set */ |
|||
semantic::interval_set parse(const syntax::range_set& input) |
|||
{ |
|||
semantic::interval_set or_set; |
|||
for (const syntax::range& range : input) |
|||
{ |
|||
std::vector<semantic::interval> and_set; |
|||
for (const syntax::simple& simple : range) |
|||
and_set.emplace_back(parse(simple)); |
|||
const std::unique_ptr<semantic::interval> conj = and_conj(and_set); |
|||
if (conj) |
|||
or_set.emplace_back(std::move(*conj)); |
|||
} |
|||
return or_set; |
|||
} |
|||
|
|||
/* check if two @c semantic::interval_set intersect with each other */ |
|||
bool intersects(const semantic::interval_set& s1, const semantic::interval_set& s2) |
|||
{ |
|||
if (s1.empty() || s2.empty()) |
|||
return false; |
|||
|
|||
for (const semantic::interval& intval_1 : s1) |
|||
for (const semantic::interval& intval_2 : s2) |
|||
if (and_conj({ intval_1, intval_2 })) |
|||
return true; |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/* check if two @c syntax::range_set intersect with each other */ |
|||
bool intersects(const syntax::range_set& rs1, const syntax::range_set& rs2) |
|||
{ |
|||
return intersects(parse(rs1), parse(rs2)); |
|||
} |
|||
|
|||
/** parse or cast as a syntax::simple if possible */ |
|||
syntax::simple as_simple(const std::string& s) |
|||
{ |
|||
syntax::range_set parsed = parse(s); |
|||
|
|||
if (!parsed.empty() && !parsed.at(0).empty()) |
|||
return std::move(parsed.at(0).at(0)); |
|||
|
|||
return{}; |
|||
} |
|||
} |
|||
|
|||
|
|||
// *************** API **************************************** //
|
|||
|
|||
|
|||
/** Return true if the comparator or range intersect */ |
|||
bool intersects(const std::string& range) |
|||
{ |
|||
return detail::intersects(detail::parse(range), detail::parse("*")); |
|||
} |
|||
|
|||
/** Return true if the two supplied ranges or comparators intersect. */ |
|||
bool intersects(const std::string& range1, const std::string& range2) |
|||
{ |
|||
return detail::intersects(detail::parse(range1), detail::parse(range2)); |
|||
} |
|||
|
|||
/** Return true if the version satisfies the range */ |
|||
bool satisfies(const std::string& version, const std::string& range) |
|||
{ |
|||
return detail::intersects(detail::parse(version), detail::parse(range)); |
|||
} |
|||
|
|||
/** v1 == v2 */ |
|||
bool eq(const std::string& v1, const std::string& v2) |
|||
{ |
|||
semantic::interval_set interval_set_1 = detail::parse(detail::parse(v1)); |
|||
semantic::interval_set interval_set_2 = detail::parse(detail::parse(v2)); |
|||
|
|||
if (interval_set_1.empty() && interval_set_2.empty()) |
|||
return true; |
|||
else if (interval_set_1.empty() != interval_set_2.empty()) |
|||
return false; |
|||
|
|||
for (const semantic::interval& interval_1 : interval_set_1) |
|||
for (const semantic::interval& interval_2 : interval_set_2) |
|||
if (interval_1 == interval_2) |
|||
return true; |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** v1 != v2 */ |
|||
bool neq(const std::string& v1, const std::string& v2) |
|||
{ |
|||
return !eq(v1, v2); |
|||
} |
|||
|
|||
/** v1 > v2 */ |
|||
bool gt(const std::string& v1, const std::string& v2) |
|||
{ |
|||
semantic::interval_set interval_set_1 = detail::parse(detail::parse(v1)); |
|||
semantic::interval_set interval_set_2 = detail::parse(detail::parse(v2)); |
|||
|
|||
if (!interval_set_1.empty() && interval_set_2.empty()) |
|||
return true; |
|||
else if (interval_set_1.empty() || interval_set_2.empty()) |
|||
return false; |
|||
|
|||
for (const semantic::interval& interval_1 : interval_set_1) |
|||
for (const semantic::interval& interval_2 : interval_set_2) |
|||
if (interval_1 > interval_2) |
|||
return true; |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** v1 >= v2 */ |
|||
bool gte(const std::string& v1, const std::string& v2) |
|||
{ |
|||
if (eq(v1, v2) || gt(v1, v2)) |
|||
return true; |
|||
return false; |
|||
} |
|||
|
|||
/** v1 < v2 */ |
|||
bool lt(const std::string& v1, const std::string& v2) |
|||
{ |
|||
if (!eq(v1, v2) && !gt(v1, v2)) |
|||
return true; |
|||
return false; |
|||
} |
|||
|
|||
/** v1 <= v2 */ |
|||
bool lte(const std::string& v1, const std::string& v2) |
|||
{ |
|||
if (eq(v1, v2) || lt(v1, v2)) |
|||
return true; |
|||
return false; |
|||
} |
|||
|
|||
/** Return true if version is greater than all the versions possible in the range. */ |
|||
bool gtr(const std::string& version, const std::string& range) |
|||
{ |
|||
semantic::interval_set interval_set_v = detail::parse(detail::parse(version)); |
|||
semantic::interval_set interval_set_r = detail::parse(detail::parse(range)); |
|||
|
|||
if (!interval_set_v.empty() && interval_set_r.empty()) |
|||
return true; |
|||
else if (interval_set_v.empty() || interval_set_r.empty()) |
|||
return false; |
|||
|
|||
for (const semantic::interval& interval_v : interval_set_v) |
|||
for (const semantic::interval& interval_r : interval_set_r) |
|||
if (interval_v <= interval_r) |
|||
return false; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** Return true if version is less than all the versions possible in the range. */ |
|||
bool ltr(const std::string& version, const std::string& range) |
|||
{ |
|||
semantic::interval_set interval_set_v = detail::parse(detail::parse(version)); |
|||
semantic::interval_set interval_set_r = detail::parse(detail::parse(range)); |
|||
|
|||
if (interval_set_v.empty() && !interval_set_r.empty()) |
|||
return true; |
|||
else if (interval_set_v.empty() || interval_set_r.empty()) |
|||
return false; |
|||
|
|||
for (const semantic::interval& interval_v : interval_set_v) |
|||
for (const semantic::interval& interval_r : interval_set_r) |
|||
if (interval_v >= interval_r) |
|||
return false; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** Return true if the version or range is valid */ |
|||
bool valid(const std::string& s) |
|||
{ |
|||
try |
|||
{ |
|||
semantic::interval_set interval_set_s = detail::parse(detail::parse(s)); |
|||
return !interval_set_s.empty(); |
|||
} |
|||
catch (semver_error const&) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** Return the major version number. */ |
|||
int major(const std::string& version) |
|||
{ |
|||
const auto& major = detail::as_simple(version).major; |
|||
return !major.is_wildcard ? major.value : 0; |
|||
} |
|||
|
|||
/** Return the minor version number. */ |
|||
int minor(const std::string& version) |
|||
{ |
|||
const auto& minor = detail::as_simple(version).minor; |
|||
return !minor.is_wildcard ? minor.value : 0; |
|||
} |
|||
|
|||
/** Return the patch version number. */ |
|||
int patch(const std::string& version) |
|||
{ |
|||
const auto& patch = detail::as_simple(version).patch; |
|||
return !patch.is_wildcard ? patch.value : 0; |
|||
} |
|||
|
|||
/** Returns an array of prerelease components. */ |
|||
std::vector<std::string> prerelease(const std::string& version) |
|||
{ |
|||
const auto& pre = detail::as_simple(version).pre; |
|||
if (pre.empty()) |
|||
return {}; |
|||
|
|||
return split(pre, "."); |
|||
} |
|||
|
|||
} |
|||
|
|||
#endif |
@ -0,0 +1,230 @@ |
|||
#ifndef CPP_SEMVER_PARSER_HPP |
|||
#define CPP_SEMVER_PARSER_HPP |
|||
|
|||
#include "../base/type.h" |
|||
#include "../base/util.h" |
|||
|
|||
#include <vector> |
|||
#include <string> |
|||
|
|||
namespace semver |
|||
{ |
|||
int parse_nr(const std::string& input) |
|||
{ |
|||
// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
|
|||
|
|||
if (input.length() == 0) |
|||
throw semver_error("empty string as invalid number"); |
|||
|
|||
if (input.find_first_not_of(any_number) != std::string::npos) |
|||
throw semver_error("unexpected char as invalid number: '" + input + "'"); |
|||
|
|||
if (input.length() > 1 && input.at(0) == '0') |
|||
throw semver_error("unexpected '0' as invalid number: '" + input + "'"); |
|||
|
|||
try |
|||
{ |
|||
return std::stoi(input); |
|||
} |
|||
catch (std::invalid_argument&) |
|||
{ |
|||
throw semver_error("invalid number: '" + input + "'"); |
|||
} |
|||
} |
|||
|
|||
syntax::xnumber parse_xr(const std::string& input) |
|||
{ |
|||
// xr ::=
|
|||
// 'x' | 'X' | '*' |
|
|||
if (input == "x" || input == "X" || input == "*") |
|||
return {}; |
|||
|
|||
// nr
|
|||
syntax::xnumber nr; |
|||
nr.is_wildcard = false; |
|||
nr.value = parse_nr(input); |
|||
return nr; |
|||
} |
|||
|
|||
std::string parse_part(const std::string& input) |
|||
{ |
|||
// part ::=
|
|||
|
|||
if (input.empty() || any_number.find(input.at(0)) != std::string::npos) |
|||
{ // nr |
|
|||
parse_nr(input); |
|||
} |
|||
else if (input.find_first_not_of("-" + any_number + any_alphabat) != std::string::npos) |
|||
{ |
|||
// [-0-9A-Za-z]+
|
|||
throw semver_error("unexpected character in part: '" + input + "'"); |
|||
} |
|||
|
|||
return input; |
|||
} |
|||
|
|||
std::string parse_parts(const std::string& input) |
|||
{ |
|||
// parts ::= part ( '.' part ) *
|
|||
|
|||
std::vector<std::string> part_tokens = split(input, "."); |
|||
for (const std::string& part_token : part_tokens) |
|||
parse_part(part_token); |
|||
|
|||
return input; |
|||
} |
|||
|
|||
syntax::simple parse_partial(const std::string& input) |
|||
{ |
|||
// partial ::= xr ( '.' xr ( '.' xr ( '-' pre )? ( '+' build )? ? )? )?
|
|||
// pre ::= parts
|
|||
// build ::= parts
|
|||
|
|||
size_t found_pre = std::string::npos; |
|||
const size_t found_build = input.find_first_of('+'); |
|||
const size_t found_dash = input.find_first_of('-'); |
|||
|
|||
if (found_build != std::string::npos && found_dash != std::string::npos) |
|||
{ |
|||
if (found_dash < found_build) |
|||
found_pre = found_dash; |
|||
} |
|||
else if (found_build == std::string::npos && found_dash != std::string::npos) |
|||
{ |
|||
found_pre = found_dash; |
|||
} |
|||
|
|||
syntax::simple result; |
|||
|
|||
{ |
|||
if (found_build != std::string::npos) |
|||
result.build = parse_parts(input.substr(found_build + 1)); |
|||
|
|||
if (found_pre != std::string::npos) |
|||
{ |
|||
if (found_build != std::string::npos) |
|||
result.pre = parse_parts(input.substr(found_pre + 1, found_build - found_pre - 1)); |
|||
else |
|||
result.pre = parse_parts(input.substr(found_pre + 1)); |
|||
} |
|||
} |
|||
|
|||
{ |
|||
size_t xr_end = found_pre; |
|||
if (xr_end == std::string::npos && found_build != std::string::npos) |
|||
xr_end = found_build; |
|||
|
|||
const std::string xr_xr_xr = (xr_end == std::string::npos) ? input : input.substr(0, xr_end); |
|||
|
|||
std::vector<std::string> xr_tokens = split(xr_xr_xr, "."); |
|||
if ((!result.pre.empty() || !result.build.empty()) && xr_tokens.size() != 3) |
|||
throw semver_error("incomplete version with pre or build tag: '" + xr_xr_xr + "'"); |
|||
|
|||
if (xr_tokens.size() > 0) |
|||
{ |
|||
// allow 'v' prefix
|
|||
std::string xr = xr_tokens.at(0); |
|||
xr = (xr.find_first_of("vV") == 0) ? xr.substr(1) : xr; |
|||
result.major = parse_xr(xr); |
|||
} |
|||
if (xr_tokens.size() > 1) |
|||
result.minor = parse_xr(xr_tokens.at(1)); |
|||
if (xr_tokens.size() > 2) |
|||
result.patch = parse_xr(xr_tokens.at(2)); |
|||
if (xr_tokens.size() > 3) |
|||
throw semver_error("invalid version: '" + xr_xr_xr + "'"); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
syntax::simple parse_simple(const std::string& input) |
|||
{ |
|||
// simple ::= primitive | partial | tilde | caret
|
|||
// primitive ::= ( '<' | '>' | '>=' | '<=' | '=' | ) partial
|
|||
// tilde ::= '~' partial
|
|||
// caret ::= '^' partial
|
|||
|
|||
const size_t partial_start = input.find_first_not_of("<>=~^"); |
|||
|
|||
if (partial_start == std::string::npos) |
|||
throw semver_error("invalid version: '" + input + "'"); |
|||
|
|||
const std::string prefix = input.substr(0, partial_start); |
|||
syntax::simple result = parse_partial(input.substr(partial_start)); |
|||
|
|||
if (prefix == "=" || prefix.empty()) |
|||
result.cmp = syntax::comparator::eq; |
|||
else if (prefix == "<") |
|||
result.cmp = syntax::comparator::lt; |
|||
else if (prefix == ">") |
|||
result.cmp = syntax::comparator::gt; |
|||
else if (prefix == "<=") |
|||
result.cmp = syntax::comparator::lte; |
|||
else if (prefix == ">=") |
|||
result.cmp = syntax::comparator::gte; |
|||
else if (prefix == "~") |
|||
result.cmp = syntax::comparator::tilde; |
|||
else if (prefix == "^") |
|||
result.cmp = syntax::comparator::caret; |
|||
else |
|||
throw semver_error("invalid operator: '" + prefix + "'"); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
syntax::range parse_range(const std::string& input) |
|||
{ |
|||
// range ::=
|
|||
std::vector<std::string> hyphen_tokens = split(input, " - ", true); |
|||
if (hyphen_tokens.size() == 2) |
|||
{ |
|||
// hyphen |
|
|||
syntax::range hyphen; |
|||
syntax::simple from = parse_partial(hyphen_tokens.at(0)); |
|||
syntax::simple to = parse_partial(hyphen_tokens.at(1)); |
|||
from.cmp = syntax::comparator::gte; |
|||
to.cmp = syntax::comparator::lte; |
|||
hyphen.emplace_back(from); |
|||
hyphen.emplace_back(to); |
|||
return hyphen; |
|||
} |
|||
else if (input.find_first_not_of(any_space) != std::string::npos) |
|||
{ |
|||
// simple ( ' ' simple ) * |
|
|||
std::vector<std::string> simple_tokens = split(reduce_space(input), " ", true); |
|||
syntax::range simples; |
|||
for (const std::string& simple_token : simple_tokens) |
|||
simples.emplace_back(parse_simple(simple_token)); |
|||
return simples; |
|||
} |
|||
else |
|||
{ |
|||
// ''
|
|||
// the input may be a blank string which is allowed as an implcit *.*.* range
|
|||
syntax::range implicit_set; |
|||
implicit_set.emplace_back(syntax::simple()); |
|||
return implicit_set; |
|||
} |
|||
} |
|||
|
|||
syntax::range_set parse_range_set(const std::string& input) |
|||
{ |
|||
syntax::range_set result; |
|||
|
|||
// range_set ::= range ( ( ' ' ) * '||' ( ' ' ) * range ) *
|
|||
std::vector<std::string> range_tokens = split(input, "||", true); |
|||
for (const std::string& range_token : range_tokens) |
|||
result.emplace_back(parse_range(range_token)); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
syntax::range_set parser(const std::string input) |
|||
{ |
|||
return parse_range_set(input); |
|||
} |
|||
|
|||
} |
|||
|
|||
#endif |
@ -0,0 +1,33 @@ |
|||
#include <Rcpp.h> |
|||
#include <iostream> |
|||
|
|||
#include "cpp-semver.h" |
|||
|
|||
using namespace Rcpp; |
|||
|
|||
bool one_is_valid(std::string v) { |
|||
|
|||
try { |
|||
return(semver::intersects(v)); |
|||
} catch(...) { |
|||
return(FALSE); |
|||
} |
|||
|
|||
} |
|||
|
|||
//' Test if semantic version strings are valid
|
|||
//'
|
|||
//' @param v character verctor of version strings
|
|||
//' @export
|
|||
// [[Rcpp::export]]
|
|||
std::vector < bool > is_valid(std::vector < std::string > v) { |
|||
|
|||
std::vector < bool > ret(v.size()); |
|||
|
|||
for (unsigned int i = 0; i < v.size(); i++) { |
|||
ret[i] = one_is_valid(v[i]); |
|||
} |
|||
|
|||
return(ret); |
|||
|
|||
} |
@ -0,0 +1,2 @@ |
|||
library(testthat) |
|||
test_check("vershist") |
@ -0,0 +1,6 @@ |
|||
context("minimal package functionality") |
|||
test_that("we can do something", { |
|||
|
|||
#expect_that(some_function(), is_a("data.frame")) |
|||
|
|||
}) |
@ -0,0 +1,21 @@ |
|||
Version: 1.0 |
|||
|
|||
RestoreWorkspace: Default |
|||
SaveWorkspace: Default |
|||
AlwaysSaveHistory: Default |
|||
|
|||
EnableCodeIndexing: Yes |
|||
UseSpacesForTab: Yes |
|||
NumSpacesForTab: 2 |
|||
Encoding: UTF-8 |
|||
|
|||
RnwWeave: Sweave |
|||
LaTeX: pdfLaTeX |
|||
|
|||
StripTrailingWhitespace: Yes |
|||
|
|||
BuildType: Package |
|||
PackageUseDevtools: Yes |
|||
PackageInstallArgs: --no-multiarch --with-keep.source |
|||
PackageBuildArgs: --resave-data |
|||
PackageRoxygenize: rd,collate,namespace |
Loading…
Reference in new issue