Allow badges with no value or no label (#89)

This commit allows badges to be generated with only a label or only
a value.

Badges must provide at least a label or a value, so it is not valid
to create a badge with no label and no value. This will result in
a `ValueError` being raised.
This commit is contained in:
Jon Grace-Cox
2023-07-18 09:31:59 -07:00
committed by Jon Grace-Cox
parent a076a4867a
commit 9d4470a1a0
8 changed files with 128 additions and 15 deletions

View File

@@ -317,6 +317,16 @@ Here are some examples to show how to use padding to fix layout:
| ![](https://cdn.rawgit.com/jongracecox/anybadge/master/examples/pipeline_smile.svg) | `anybadge.Badge("Pipeline status", "😄")` | | ![](https://cdn.rawgit.com/jongracecox/anybadge/master/examples/pipeline_smile.svg) | `anybadge.Badge("Pipeline status", "😄")` |
| ![](https://cdn.rawgit.com/jongracecox/anybadge/master/examples/pipeline_smile_padding.svg) | `anybadge.Badge("Pipeline status", "😄", num_value_padding_chars=1)` | | ![](https://cdn.rawgit.com/jongracecox/anybadge/master/examples/pipeline_smile_padding.svg) | `anybadge.Badge("Pipeline status", "😄", num_value_padding_chars=1)` |
### Value or label only
It is possible to create badges with only a label or only a value. This can be done by passing
an empty string to the appropriate field. Note that either a label or value must be provided.
| Badge | Code |
|---------------------------------------------------------------------------------|--------------------------------------|
| ![](https://cdn.rawgit.com/jongracecox/anybadge/master/examples/label_only.svg) | `anybadge.Badge(label="Label only")` |
| ![](https://cdn.rawgit.com/jongracecox/anybadge/master/examples/value_only.svg) | `anybadge.Badge(value="Value only")` |
### Semantic version support ### Semantic version support
Anybadge supports semantic versions for value and threshold keys. This supports color-coded Anybadge supports semantic versions for value and threshold keys. This supports color-coded

View File

@@ -153,6 +153,14 @@ class Badge:
self.label = label self.label = label
self.value = value self.value = value
if self.label is None:
self.label = ""
if self.value is None:
self.value = ""
if len(str(self.label)) == 0 and len(str(self.value)) == 0:
raise ValueError("Either a label or a value must be provided for a badge.")
self.value_is_version = semver self.value_is_version = semver
self.value_format = value_format self.value_format = value_format
@@ -414,6 +422,9 @@ class Badge:
Returns: int Returns: int
""" """
if len(str(self.label)) == 0:
return 0
return int( return int(
self.get_text_width(str(self.label)) self.get_text_width(str(self.label))
+ (2.0 * self.num_label_padding_chars * self.font_width) + (2.0 * self.num_label_padding_chars * self.font_width)
@@ -433,6 +444,9 @@ class Badge:
Returns: int Returns: int
""" """
if len(str(self.value_text)) == 0:
return 0
return int( return int(
self.get_text_width(str(self.value_text)) self.get_text_width(str(self.value_text))
+ (2.0 * self.num_value_padding_chars * self.font_width) + (2.0 * self.num_value_padding_chars * self.font_width)

View File

@@ -52,10 +52,8 @@ examples:
""" """
), ),
) )
parser.add_argument("-l", "--label", type=str, help="The badge label.") parser.add_argument("-l", "--label", type=str, help="The badge label.", default="")
parser.add_argument( parser.add_argument("-v", "--value", type=str, help="The badge value.", default="")
"-v", "--value", type=str, help="The badge value.", required=True
)
parser.add_argument( parser.add_argument(
"-m", "-m",
"--value-format", "--value-format",
@@ -196,9 +194,6 @@ def main(args=None):
if not args.suffix and style.suffix: if not args.suffix and style.suffix:
suffix = style.suffix suffix = style.suffix
if not label:
raise ValueError("Label has not been set. Please use --label argument.")
# Create threshold list from args # Create threshold list from args
threshold_list = [x.split("=") for x in threshold_text] threshold_list = [x.split("=") for x in threshold_text]
threshold_dict = {x[0]: x[1] for x in threshold_list} threshold_dict = {x[0]: x[1] for x in threshold_list}

View File

@@ -24,7 +24,7 @@ def color_examples_table():
) )
def emoji_examples(): def other_examples():
"""Generate emoji example badges used in documentation.""" """Generate emoji example badges used in documentation."""
examples_dir = Path(__file__).parent / Path("examples") examples_dir = Path(__file__).parent / Path("examples")
for label, value, file, kwargs in [ for label, value, file, kwargs in [
@@ -38,6 +38,8 @@ def emoji_examples():
("Pipeline status", "😟", "pipeline_frown.svg", {"default_color": "Red"}), ("Pipeline status", "😟", "pipeline_frown.svg", {"default_color": "Red"}),
("🔗", "Documentation", "documentation_link.svg", {}), ("🔗", "Documentation", "documentation_link.svg", {}),
("🔗", "PyPi", "pypi_link.svg", {}), ("🔗", "PyPi", "pypi_link.svg", {}),
("", "Value only", "value_only.svg", {}),
("Label only", "", "label_only.svg", {}),
]: ]:
anybadge.Badge(label=label, value=value, **kwargs).write_badge( anybadge.Badge(label=label, value=value, **kwargs).write_badge(
examples_dir / Path(file), overwrite=True examples_dir / Path(file), overwrite=True
@@ -46,7 +48,7 @@ def emoji_examples():
def main(): def main():
color_examples_table() color_examples_table()
emoji_examples() other_examples()
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -34,6 +34,16 @@ anybadge --label="Label" --value="Value" --file "${TEST_FILES}/test_command_line
check_rc check_rc
echo "OK" echo "OK"
echo -n "Testing with no label..."
anybadge --value="Value" --file "${TEST_FILES}/test_command_line_no_label.svg"
check_rc
echo "OK"
echo -n "Testing with no value..."
anybadge --label="Label" --file "${TEST_FILES}/test_command_line_no_value.svg"
check_rc
echo "OK"
echo -n "Testing python -m call... " echo -n "Testing python -m call... "
python -m anybadge --label="Label" --value="Value" --file "${TEST_FILES}/test_m_command_line.svg" python -m anybadge --label="Label" --value="Value" --file "${TEST_FILES}/test_m_command_line.svg"
check_rc check_rc

23
examples/label_only.svg Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="71" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="anybadge_152">
<rect width="71" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#anybadge_152)">
<path fill="#555" d="M0 0h71v20H0z"/>
<path fill="#4c1" d="M71 0h0v20H71z"/>
<path fill="url(#b)" d="M0 0h71v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="36.5" y="15" fill="#010101" fill-opacity=".3">Label only</text>
<text x="35.5" y="14">Label only</text>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="72.0" y="15" fill="#010101" fill-opacity=".3"></text>
<text x="71.0" y="14"></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

23
examples/value_only.svg Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="71" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="anybadge_151">
<rect width="71" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#anybadge_151)">
<path fill="#555" d="M0 0h0v20H0z"/>
<path fill="#4c1" d="M0 0h71v20H0z"/>
<path fill="url(#b)" d="M0 0h71v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="1.0" y="15" fill="#010101" fill-opacity=".3"></text>
<text x="0.0" y="14"></text>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="36.5" y="15" fill="#010101" fill-opacity=".3">Value only</text>
<text x="35.5" y="14">Value only</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -328,12 +328,6 @@ class TestAnybadge(TestCase):
] ]
) )
def test_main_missing_value(self):
with self.assertRaisesRegex(
ValueError, r"Label has not been set\. Please use --label argument\."
):
main(["--value", "123", "--file", "test_badge_main.svg", "--overwrite"])
def test_version_comparison(self): def test_version_comparison(self):
# Define thresholds: <3.0.0=red, <3.2.0=orange <999.0.0=green # Define thresholds: <3.0.0=red, <3.2.0=orange <999.0.0=green
badge = Badge( badge = Badge(
@@ -410,3 +404,45 @@ class TestAnybadge(TestCase):
output_module = subprocess.check_output(["python", "-m", "anybadge", "--help"]) output_module = subprocess.check_output(["python", "-m", "anybadge", "--help"])
output_script = subprocess.check_output(["anybadge", "--help"]) output_script = subprocess.check_output(["anybadge", "--help"])
self.assertEqual(output_module, output_script) self.assertEqual(output_module, output_script)
def test_badge_with_no_label(self):
"""Test the dimensions for a badge with no label."""
badge = Badge(
label="",
value="Value",
)
self.assertEqual(
badge.label_width,
0,
"Expected label width to be 0 for badge with no label.",
)
def test_badge_with_no_value(self):
"""Test the dimensions for a badge with no value."""
badge = Badge(
label="Label",
value="",
)
self.assertEqual(
badge.value_width,
0,
"Expected value width to be 0 for badge with no value.",
)
def test_badge_with_no_label_and_no_value(self):
"""Test that an exception is raised when trying to create a badge with no label or value."""
with self.assertRaisesRegex(
ValueError, r"Either a label or a value must be provided for a badge\."
):
_ = Badge(
label="",
value="",
)
with self.assertRaisesRegex(
ValueError, r"Either a label or a value must be provided for a badge\."
):
_ = Badge(
label=None,
value=None,
)