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