diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea2d1a1..69c42e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,11 @@ instructions, because git commits are used to generate release notes:
+
+## v15.3.1 (2023-02-28)
+
+- [Bugfix] `patchStrategicMerge` can now be applied to jobs. (by @keithgg)
+
## v15.3.0 (2023-02-10)
diff --git a/changelog.d/20230214_105510_keith_fix_jobs_merge.md b/changelog.d/20230214_105510_keith_fix_jobs_merge.md
deleted file mode 100644
index 4bd44fd..0000000
--- a/changelog.d/20230214_105510_keith_fix_jobs_merge.md
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-[Bugfix] `patchStrategicMerge` can now be applied to jobs (by @keithgg)
diff --git a/tests/commands/test_plugins.py b/tests/commands/test_plugins.py
index 4056fb4..34fd6a4 100644
--- a/tests/commands/test_plugins.py
+++ b/tests/commands/test_plugins.py
@@ -56,17 +56,3 @@ class PluginsTests(unittest.TestCase, TestCommandMixin):
self.assertEqual(
["all", "alba"], plugins_commands.PluginName(allow_all=True).get_names("al")
)
-
- def test_format_table(self) -> None:
- rows: t.List[t.Tuple[str, ...]] = [
- ("a", "xyz", "value 1"),
- ("abc", "x", "value 12345"),
- ]
- formatted = plugins_commands.format_table(rows, separator=" ")
- self.assertEqual(
- """
-a xyz value 1
-abc x value 12345
-""".strip(),
- formatted,
- )
diff --git a/tests/test_utils.py b/tests/test_utils.py
index eb1ceaf..93d4a48 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -3,6 +3,7 @@ import os
import tempfile
import unittest
from io import StringIO
+from typing import List, Tuple
from unittest.mock import MagicMock, mock_open, patch
from tutor import exceptions, utils
@@ -239,3 +240,17 @@ class UtilsTests(unittest.TestCase):
self.assertFalse(utils.is_http("/home/user/"))
self.assertFalse(utils.is_http("home/user/"))
self.assertFalse(utils.is_http("http-home/user/"))
+
+ def test_format_table(self) -> None:
+ rows: List[Tuple[str, ...]] = [
+ ("a", "xyz", "value 1"),
+ ("abc", "x", "value 12345"),
+ ]
+ formatted = utils.format_table(rows, separator=" ")
+ self.assertEqual(
+ """
+a xyz value 1
+abc x value 12345
+""".strip(),
+ formatted,
+ )
diff --git a/tutor/__about__.py b/tutor/__about__.py
index 4282e2a..59a6eb1 100644
--- a/tutor/__about__.py
+++ b/tutor/__about__.py
@@ -2,7 +2,7 @@ import os
# Increment this version number to trigger a new release. See
# docs/tutor.html#versioning for information on the versioning scheme.
-__version__ = "15.3.0"
+__version__ = "15.3.1"
# The version suffix will be appended to the actual version, separated by a
# dash. Use this suffix to differentiate between the actual released version and
diff --git a/tutor/commands/plugins.py b/tutor/commands/plugins.py
index ac5c79a..2b9bdee 100644
--- a/tutor/commands/plugins.py
+++ b/tutor/commands/plugins.py
@@ -128,7 +128,7 @@ def list_command(show_enabled_only: bool) -> None:
(plugin_info or "").replace("\n", " "),
)
)
- fmt.echo(format_table(plugins_table))
+ fmt.echo(utils.format_table(plugins_table))
@click.command(help="Enable a plugin")
@@ -302,7 +302,7 @@ def search(pattern: str) -> None:
plugin.short_description,
)
)
- print(format_table(results))
+ print(utils.format_table(results))
@click.command()
@@ -408,34 +408,6 @@ def index_remove(context: Context, url: str) -> None:
fmt.echo_alert("Plugin index not present")
-def format_table(rows: t.List[t.Tuple[str, ...]], separator: str = "\t") -> str:
- """
- Format a list of values as a tab-separated table. Column sizes are determined such
- that row values are vertically aligned.
- """
- formatted = ""
- if not rows:
- return formatted
- columns_count = len(rows[0])
- # Determine each column size
- col_sizes = [1] * columns_count
- for row in rows:
- for c, value in enumerate(row):
- col_sizes[c] = max(col_sizes[c], len(value))
- # Print all values
- for r, row in enumerate(rows):
- for c, value in enumerate(row):
- if c < len(col_sizes) - 1:
- formatted += f"{value:{col_sizes[c]}}{separator}"
- else:
- # The last column is not left-justified
- formatted += f"{value}"
- if r < len(rows) - 1:
- # Append EOL at all lines but the last one
- formatted += "\n"
- return formatted
-
-
index_command.add_command(index_add)
index_command.add_command(index_list)
index_command.add_command(index_remove)
diff --git a/tutor/utils.py b/tutor/utils.py
index 705c9de..15291cd 100644
--- a/tutor/utils.py
+++ b/tutor/utils.py
@@ -345,3 +345,31 @@ def is_http(url: str) -> bool:
Basic test to check whether a string is a web URL. Use only for basic use cases.
"""
return re.match(r"^https?://", url) is not None
+
+
+def format_table(rows: List[Tuple[str, ...]], separator: str = "\t") -> str:
+ """
+ Format a list of values as a tab-separated table. Column sizes are determined such
+ that row values are vertically aligned.
+ """
+ formatted = ""
+ if not rows:
+ return formatted
+ columns_count = len(rows[0])
+ # Determine each column size
+ col_sizes = [1] * columns_count
+ for row in rows:
+ for c, value in enumerate(row):
+ col_sizes[c] = max(col_sizes[c], len(value))
+ # Print all values
+ for r, row in enumerate(rows):
+ for c, value in enumerate(row):
+ if c < len(col_sizes) - 1:
+ formatted += f"{value:{col_sizes[c]}}{separator}"
+ else:
+ # The last column is not left-justified
+ formatted += f"{value}"
+ if r < len(rows) - 1:
+ # Append EOL at all lines but the last one
+ formatted += "\n"
+ return formatted