Sorting semantic version numbers

This is the solution I came up with. I’m all ears if someone can come up with a simpler solution or ways to simplify this.

I should add that I had a few more interesting cases than I initially mentioned, like a few pre-release version numbers.

Also, the solution above from @zwbetz can’t handle a number like “3.9.10” because 3910 is greater than 3100, even though the semver number is lower than “3.10.0”. Thanks for the suggestion though.

Basically my solution is to create a set of nested maps with each major, minor, and patch numbers, and prerelease version numbers are a slice in the value of the patch map.

Each new version number is converted to a map and then merged in. Then I range through each level of each map creating sorted slices of integers and then index the map of version numbers by each item of the slice. Once it’s ranged all the way up to the patch or pre-release version numbers, it appends the whole semver version number to a slice of version numbers.

One big caveat, it doesn’t sort pre-release version numbers correctly, but I have so few that it wasn’t worth my time to address this. If anyone is planning to use this and has a lot of pre-release version numbers, you’re going to want to spend some time figuring out a way to sort those.

I’ve also left a bunch of headings and text in the page output so it’s easier to make sense of for anyone who wants is crazy enough to try this.


<!-- Create a bunch of numbers for testing -->
{{ $map1 := dict "17" (dict "1" (dict "52" slice )) }}
{{ $map2 := dict "17" (dict "21" (dict "46" slice)) }}
{{ $map3 := dict "17" (dict "11" (dict "42" slice)) }}
{{ $map4 := dict "17" (dict "9" (dict "26" slice)) }}
{{ $map5 := dict "17" (dict "9" (dict "200" slice)) }}

{{ $map6 := dict "17" (dict "8" (dict "20" slice)) }}
{{ $map7 := dict "17" (dict "8" (dict "10" slice)) }}

{{ $map8 := dict "16" (dict "8" (dict "21" slice )) }}
{{ $map9 := dict "16" (dict "8" (dict "25" (slice "-rc.2") )) }}
{{ $map10 := dict "16" (dict "8" (dict "93" slice)) }}
{{ $map11 := dict "16" (dict "8" (dict "201" slice)) }}
{{ $map12 := dict "16" (dict "8" (dict "2" slice)) }}
{{ $map15 := dict "16" (dict "8" (dict "100" slice)) }}

{{ $map13 := dict "20" (dict "8" (dict "2" slice)) }}
{{ $map14 := dict "2" (dict "8" (dict "2" slice)) }}

{{ $versionsMap := merge $map2 $map1 $map3 $map4 $map5 $map6 $map7 $map8 $map9 $map10 $map11 $map12 $map13 $map14 $map15 }}

<h2>Existing Map of Versions</h2>
<p>VersionsMap:</p>
{{ range $k, $v := $versionsMap }}
  <p>Major Version:{{ $k }} Major Version Map:{{ $v }}</p>
{{ end }}


<h2>Create new version number</h2>
<!-- A bunch of other numbers for testing how they will be added/sorted -->
{{/* $newNumber := "17.9.21" */}}
{{ $newNumber := "16.8.25-rc.0" }}
{{/* $newNumber := "1.2.3" */}}
{{/* $newNumber := "20.100.3" */}}
<p>Add new number: {{ $newNumber }}</p>

{{ $preReleaseVersion := index (findRE `\-\w+\.{0,1}\d{0,}$` $newNumber 1) 0 }}
{{ $versionCore := strings.TrimSuffix $preReleaseVersion $newNumber }}
{{ $versionNumberSlice := split $versionCore "." }}
{{ $majorVersion := index $versionNumberSlice 0 }}
{{ $minorVersion := index $versionNumberSlice 1 }}
{{ $patchVersion := index $versionNumberSlice 2 }}


<p>Version Core: {{ $versionCore }}</p>
<p>Major Version: {{ $majorVersion }}</p>
<p>Minor Version: {{ $minorVersion }}</p>
<p>Patch Version: {{ $patchVersion }}</p>
<p>Pre Release Version: {{ $preReleaseVersion }}</p>

{{ $newMap := dict }}
{{ if ne $preReleaseVersion nil }}
  {{ $newMap = dict $majorVersion (dict $minorVersion (dict $patchVersion (slice $preReleaseVersion))) }}
{{ else }}
  {{ $newMap = dict $majorVersion (dict $minorVersion (dict $patchVersion slice )) }}
{{ end }}

<p>New Version Number Map: {{ $newMap }}</p>

<h2>Merge Version Number Map into existing dict of version numbers</h2>

{{ if isset $versionsMap $majorVersion }}
  {{ if isset (index $versionsMap $majorVersion ) $minorVersion }}
    {{ if isset (index $versionsMap $majorVersion $minorVersion) $patchVersion }}
      {{ if ne (index $versionsMap $majorVersion $minorVersion $patchVersion) nil }}
        {{ $preReleaseVersions := index $versionsMap $majorVersion $minorVersion $patchVersion }}
        {{ $preReleaseVersions = sort ($preReleaseVersions | append $preReleaseVersion) "value" "desc" }}
        {{ $newMap = dict $majorVersion (dict $minorVersion (dict $patchVersion $preReleaseVersions )) }}
      {{ end }}
    {{ end }}
  {{ end }}
{{ end }}

{{ $versionsMap = merge $versionsMap $newMap }}

<p>VersionsMap:</p>
{{ range $k, $v := $versionsMap }}
  <p>{{ $k }} {{ $v }}</p>
{{ end }}

<h2>Convert Map to Slice</h2>

{{ $versionsSlice := slice }}

{{$majVersions := slice }}
{{ range $maj, $minPatchPre := $versionsMap }}
  {{ $majVersions = sort ($majVersions | append (int $maj)) "value" "desc" }}
{{ end }}

<p>Sorted Major Versions: {{ $majVersions }}</p>

{{ range $maj := $majVersions }}
  <p>Major version: {{ $maj }}</p>
  <p>{{ index $versionsMap (string $maj) }}</p>
  {{ $minVersions := slice }}
  {{ range $min, $patchPre := index $versionsMap (string $maj) }}
    {{ $minVersions = sort ($minVersions | append (int $min)) "value" "desc" }}
    <p>Min version: {{ $min }}</p>
  {{ end }}
  <p>Minor Versions: {{ $minVersions }}</p>

  {{ range $min := $minVersions }}
    <p>{{ index $versionsMap (string $maj) (string $min) }}</p>
    {{ $patchVersions := slice }}
    {{ range $patch, $pre := index $versionsMap (string $maj) (string $min) }}
      <p>Patch version: {{ $patch }}</p>
      {{ $patchVersions = sort ($patchVersions | append $patch ) "value" "desc" }}
    {{ end }}
    <p>Patch Versions: {{ $patchVersions }}</p>
    {{ range $patch := $patchVersions }}
      <p>Index of maj, min, patch: {{ index $versionsMap (string $maj) (string $min) (string $patch) }}</p>
      {{ range $pre := sort (index $versionsMap (string $maj) (string $min) (string $patch)) "value" "desc" }}
        <p>Pre release version: {{ $pre }}</p>
        <p>Full version: {{ (printf "%v%s%v%s%v%s" $maj "." $min "." $patch $pre ) }}</p>
        {{ $versionsSlice = $versionsSlice | append (printf "%v%s%v%s%v%s" $maj "." $min "." $patch $pre )}}
      {{ else }}
        <p>Full version: {{ (printf "%v%s%v%s%v" $maj "." $min "." $patch ) }}</p>
        {{ $versionsSlice = $versionsSlice | append (printf "%v%s%v%s%v" $maj "." $min "." $patch)}}
      {{ end }}
    {{ end }}
  {{ end }}
{{ end }}

<h2>Final Slice of sorted versions</h2>
<p>Versions Slice: {{ $versionsSlice }}</p>

<ul>
  {{ range $version := $versionsSlice }}
    <li>{{ $version }}</li>
  {{ end }}
</ul>

The final UL list output is this:

20.8.2
17.21.46
17.11.42
17.9.200
17.9.26
17.8.20
17.8.10
17.1.52
16.8.201
16.8.100
16.8.93
16.8.25-rc.2
16.8.25-rc.0
16.8.21
16.8.2
2.8.2