badgepy 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
badgepy/__init__.py ADDED
@@ -0,0 +1,252 @@
1
+ # Copyright 2018 The pybadge Authors
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
+ # https://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
+ """Creates a github-style badge as a SVG image.
15
+
16
+ This package seeks to generate semantically-identical output to the JavaScript
17
+ gh-badges library
18
+ (https://github.com/badges/shields/blob/master/doc/gh-badges.md)
19
+
20
+ >>> badge(left_text='coverage', right_text='23%', right_color='red')
21
+ '<svg...</svg>'
22
+ >>> badge(left_text='build', right_text='green', right_color='green',
23
+ ... whole_link="http://www.example.com/")
24
+ '<svg...</svg>'
25
+ >>> # base64-encoded PNG image
26
+ >>> image_data = 'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAD0lEQVQI12P4zwAD/xkYAA/+Af8iHnLUAAAAAElFTkSuQmCC'
27
+ >>> badge(left_text='build', right_text='green', right_color='green',
28
+ ... logo="data:image/png;base64," + image_data)
29
+ '<svg...</svg>'
30
+ """
31
+
32
+ import base64
33
+ import mimetypes
34
+ from typing import Optional
35
+ import urllib.parse
36
+ from xml.dom import minidom
37
+
38
+ import filetype
39
+ import jinja2
40
+ import requests
41
+
42
+ from badgepy import text_measurer
43
+ from badgepy import precalculated_text_measurer
44
+ from badgepy.version import __version__
45
+
46
+ _JINJA2_ENVIRONMENT = jinja2.Environment(
47
+ trim_blocks=True,
48
+ lstrip_blocks=True,
49
+ loader=jinja2.PackageLoader('badgepy', '.'),
50
+ autoescape=jinja2.select_autoescape(['svg']))
51
+
52
+ # Use the same color scheme as describe in:
53
+ # https://github.com/badges/shields/blob/master/lib/colorscheme.json
54
+
55
+ _NAME_TO_COLOR = {
56
+ 'brightgreen': '#4c1',
57
+ 'green': '#97CA00',
58
+ 'yellow': '#dfb317',
59
+ 'yellowgreen': '#a4a61d',
60
+ 'orange': '#fe7d37',
61
+ 'red': '#e05d44',
62
+ 'blue': '#007ec6',
63
+ 'grey': '#555',
64
+ 'gray': '#555',
65
+ 'lightgrey': '#9f9f9f',
66
+ 'lightgray': '#9f9f9f',
67
+ 'critical': '#e05d44',
68
+ 'important': '#fe7d37',
69
+ 'success': '#4c1',
70
+ 'informational': '#007ec6',
71
+ 'inactive': '#9f9f9f',
72
+ }
73
+
74
+
75
+ def _remove_blanks(node):
76
+ for x in node.childNodes:
77
+ if x.nodeType == minidom.Node.TEXT_NODE:
78
+ if x.nodeValue:
79
+ x.nodeValue = x.nodeValue.strip()
80
+ elif x.nodeType == minidom.Node.ELEMENT_NODE:
81
+ _remove_blanks(x)
82
+
83
+
84
+ def _embed_image(url: str) -> str:
85
+ parsed_url = urllib.parse.urlparse(url)
86
+
87
+ if parsed_url.scheme == 'data':
88
+ return url
89
+ elif parsed_url.scheme.startswith('http'):
90
+ r = requests.get(url)
91
+ r.raise_for_status()
92
+ content_type = r.headers.get('content-type')
93
+ if content_type is None:
94
+ raise ValueError('no "Content-Type" header')
95
+ content_type, image_type = content_type.split('/')
96
+ if content_type != 'image':
97
+ raise ValueError(
98
+ 'expected an image, got "{0}"'.format(content_type))
99
+ image_data = r.content
100
+ elif parsed_url.scheme:
101
+ raise ValueError('unsupported scheme "{0}"'.format(parsed_url.scheme))
102
+ else:
103
+ with open(url, 'rb') as f:
104
+ image_data = f.read()
105
+ kind = filetype.guess(image_data)
106
+ image_type = kind.extension if kind is not None else None
107
+ if not image_type:
108
+ mime_type, _ = mimetypes.guess_type(url, strict=False)
109
+ if not mime_type:
110
+ raise ValueError('not able to determine file type')
111
+ else:
112
+ content_type, image_type = mime_type.split('/')
113
+ if content_type != 'image':
114
+ raise ValueError('expected an image, got "{0}"'.format(
115
+ content_type or 'unknown'))
116
+
117
+ encoded_image = base64.b64encode(image_data).decode('ascii')
118
+ return 'data:image/{};base64,{}'.format(image_type, encoded_image)
119
+
120
+
121
+ def badge(
122
+ left_text: str,
123
+ right_text: Optional[str] = None,
124
+ left_link: Optional[str] = None,
125
+ right_link: Optional[str] = None,
126
+ center_link: Optional[str] = None,
127
+ whole_link: Optional[str] = None,
128
+ logo: Optional[str] = None,
129
+ left_color: str = '#555',
130
+ right_color: str = '#007ec6',
131
+ center_color: Optional[str] = None,
132
+ measurer: Optional[text_measurer.TextMeasurer] = None,
133
+ left_title: Optional[str] = None,
134
+ right_title: Optional[str] = None,
135
+ center_title: Optional[str] = None,
136
+ whole_title: Optional[str] = None,
137
+ right_image: Optional[str] = None,
138
+ center_image: Optional[str] = None,
139
+ embed_logo: bool = False,
140
+ embed_right_image: bool = False,
141
+ embed_center_image: bool = False,
142
+ id_suffix: str = '',
143
+ ) -> str:
144
+ """Creates a github-style badge as an SVG image.
145
+
146
+ >>> badge(left_text='coverage', right_text='23%', right_color='red')
147
+ '<svg...</svg>'
148
+ >>> badge(left_text='build', right_text='green', right_color='green',
149
+ ... whole_link="http://www.example.com/")
150
+ '<svg...</svg>'
151
+
152
+ Args:
153
+ left_text: The text that should appear on the left-hand-side of the
154
+ badge e.g. "coverage".
155
+ right_text: The text that should appear on the right-hand-side of the
156
+ badge e.g. "23%".
157
+ left_link: The URL that should be redirected to when the left-hand text
158
+ is selected.
159
+ right_link: The URL that should be redirected to when the right-hand
160
+ text is selected.
161
+ whole_link: The link that should be redirected to when the badge is
162
+ selected. If set then left_link and right_right may not be set.
163
+ logo: A url representing a logo that will be displayed inside the
164
+ badge. Can be a data URL e.g. "data:image/svg+xml;utf8,<svg..."
165
+ left_color: The color of the part of the badge containing the left-hand
166
+ text. Can be an valid CSS color
167
+ (see https://developer.mozilla.org/en-US/docs/Web/CSS/color) or a
168
+ color name defined here:
169
+ https://github.com/badges/shields/blob/master/badge-maker/lib/color.js
170
+ right_color: The color of the part of the badge containing the
171
+ right-hand text. Can be an valid CSS color
172
+ (see https://developer.mozilla.org/en-US/docs/Web/CSS/color) or a
173
+ color name defined here:
174
+ https://github.com/badges/shields/blob/master/badge-maker/lib/color.js
175
+ measurer: A text_measurer.TextMeasurer that can be used to measure the
176
+ width of left_text and right_text.
177
+ embed_logo: If True then embed the logo image directly in the badge.
178
+ This can prevent an HTTP request and some browsers will not render
179
+ external image referenced. When True, `logo` must be a HTTP/HTTPS
180
+ URI or a filesystem path. Also, the `badge` call may raise an
181
+ exception if the logo cannot be loaded, is not an image, etc.
182
+ whole_title: The title attribute to associate with the entire badge.
183
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title.
184
+ left_title: The title attribute to associate with the left part of the
185
+ badge.
186
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title.
187
+ right_title: The title attribute to associate with the right part of
188
+ the badge.
189
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title.
190
+ id_suffix: The suffix of the id attributes used in the SVG's elements.
191
+ Use to prevent duplicate ids if several badges are embedded on the
192
+ same page.
193
+ """
194
+ if measurer is None:
195
+ measurer = (
196
+ precalculated_text_measurer.PrecalculatedTextMeasurer.default())
197
+
198
+ if (left_link or right_link or center_link) and whole_link:
199
+ raise ValueError(
200
+ 'whole_link may not bet set with left_link, right_link, or center_link'
201
+ )
202
+
203
+ if center_image and not (right_image or right_text):
204
+ raise ValueError('cannot have a center_image without a right element')
205
+
206
+ if (center_image and not center_color) or (not center_image and
207
+ center_color):
208
+ raise ValueError('must have both a center_image and a center_color')
209
+
210
+ if logo and embed_logo:
211
+ logo = _embed_image(logo)
212
+
213
+ if right_image and embed_right_image:
214
+ right_image = _embed_image(right_image)
215
+
216
+ if center_image and embed_center_image:
217
+ center_image = _embed_image(center_image)
218
+
219
+ if center_color:
220
+ center_color = _NAME_TO_COLOR.get(center_color, center_color)
221
+
222
+ right_text_width = None
223
+ if right_text:
224
+ right_text_width = measurer.text_width(right_text) / 10.0
225
+
226
+ template = _JINJA2_ENVIRONMENT.get_template('badge-template-full.svg')
227
+
228
+ svg = template.render(
229
+ left_text=left_text,
230
+ right_text=right_text,
231
+ left_text_width=measurer.text_width(left_text) / 10.0,
232
+ right_text_width=right_text_width,
233
+ left_link=left_link,
234
+ right_link=right_link,
235
+ whole_link=whole_link,
236
+ center_link=center_link,
237
+ logo=logo,
238
+ left_color=_NAME_TO_COLOR.get(left_color, left_color),
239
+ right_color=_NAME_TO_COLOR.get(right_color, right_color),
240
+ center_color=center_color,
241
+ left_title=left_title,
242
+ right_title=right_title,
243
+ center_title=center_title,
244
+ whole_title=whole_title,
245
+ right_image=right_image,
246
+ center_image=center_image,
247
+ id_suffix=id_suffix,
248
+ )
249
+ xml = minidom.parseString(svg)
250
+ _remove_blanks(xml)
251
+ xml.normalize()
252
+ return xml.documentElement.toxml()
badgepy/__main__.py ADDED
@@ -0,0 +1,209 @@
1
+ # Copyright 2018 The pybadge Authors
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
+ # https://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
+ """Output a github-style badge as an SVG image given some text and colors.
15
+
16
+ For more information, run:
17
+ $ python3 -m badgepy --help
18
+ """
19
+
20
+ import argparse
21
+ import sys
22
+ import tempfile
23
+ import webbrowser
24
+
25
+ sys.path.append('/home/nick/git/badgepy/')
26
+ import badgepy
27
+ from badgepy.version import __version__
28
+
29
+
30
+ def main():
31
+ parser = argparse.ArgumentParser(
32
+ 'badgepy',
33
+ description='generate a github-style badge given some text and colors')
34
+
35
+ parser.add_argument(
36
+ '--left-text',
37
+ default='license',
38
+ help='the text to show on the left-hand-side of the badge')
39
+ parser.add_argument(
40
+ '--right-text',
41
+ default=None,
42
+ help='the text to show on the right-hand-side of the badge')
43
+ parser.add_argument(
44
+ '--left-link',
45
+ default=None,
46
+ help='the url to redirect to when the left-hand of the badge is ' +
47
+ 'clicked')
48
+ parser.add_argument(
49
+ '--right-link',
50
+ default=None,
51
+ help='the url to redirect to when the right-hand of the badge is ' +
52
+ 'clicked')
53
+ parser.add_argument(
54
+ '--center-link',
55
+ default=None,
56
+ help='the url to redirect to when the center of the badge is ' +
57
+ 'clicked')
58
+ parser.add_argument('--whole-link',
59
+ default=None,
60
+ help='the url to redirect to when the badge is clicked')
61
+ parser.add_argument(
62
+ '--logo',
63
+ default=None,
64
+ help='a URI reference to a logo to display in the badge')
65
+ parser.add_argument(
66
+ '--left-color',
67
+ default='#555',
68
+ help='the background color of the left-hand-side of the badge')
69
+ parser.add_argument(
70
+ '--right-color',
71
+ default='#007ec6',
72
+ help='the background color of the right-hand-side of the badge')
73
+ parser.add_argument(
74
+ '--center-color',
75
+ default=None,
76
+ help='the background color of the right-hand-side of the badge')
77
+ parser.add_argument('--browser',
78
+ action='store_true',
79
+ default=False,
80
+ help='display the badge in a browser tab')
81
+ parser.add_argument(
82
+ '--use-pil-text-measurer',
83
+ action='store_true',
84
+ default=False,
85
+ help='use the PilMeasurer to measure the length of text (kerning may '
86
+ 'be more precise for non-Western languages. ' +
87
+ '--deja-vu-sans-path must also be set.')
88
+ parser.add_argument(
89
+ '--deja-vu-sans-path',
90
+ default=None,
91
+ help='the path to the ttf font file containing DejaVu Sans. If not ' +
92
+ 'present on your system, you can download it from ' +
93
+ 'https://www.fontsquirrel.com/fonts/dejavu-sans')
94
+ parser.add_argument(
95
+ '--left-title',
96
+ default=None,
97
+ help='the title to associate with the left part of the badge. See '
98
+ 'https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title')
99
+ parser.add_argument(
100
+ '--right-title',
101
+ default=None,
102
+ help='the title to associate with the right part of the badge. See '
103
+ 'https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title')
104
+ parser.add_argument(
105
+ '--center-title',
106
+ default=None,
107
+ help='the title to associate with the center part of the badge. See '
108
+ 'https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title')
109
+ parser.add_argument(
110
+ '--whole-title',
111
+ default=None,
112
+ help='the title to associate with the entire badge. See '
113
+ 'https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title')
114
+ parser.add_argument(
115
+ '--right-image',
116
+ default=None,
117
+ help='the image to associate with the right-hand side of the badge')
118
+ parser.add_argument(
119
+ '--center-image',
120
+ default=None,
121
+ help='the image to associate with the center of the badge')
122
+ parser.add_argument(
123
+ '--embed-logo',
124
+ nargs='?',
125
+ type=lambda x: x.lower() in ['y', 'yes', 't', 'true', '1', ''],
126
+ const='yes',
127
+ default='no',
128
+ help='if the logo is specified then include the image data directly in '
129
+ 'the badge (this will prevent a URL fetch and may work around the '
130
+ 'fact that some browsers do not fetch external image references); '
131
+ 'only works if --logo is a HTTP/HTTPS URI or a file path')
132
+ parser.add_argument(
133
+ '--embed-right-image',
134
+ nargs='?',
135
+ type=lambda x: x.lower() in ['y', 'yes', 't', 'true', '1', ''],
136
+ const='yes',
137
+ default='no',
138
+ help=
139
+ 'if the right image is specified then include the image data directly in '
140
+ 'the badge (this will prevent a URL fetch and may work around the '
141
+ 'fact that some browsers do not fetch external image references); '
142
+ 'only works if --logo is a HTTP/HTTPS URI or a file path')
143
+ parser.add_argument(
144
+ '--embed-center-image',
145
+ nargs='?',
146
+ type=lambda x: x.lower() in ['y', 'yes', 't', 'true', '1', ''],
147
+ const='yes',
148
+ default='no',
149
+ help=
150
+ 'if the center image is specified then include the image data directly in '
151
+ 'the badge (this will prevent a URL fetch and may work around the '
152
+ 'fact that some browsers do not fetch external image references); '
153
+ 'only works if --logo is a HTTP/HTTPS URI or a file path')
154
+ parser.add_argument(
155
+ '-v',
156
+ '--version',
157
+ action='version',
158
+ version='%(prog)s {version}'.format(version=__version__))
159
+ args = parser.parse_args()
160
+
161
+ if (args.left_link or args.right_link or
162
+ args.center_link) and args.whole_link:
163
+ print('argument --whole-link: cannot be set with ' +
164
+ '--left-link, --right-link, or --center_link',
165
+ file=sys.stderr)
166
+ sys.exit(1)
167
+
168
+ measurer = None
169
+ if args.use_pil_text_measurer:
170
+ if args.deja_vu_sans_path is None:
171
+ print('argument --use-pil-text-measurer: must also set ' +
172
+ '--deja-vu-sans-path',
173
+ file=sys.stderr)
174
+ sys.exit(1)
175
+ from badgepy import pil_text_measurer
176
+ measurer = pil_text_measurer.PilMeasurer(args.deja_vu_sans_path)
177
+
178
+ badge = badgepy.badge(left_text=args.left_text,
179
+ right_text=args.right_text,
180
+ left_link=args.left_link,
181
+ right_link=args.right_link,
182
+ center_link=args.center_link,
183
+ whole_link=args.whole_link,
184
+ logo=args.logo,
185
+ left_color=args.left_color,
186
+ right_color=args.right_color,
187
+ center_color=args.center_color,
188
+ measurer=measurer,
189
+ left_title=args.left_title,
190
+ right_title=args.right_title,
191
+ center_title=args.center_title,
192
+ whole_title=args.whole_title,
193
+ right_image=args.right_image,
194
+ center_image=args.center_image,
195
+ embed_logo=args.embed_logo,
196
+ embed_right_image=args.embed_right_image,
197
+ embed_center_image=args.embed_center_image)
198
+
199
+ if args.browser:
200
+ _, badge_path = tempfile.mkstemp(suffix='.svg')
201
+ with open(badge_path, 'w') as f:
202
+ f.write(badge)
203
+
204
+ webbrowser.open_new_tab('file://' + badge_path)
205
+ else:
206
+ print(badge, end='')
207
+
208
+
209
+ main()
@@ -0,0 +1,80 @@
1
+ {% set logo_width = 14 if logo else 0 %}
2
+ {% set logo_padding = 3 if (logo and left_text) else 0 %}
3
+ {% set image_width = 107 if center_image else 0 %}
4
+ {% set left_width = left_text_width + 10 + logo_width + logo_padding %}
5
+ {% set center_width = image_width + 10 if center_image else 0 %}
6
+ {% set right_width = center_width + right_text_width + 10 if right_text else 0 %}
7
+ {% set id_smooth = 'smooth' + id_suffix %}
8
+ {% set id_round = 'round' + id_suffix %}
9
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{ left_width + right_width }}" height="20">
10
+ {% if whole_title %}
11
+ <title>{{ whole_title }}</title>
12
+ {% endif %}
13
+ <linearGradient id="{{ id_smooth }}" x2="0" y2="100%">
14
+ <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
15
+ <stop offset="1" stop-opacity=".1"/>
16
+ </linearGradient>
17
+
18
+ <clipPath id="{{ id_round }}">
19
+ <rect width="{{ left_width + right_width }}" height="20" rx="3" fill="#fff"/>
20
+ </clipPath>
21
+
22
+ <g clip-path="url(#{{ id_round }})">
23
+
24
+ <rect width="{{ left_width }}" height="20" fill="{{ left_color }}">
25
+ {% if left_title %}
26
+ <title>{{ left_title }}</title>
27
+ {% endif %}
28
+ </rect>
29
+
30
+ {% if center_image %}
31
+ <rect x="{{ left_width }}" width="{{ center_width }}" height="20" fill="{{ center_color }}">
32
+ {% if center_title %}
33
+ <title>{{ center_title }}</title>
34
+ {% endif %}
35
+ </rect>
36
+ {% endif %}
37
+
38
+ <rect x="{{ left_width + center_width }}" width="{{ right_width }}" height="20" fill="{{ right_color }}">
39
+ {% if right_title %}
40
+ <title>{{ right_title }}</title>
41
+ {% endif %}
42
+ </rect>
43
+
44
+ <rect width="{{ left_width + right_width }}" height="20" fill="url(#{{ id_smooth }})"/>
45
+ </g>
46
+
47
+ <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
48
+ {% if logo %}
49
+ <image x="5" y="3" width="{{ logo_width }}" height="14" xlink:href="{{ logo}}"/>
50
+ {% endif %}
51
+ <text x="{{ (((left_width+logo_width+logo_padding)/2)+1)*10 }}" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="{{ (left_width-(10+logo_width+logo_padding))*10 }}" lengthAdjust="spacing">{{ left_text }}</text>
52
+ <text x="{{ (((left_width+logo_width+logo_padding)/2)+1)*10 }}" y="140" transform="scale(0.1)" textLength="{{ (left_width-(10+logo_width+logo_padding))*10 }}" lengthAdjust="spacing">{{ left_text }}</text>
53
+ {% if center_image %}
54
+ <image x="{{ left_width }}" y="3" width="{{ center_width }}" height="14" xlink:href="{{ center_image }}"/>
55
+ {% endif %}
56
+ {% if right_image %}
57
+ <image x="{{ left_width + center_width }}" y="3" width="{{ right_width }}" height="14" xlink:href="{{ right_image }}"/>
58
+ {% endif %}
59
+ {% if right_text %}
60
+ <text x="{{ (left_width+center_width/2+right_width/2-1)*10 }}" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="{{ (right_width-center_width-10)*10 }}" lengthAdjust="spacing">{{ right_text }}</text>
61
+ <text x="{{ (left_width+center_width/2+right_width/2-1)*10 }}" y="140" transform="scale(0.1)" textLength="{{ (right_width-center_width-10)*10 }}" lengthAdjust="spacing">{{ right_text }}</text>
62
+ {% endif %}
63
+
64
+ {% if left_link or whole_link %}
65
+ <a xlink:href="{{ left_link or whole_link }}">
66
+ <rect width="{{ left_width }}" height="20" fill="rgba(0,0,0,0)"/>
67
+ </a>
68
+ {% endif %}
69
+ {% if center_image and (center_width or whole_link) %}
70
+ <a xlink:href="{{ center_link or whole_link }}">
71
+ <rect x="{{ left_width }}" width="{{ center_width }}" height="20" fill="rgba(0,0,0,0)"/>
72
+ </a>
73
+ {% endif %}
74
+ {% if right_link or whole_link %}
75
+ <a xlink:href="{{ right_link or whole_link }}">
76
+ <rect x="{{ left_width + center_width }}" width="{{ right_width }}" height="20" fill="rgba(0,0,0,0)"/>
77
+ </a>
78
+ {% endif %}
79
+ </g>
80
+ </svg>