Conversation
|
What does "nl" mean for those variant comparators? And why do they mean the left-hand side is less than the right-hand no matter what? And what no e.g., |
|
Hi @brettcannon, The "nl" in those variant comparators comes from dpkg --compare-versions, which I originally intended to support as a drop-in replacement. From the manpage:
Regarding the lack of an eq-nl operator, it's likely because version equality doesn't depend on whether one of the versions is empty—if both versions are non-empty, eq handles it naturally. However, I decided to remove support for I also removed the non-textual operators So, while the tool is still inspired by dpkg, it’s not a compatible API anymore, but it is simpler and still useful. |
|
I don’t really mind, but aren’t non-textual operators more readable than |
dd7f329 to
f497782
Compare
|
I feel if we do this we should have a subcommand |
f497782 to
7d3694f
Compare
|
I also used AI in CoPilot to add a CLI output (for a followup). It took a few prompts to get the 3.14+ color & dark mode looking nice: Followup PR could be based on:diff --git a/docs/conf.py b/docs/conf.py
index 9419c41..bc034f7 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -3,6 +3,10 @@
# for complete details.
import os
+import re
+import subprocess
+from docutils import nodes
+from docutils.parsers.rst import Directive
# -- Project information loading ----------------------------------------------
@@ -104,3 +108,111 @@ intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
"pypug": ("https://packaging.python.org/", None),
}
+
+
+# -- Custom directives --------------------------------------------------------
+
+
+def ansi_to_html(text):
+ """Convert ANSI color codes to HTML."""
+ # ANSI color code to HTML color mapping
+ ansi_colors = {
+ "30": "#000000", # black
+ "31": "#dc3545", # red
+ "32": "#28a745", # green
+ "33": "#ffc107", # yellow
+ "34": "#007bff", # blue
+ "35": "#6f42c1", # magenta
+ "36": "#17a2b8", # cyan
+ "37": "#f8f9fa", # white
+ "90": "#6c757d", # bright black (gray)
+ "91": "#ff6b6b", # bright red
+ "92": "#51cf66", # bright green
+ "93": "#ffd43b", # bright yellow
+ "94": "#4d7fff", # bright blue
+ "95": "#da77f2", # bright magenta
+ "96": "#15aabf", # bright cyan
+ "97": "#ffffff", # bright white
+ }
+
+ # Pattern to match ANSI escape sequences
+ ansi_escape_pattern = re.compile(r"\x1b\[([0-9;]*)m")
+
+ def replace_ansi(match):
+ codes = match.group(1).split(";") if match.group(1) else ["0"]
+ html_parts = []
+
+ for code in codes:
+ if code == "0":
+ # Reset
+ return "</span>"
+ elif code == "1":
+ # Bold
+ html_parts.append('<span style="font-weight: bold;">')
+ elif code in ansi_colors:
+ # Foreground color
+ color = ansi_colors[code]
+ html_parts.append(f'<span style="color: {color};">')
+
+ return "".join(html_parts) if html_parts else ""
+
+ # Replace ANSI codes with HTML
+ html = ansi_escape_pattern.sub(replace_ansi, text)
+
+ # Clean up any remaining unclosed spans
+ html = html.replace("</span></span>", "</span>")
+
+ return html
+
+
+class ShowCliDirective(Directive):
+ """Include the output of a CLI command in the documentation."""
+
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+
+ def run(self):
+ """Execute the command and return its output as a code block."""
+ command = self.arguments[0]
+ try:
+ # Run the command and capture output
+ result = subprocess.run(
+ command,
+ shell=True,
+ capture_output=True,
+ text=True,
+ cwd=_BASE_DIR,
+ env={**os.environ, "FORCE_COLOR": "1"},
+ )
+ output = result.stdout or result.stderr
+ except Exception as e:
+ return [
+ nodes.error(
+ None,
+ nodes.paragraph(text=f"Error running command: {command}\n{e}"),
+ )
+ ]
+
+ # Convert ANSI codes to HTML
+ html_output = ansi_to_html(output)
+
+ # Create a raw HTML node with the colored output
+ raw_html = nodes.raw("", html_output, format="html")
+ literal_block = nodes.literal_block(output, raw_html)
+ literal_block["language"] = "text"
+
+ # Return as a container with pre styling
+ container = nodes.container()
+ container += nodes.raw(
+ "",
+ f'<pre style="background-color: var(--color-background-secondary, #f5f5f5); color: var(--color-foreground-primary, inherit); padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 0.9em; font-weight: normal; line-height: 1.4;">{html_output}</pre>',
+ format="html",
+ )
+ return [container]
+
+
+def setup(app):
+ """Register custom directives."""
+ app.add_directive("show-cli", ShowCliDirective)
diff --git a/docs/version.rst b/docs/version.rst
index 2adf336..7210a3d 100644
--- a/docs/version.rst
+++ b/docs/version.rst
@@ -46,9 +46,21 @@ Usage
True
+Command Line Interface
+----------------------
+
+The ``packaging.version`` module can be used as a command-line tool:
+
+.. show-cli:: python -m packaging.version --help
+
+You can compare two versions:
+
+.. show-cli:: python -m packaging.version compare --help
+
Reference
---------
.. automodule:: packaging.version
:members:
:special-members:
Edit: I have a better version of this (without the bold bug) in scikit-build/scikit-build-core#1218. Edit edit: The version I went with uses a new package, https://github.com/erbsland-dev/erbsland-sphinx-ansi, and I think works well. I've got a proposal to add it to Edit edit edit: It's in |
28eab80 to
d78e7ef
Compare
44a7caa to
db3dc44
Compare
Co-authored-by: Brett Cannon <brett@python.org>
Updated the argument parser description and replaced sys.exit with raise SystemExit.
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
db3dc44 to
9df93ce
Compare


I want to propose this simple CLI tool in the
packaging.versionmodule to perform semantic version comparisons directly from the command line. This utility is modeled afterdpkg --compare-versionsbut designed to be platform-agnostic, filling a gap for non-Debian users and providing a Python-native solution.Version comparison is a common requirement in deployment scripts, package management, and development workflows. Currently, developers resort to complex shell scripts or third-party tools to compare versions. Examples of community solutions include intricate bash functions and snippets found on Stack Overflow and GitHub Gists that, while functional for basic cases, vary in reliability and can be unnecessarily complex 1, 2, 3.
My proposal leverages the robustness of the
packaging.version.Versionclass for parsing and comparing semantic versions, supporting standard comparison operators (e.g., lt, gt, eq) and provide straightforward syntax that resembles the mentioned dpkg commandUsage