

{"id":8416,"date":"2018-03-07T15:18:04","date_gmt":"2018-03-07T20:18:04","guid":{"rendered":"https:\/\/rud.is\/b\/?p=8416"},"modified":"2018-03-10T07:53:58","modified_gmt":"2018-03-10T12:53:58","slug":"handling-semantic-version-string-like-a-boss-with-the-semver-package","status":"publish","type":"post","link":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/","title":{"rendered":"Handling Semantic Version Strings Like a Boss with the semver Package"},"content":{"rendered":"<p>I work with internet-scale data and do my fair share of macro-analyses on vulnerabilities. I use the R <a href=\"https:\/\/cran.r-project.org\/web\/packages\/semver\/index.html\"><code>semver<\/code><\/a> package for most of my work and wanted to blather on a bit about it since it&#8217;s super-helpful for this work and doesn&#8217;t get the attention it deserves. <code>semver<\/code> makes it possible to create charts like this:<\/p>\n<p><a href=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?ssl=1\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" data-attachment-id=\"8516\" data-permalink=\"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/unnamed-chunk-6-1\/\" data-orig-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=1920%2C768&amp;ssl=1\" data-orig-size=\"1920,768\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"unnamed-chunk-6-1\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=510%2C204&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?resize=510%2C204&#038;ssl=1\" alt=\"\" width=\"510\" height=\"204\" class=\"aligncenter size-full wp-image-8516\" \/><\/a><\/p>\n<p>which are very helpful in when conducting exposure analytics.<\/p>\n<p>We&#8217;ll need a few packages to help us along the way:<\/p>\n<pre id=\"2018semver01\"><code class=\"language-r\">library(here) # file mgmt\r\nlibrary(semver) # the whole purpose of the blog post\r\nlibrary(rvest) # we&#039;ll need this to get version-&gt;year mappings\r\nlibrary(stringi) # b\/c I&#039;m still too lazy to switch to ore\r\nlibrary(hrbrthemes) # pretty graphs\r\nlibrary(tidyverse) # sane data processing idioms<\/code><\/pre>\n<p>By issuing a <code>stats<\/code> command to a <code>memcached<\/code> instance you can get a full list of statistics for the server. The recent newsmaking DDoS used this feature in conjunction with address spoofing to create 30 minutes of chaos for GitHub.<\/p>\n<p>I sent a <code>stats<\/code> command (followed by a newline) to a vanilla <code>memcached<\/code> installation and it returned 53 lines (1108 bytes) of <code>STAT<\/code> results that look something like this:<\/p>\n<pre><code>STAT pid 7646\nSTAT uptime 141\nSTAT time 1520447469\nSTAT version 1.4.25 Ubuntu\nSTAT libevent 2.0.21-stable\n...\n<\/code><\/pre>\n<p>The <code>version<\/code> bit is what we&#8217;re after, but there are plenty of other variables you could just as easily focus on if you use <code>memcached<\/code> in any production capacity.<\/p>\n<p>I extracted raw version response data from our most recent scan for open <code>memcached<\/code> servers on the internet. For ethical reasons, I cannot blindly share the entire raw data set but hit up research@rapid7.com if you have a need or desire to work with this data.<\/p>\n<p>Let&#8217;s read it in and take a look:<\/p>\n<pre id=\"2018semver02\"><code class=\"language-r\">version_strings &lt;- read_lines(here(&quot;data&quot;, &quot;versions.txt&quot;))\r\n\r\nset.seed(2018-03-07)\r\n\r\nsample(version_strings, 50)\r\n\r\n##  [1] &quot;STAT version 1.4.5&quot;             &quot;STAT version 1.4.17&quot;           \r\n##  [3] &quot;STAT version 1.4.25&quot;            &quot;STAT version 1.4.31&quot;           \r\n##  [5] &quot;STAT version 1.4.25&quot;            &quot;STAT version 1.2.6&quot;            \r\n##  [7] &quot;STAT version 1.2.6&quot;             &quot;STAT version 1.4.15&quot;           \r\n##  [9] &quot;STAT version 1.4.17&quot;            &quot;STAT version 1.4.4&quot;            \r\n## [11] &quot;STAT version 1.4.5&quot;             &quot;STAT version 1.2.6&quot;            \r\n## [13] &quot;STAT version 1.4.2&quot;             &quot;STAT version 1.4.14 (Ubuntu)&quot;  \r\n## [15] &quot;STAT version 1.4.7&quot;             &quot;STAT version 1.4.39&quot;           \r\n## [17] &quot;STAT version 1.4.4-14-g9c660c0&quot; &quot;STAT version 1.2.6&quot;            \r\n## [19] &quot;STAT version 1.2.6&quot;             &quot;STAT version 1.4.14&quot;           \r\n## [21] &quot;STAT version 1.4.4-14-g9c660c0&quot; &quot;STAT version 1.4.37&quot;           \r\n## [23] &quot;STAT version 1.4.13&quot;            &quot;STAT version 1.4.4&quot;            \r\n## [25] &quot;STAT version 1.4.17&quot;            &quot;STAT version 1.2.6&quot;            \r\n## [27] &quot;STAT version 1.4.37&quot;            &quot;STAT version 1.4.13&quot;           \r\n## [29] &quot;STAT version 1.4.25&quot;            &quot;STAT version 1.4.15&quot;           \r\n## [31] &quot;STAT version 1.4.25&quot;            &quot;STAT version 1.2.6&quot;            \r\n## [33] &quot;STAT version 1.4.10&quot;            &quot;STAT version 1.4.25&quot;           \r\n## [35] &quot;STAT version 1.4.25&quot;            &quot;STAT version 1.4.9&quot;            \r\n## [37] &quot;STAT version 1.4.30&quot;            &quot;STAT version 1.4.21&quot;           \r\n## [39] &quot;STAT version 1.4.15&quot;            &quot;STAT version 1.4.31&quot;           \r\n## [41] &quot;STAT version 1.4.13&quot;            &quot;STAT version 1.2.6&quot;            \r\n## [43] &quot;STAT version 1.4.13&quot;            &quot;STAT version 1.4.15&quot;           \r\n## [45] &quot;STAT version 1.4.19&quot;            &quot;STAT version 1.4.25 Ubuntu&quot;    \r\n## [47] &quot;STAT version 1.4.37&quot;            &quot;STAT version 1.4.4-14-g9c660c0&quot;\r\n## [49] &quot;STAT version 1.2.6&quot;             &quot;STAT version 1.4.25 Ubuntu&quot;<\/code><\/pre>\n<p>It&#8217;s in decent shape, but it needs some work if we&#8217;re going to do a version analysis with it. Let&#8217;s clean it up a bit:<\/p>\n<pre id=\"2018semver03\"><code class=\"language-r\">data_frame(\r\n  string = stri_match_first_regex(version_strings, &quot;STAT version (.*)$&quot;)[,2]\r\n) -&gt; versions\r\n\r\ncount(versions, string, sort = TRUE) %&gt;%\r\n  knitr::kable(format=&quot;markdown&quot;)<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">string<\/th>\n<th align=\"right\">n<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">1.4.15<\/td>\n<td align=\"right\">1966<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.2.6<\/td>\n<td align=\"right\">1764<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.17<\/td>\n<td align=\"right\">1101<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.37<\/td>\n<td align=\"right\">949<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.13<\/td>\n<td align=\"right\">725<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.4<\/td>\n<td align=\"right\">531<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.25<\/td>\n<td align=\"right\">511<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.20<\/td>\n<td align=\"right\">368<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.14 (Ubuntu)<\/td>\n<td align=\"right\">334<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.21<\/td>\n<td align=\"right\">309<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.25 Ubuntu<\/td>\n<td align=\"right\">290<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.24<\/td>\n<td align=\"right\">259<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">&#8230;<\/td>\n<td align=\"right\">&#8230;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Much better! However, we really only need the major parts of the <a href=\"https:\/\/semver.org\/\">semantic version string<\/a> for a macro view, so let&#8217;s remove non-version strings completely and extract just the <em>major<\/em>, <em>minor<\/em> and <em>patch<\/em> bits:<\/p>\n<pre id=\"2018semver04\"><code class=\"language-r\">filter(versions, !stri_detect_fixed(string, &quot;UNKNOWN&quot;)) %&gt;% # get rid of things we can&#039;t use\r\n  mutate(string = stri_match_first_regex(\r\n    string, &quot;([[:digit:]]+\\\\.[[:digit:]]+\\\\.[[:digit:]]+)&quot;)[,2] # for a macro-view, the discrete sub-versions aren&#039;t important\r\n  ) -&gt; versions\r\n\r\ncount(versions, string, sort = TRUE) %&gt;%\r\n  knitr::kable(format=&quot;markdown&quot;)<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">string<\/th>\n<th align=\"right\">n<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">1.4.15<\/td>\n<td align=\"right\">1966<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.2.6<\/td>\n<td align=\"right\">1764<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.17<\/td>\n<td align=\"right\">1101<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.37<\/td>\n<td align=\"right\">949<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.25<\/td>\n<td align=\"right\">801<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.4<\/td>\n<td align=\"right\">747<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.13<\/td>\n<td align=\"right\">727<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.14<\/td>\n<td align=\"right\">385<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.20<\/td>\n<td align=\"right\">368<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.21<\/td>\n<td align=\"right\">309<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.24<\/td>\n<td align=\"right\">264<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">&#8230;<\/td>\n<td align=\"right\">&#8230;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Much, <em>much<\/em> better! Now, let&#8217;s dig into the versions a bit. Using <code>semver<\/code> is dirt-simple. Just use <code>parse_version()<\/code> to get the usable bits out:<\/p>\n<pre id=\"2018semver05\"><code class=\"language-r\">ex_ver &lt;- semver::parse_version(head(versions$string[1]))\r\n\r\nex_ver\r\n## [1] Maj: 1 Min: 4 Pat: 25\r\n\r\nstr(ex_ver)\r\n## List of 1\r\n##  $ :Class &#039;svptr&#039; &lt;externalptr&gt; \r\n##  - attr(*, &quot;class&quot;)= chr &quot;svlist&quot;<\/code><\/pre>\n<p>It&#8217;s a special class, referencing an external pointer (the package relies on an underling C++ library and wraps everything up in a bow for us).<\/p>\n<p>These objects can be compared, ordered, sorted, etc but I tend to just turn the parsed versions into a data frame that can be associated back with the main strings. That way we keep things pretty tidy and have tons of flexibility.<\/p>\n<pre id=\"2018semver06\"><code class=\"language-r\">bind_cols(\r\n  versions,\r\n  pull(versions, string) %&gt;%\r\n    semver::parse_version() %&gt;%\r\n    as.data.frame()\r\n) %&gt;%\r\n  arrange(major, minor, patch) %&gt;%\r\n  mutate(string = factor(string, levels = unique(string))) -&gt; versions\r\n\r\nversions\r\n## # A tibble: 11,157 x 6\r\n##    string major minor patch prerelease build\r\n##    &lt;fct&gt;  &lt;int&gt; &lt;int&gt; &lt;int&gt; &lt;chr&gt;      &lt;chr&gt;\r\n##  1 1.2.0      1     2     0 &quot;&quot;         &quot;&quot;   \r\n##  2 1.2.0      1     2     0 &quot;&quot;         &quot;&quot;   \r\n##  3 1.2.5      1     2     5 &quot;&quot;         &quot;&quot;   \r\n##  4 1.2.5      1     2     5 &quot;&quot;         &quot;&quot;   \r\n##  5 1.2.5      1     2     5 &quot;&quot;         &quot;&quot;   \r\n##  6 1.2.5      1     2     5 &quot;&quot;         &quot;&quot;   \r\n##  7 1.2.5      1     2     5 &quot;&quot;         &quot;&quot;   \r\n##  8 1.2.5      1     2     5 &quot;&quot;         &quot;&quot;   \r\n##  9 1.2.5      1     2     5 &quot;&quot;         &quot;&quot;   \r\n## 10 1.2.5      1     2     5 &quot;&quot;         &quot;&quot;   \r\n## # ... with 11,147 more rows<\/code><\/pre>\n<p>Now we have a tidy data frame and I did the extra step of creating an ordered <code>factor<\/code> out of the version strings since they are ordinal values. With just this step, we have everything we need to do a basic plot shoing the version counts in-order:<\/p>\n<pre id=\"2018semver07\"><code class=\"language-r\">count(versions, string) %&gt;%\r\n  ggplot() +\r\n  geom_segment(\r\n    aes(string, n, xend = string, yend = 0),\r\n    size = 2, color = &quot;lightslategray&quot;\r\n  ) +\r\n  scale_y_comma() +\r\n  labs(\r\n    x = &quot;memcached version&quot;, y = &quot;# instances found&quot;,\r\n    title = &quot;Distribution of memcached versions&quot;\r\n  ) +\r\n  theme_ipsum_ps(grid = &quot;Y&quot;) +\r\n  theme(axis.text.x = element_text(hjust = 1, vjust = 0.5, angle = 90))<\/code><\/pre>\n<p><a href=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-2-1.png?ssl=1\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" data-attachment-id=\"8518\" data-permalink=\"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/unnamed-chunk-2-1\/\" data-orig-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-2-1.png?fit=1920%2C768&amp;ssl=1\" data-orig-size=\"1920,768\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"memcached versions (raw)\" data-image-description=\"&lt;p&gt;memcached versions (raw)&lt;\/p&gt;\n\" data-image-caption=\"\" data-large-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-2-1.png?fit=510%2C204&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-2-1.png?resize=510%2C204&#038;ssl=1\" alt=\"memcached versions (raw)\" width=\"510\" height=\"204\" class=\"aligncenter size-full wp-image-8518\" \/><\/a><\/p>\n<p>That chart is informative on its own since we get the perspective that there are some really old versions exposed. But, how old are they? Projects like Chrome or Firefox churn through versions regularly\/quickly (on purpose). To make more sense out of this we&#8217;ll need more info on releases.<\/p>\n<p>This is where things can get ugly for folks who do not have commercial software management databases handy (or are analyzing a piece of software that hasn&#8217;t made it to one of those databases yet). The <code>memcached<\/code> project maintains a <a href=\"https:\/\/github.com\/memcached\/memcached\/wiki\/ReleaseNotes\">wiki page<\/a> of version history that&#8217;s mostly complete, and definitely complete enough for this exercise. It <em>will<\/em> some processing before we can associate a version to a year.<\/p>\n<p>GitHub does not allow scraping of their site and &#8212; off the top of my head &#8212; I do not know if there is a &#8220;wiki&#8221; API endpoint, but I <em>do<\/em> know that you can tack on <code>.wiki.git<\/code> to the end of a GitHub repo to clone the wiki pages, so we&#8217;ll use that knowledge and the <code>git2r<\/code> package to gain access to the<br \/>\n<code>ReleaseNotes.md<\/code> file that has the data we need:<\/p>\n<pre id=\"2018semver08\"><code class=\"language-r\">td &lt;- tempfile(&quot;wiki&quot;, fileext=&quot;git&quot;) # temporary &quot;directory&quot;\r\n\r\ndir.create(td)\r\n\r\ngit2r::clone(\r\n  url = &quot;git@github.com:memcached\/memcached.wiki.git&quot;,\r\n  local_path = td,\r\n  credentials = git2r::cred_ssh_key() # need GH ssh keys setup!\r\n) -&gt; repo\r\n## cloning into &#039;\/var\/folders\/1w\/2d82v7ts3gs98tc6v772h8s40000gp\/T\/\/Rtmpb209Sk\/wiki180eb3c6addcbgit&#039;...\r\n## Receiving objects:   1% (5\/481),    8 kb\r\n## Receiving objects:  11% (53\/481),    8 kb\r\n## Receiving objects:  21% (102\/481),   49 kb\r\n## Receiving objects:  31% (150\/481),   81 kb\r\n## Receiving objects:  41% (198\/481),  113 kb\r\n## Receiving objects:  51% (246\/481),  177 kb\r\n## Receiving objects:  61% (294\/481),  177 kb\r\n## Receiving objects:  71% (342\/481),  192 kb\r\n## Receiving objects:  81% (390\/481),  192 kb\r\n## Receiving objects:  91% (438\/481),  192 kb\r\n## Receiving objects: 100% (481\/481),  192 kb, done.\r\n\r\nread_lines(file.path(repo@path, &quot;ReleaseNotes.md&quot;)) %&gt;%\r\n  keep(stri_detect_fixed, &quot;[[ReleaseNotes&quot;) %&gt;%\r\n  stri_replace_first_regex(&quot; \\\\* \\\\[\\\\[.*]] &quot;, &quot;&quot;) %&gt;%\r\n  stri_split_fixed(&quot; &quot;, 2, simplify = TRUE) %&gt;%\r\n  as_data_frame() %&gt;%\r\n  set_names(c(&quot;string&quot;, &quot;release_year&quot;)) %&gt;%\r\n  mutate(string = stri_trim_both(string)) %&gt;%\r\n  mutate(release_year = stri_replace_first_fixed(release_year, &quot;(&quot;, &quot;&quot;)) %&gt;% # remove leading parens\r\n  mutate(release_year = stri_replace_all_regex(release_year, &quot;\\\\-.*$&quot;, &quot;&quot;)) %&gt;% # we only want year so remove remaining date bits from easy ones\r\n  mutate(release_year = stri_replace_all_regex(release_year, &quot;^.*, &quot;, &quot;&quot;)) %&gt;% # take care of most of the rest of the ugly ones\r\n  mutate(release_year = stri_replace_all_regex(release_year, &quot;^[[:alpha:]].* &quot;, &quot;&quot;)) %&gt;% # take care of the straggler\r\n  mutate(release_year = stri_replace_last_fixed(release_year, &quot;)&quot;, &quot;&quot;)) %&gt;% # remove any trailing parens\r\n  mutate(release_year = as.numeric(release_year)) -&gt; memcached_releases # make it numeric\r\n\r\nunlink(td, recursive = TRUE) # cleanup the git repo we downloaded\r\n\r\nmemcached_releases\r\n## # A tibble: 49 x 2\r\n##    string release_year\r\n##    &lt;chr&gt;         &lt;dbl&gt;\r\n##  1 1.5.6          2018\r\n##  2 1.5.5          2018\r\n##  3 1.5.4          2017\r\n##  4 1.5.3          2017\r\n##  5 1.5.2          2017\r\n##  6 1.5.1          2017\r\n##  7 1.5.0          2017\r\n##  8 1.4.39         2017\r\n##  9 1.4.38         2017\r\n## 10 1.4.37         2017\r\n## # ... with 39 more rows<\/code><\/pre>\n<p>We have more versions in our internet-scraped <code>memcached<\/code> <code>versions<\/code> data<br \/>\nset than this wiki page has on it, so we need to restrict the official<br \/>\nrelease history to what we have. Then, we only want a single instance of<br \/>\neach year for the annotations, so we&#8217;ll have to do some further processing:<\/p>\n<pre id=\"2018semver09\"><code class=\"language-r\">filter(memcached_releases, string %in% unique(versions$string)) %&gt;%\r\n  mutate(string = factor(string, levels = levels(versions$string))) %&gt;%\r\n  group_by(release_year) %&gt;%\r\n  arrange(desc(string)) %&gt;%\r\n  slice(1) %&gt;%\r\n  ungroup() -&gt; annotation_df\r\n\r\nknitr::kable(annotation_df, &quot;markdown&quot;)<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th align=\"left\">string<\/th>\n<th align=\"right\">release_year<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"left\">1.4.4<\/td>\n<td align=\"right\">2009<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.5<\/td>\n<td align=\"right\">2010<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.10<\/td>\n<td align=\"right\">2011<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.15<\/td>\n<td align=\"right\">2012<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.17<\/td>\n<td align=\"right\">2013<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.22<\/td>\n<td align=\"right\">2014<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.25<\/td>\n<td align=\"right\">2015<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.4.33<\/td>\n<td align=\"right\">2016<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.5.4<\/td>\n<td align=\"right\">2017<\/td>\n<\/tr>\n<tr>\n<td align=\"left\">1.5.6<\/td>\n<td align=\"right\">2018<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Now, we&#8217;re ready to add the annotation layers! We&#8217;ll take a blind stab at it before adding in further aesthetic customization:<\/p>\n<pre id=\"2018semver10\"><code class=\"language-r\">version_counts &lt;- count(versions, string) # no piping this time\r\n\r\nggplot() +\r\n  geom_blank(data = version_counts,aes(string, n)) + # prime the scales\r\n  geom_vline(\r\n    data = annotation_df, aes(xintercept = as.numeric(string)),\r\n    size = 0.5, linetype = &quot;dotted&quot;, color = &quot;orange&quot;\r\n  ) +\r\n  geom_segment(\r\n    data = version_counts,\r\n    aes(string, n, xend = string, yend = 0),\r\n    size = 2, color = &quot;lightslategray&quot;\r\n  ) +\r\n  geom_label(\r\n    data = annotation_df, aes(string, Inf, label=release_year),\r\n    family = font_ps, size = 2.5, color = &quot;lightslateblue&quot;,\r\n    hjust = 0, vjust = 1, label.size = 0\r\n  ) +\r\n  scale_y_comma() +\r\n  labs(\r\n    x = &quot;memcached version&quot;, y = &quot;# instances found&quot;,\r\n    title = &quot;Distribution of memcached versions&quot;\r\n  ) +\r\n  theme_ipsum_ps(grid = &quot;Y&quot;) +\r\n  theme(axis.text.x = element_text(hjust = 1, vjust = 0.5, angle = 90))<\/code><\/pre>\n<p><a href=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-5-1.png?ssl=1\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" data-attachment-id=\"8517\" data-permalink=\"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/unnamed-chunk-5-1-3\/\" data-orig-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-5-1.png?fit=1920%2C768&amp;ssl=1\" data-orig-size=\"1920,768\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"unnamed-chunk-5-1\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-5-1.png?fit=510%2C204&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-5-1.png?resize=510%2C204&#038;ssl=1\" alt=\"\" width=\"510\" height=\"204\" class=\"aligncenter size-full wp-image-8517\" \/><\/a><\/p>\n<p><em>Almost<\/em> got it in ggpar 1! We need to tweak this so that the labels do not overlap each other and do not obstruct the segment bars. We can do most of this work in <code>geom_segment()<\/code> itself, plus add a bit of a tweak to the Y axis scale:<\/p>\n<pre id=\"2018semver11\"><code class=\"language-r\">ggplot() +\r\n  geom_blank(data = version_counts,aes(string, n)) + # prime the scales\r\n  geom_vline(\r\n    data = annotation_df, aes(xintercept = as.numeric(string)),\r\n    size = 0.5, linetype = &quot;dotted&quot;, color = &quot;orange&quot;\r\n  ) +\r\n  geom_segment(\r\n    data = version_counts,\r\n    aes(string, n, xend = string, yend = 0),\r\n    size = 2, color = &quot;lightslategray&quot;\r\n  ) +\r\n  geom_label(\r\n    data = annotation_df, aes(string, Inf, label=release_year), vjust = 1,\r\n    family = font_ps, size = 2.5, color = &quot;lightslateblue&quot;, label.size = 0,\r\n    hjust = c(1, 0, 1, 1, 0, 1, 0, 0, 1, 0),\r\n    nudge_x = c(-0.1, 0.1, -0.1, -0.1, 0.1, -0.1, 0.1, 0.1, -0.1, 0.1)\r\n  ) +\r\n  scale_y_comma(limits = c(0, 2050)) +\r\n  labs(\r\n    x = &quot;memcached version&quot;, y = &quot;# instances found&quot;,\r\n    title = &quot;Distribution of memcached versions&quot;\r\n  ) +\r\n  theme_ipsum_ps(grid = &quot;Y&quot;) +\r\n  theme(axis.text.x = element_text(hjust = 1, vjust = 0.5, angle = 90))<\/code><\/pre>\n<p><a href=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?ssl=1\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" data-attachment-id=\"8516\" data-permalink=\"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/unnamed-chunk-6-1\/\" data-orig-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=1920%2C768&amp;ssl=1\" data-orig-size=\"1920,768\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"unnamed-chunk-6-1\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=510%2C204&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?resize=510%2C204&#038;ssl=1\" alt=\"\" width=\"510\" height=\"204\" class=\"aligncenter size-full wp-image-8516\" \/><\/a><\/p>\n<p>Now, we have version and year info to we can get a better idea of the scope of exposure (and, just how much technical debt many organizations have accrued).<\/p>\n<p>With the ordinal version inforamtion we can also perform other statistical operations as well. All due to the <code>semver<\/code> package.<\/p>\n<p>You can find this R project over at <a href=\"https:\/\/github.com\/hrbrmstr\/a-version\">GitHub<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I work with internet-scale data and do my fair share of macro-analyses on vulnerabilities. I use the R semver package for most of my work and wanted to blather on a bit about it since it&#8217;s super-helpful for this work and doesn&#8217;t get the attention it deserves. semver makes it possible to create charts like [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":8516,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":3,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":""},"categories":[91],"tags":[810],"class_list":["post-8416","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-r","tag-post"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Handling Semantic Version Strings Like a Boss with the semver Package - rud.is<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Handling Semantic Version Strings Like a Boss with the semver Package - rud.is\" \/>\n<meta property=\"og:description\" content=\"I work with internet-scale data and do my fair share of macro-analyses on vulnerabilities. I use the R semver package for most of my work and wanted to blather on a bit about it since it&#8217;s super-helpful for this work and doesn&#8217;t get the attention it deserves. semver makes it possible to create charts like [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/\" \/>\n<meta property=\"og:site_name\" content=\"rud.is\" \/>\n<meta property=\"article:published_time\" content=\"2018-03-07T20:18:04+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2018-03-10T12:53:58+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"768\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"hrbrmstr\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"hrbrmstr\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"4 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/\"},\"author\":{\"name\":\"hrbrmstr\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#\\\/schema\\\/person\\\/d7cb7487ab0527447f7fda5c423ff886\"},\"headline\":\"Handling Semantic Version Strings Like a Boss with the semver Package\",\"datePublished\":\"2018-03-07T20:18:04+00:00\",\"dateModified\":\"2018-03-10T12:53:58+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/\"},\"wordCount\":865,\"commentCount\":8,\"publisher\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#\\\/schema\\\/person\\\/d7cb7487ab0527447f7fda5c423ff886\"},\"image\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2018\\\/03\\\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1\",\"keywords\":[\"post\"],\"articleSection\":[\"R\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/\",\"url\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/\",\"name\":\"Handling Semantic Version Strings Like a Boss with the semver Package - rud.is\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2018\\\/03\\\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1\",\"datePublished\":\"2018-03-07T20:18:04+00:00\",\"dateModified\":\"2018-03-10T12:53:58+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/#primaryimage\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2018\\\/03\\\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1\",\"contentUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2018\\\/03\\\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1\",\"width\":\"1920\",\"height\":\"768\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/2018\\\/03\\\/07\\\/handling-semantic-version-string-like-a-boss-with-the-semver-package\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/rud.is\\\/b\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Handling Semantic Version Strings Like a Boss with the semver Package\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#website\",\"url\":\"https:\\\/\\\/rud.is\\\/b\\\/\",\"name\":\"rud.is\",\"description\":\"&quot;In God we trust. All others must bring data&quot;\",\"publisher\":{\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#\\\/schema\\\/person\\\/d7cb7487ab0527447f7fda5c423ff886\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/rud.is\\\/b\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/rud.is\\\/b\\\/#\\\/schema\\\/person\\\/d7cb7487ab0527447f7fda5c423ff886\",\"name\":\"hrbrmstr\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/10\\\/ukr-shield.png?fit=460%2C460&ssl=1\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/10\\\/ukr-shield.png?fit=460%2C460&ssl=1\",\"contentUrl\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/10\\\/ukr-shield.png?fit=460%2C460&ssl=1\",\"width\":460,\"height\":460,\"caption\":\"hrbrmstr\"},\"logo\":{\"@id\":\"https:\\\/\\\/i0.wp.com\\\/rud.is\\\/b\\\/wp-content\\\/uploads\\\/2023\\\/10\\\/ukr-shield.png?fit=460%2C460&ssl=1\"},\"description\":\"Don't look at me\u2026I do what he does \u2014 just slower. #rstats avuncular \u2022 ?Resistance Fighter \u2022 Cook \u2022 Christian \u2022 [Master] Chef des Donn\u00e9es de S\u00e9curit\u00e9 @ @rapid7\",\"sameAs\":[\"http:\\\/\\\/rud.is\"],\"url\":\"https:\\\/\\\/rud.is\\\/b\\\/author\\\/hrbrmstr\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Handling Semantic Version Strings Like a Boss with the semver Package - rud.is","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/","og_locale":"en_US","og_type":"article","og_title":"Handling Semantic Version Strings Like a Boss with the semver Package - rud.is","og_description":"I work with internet-scale data and do my fair share of macro-analyses on vulnerabilities. I use the R semver package for most of my work and wanted to blather on a bit about it since it&#8217;s super-helpful for this work and doesn&#8217;t get the attention it deserves. semver makes it possible to create charts like [&hellip;]","og_url":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/","og_site_name":"rud.is","article_published_time":"2018-03-07T20:18:04+00:00","article_modified_time":"2018-03-10T12:53:58+00:00","og_image":[{"width":1920,"height":768,"url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1","type":"image\/png"}],"author":"hrbrmstr","twitter_card":"summary_large_image","twitter_misc":{"Written by":"hrbrmstr","Est. reading time":"4 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/#article","isPartOf":{"@id":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/"},"author":{"name":"hrbrmstr","@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"headline":"Handling Semantic Version Strings Like a Boss with the semver Package","datePublished":"2018-03-07T20:18:04+00:00","dateModified":"2018-03-10T12:53:58+00:00","mainEntityOfPage":{"@id":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/"},"wordCount":865,"commentCount":8,"publisher":{"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"image":{"@id":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1","keywords":["post"],"articleSection":["R"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/","url":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/","name":"Handling Semantic Version Strings Like a Boss with the semver Package - rud.is","isPartOf":{"@id":"https:\/\/rud.is\/b\/#website"},"primaryImageOfPage":{"@id":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/#primaryimage"},"image":{"@id":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1","datePublished":"2018-03-07T20:18:04+00:00","dateModified":"2018-03-10T12:53:58+00:00","breadcrumb":{"@id":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/#primaryimage","url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1","contentUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1","width":"1920","height":"768"},{"@type":"BreadcrumbList","@id":"https:\/\/rud.is\/b\/2018\/03\/07\/handling-semantic-version-string-like-a-boss-with-the-semver-package\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/rud.is\/b\/"},{"@type":"ListItem","position":2,"name":"Handling Semantic Version Strings Like a Boss with the semver Package"}]},{"@type":"WebSite","@id":"https:\/\/rud.is\/b\/#website","url":"https:\/\/rud.is\/b\/","name":"rud.is","description":"&quot;In God we trust. All others must bring data&quot;","publisher":{"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/rud.is\/b\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/rud.is\/b\/#\/schema\/person\/d7cb7487ab0527447f7fda5c423ff886","name":"hrbrmstr","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1","url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1","contentUrl":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1","width":460,"height":460,"caption":"hrbrmstr"},"logo":{"@id":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2023\/10\/ukr-shield.png?fit=460%2C460&ssl=1"},"description":"Don't look at me\u2026I do what he does \u2014 just slower. #rstats avuncular \u2022 ?Resistance Fighter \u2022 Cook \u2022 Christian \u2022 [Master] Chef des Donn\u00e9es de S\u00e9curit\u00e9 @ @rapid7","sameAs":["http:\/\/rud.is"],"url":"https:\/\/rud.is\/b\/author\/hrbrmstr\/"}]}},"jetpack_featured_media_url":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2018\/03\/unnamed-chunk-6-1.png?fit=1920%2C768&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/p23idr-2bK","jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":5854,"url":"https:\/\/rud.is\/b\/2017\/04\/30\/r%e2%81%b6-using-pandoc-from-r-a-neat-package-for-reading-subtitles\/","url_meta":{"origin":8416,"position":0},"title":"R\u2076 \u2014 Using pandoc from R + A Neat Package For Reading Subtitles","author":"hrbrmstr","date":"2017-04-30","format":false,"excerpt":"Once I realized that my planned, larger post would not come to fruition today I took the R\u2076 post (i.e. \"minimal expository, keen focus\") route, prompted by a Twitter discussion with some R mates who needed to convert \"lightly formatted\" Microsoft Word (docx) documents to markdown. Something like this: to:\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/04\/flash.png?fit=1200%2C643&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/04\/flash.png?fit=1200%2C643&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/04\/flash.png?fit=1200%2C643&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/04\/flash.png?fit=1200%2C643&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/04\/flash.png?fit=1200%2C643&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":9299,"url":"https:\/\/rud.is\/b\/2018\/03\/23\/on-mimes-software-versions-and-web-site-promiscuity-a-k-a-three-new-packages-to-round-out-the-week\/","url_meta":{"origin":8416,"position":1},"title":"On MIMEs, software versions and web site promiscuity (a.k.a. three new packages to round out the week)","author":"hrbrmstr","date":"2018-03-23","format":false,"excerpt":"A quick Friday post to let folks know about three in-development R packages that you're encouraged to poke the tyres o[fn] and also jump in and file issues or PRs for. Alleviating aversion to versions I introduced a \"version chart\" in a recent post and one key element of tagging\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":4852,"url":"https:\/\/rud.is\/b\/2017\/01\/08\/2017-01-authored-package-updates\/","url_meta":{"origin":8416,"position":2},"title":"2017-01 Authored Package Updates","author":"hrbrmstr","date":"2017-01-08","format":false,"excerpt":"The rest of the month is going to be super-hectic and it's unlikely I'll be able to do any more to help the push to CRAN 10K, so here's a breakdown of CRAN and GitHub new packages & package updates that I felt were worth raising awareness on: epidata I\u2026","rel":"","context":"In &quot;dplyr&quot;","block_context":{"text":"dplyr","link":"https:\/\/rud.is\/b\/category\/dplyr\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/01\/epi2.png?fit=982%2C1200&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/01\/epi2.png?fit=982%2C1200&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/01\/epi2.png?fit=982%2C1200&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/01\/epi2.png?fit=982%2C1200&ssl=1&resize=700%2C400 2x"},"classes":[]},{"id":12586,"url":"https:\/\/rud.is\/b\/2020\/01\/01\/writing-frictionless-r-package-wrappers-introduction\/","url_meta":{"origin":8416,"position":3},"title":"Writing Frictionless R Package Wrappers \u2014 Introduction","author":"hrbrmstr","date":"2020-01-01","format":false,"excerpt":"The R language and RStudio IDE are a powerful combination for \"getting stuff done\", and one aspect of R itself that makes it especially useful is the ability to use it with other programming languages via a robust foreign language interface capability1. The term \"foreign language\" refers to another programming\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":7149,"url":"https:\/\/rud.is\/b\/2017\/11\/18\/statebins-reimagined\/","url_meta":{"origin":8416,"position":4},"title":"Statebins Reimagined","author":"hrbrmstr","date":"2017-11-18","format":false,"excerpt":"A long time ago, in a github repo far, far away there lived a tiny package that made it possible to create equal area, square U.S. state cartograms in R dubbed statebins?. Three years have come and gone and --- truth be told --- I've never been happy with that\u2026","rel":"","context":"In &quot;R&quot;","block_context":{"text":"R","link":"https:\/\/rud.is\/b\/category\/r\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/11\/unnamed-chunk-3-1.png?fit=1200%2C857&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/11\/unnamed-chunk-3-1.png?fit=1200%2C857&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/11\/unnamed-chunk-3-1.png?fit=1200%2C857&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/11\/unnamed-chunk-3-1.png?fit=1200%2C857&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2017\/11\/unnamed-chunk-3-1.png?fit=1200%2C857&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3841,"url":"https:\/\/rud.is\/b\/2016\/01\/03\/zellingenach-a-visual-exploration-of-the-spatial-patterns-in-the-endings-of-german-town-and-village-names-in-r\/","url_meta":{"origin":8416,"position":5},"title":"Zellingenach: A visual exploration of the spatial patterns in the endings of German town and village names in R","author":"hrbrmstr","date":"2016-01-03","format":false,"excerpt":"Moritz Stefaner started off 2016 with a [very spiffy post](http:\/\/truth-and-beauty.net\/experiments\/ach-ingen-zell\/) on _\"a visual exploration of the spatial patterns in the endings of German town and village names\"_. Moritz was [exploring some new data processing & visualization tools](https:\/\/github.com\/moritzstefaner\/ach-ingen-zell) for the post, but when I saw what he was doing I wondered\u2026","rel":"","context":"In &quot;cartography&quot;","block_context":{"text":"cartography","link":"https:\/\/rud.is\/b\/category\/cartography\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/01\/rud_is_zellingenach_html.png?fit=597%2C798&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/01\/rud_is_zellingenach_html.png?fit=597%2C798&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/rud.is\/b\/wp-content\/uploads\/2016\/01\/rud_is_zellingenach_html.png?fit=597%2C798&ssl=1&resize=525%2C300 1.5x"},"classes":[]}],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/8416","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/comments?post=8416"}],"version-history":[{"count":0,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/posts\/8416\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media\/8516"}],"wp:attachment":[{"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/media?parent=8416"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/categories?post=8416"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rud.is\/b\/wp-json\/wp\/v2\/tags?post=8416"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}