mirror of
https://github.com/jongracecox/anybadge.git
synced 2025-12-16 02:20:06 +01:00
Merge pull request #15 from jongracecox/14-fix-extra-padding-around-long-value-text
14 fix extra padding around long value text
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -105,3 +105,12 @@ ENV/
|
||||
|
||||
.DS_Store
|
||||
demo.svg
|
||||
|
||||
# py.test
|
||||
.pytest_cache
|
||||
|
||||
# SVG files generated by the unittests
|
||||
test_badge_*.svg
|
||||
|
||||
# html coverage report
|
||||
htmlcov/
|
||||
|
||||
16
.travis.yml
16
.travis.yml
@@ -1,12 +1,16 @@
|
||||
dist: xenial
|
||||
language: python
|
||||
python:
|
||||
- '2.7'
|
||||
- '3.5'
|
||||
- '3.6'
|
||||
- '3.7'
|
||||
install:
|
||||
- pip install -U setuptools pip -r build-requirements.txt
|
||||
script:
|
||||
- python ./test.py
|
||||
- pytest --doctest-modules --cov=anybadge anybadge.py tests
|
||||
before_deploy:
|
||||
- sed -i "s/^version = .*/version = __version__ = \"$TRAVIS_TAG\"/" anybadge.py
|
||||
deploy:
|
||||
- provider: pypi
|
||||
user: jongracecox
|
||||
@@ -16,12 +20,4 @@ deploy:
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
python: '3.6'
|
||||
- provider: pypi
|
||||
user: jongracecox
|
||||
password:
|
||||
secure: "TwKVv2wGesF3zHMtSzC/whgtBfHOG03wYS8bUOeeH4x8g5wQIu9SVyrYSffYE3FxapHbMCzmx7A3IgP4Ma6v4Ik6HqJY7a5DY/na0bcuI40IyCM2J0S0Hbq4E7WXaCCe6t7C5KE7NO3QzIcZboSZBWb78mcKLB2XbuIWMPCXqayGhqh8hynQmhwQ4C5b+jpBXlLtm6+AFH7eJOSdl8iO39RU5TL6FrOjgmks/KvTO0yHaXxmBoRcVudZsv9sAGsUx/UtRA65FPViIgu/dEV8cNtz7HOtL4v9x1mkvsiHF7OJiB1KCzSdUSCI83JSjdh44jjlNx0x2SnPbbwmCR7hi53xszMLbAHqY27hK1O4ntR1Iaui8HhBx8inxwS5z271xEQ9HZ8W9veRaxXxGkzj3LKGzjYflK1UN/ZK2syy5+cF4AEUpqEuQYi2waGhMxxWRo5KA05RLXetvsJYFEHUlATq9aXjMv29yH77KzySgpZe/Emd+Hb7r/7TKDeWXRaWDtUa+lc7G5oHLn6Kmm0iM1lerFKalEO9eoZwO2m1+O47CJ5yTKw1mNsC5Dj6EhG1QeonJWPS5z32vjNvnzRm2psbInt00SE2ClPxY5ASQOdcCMkq4ELGUHVssOGlniVYILs+tiwzrDTaXcckf8lvVvx6CiXfmFY+JYK9q3HYEuE="
|
||||
distributions: sdist bdist_wheel
|
||||
on:
|
||||
branch: master
|
||||
python: '3.6'
|
||||
python: '3.7'
|
||||
375
README.md
375
README.md
@@ -208,185 +208,218 @@ Here is the output of ``help(anybadge)``::
|
||||
Help on module anybadge:
|
||||
|
||||
NAME
|
||||
anybadge - anybadge
|
||||
|
||||
FILE
|
||||
/home/jon/Git/anybadge/anybadge.py
|
||||
anybadge - anybadge
|
||||
|
||||
DESCRIPTION
|
||||
A Python module for generating badges for your projects, with a focus on
|
||||
simplicity and flexibility.
|
||||
A Python module for generating badges for your projects, with a focus on
|
||||
simplicity and flexibility.
|
||||
|
||||
CLASSES
|
||||
__builtin__.object
|
||||
Badge
|
||||
|
||||
class Badge(__builtin__.object)
|
||||
| Badge class used to generate badges.
|
||||
|
|
||||
| Examples:
|
||||
|
|
||||
| Create a simple green badge:
|
||||
|
|
||||
| >>> badge = Badge('label', 123, default_color='green')
|
||||
|
|
||||
| Write a badge to file, overwriting any existing file:
|
||||
|
|
||||
| >>> badge = Badge('label', 123, default_color='green')
|
||||
| >>> badge.write_badge('demo.svg', overwrite=True)
|
||||
|
|
||||
| Here are a number of examples showing thresholds, since there
|
||||
| are certain situations that may not be obvious:
|
||||
|
|
||||
| >>> badge = Badge('pipeline', 'passing', thresholds={'passing': 'green', 'failing': 'red'})
|
||||
| >>> badge.badge_color
|
||||
| 'green'
|
||||
|
|
||||
| 2.32 is not <2
|
||||
| 2.32 is < 4, so 2.32 yields orange
|
||||
| >>> badge = Badge('pylint', 2.32, thresholds={2: 'red',
|
||||
| ... 4: 'orange',
|
||||
| ... 8: 'yellow',
|
||||
| ... 10: 'green'})
|
||||
| >>> badge.badge_color
|
||||
| 'orange'
|
||||
|
|
||||
| 8 is not <8
|
||||
| 8 is <4, so 8 yields orange
|
||||
| >>> badge = Badge('pylint', 6, thresholds={2: 'red',
|
||||
| ... 4: 'orange',
|
||||
| ... 8: 'yellow',
|
||||
| ... 10: 'green'})
|
||||
| >>> badge.badge_color
|
||||
| 'green'
|
||||
|
|
||||
| 10 is not <8, but use_max_when_value_exceeds defaults to
|
||||
| True, so 10 yields green
|
||||
| >>> badge = Badge('pylint', 11, thresholds={2: 'red',
|
||||
| ... 4: 'orange',
|
||||
| ... 8: 'yellow',
|
||||
| ... 10: 'green'})
|
||||
| >>> badge.badge_color
|
||||
| 'green'
|
||||
|
|
||||
| 11 is not <10, and use_max_when_value_exceeds is set to
|
||||
| False, so 11 yields the default color '#a4a61d'
|
||||
| >>> badge = Badge('pylint', 11, use_max_when_value_exceeds=False,
|
||||
| ... thresholds={2: 'red', 4: 'orange', 8: 'yellow',
|
||||
| ... 10: 'green'})
|
||||
| >>> badge.badge_color
|
||||
| '#a4a61d'
|
||||
|
|
||||
| Methods defined here:
|
||||
|
|
||||
| __init__(self, label, value, font_name='DejaVu Sans,Verdana,Geneva,sans-serif', font_size=11, num_padding_chars=0.5, template='<?xml version="1.0" encoding="UTF-8"?>\n<svg xmln...hor }}" y="14">{{ value }}</text>\n </g>\n</svg>', value_prefix='', value_suffix='', thresholds=None, default_color='#a4a61d', use_max_when_value_exceeds=True, value_format=None, text_color='#fff')
|
||||
| Constructor for Badge class.
|
||||
|
|
||||
| get_text_width(self, text)
|
||||
| Return the width of text.
|
||||
|
|
||||
| This implementation assumes a fixed font of:
|
||||
|
|
||||
| font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"
|
||||
| >>> badge = Badge('x', 1, font_name='DejaVu Sans,Verdana,Geneva,sans-serif', font_size=11)
|
||||
| >>> badge.get_text_width('pylint')
|
||||
| 42
|
||||
|
|
||||
| write_badge(self, file_path, overwrite=False)
|
||||
| Write badge to file.
|
||||
|
|
||||
| ----------------------------------------------------------------------
|
||||
| Static methods defined here:
|
||||
|
|
||||
| get_font_width(font_name, font_size)
|
||||
| Return the width multiplier for a font.
|
||||
|
|
||||
| >>> Badge.get_font_width('DejaVu Sans,Verdana,Geneva,sans-serif', 11)
|
||||
| 7
|
||||
|
|
||||
| ----------------------------------------------------------------------
|
||||
| Data descriptors defined here:
|
||||
|
|
||||
| __dict__
|
||||
| dictionary for instance variables (if defined)
|
||||
|
|
||||
| __weakref__
|
||||
| list of weak references to the object (if defined)
|
||||
|
|
||||
| badge_color
|
||||
| Find the badge color based on the thresholds.
|
||||
|
|
||||
| badge_color_code
|
||||
| Return the color code for the badge.
|
||||
|
|
||||
| badge_svg_text
|
||||
| The badge SVG text.
|
||||
|
|
||||
| badge_width
|
||||
| The total width of badge.
|
||||
|
|
||||
| >>> badge = Badge('pylint', '5', font_name='DejaVu Sans,Verdana,Geneva,sans-serif',
|
||||
| ... font_size=11)
|
||||
| >>> badge.badge_width
|
||||
| 91
|
||||
|
|
||||
| color_split_position
|
||||
| The SVG x position where the color split should occur.
|
||||
|
|
||||
| font_width
|
||||
| Return the badge font width.
|
||||
|
|
||||
| label_anchor
|
||||
| The SVG x position of the middle anchor for the label text.
|
||||
|
|
||||
| label_anchor_shadow
|
||||
| The SVG x position of the label shadow anchor.
|
||||
|
|
||||
| label_width
|
||||
| The SVG width of the label text.
|
||||
|
|
||||
| value_anchor
|
||||
| The SVG x position of the middle anchor for the value text.
|
||||
|
|
||||
| value_anchor_shadow
|
||||
| The SVG x position of the value shadow anchor.
|
||||
|
|
||||
| value_is_float
|
||||
| Identify whether the value text is a float.
|
||||
|
|
||||
| value_is_int
|
||||
| Identify whether the value text is an int.
|
||||
|
|
||||
| value_type
|
||||
| The Python type associated with the value.
|
||||
|
|
||||
| value_width
|
||||
| The SVG width of the value text.
|
||||
builtins.object
|
||||
Badge
|
||||
|
||||
class Badge(builtins.object)
|
||||
| Badge(label, value, font_name='DejaVu Sans,Verdana,Geneva,sans-serif', font_size=11, num_padding_chars=0.5, template='<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" width="{{ badge width }}" height="20">\n <linearGradient id="b" x2="0" y2="100%">\n <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>\n <stop offset="1" stop-opacity=".1"/>\n </linearGradient>\n <mask id="a">\n <rect width="{{ badge width }}" height="20" rx="3" fill="#fff"/>\n </mask>\n <g mask="url(#a)">\n <path fill="#555" d="M0 0h{{ color split x }}v20H0z"/>\n <path fill="{{ color }}" d="M{{ color split x }} 0h{{ value width }}v20H{{ color split x }}z"/>\n <path fill="url(#b)" d="M0 0h{{ badge width }}v20H0z"/>\n </g>\n <g fill="{{ label text color }}" text-anchor="middle" font-family="{{ font name }}" font-size="{{ font size }}">\n <text x="{{ label anchor shadow }}" y="15" fill="#010101" fill-opacity=".3">{{ label }}</text>\n <text x="{{ label anchor }}" y="14">{{ label }}</text>\n </g>\n <g fill="{{ value text color }}" text-anchor="middle" font-family="{{ font name }}" font-size="{{ font size }}">\n <text x="{{ value anchor shadow }}" y="15" fill="#010101" fill-opacity=".3">{{ value }}</text>\n <text x="{{ value anchor }}" y="14">{{ value }}</text>\n </g>\n</svg>', value_prefix='', value_suffix='', thresholds=None, default_color='#4c1', use_max_when_value_exceeds=True, value_format=None, text_color='#fff')
|
||||
|
|
||||
| Badge class used to generate badges.
|
||||
|
|
||||
| Examples:
|
||||
|
|
||||
| Create a simple green badge:
|
||||
|
|
||||
| >>> badge = Badge('label', 123, default_color='green')
|
||||
|
|
||||
| Write a badge to file, overwriting any existing file:
|
||||
|
|
||||
| >>> badge = Badge('label', 123, default_color='green')
|
||||
| >>> badge.write_badge('demo.svg', overwrite=True)
|
||||
|
|
||||
| Here are a number of examples showing thresholds, since there
|
||||
| are certain situations that may not be obvious:
|
||||
|
|
||||
| >>> badge = Badge('pipeline', 'passing', thresholds={'passing': 'green', 'failing': 'red'})
|
||||
| >>> badge.badge_color
|
||||
| 'green'
|
||||
|
|
||||
| 2.32 is not <2
|
||||
| 2.32 is < 4, so 2.32 yields orange
|
||||
| >>> badge = Badge('pylint', 2.32, thresholds={2: 'red',
|
||||
| ... 4: 'orange',
|
||||
| ... 8: 'yellow',
|
||||
| ... 10: 'green'})
|
||||
| >>> badge.badge_color
|
||||
| 'orange'
|
||||
|
|
||||
| 8 is not <8
|
||||
| 8 is <4, so 8 yields orange
|
||||
| >>> badge = Badge('pylint', 8, thresholds={2: 'red',
|
||||
| ... 4: 'orange',
|
||||
| ... 8: 'yellow',
|
||||
| ... 10: 'green'})
|
||||
| >>> badge.badge_color
|
||||
| 'green'
|
||||
|
|
||||
| 10 is not <8, but use_max_when_value_exceeds defaults to
|
||||
| True, so 10 yields green
|
||||
| >>> badge = Badge('pylint', 11, thresholds={2: 'red',
|
||||
| ... 4: 'orange',
|
||||
| ... 8: 'yellow',
|
||||
| ... 10: 'green'})
|
||||
| >>> badge.badge_color
|
||||
| 'green'
|
||||
|
|
||||
| 11 is not <10, and use_max_when_value_exceeds is set to
|
||||
| False, so 11 yields the default color '#4c1'
|
||||
| >>> badge = Badge('pylint', 11, use_max_when_value_exceeds=False,
|
||||
| ... thresholds={2: 'red', 4: 'orange', 8: 'yellow',
|
||||
| ... 10: 'green'})
|
||||
| >>> badge.badge_color
|
||||
| '#4c1'
|
||||
|
|
||||
| Methods defined here:
|
||||
|
|
||||
| __init__(self, label, value, font_name='DejaVu Sans,Verdana,Geneva,sans-serif', font_size=11, num_padding_chars=0.5, template='<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" width="{{ badge width }}" height="20">\n <linearGradient id="b" x2="0" y2="100%">\n <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>\n <stop offset="1" stop-opacity=".1"/>\n </linearGradient>\n <mask id="a">\n <rect width="{{ badge width }}" height="20" rx="3" fill="#fff"/>\n </mask>\n <g mask="url(#a)">\n <path fill="#555" d="M0 0h{{ color split x }}v20H0z"/>\n <path fill="{{ color }}" d="M{{ color split x }} 0h{{ value width }}v20H{{ color split x }}z"/>\n <path fill="url(#b)" d="M0 0h{{ badge width }}v20H0z"/>\n </g>\n <g fill="{{ label text color }}" text-anchor="middle" font-family="{{ font name }}" font-size="{{ font size }}">\n <text x="{{ label anchor shadow }}" y="15" fill="#010101" fill-opacity=".3">{{ label }}</text>\n <text x="{{ label anchor }}" y="14">{{ label }}</text>\n </g>\n <g fill="{{ value text color }}" text-anchor="middle" font-family="{{ font name }}" font-size="{{ font size }}">\n <text x="{{ value anchor shadow }}" y="15" fill="#010101" fill-opacity=".3">{{ value }}</text>\n <text x="{{ value anchor }}" y="14">{{ value }}</text>\n </g>\n</svg>', value_prefix='', value_suffix='', thresholds=None, default_color='#4c1', use_max_when_value_exceeds=True, value_format=None, text_color='#fff')
|
||||
| Constructor for Badge class.
|
||||
|
|
||||
| get_text_width(self, text)
|
||||
| Return the width of text.
|
||||
|
|
||||
| Args:
|
||||
| text(str): Text to get the pixel width of.
|
||||
|
|
||||
| Returns:
|
||||
| int: Pixel width of the given text based on the badges selected font.
|
||||
|
|
||||
| This implementation assumes a fixed font of:
|
||||
|
|
||||
| font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"
|
||||
| >>> badge = Badge('x', 1, font_name='DejaVu Sans,Verdana,Geneva,sans-serif', font_size=11)
|
||||
| >>> badge.get_text_width('pylint')
|
||||
| 34
|
||||
|
|
||||
| write_badge(self, file_path, overwrite=False)
|
||||
| Write badge to file.
|
||||
|
|
||||
| ----------------------------------------------------------------------
|
||||
| Data descriptors defined here:
|
||||
|
|
||||
| __dict__
|
||||
| dictionary for instance variables (if defined)
|
||||
|
|
||||
| __weakref__
|
||||
| list of weak references to the object (if defined)
|
||||
|
|
||||
| badge_color
|
||||
| Badge color based on the configured thresholds.
|
||||
|
|
||||
| Returns: str
|
||||
|
|
||||
| badge_color_code
|
||||
| Return the color code for the badge.
|
||||
|
|
||||
| Returns: str
|
||||
|
|
||||
| badge_svg_text
|
||||
| The badge SVG text.
|
||||
|
|
||||
| Returns: str
|
||||
|
|
||||
| badge_width
|
||||
| The total width of badge.
|
||||
|
|
||||
| Returns: int
|
||||
|
|
||||
| Examples:
|
||||
|
|
||||
| >>> badge = Badge('pylint', '5')
|
||||
| >>> badge.badge_width
|
||||
| 103
|
||||
|
|
||||
| color_split_position
|
||||
| The SVG x position where the color split should occur.
|
||||
|
|
||||
| Returns: int
|
||||
|
|
||||
| font_width
|
||||
| Return the width multiplier for a font.
|
||||
|
|
||||
| Returns:
|
||||
| int: Maximum pixel width of badges selected font.
|
||||
|
|
||||
| Example:
|
||||
|
|
||||
| >>> Badge(label='x', value='1').font_width
|
||||
| 10
|
||||
|
|
||||
| label_anchor
|
||||
| The SVG x position of the middle anchor for the label text.
|
||||
|
|
||||
| Returns: float
|
||||
|
|
||||
| label_anchor_shadow
|
||||
| The SVG x position of the label shadow anchor.
|
||||
|
|
||||
| Returns: float
|
||||
|
|
||||
| label_width
|
||||
| The SVG width of the label text.
|
||||
|
|
||||
| Returns: int
|
||||
|
|
||||
| value_anchor
|
||||
| The SVG x position of the middle anchor for the value text.
|
||||
|
|
||||
| Returns: float
|
||||
|
|
||||
| value_anchor_shadow
|
||||
| The SVG x position of the value shadow anchor.
|
||||
|
|
||||
| Returns: float
|
||||
|
|
||||
| value_is_float
|
||||
| Identify whether the value text is a float.
|
||||
|
|
||||
| Returns: bool
|
||||
|
|
||||
| value_is_int
|
||||
| Identify whether the value text is an int.
|
||||
|
|
||||
| Returns: bool
|
||||
|
|
||||
| value_type
|
||||
| The Python type associated with the value.
|
||||
|
|
||||
| Returns: type
|
||||
|
|
||||
| value_width
|
||||
| The SVG width of the value text.
|
||||
|
|
||||
| Returns: int
|
||||
|
||||
FUNCTIONS
|
||||
main()
|
||||
Generate a badge based on command line arguments.
|
||||
|
||||
parse_args()
|
||||
Parse the command line arguments.
|
||||
main()
|
||||
Generate a badge based on command line arguments.
|
||||
|
||||
parse_args()
|
||||
Parse the command line arguments.
|
||||
|
||||
DATA
|
||||
BADGE_TEMPLATES = {'coverage': {'label': 'coverage', 'suffix': '%', 't...
|
||||
COLORS = {'green': '#97CA00', 'lightgrey': '#9f9f9f', 'orange': '#fe7d...
|
||||
DEFAULT_COLOR = '#a4a61d'
|
||||
DEFAULT_FONT = 'DejaVu Sans,Verdana,Geneva,sans-serif'
|
||||
DEFAULT_FONT_SIZE = 11
|
||||
DEFAULT_TEXT_COLOR = '#fff'
|
||||
FONT_WIDTHS = {'DejaVu Sans,Verdana,Geneva,sans-serif': {11: 7}}
|
||||
NUM_PADDING_CHARS = 0.5
|
||||
TEMPLATE_SVG = '<?xml version="1.0" encoding="UTF-8"?>\n<svg xmln...ho...
|
||||
__summary__ = 'A simple, flexible badge generator.'
|
||||
__title__ = 'anybadge'
|
||||
__uri__ = 'https://github.com/jongracecox/anybadge'
|
||||
__version__ = '0.2.0.dev1'
|
||||
__version_info__ = ('0', '2', '0', 'dev1')
|
||||
version = '0.2.0.dev1'
|
||||
BADGE_TEMPLATES = {'coverage': {'label': 'coverage', 'suffix': '%', 't...
|
||||
COLORS = {'green': '#4c1', 'lightgrey': '#9f9f9f', 'orange': '#fe7d37'...
|
||||
DEFAULT_COLOR = '#4c1'
|
||||
DEFAULT_FONT = 'DejaVu Sans,Verdana,Geneva,sans-serif'
|
||||
DEFAULT_FONT_SIZE = 11
|
||||
DEFAULT_TEXT_COLOR = '#fff'
|
||||
FONT_WIDTHS = {'DejaVu Sans,Verdana,Geneva,sans-serif': {11: 10}}
|
||||
NUM_PADDING_CHARS = 0.5
|
||||
TEMPLATE_SVG = '<?xml version="1.0" encoding="UTF-8"?>\n<svg xmln...ho...
|
||||
__summary__ = 'A simple, flexible badge generator.'
|
||||
__title__ = 'anybadge'
|
||||
__uri__ = 'https://github.com/jongracecox/anybadge'
|
||||
__version_info__ = ('0', '0', '0')
|
||||
digits = '0123456789'
|
||||
version = '0.0.0'
|
||||
|
||||
VERSION
|
||||
0.2.0.dev1
|
||||
0.0.0
|
||||
```
|
||||
|
||||
232
anybadge.py
232
anybadge.py
@@ -8,8 +8,9 @@ simplicity and flexibility.
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
# Package information
|
||||
version = __version__ = "0.1.0.dev2"
|
||||
version = __version__ = "0.0.0"
|
||||
__version_info__ = tuple(re.split('[.-]', __version__))
|
||||
__title__ = "anybadge"
|
||||
__summary__ = "A simple, flexible badge generator."
|
||||
@@ -27,7 +28,7 @@ DEFAULT_TEXT_COLOR = '#fff'
|
||||
# supported fonts and font sizes.
|
||||
FONT_WIDTHS = {
|
||||
'DejaVu Sans,Verdana,Geneva,sans-serif': {
|
||||
11: 7
|
||||
11: 10
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +172,10 @@ class Badge(object):
|
||||
|
||||
@property
|
||||
def value_is_float(self):
|
||||
"""Identify whether the value text is a float."""
|
||||
"""Identify whether the value text is a float.
|
||||
|
||||
Returns: bool
|
||||
"""
|
||||
try:
|
||||
_ = float(self.value)
|
||||
except ValueError:
|
||||
@@ -181,7 +185,10 @@ class Badge(object):
|
||||
|
||||
@property
|
||||
def value_is_int(self):
|
||||
"""Identify whether the value text is an int."""
|
||||
"""Identify whether the value text is an int.
|
||||
|
||||
Returns: bool
|
||||
"""
|
||||
try:
|
||||
a = float(self.value)
|
||||
b = int(a)
|
||||
@@ -192,7 +199,10 @@ class Badge(object):
|
||||
|
||||
@property
|
||||
def value_type(self):
|
||||
"""The Python type associated with the value."""
|
||||
"""The Python type associated with the value.
|
||||
|
||||
Returns: type
|
||||
"""
|
||||
if self.value_is_float:
|
||||
return float
|
||||
elif self.value_is_int:
|
||||
@@ -202,60 +212,96 @@ class Badge(object):
|
||||
|
||||
@property
|
||||
def label_width(self):
|
||||
"""The SVG width of the label text."""
|
||||
return self.get_text_width(self.label)
|
||||
"""The SVG width of the label text.
|
||||
|
||||
Returns: int
|
||||
"""
|
||||
return int(self.get_text_width(self.label) + (2.0 * self.num_padding_chars * self.font_width))
|
||||
|
||||
@property
|
||||
def value_width(self):
|
||||
"""The SVG width of the value text."""
|
||||
return self.get_text_width(str(self.value_text))
|
||||
"""The SVG width of the value text.
|
||||
|
||||
Returns: int
|
||||
"""
|
||||
return int(self.get_text_width(str(self.value_text)) + (self.num_padding_chars * self.font_width))
|
||||
|
||||
@property
|
||||
def font_width(self):
|
||||
"""Return the badge font width."""
|
||||
return self.get_font_width(font_name=self.font_name, font_size=self.font_size)
|
||||
"""Return the width multiplier for a font.
|
||||
|
||||
Returns:
|
||||
int: Maximum pixel width of badges selected font.
|
||||
|
||||
Example:
|
||||
|
||||
>>> Badge(label='x', value='1').font_width
|
||||
10
|
||||
"""
|
||||
return FONT_WIDTHS[self.font_name][self.font_size]
|
||||
|
||||
@property
|
||||
def color_split_position(self):
|
||||
"""The SVG x position where the color split should occur."""
|
||||
return self.get_text_width(' ') + self.label_width + \
|
||||
int(float(self.font_width) * float(self.num_padding_chars))
|
||||
"""The SVG x position where the color split should occur.
|
||||
|
||||
Returns: int
|
||||
"""
|
||||
return int(self.font_width + self.label_width +
|
||||
float(self.font_width) * float(self.num_padding_chars))
|
||||
|
||||
@property
|
||||
def label_anchor(self):
|
||||
"""The SVG x position of the middle anchor for the label text."""
|
||||
"""The SVG x position of the middle anchor for the label text.
|
||||
|
||||
Returns: float
|
||||
"""
|
||||
return self.color_split_position / 2
|
||||
|
||||
@property
|
||||
def value_anchor(self):
|
||||
"""The SVG x position of the middle anchor for the value text."""
|
||||
"""The SVG x position of the middle anchor for the value text.
|
||||
|
||||
Returns: float
|
||||
"""
|
||||
return self.color_split_position + ((self.badge_width - self.color_split_position) / 2)
|
||||
|
||||
@property
|
||||
def label_anchor_shadow(self):
|
||||
"""The SVG x position of the label shadow anchor."""
|
||||
"""The SVG x position of the label shadow anchor.
|
||||
|
||||
Returns: float
|
||||
"""
|
||||
return self.label_anchor + 1
|
||||
|
||||
@property
|
||||
def value_anchor_shadow(self):
|
||||
"""The SVG x position of the value shadow anchor."""
|
||||
"""The SVG x position of the value shadow anchor.
|
||||
|
||||
Returns: float
|
||||
"""
|
||||
return self.value_anchor + 1
|
||||
|
||||
@property
|
||||
def badge_width(self):
|
||||
"""The total width of badge.
|
||||
|
||||
>>> badge = Badge('pylint', '5', font_name='DejaVu Sans,Verdana,Geneva,sans-serif',
|
||||
... font_size=11)
|
||||
>>> badge.badge_width
|
||||
91
|
||||
Returns: int
|
||||
|
||||
Examples:
|
||||
|
||||
>>> badge = Badge('pylint', '5')
|
||||
>>> badge.badge_width
|
||||
103
|
||||
"""
|
||||
return self.get_text_width(' ' + ' ' * int(float(self.num_padding_chars) * 2.0)) \
|
||||
+ self.label_width + self.value_width
|
||||
padding = int(self.font_width * (self.num_padding_chars + 3))
|
||||
return padding + self.label_width + self.value_width
|
||||
|
||||
@property
|
||||
def badge_svg_text(self):
|
||||
"""The badge SVG text."""
|
||||
"""The badge SVG text.
|
||||
|
||||
Returns: str
|
||||
"""
|
||||
|
||||
# Identify whether template is a file or the actual template text
|
||||
if len(self.template.split('\n')) == 1:
|
||||
@@ -279,30 +325,29 @@ class Badge(object):
|
||||
.replace('{{ color split x }}', str(self.color_split_position)) \
|
||||
.replace('{{ value width }}', str(self.badge_width - self.color_split_position))
|
||||
|
||||
@staticmethod
|
||||
def get_font_width(font_name, font_size):
|
||||
"""Return the width multiplier for a font.
|
||||
|
||||
>>> Badge.get_font_width('DejaVu Sans,Verdana,Geneva,sans-serif', 11)
|
||||
7
|
||||
"""
|
||||
return FONT_WIDTHS[font_name][font_size]
|
||||
|
||||
def get_text_width(self, text):
|
||||
"""Return the width of text.
|
||||
|
||||
Args:
|
||||
text(str): Text to get the pixel width of.
|
||||
|
||||
Returns:
|
||||
int: Pixel width of the given text based on the badges selected font.
|
||||
|
||||
This implementation assumes a fixed font of:
|
||||
|
||||
font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"
|
||||
>>> badge = Badge('x', 1, font_name='DejaVu Sans,Verdana,Geneva,sans-serif', font_size=11)
|
||||
>>> badge.get_text_width('pylint')
|
||||
42
|
||||
34
|
||||
"""
|
||||
return len(text) * self.get_font_width(self.font_name, self.font_size)
|
||||
return _get_approx_string_width(text, self.font_width)
|
||||
|
||||
@property
|
||||
def badge_color(self):
|
||||
"""Find the badge color based on the thresholds."""
|
||||
"""Badge color based on the configured thresholds.
|
||||
|
||||
Returns: str"""
|
||||
# If no thresholds were passed then return the default color
|
||||
if not self.thresholds:
|
||||
return self.default_color
|
||||
@@ -331,7 +376,10 @@ class Badge(object):
|
||||
|
||||
@property
|
||||
def badge_color_code(self):
|
||||
"""Return the color code for the badge."""
|
||||
"""Return the color code for the badge.
|
||||
|
||||
Returns: str
|
||||
"""
|
||||
color = self.badge_color
|
||||
if color[0] == '#':
|
||||
return color
|
||||
@@ -357,6 +405,113 @@ class Badge(object):
|
||||
file_handle.write(self.badge_svg_text)
|
||||
|
||||
|
||||
# Based on the following SO answer: https://stackoverflow.com/a/16008023/6252525
|
||||
def _get_approx_string_width(text, font_width, fixed_width=False):
|
||||
"""
|
||||
Get the approximate width of a string using a specific average font width.
|
||||
|
||||
Args:
|
||||
text(str): Text string to calculate width of.
|
||||
font_width(int): Average width of font characters.
|
||||
fixed_width(bool): Indicates that the font is fixed width.
|
||||
|
||||
Returns:
|
||||
int: Width of string in pixels.
|
||||
|
||||
Examples:
|
||||
|
||||
Call the function with a string and the maximum character width of the font you are using:
|
||||
|
||||
>>> int(_get_approx_string_width('hello', 10))
|
||||
29
|
||||
|
||||
This example shows the comparison of simplistic calculation based on a fixed width.
|
||||
Given a test string and a fixed font width of 10, we can calculate the width
|
||||
by multiplying the length and the font character with:
|
||||
|
||||
>>> test_string = 'GOOGLE|ijkl'
|
||||
>>> _get_approx_string_width(test_string, 10, fixed_width=True)
|
||||
110
|
||||
|
||||
Since some characters in the string are thinner than others we expect that the
|
||||
apporximate text width will be narrower than the fixed width calculation:
|
||||
|
||||
>>> _get_approx_string_width(test_string, 10)
|
||||
77
|
||||
|
||||
"""
|
||||
if fixed_width:
|
||||
return len(text) * font_width
|
||||
|
||||
size = 0.0
|
||||
|
||||
# A dictionary containing percentages that relate to how wide
|
||||
# each character will be represented in a variable width font.
|
||||
# These percentages can be calculated using the ``_get_character_percentage_dict`` function.
|
||||
char_width_percentages = {
|
||||
"lij|' ": 40.0,
|
||||
'![]fI.,:;/\\t': 50.0,
|
||||
'`-(){}r"': 60.0,
|
||||
'*^zcsJkvxy': 70.0,
|
||||
'aebdhnopqug#$L+<>=?_~FZT0123456789': 70.0,
|
||||
'BSPEAKVXY&UwNRCHD': 70.0,
|
||||
'QGOMm%W@': 100.0
|
||||
}
|
||||
|
||||
for s in text:
|
||||
percentage = 100.0
|
||||
for k in char_width_percentages.keys():
|
||||
if s in k:
|
||||
percentage = char_width_percentages[k]
|
||||
break
|
||||
size += (percentage / 100.0) * float(font_width)
|
||||
|
||||
return int(size)
|
||||
|
||||
# This is a helper function that can be used to generate alternate dictionaries
|
||||
# for the _get_approx_string_width function. The function is not needed for
|
||||
# normal operation of this package, and since it depends on the PIL package,
|
||||
# which is not included in the dependencies the function will remain commented out.
|
||||
#
|
||||
# def _get_character_percentage_dict(font_path, font_size):
|
||||
# """Get the dictionary used to estimate variable width font text lengths.
|
||||
#
|
||||
# Args:
|
||||
# font_path(str): Path to valid font file.
|
||||
# font_size(int): Font size to use.
|
||||
#
|
||||
# Returns: dict
|
||||
#
|
||||
# This function can be used to calculate the dictionary used in the
|
||||
# ``get_approx_string_width`` function.
|
||||
#
|
||||
# Examples:
|
||||
# >>> _get_character_percentage_dict('/Library/Fonts/Verdana.ttf', 9) # doctest: +ELLIPSIS
|
||||
# {"lij|' ": 40, '![]fI.,:;/\\\\t': 50, '`-(){}r"': 60, '*^zcsJkvxy': 70, ...
|
||||
# """
|
||||
# from PIL import ImageFont
|
||||
#
|
||||
# # List of groups in size order, smallest to largest
|
||||
# char_width_groups = [
|
||||
# "lij|' ",
|
||||
# '![]fI.,:;/\\t',
|
||||
# '`-(){}r"',
|
||||
# '*^zcsJkvxy',
|
||||
# 'aebdhnopqug#$L+<>=?_~FZT' + digits,
|
||||
# 'BSPEAKVXY&UwNRCHD',
|
||||
# 'QGOMm%W@',
|
||||
# ]
|
||||
#
|
||||
# def get_largest_in_group(group):
|
||||
# """Get the widest character from the group."""
|
||||
# return max([ImageFont.truetype(font_path, font_size).getsize(c)[0] for c in group])
|
||||
#
|
||||
# largest = char_width_groups[-1]
|
||||
# font_width = get_largest_in_group(largest)
|
||||
# return {group: int((get_largest_in_group(group) / font_width) * 100)
|
||||
# for group in char_width_groups}
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse the command line arguments."""
|
||||
import argparse
|
||||
@@ -477,5 +632,6 @@ def main():
|
||||
else:
|
||||
print(badge.badge_svg_text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mister-bump>=1.0.0
|
||||
m2r
|
||||
restructuredtext_lint
|
||||
pygments
|
||||
pytest
|
||||
pytest-cov
|
||||
13
setup.py
13
setup.py
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
import os
|
||||
import re
|
||||
from setuptools import setup
|
||||
from mister_bump import bump
|
||||
from m2r import parse_from_file
|
||||
import restructuredtext_lint
|
||||
|
||||
@@ -16,16 +17,22 @@ if errors:
|
||||
raise ValueError('README.md contains errors: ',
|
||||
', '.join([e.message for e in errors]))
|
||||
|
||||
# Attempt to get version number from TravisCI environment variable
|
||||
version = os.environ.get('TRAVIS_TAG', default='0.0.0')
|
||||
|
||||
# Remove leading 'v'
|
||||
version = re.sub('^v', '', version)
|
||||
|
||||
setup(
|
||||
name='anybadge',
|
||||
description='Simple, flexible badge generator for project badges.',
|
||||
long_description=rst_readme,
|
||||
version=bump(),
|
||||
version=version,
|
||||
author='Jon Grace-Cox',
|
||||
author_email='jongracecox@gmail.com',
|
||||
py_modules=['anybadge', 'anybadge_server'],
|
||||
setup_requires=['setuptools', 'wheel'],
|
||||
tests_require=[],
|
||||
tests_require=['unittest'],
|
||||
install_requires=[],
|
||||
data_files=[],
|
||||
options={
|
||||
|
||||
15
test.py
15
test.py
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
"""
|
||||
anybadge test module.
|
||||
"""
|
||||
|
||||
import anybadge
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
thresholds={2: 'red', 4: 'orange', 6: 'green', 8: 'brightgreen'}
|
||||
|
||||
badge = anybadge.Badge('test', '2.22', value_suffix='%',
|
||||
thresholds=thresholds, text_color='#010101,#101010')
|
||||
|
||||
print(badge.badge_svg_text)
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
105
tests/test_anybadge.py
Normal file
105
tests/test_anybadge.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from unittest import TestCase
|
||||
from anybadge import Badge
|
||||
|
||||
|
||||
class TestAnybadge(TestCase):
|
||||
"""Test case class for anybadge package."""
|
||||
|
||||
def test_badge_width_with_long_value_text(self):
|
||||
"""Test the width of a badge generated with a long text value."""
|
||||
|
||||
badge = Badge(label='CppCheck',
|
||||
value='err: 2 | warn: 9 | info: 99 | style: 365',
|
||||
default_color='red')
|
||||
|
||||
badge.write_badge('test_badge_1.svg', overwrite=True)
|
||||
|
||||
self.assertLessEqual(badge.badge_width, 326)
|
||||
|
||||
def test_badge_width_with_long_value_text_zero_padding(self):
|
||||
"""Test the width of a badge generated with a long text value."""
|
||||
|
||||
badge = Badge(label='CppCheck',
|
||||
value='err: 2 | warn: 9 | info: 99 | style: 365',
|
||||
default_color='red',
|
||||
num_padding_chars=0)
|
||||
|
||||
badge.write_badge('test_badge_2.svg', overwrite=True)
|
||||
|
||||
self.assertLessEqual(badge.badge_width, 306)
|
||||
|
||||
def test_badge_width_with_medium_value_text(self):
|
||||
"""Test the width of a badge generated with a medium text value."""
|
||||
|
||||
badge = Badge(label='medium',
|
||||
value='89.67%',
|
||||
default_color='green')
|
||||
|
||||
badge.write_badge('test_badge_3.svg', overwrite=True)
|
||||
|
||||
self.assertLessEqual(badge.badge_width, 138)
|
||||
|
||||
def test_badge_width_with_medium_value_text_zero_pad(self):
|
||||
"""Test the width of a badge generated with a medium text value."""
|
||||
|
||||
badge = Badge(label='medium',
|
||||
value='89.67%',
|
||||
default_color='green',
|
||||
num_padding_chars=0)
|
||||
|
||||
badge.write_badge('test_badge_4.svg', overwrite=True)
|
||||
|
||||
self.assertLessEqual(badge.badge_width, 118)
|
||||
|
||||
def test_badge_width_with_short_value_text(self):
|
||||
"""Test the width of a badge generated with a short text value."""
|
||||
|
||||
badge = Badge(label='short',
|
||||
value='1',
|
||||
default_color='green')
|
||||
|
||||
badge.write_badge('test_badge_5.svg', overwrite=True)
|
||||
|
||||
self.assertLessEqual(badge.badge_width, 101)
|
||||
|
||||
def test_badge_width_with_short_value_text_zero_pad(self):
|
||||
"""Test the width of a badge generated with a short text value."""
|
||||
|
||||
badge = Badge(label='short',
|
||||
value='1',
|
||||
default_color='green',
|
||||
num_padding_chars=0)
|
||||
|
||||
badge.write_badge('test_badge_6.svg', overwrite=True)
|
||||
|
||||
self.assertLessEqual(badge.badge_width, 81)
|
||||
|
||||
def test_badge_width_with_tiny_value_text(self):
|
||||
"""Test the width of a badge generated with a short text value."""
|
||||
|
||||
badge = Badge(label='a',
|
||||
value='1',
|
||||
default_color='green')
|
||||
|
||||
badge.write_badge('test_badge_7.svg', overwrite=True)
|
||||
|
||||
self.assertLessEqual(badge.badge_width, 76)
|
||||
|
||||
def test_badge_with_thresholds(self):
|
||||
"""Test generating a badge using thresholds."""
|
||||
thresholds = {
|
||||
2: 'red', 4: 'orange', 6: 'green', 8: 'brightgreen'
|
||||
}
|
||||
|
||||
badge = Badge('test', '2.22', value_suffix='%',
|
||||
thresholds=thresholds)
|
||||
|
||||
badge.write_badge('test_badge_8.svg')
|
||||
|
||||
def test_badge_with_text_color(self):
|
||||
"""Test generating a badge with alternate text_color."""
|
||||
|
||||
badge = Badge('test', '2.22', value_suffix='%',
|
||||
text_color='#010101,#101010')
|
||||
|
||||
badge.write_badge('test_badge_9.svg')
|
||||
Reference in New Issue
Block a user