Skip to content

Commit 8d8ac7f

Browse files
committed
cli: Implement new 'compare-versions' command
Sometimes it's useful to check how two versions relate to one another. Inspired by Debian's `dpkg --compare-versions`, I decided to implement our very own version comparison CLI. Signed-off-by: Sergio Durigan Junior <sergiodj@chainguard.dev>
1 parent 5b4c1e4 commit 8d8ac7f

File tree

4 files changed

+153
-0
lines changed

4 files changed

+153
-0
lines changed

docs/md/melange.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ toc: true
2222

2323
* [melange build](/docs/md/melange_build.md) - Build a package from a YAML configuration file
2424
* [melange bump](/docs/md/melange_bump.md) - Update a Melange YAML file to reflect a new package version
25+
* [melange compare-versions](/docs/md/melange_compare-versions.md) - Compare two package versions
2526
* [melange compile](/docs/md/melange_compile.md) - Compile a YAML configuration file
2627
* [melange completion](/docs/md/melange_completion.md) - Generate completion script
2728
* [melange convert](/docs/md/melange_convert.md) - EXPERIMENTAL COMMAND - Attempts to convert packages/gems/apkbuild files into melange configuration files
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
title: "melange compare-versions"
3+
slug: melange_compare-versions
4+
url: /docs/md/melange_compare-versions.md
5+
draft: false
6+
images: []
7+
type: "article"
8+
toc: true
9+
---
10+
## melange compare-versions
11+
12+
Compare two package versions
13+
14+
### Synopsis
15+
16+
Compare two package versions according to a specified operator.
17+
18+
The operator can be: eq (equal), ne (not-equal),
19+
lt (less-than), le (less-than or equal),
20+
gt (greater-than), ge (greater-than or equal)
21+
22+
```
23+
melange compare-versions [flags]
24+
```
25+
26+
### Examples
27+
28+
```
29+
melange compare-versions version1 operator version2
30+
```
31+
32+
### Options
33+
34+
```
35+
-h, --help help for compare-versions
36+
-s, --silent don't print anything; use the return code ($?) to signal whether the comparison is true or false
37+
```
38+
39+
### Options inherited from parent commands
40+
41+
```
42+
--log-level string log level (e.g. debug, info, warn, error) (default "INFO")
43+
```
44+
45+
### SEE ALSO
46+
47+
* [melange](/docs/md/melange.md) -
48+

pkg/cli/commands.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func New() *cobra.Command {
5353

5454
cmd.AddCommand(buildCmd())
5555
cmd.AddCommand(bumpCmd())
56+
cmd.AddCommand(compareVersions())
5657
cmd.AddCommand(completion())
5758
cmd.AddCommand(compile())
5859
cmd.AddCommand(convert())

pkg/cli/compareversions.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2025 Chainguard, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cli
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"os"
21+
22+
"chainguard.dev/apko/pkg/apk/apk"
23+
"github.com/spf13/cobra"
24+
)
25+
26+
func compareVersions() *cobra.Command {
27+
var silent bool
28+
29+
cmd := &cobra.Command {
30+
Use: "compare-versions",
31+
Short: "Compare two package versions",
32+
Long: `Compare two package versions according to a specified operator.
33+
34+
The operator can be: eq (equal), ne (not-equal),
35+
lt (less-than), le (less-than or equal),
36+
gt (greater-than), ge (greater-than or equal)`,
37+
Example: `melange compare-versions version1 operator version2`,
38+
Args: cobra.ExactArgs(3),
39+
RunE: func(cmd *cobra.Command, args []string) error {
40+
return compareVersionsCli(cmd.Context(), args[0], args[1], args[2], silent)
41+
},
42+
}
43+
44+
cmd.Flags().BoolVarP(&silent, "silent", "s", false, "don't print anything; use the return code ($?) to signal whether the comparison is true or false")
45+
46+
return cmd
47+
}
48+
49+
func compareVersionsCli(_ context.Context, version1 string, operator string, version2 string, silent bool) error {
50+
pkgversion1, err := apk.ParseVersion(version1)
51+
if err != nil {
52+
return err
53+
}
54+
55+
pkgversion2, err := apk.ParseVersion(version2)
56+
if err != nil {
57+
return err
58+
}
59+
60+
r := apk.CompareVersions(pkgversion1, pkgversion2)
61+
62+
var succeeded bool
63+
var operatorStr string
64+
65+
switch operator {
66+
case "eq":
67+
succeeded = r == 0
68+
operatorStr = "equal to"
69+
case "lt":
70+
succeeded = r < 0
71+
operatorStr = "less than"
72+
case "le":
73+
succeeded = r <= 0
74+
operatorStr = "less than or equal to"
75+
case "gt":
76+
succeeded = r > 0
77+
operatorStr = "greater than"
78+
case "ge":
79+
succeeded = r >= 0
80+
operatorStr = "greater than or equal to"
81+
case "ne":
82+
succeeded = r != 0
83+
operatorStr = "different than"
84+
default:
85+
return fmt.Errorf("invalid operator %q", operator)
86+
}
87+
88+
if succeeded {
89+
if silent {
90+
os.Exit(0)
91+
} else {
92+
fmt.Printf("%q is %s %q\n", version1, operatorStr, version2)
93+
}
94+
} else {
95+
if silent {
96+
os.Exit(1)
97+
} else {
98+
fmt.Printf("%q is NOT %s %q\n", version1, operatorStr, version2)
99+
}
100+
}
101+
102+
return nil
103+
}

0 commit comments

Comments
 (0)