We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Recognize CC licenses with https URLs
https://gpodder.net/directory/+license contains a list of licenses. When selecting a license the podcasts that use this particular license are shown.

In 
https://github.com/gpodder/mygpo/blob/1950aae95d79c87bf703ec7f457fd84c2c9613f4/mygpo/web/utils.py#L204
the license URLs are matched against known patterns to show "nice" names instead of URLs. For creative commons licenses, only http URLs are recognized. http and https URLs should be recognized as identical.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mygpo/web/utils.py`
Content:
```
1 import re
2 import math
3 import string
4 import collections
5 from datetime import datetime
6 
7 from django.utils.translation import ungettext
8 from django.views.decorators.cache import never_cache
9 from django.utils.html import strip_tags
10 from django.urls import reverse
11 from django.shortcuts import render
12 from django.http import Http404
13 
14 from babel import Locale, UnknownLocaleError
15 
16 from mygpo.podcasts.models import Podcast
17 
18 
19 def get_accepted_lang(request):
20     """ returns a list of language codes accepted by the HTTP request """
21 
22     lang_str = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
23     lang_str = ''.join([c for c in lang_str if c in string.ascii_letters + ','])
24     langs = lang_str.split(',')
25     langs = [s[:2] for s in langs]
26     langs = list(map(str.strip, langs))
27     langs = [_f for _f in langs if _f]
28     return list(set(langs))
29 
30 
31 RE_LANG = re.compile('^[a-zA-Z]{2}[-_]?.*$')
32 
33 
34 def sanitize_language_code(lang):
35     return lang[:2].lower()
36 
37 
38 def sanitize_language_codes(ls):
39     """
40     expects a list of language codes and returns a unique lost of the first
41     part of all items. obviously invalid entries are skipped
42 
43     >>> sanitize_language_codes(['de-at', 'de-ch'])
44     ['de']
45 
46     >>> set(sanitize_language_codes(['de-at', 'en', 'en-gb', '(asdf', 'Deutsch'])) == {'de', 'en'}
47     True
48     """
49 
50     ls = [sanitize_language_code(l) for l in ls if l and RE_LANG.match(l)]
51     return list(set(ls))
52 
53 
54 def get_language_names(lang):
55     """
56     Takes a list of language codes and returns a list of tuples
57     with (code, name)
58     """
59     res = {}
60     for l in lang:
61         try:
62             locale = Locale(l)
63         except UnknownLocaleError:
64             continue
65 
66         if locale.display_name:
67             res[l] = locale.display_name
68 
69     return res
70 
71 
72 def get_page_list(start, total, cur, show_max):
73     """
74     returns a list of pages to be linked for navigation in a paginated view
75 
76     >>> get_page_list(1, 100, 1, 10)
77     [1, 2, 3, 4, 5, 6, '...', 98, 99, 100]
78 
79     >>> get_page_list(1, 995/10, 1, 10)
80     [1, 2, 3, 4, 5, 6, '...', 98, 99, 100]
81 
82     >>> get_page_list(1, 100, 50, 10)
83     [1, '...', 48, 49, 50, 51, '...', 98, 99, 100]
84 
85     >>> get_page_list(1, 100, 99, 10)
86     [1, '...', 97, 98, 99, 100]
87 
88     >>> get_page_list(1, 3, 2, 10)
89     [1, 2, 3]
90     """
91 
92     # if we get "total" as a float (eg from total_entries / entries_per_page)
93     # we round up
94     total = math.ceil(total)
95 
96     if show_max >= (total - start):
97         return list(range(start, total + 1))
98 
99     ps = []
100     if (cur - start) > show_max / 2:
101         ps.extend(list(range(start, int(show_max / 4))))
102         ps.append('...')
103         ps.extend(list(range(cur - int(show_max / 4), cur)))
104 
105     else:
106         ps.extend(list(range(start, cur)))
107 
108     ps.append(cur)
109 
110     if (total - cur) > show_max / 2:
111         # for the first pages, show more pages at the beginning
112         add = math.ceil(show_max / 2 - len(ps))
113         ps.extend(list(range(cur + 1, cur + int(show_max / 4) + add)))
114         ps.append('...')
115         ps.extend(list(range(total - int(show_max / 4), total + 1)))
116 
117     else:
118         ps.extend(list(range(cur + 1, total + 1)))
119 
120     return ps
121 
122 
123 def process_lang_params(request):
124 
125     lang = request.GET.get('lang', None)
126 
127     if lang is None:
128         langs = get_accepted_lang(request)
129         lang = next(iter(langs), '')
130 
131     return sanitize_language_code(lang)
132 
133 
134 def symbian_opml_changes(podcast):
135     podcast.description = podcast.display_title + '\n' + (podcast.description or '')
136     return podcast
137 
138 
139 @never_cache
140 def maintenance(request, *args, **kwargs):
141     resp = render(request, 'maintenance.html', {})
142     resp.status_code = 503
143     return resp
144 
145 
146 def get_podcast_link_target(podcast, view_name='podcast', add_args=[]):
147     """ Returns the link-target for a Podcast, preferring slugs over Ids """
148 
149     # we prefer slugs
150     if podcast.slug:
151         args = [podcast.slug]
152         view_name = '%s-slug' % view_name
153 
154     # as a fallback we use UUIDs
155     else:
156         args = [podcast.id]
157         view_name = '%s-id' % view_name
158 
159     return reverse(view_name, args=args + add_args)
160 
161 
162 def get_podcast_group_link_target(group, view_name, add_args=[]):
163     """ the link-target for a Podcast group, preferring slugs over Ids """
164     args = [group.slug]
165     view_name = '%s-slug-id' % view_name
166     return reverse(view_name, args=args + add_args)
167 
168 
169 def get_episode_link_target(episode, podcast, view_name='episode', add_args=[]):
170     """ Returns the link-target for an Episode, preferring slugs over Ids """
171 
172     # prefer slugs
173     if episode.slug:
174         args = [podcast.slug, episode.slug]
175         view_name = '%s-slug' % view_name
176 
177     # fallback: UUIDs
178     else:
179         podcast = podcast or episode.podcast
180         args = [podcast.id, episode.id]
181         view_name = '%s-id' % view_name
182 
183     return strip_tags(reverse(view_name, args=args + add_args))
184 
185 
186 # doesn't include the '@' because it's not stored as part of a twitter handle
187 TWITTER_CHARS = string.ascii_letters + string.digits + '_'
188 
189 
190 def normalize_twitter(s):
191     """ normalize user input that is supposed to be a Twitter handle """
192     return "".join(i for i in s if i in TWITTER_CHARS)
193 
194 
195 CCLICENSE = re.compile(
196     r'http://(www\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?'
197 )
198 CCPUBLICDOMAIN = re.compile(
199     r'http://(www\.)?creativecommons.org/licenses/publicdomain/?'
200 )
201 LicenseInfo = collections.namedtuple('LicenseInfo', 'name version url')
202 
203 
204 def license_info(license_url):
205     """ Extracts license information from the license URL
206 
207     >>> i = license_info('http://creativecommons.org/licenses/by/3.0/')
208     >>> i.name
209     'CC BY'
210     >>> i.version
211     '3.0'
212     >>> i.url
213     'http://creativecommons.org/licenses/by/3.0/'
214 
215     >>> iwww = license_info('http://www.creativecommons.org/licenses/by/3.0/')
216     >>> i.name == iwww.name and i.version == iwww.version
217     True
218 
219     >>> i = license_info('http://www.creativecommons.org/licenses/publicdomain')
220     >>> i.name
221     'Public Domain'
222     >>> i.version is None
223     True
224 
225     >>> i = license_info('http://example.com/my-own-license')
226     >>> i.name is None
227     True
228     >>> i.version is None
229     True
230     >>> i.url
231     'http://example.com/my-own-license'
232     """
233     m = CCLICENSE.match(license_url)
234     if m:
235         _, name, version = m.groups()
236         return LicenseInfo('CC %s' % name.upper(), version, license_url)
237 
238     m = CCPUBLICDOMAIN.match(license_url)
239     if m:
240         return LicenseInfo('Public Domain', None, license_url)
241 
242     return LicenseInfo(None, None, license_url)
243 
244 
245 def check_restrictions(obj):
246     """ checks for known restrictions of the object """
247 
248     restrictions = obj.restrictions.split(',')
249     if "hide" in restrictions:
250         raise Http404
251 
252     if "hide-author" in restrictions:
253         obj.author = None
254 
255     return obj
256 
257 
258 def hours_to_str(hours_total):
259     """ returns a human-readable string representation of some hours
260 
261     >>> hours_to_str(1)
262     '1 hour'
263 
264     >>> hours_to_str(5)
265     '5 hours'
266 
267     >>> hours_to_str(100)
268     '4 days, 4 hours'
269 
270     >>> hours_to_str(960)
271     '5 weeks, 5 days'
272 
273     >>> hours_to_str(961)
274     '5 weeks, 5 days, 1 hour'
275     """
276 
277     weeks = int(hours_total / 24 / 7)
278     days = int(hours_total / 24) % 7
279     hours = hours_total % 24
280 
281     strs = []
282 
283     if weeks:
284         strs.append(
285             ungettext('%(weeks)d week', '%(weeks)d weeks', weeks) % {'weeks': weeks}
286         )
287 
288     if days:
289         strs.append(ungettext('%(days)d day', '%(days)d days', days) % {'days': days})
290 
291     if hours:
292         strs.append(
293             ungettext('%(hours)d hour', '%(hours)d hours', hours) % {'hours': hours}
294         )
295 
296     return ', '.join(strs)
297
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mygpo/web/utils.py b/mygpo/web/utils.py
--- a/mygpo/web/utils.py
+++ b/mygpo/web/utils.py
@@ -193,10 +193,10 @@
 
 
 CCLICENSE = re.compile(
-    r'http://(www\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?'
+    r'https?://(www\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?'
 )
 CCPUBLICDOMAIN = re.compile(
-    r'http://(www\.)?creativecommons.org/licenses/publicdomain/?'
+    r'https?://(www\.)?creativecommons.org/licenses/publicdomain/?'
 )
 LicenseInfo = collections.namedtuple('LicenseInfo', 'name version url')
 
@@ -212,16 +212,28 @@
     >>> i.url
     'http://creativecommons.org/licenses/by/3.0/'
 
+    >>> ihttps = license_info('https://creativecommons.org/licenses/by/3.0/')
+    >>> i.name == ihttps.name and i.version == ihttps.version
+    True
+
     >>> iwww = license_info('http://www.creativecommons.org/licenses/by/3.0/')
     >>> i.name == iwww.name and i.version == iwww.version
     True
 
+    >>> iwww = license_info('https://www.creativecommons.org/licenses/by/3.0/')
+    >>> i.name == iwww.name and i.version == iwww.version
+    True
+
     >>> i = license_info('http://www.creativecommons.org/licenses/publicdomain')
     >>> i.name
     'Public Domain'
     >>> i.version is None
     True
 
+    >>> ihttps = license_info('https://www.creativecommons.org/licenses/publicdomain')
+    >>> i.name == ihttps.name and i.version == ihttps.version
+    True
+
     >>> i = license_info('http://example.com/my-own-license')
     >>> i.name is None
     True

{"golden_diff": "diff --git a/mygpo/web/utils.py b/mygpo/web/utils.py\n--- a/mygpo/web/utils.py\n+++ b/mygpo/web/utils.py\n@@ -193,10 +193,10 @@\n \n \n CCLICENSE = re.compile(\n-    r'http://(www\\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?'\n+    r'https?://(www\\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?'\n )\n CCPUBLICDOMAIN = re.compile(\n-    r'http://(www\\.)?creativecommons.org/licenses/publicdomain/?'\n+    r'https?://(www\\.)?creativecommons.org/licenses/publicdomain/?'\n )\n LicenseInfo = collections.namedtuple('LicenseInfo', 'name version url')\n \n@@ -212,16 +212,28 @@\n     >>> i.url\n     'http://creativecommons.org/licenses/by/3.0/'\n \n+    >>> ihttps = license_info('https://creativecommons.org/licenses/by/3.0/')\n+    >>> i.name == ihttps.name and i.version == ihttps.version\n+    True\n+\n     >>> iwww = license_info('http://www.creativecommons.org/licenses/by/3.0/')\n     >>> i.name == iwww.name and i.version == iwww.version\n     True\n \n+    >>> iwww = license_info('https://www.creativecommons.org/licenses/by/3.0/')\n+    >>> i.name == iwww.name and i.version == iwww.version\n+    True\n+\n     >>> i = license_info('http://www.creativecommons.org/licenses/publicdomain')\n     >>> i.name\n     'Public Domain'\n     >>> i.version is None\n     True\n \n+    >>> ihttps = license_info('https://www.creativecommons.org/licenses/publicdomain')\n+    >>> i.name == ihttps.name and i.version == ihttps.version\n+    True\n+\n     >>> i = license_info('http://example.com/my-own-license')\n     >>> i.name is None\n     True\n", "issue": "Recognize CC licenses with https URLs\nhttps://gpodder.net/directory/+license contains a list of licenses. When selecting a license the podcasts that use this particular license are shown.\r\n\r\nIn \r\nhttps://github.com/gpodder/mygpo/blob/1950aae95d79c87bf703ec7f457fd84c2c9613f4/mygpo/web/utils.py#L204\r\nthe license URLs are matched against known patterns to show \"nice\" names instead of URLs. For creative commons licenses, only http URLs are recognized. http and https URLs should be recognized as identical.\n", "before_files": [{"content": "import re\nimport math\nimport string\nimport collections\nfrom datetime import datetime\n\nfrom django.utils.translation import ungettext\nfrom django.views.decorators.cache import never_cache\nfrom django.utils.html import strip_tags\nfrom django.urls import reverse\nfrom django.shortcuts import render\nfrom django.http import Http404\n\nfrom babel import Locale, UnknownLocaleError\n\nfrom mygpo.podcasts.models import Podcast\n\n\ndef get_accepted_lang(request):\n    \"\"\" returns a list of language codes accepted by the HTTP request \"\"\"\n\n    lang_str = request.META.get('HTTP_ACCEPT_LANGUAGE', '')\n    lang_str = ''.join([c for c in lang_str if c in string.ascii_letters + ','])\n    langs = lang_str.split(',')\n    langs = [s[:2] for s in langs]\n    langs = list(map(str.strip, langs))\n    langs = [_f for _f in langs if _f]\n    return list(set(langs))\n\n\nRE_LANG = re.compile('^[a-zA-Z]{2}[-_]?.*$')\n\n\ndef sanitize_language_code(lang):\n    return lang[:2].lower()\n\n\ndef sanitize_language_codes(ls):\n    \"\"\"\n    expects a list of language codes and returns a unique lost of the first\n    part of all items. obviously invalid entries are skipped\n\n    >>> sanitize_language_codes(['de-at', 'de-ch'])\n    ['de']\n\n    >>> set(sanitize_language_codes(['de-at', 'en', 'en-gb', '(asdf', 'Deutsch'])) == {'de', 'en'}\n    True\n    \"\"\"\n\n    ls = [sanitize_language_code(l) for l in ls if l and RE_LANG.match(l)]\n    return list(set(ls))\n\n\ndef get_language_names(lang):\n    \"\"\"\n    Takes a list of language codes and returns a list of tuples\n    with (code, name)\n    \"\"\"\n    res = {}\n    for l in lang:\n        try:\n            locale = Locale(l)\n        except UnknownLocaleError:\n            continue\n\n        if locale.display_name:\n            res[l] = locale.display_name\n\n    return res\n\n\ndef get_page_list(start, total, cur, show_max):\n    \"\"\"\n    returns a list of pages to be linked for navigation in a paginated view\n\n    >>> get_page_list(1, 100, 1, 10)\n    [1, 2, 3, 4, 5, 6, '...', 98, 99, 100]\n\n    >>> get_page_list(1, 995/10, 1, 10)\n    [1, 2, 3, 4, 5, 6, '...', 98, 99, 100]\n\n    >>> get_page_list(1, 100, 50, 10)\n    [1, '...', 48, 49, 50, 51, '...', 98, 99, 100]\n\n    >>> get_page_list(1, 100, 99, 10)\n    [1, '...', 97, 98, 99, 100]\n\n    >>> get_page_list(1, 3, 2, 10)\n    [1, 2, 3]\n    \"\"\"\n\n    # if we get \"total\" as a float (eg from total_entries / entries_per_page)\n    # we round up\n    total = math.ceil(total)\n\n    if show_max >= (total - start):\n        return list(range(start, total + 1))\n\n    ps = []\n    if (cur - start) > show_max / 2:\n        ps.extend(list(range(start, int(show_max / 4))))\n        ps.append('...')\n        ps.extend(list(range(cur - int(show_max / 4), cur)))\n\n    else:\n        ps.extend(list(range(start, cur)))\n\n    ps.append(cur)\n\n    if (total - cur) > show_max / 2:\n        # for the first pages, show more pages at the beginning\n        add = math.ceil(show_max / 2 - len(ps))\n        ps.extend(list(range(cur + 1, cur + int(show_max / 4) + add)))\n        ps.append('...')\n        ps.extend(list(range(total - int(show_max / 4), total + 1)))\n\n    else:\n        ps.extend(list(range(cur + 1, total + 1)))\n\n    return ps\n\n\ndef process_lang_params(request):\n\n    lang = request.GET.get('lang', None)\n\n    if lang is None:\n        langs = get_accepted_lang(request)\n        lang = next(iter(langs), '')\n\n    return sanitize_language_code(lang)\n\n\ndef symbian_opml_changes(podcast):\n    podcast.description = podcast.display_title + '\\n' + (podcast.description or '')\n    return podcast\n\n\n@never_cache\ndef maintenance(request, *args, **kwargs):\n    resp = render(request, 'maintenance.html', {})\n    resp.status_code = 503\n    return resp\n\n\ndef get_podcast_link_target(podcast, view_name='podcast', add_args=[]):\n    \"\"\" Returns the link-target for a Podcast, preferring slugs over Ids \"\"\"\n\n    # we prefer slugs\n    if podcast.slug:\n        args = [podcast.slug]\n        view_name = '%s-slug' % view_name\n\n    # as a fallback we use UUIDs\n    else:\n        args = [podcast.id]\n        view_name = '%s-id' % view_name\n\n    return reverse(view_name, args=args + add_args)\n\n\ndef get_podcast_group_link_target(group, view_name, add_args=[]):\n    \"\"\" the link-target for a Podcast group, preferring slugs over Ids \"\"\"\n    args = [group.slug]\n    view_name = '%s-slug-id' % view_name\n    return reverse(view_name, args=args + add_args)\n\n\ndef get_episode_link_target(episode, podcast, view_name='episode', add_args=[]):\n    \"\"\" Returns the link-target for an Episode, preferring slugs over Ids \"\"\"\n\n    # prefer slugs\n    if episode.slug:\n        args = [podcast.slug, episode.slug]\n        view_name = '%s-slug' % view_name\n\n    # fallback: UUIDs\n    else:\n        podcast = podcast or episode.podcast\n        args = [podcast.id, episode.id]\n        view_name = '%s-id' % view_name\n\n    return strip_tags(reverse(view_name, args=args + add_args))\n\n\n# doesn't include the '@' because it's not stored as part of a twitter handle\nTWITTER_CHARS = string.ascii_letters + string.digits + '_'\n\n\ndef normalize_twitter(s):\n    \"\"\" normalize user input that is supposed to be a Twitter handle \"\"\"\n    return \"\".join(i for i in s if i in TWITTER_CHARS)\n\n\nCCLICENSE = re.compile(\n    r'http://(www\\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?'\n)\nCCPUBLICDOMAIN = re.compile(\n    r'http://(www\\.)?creativecommons.org/licenses/publicdomain/?'\n)\nLicenseInfo = collections.namedtuple('LicenseInfo', 'name version url')\n\n\ndef license_info(license_url):\n    \"\"\" Extracts license information from the license URL\n\n    >>> i = license_info('http://creativecommons.org/licenses/by/3.0/')\n    >>> i.name\n    'CC BY'\n    >>> i.version\n    '3.0'\n    >>> i.url\n    'http://creativecommons.org/licenses/by/3.0/'\n\n    >>> iwww = license_info('http://www.creativecommons.org/licenses/by/3.0/')\n    >>> i.name == iwww.name and i.version == iwww.version\n    True\n\n    >>> i = license_info('http://www.creativecommons.org/licenses/publicdomain')\n    >>> i.name\n    'Public Domain'\n    >>> i.version is None\n    True\n\n    >>> i = license_info('http://example.com/my-own-license')\n    >>> i.name is None\n    True\n    >>> i.version is None\n    True\n    >>> i.url\n    'http://example.com/my-own-license'\n    \"\"\"\n    m = CCLICENSE.match(license_url)\n    if m:\n        _, name, version = m.groups()\n        return LicenseInfo('CC %s' % name.upper(), version, license_url)\n\n    m = CCPUBLICDOMAIN.match(license_url)\n    if m:\n        return LicenseInfo('Public Domain', None, license_url)\n\n    return LicenseInfo(None, None, license_url)\n\n\ndef check_restrictions(obj):\n    \"\"\" checks for known restrictions of the object \"\"\"\n\n    restrictions = obj.restrictions.split(',')\n    if \"hide\" in restrictions:\n        raise Http404\n\n    if \"hide-author\" in restrictions:\n        obj.author = None\n\n    return obj\n\n\ndef hours_to_str(hours_total):\n    \"\"\" returns a human-readable string representation of some hours\n\n    >>> hours_to_str(1)\n    '1 hour'\n\n    >>> hours_to_str(5)\n    '5 hours'\n\n    >>> hours_to_str(100)\n    '4 days, 4 hours'\n\n    >>> hours_to_str(960)\n    '5 weeks, 5 days'\n\n    >>> hours_to_str(961)\n    '5 weeks, 5 days, 1 hour'\n    \"\"\"\n\n    weeks = int(hours_total / 24 / 7)\n    days = int(hours_total / 24) % 7\n    hours = hours_total % 24\n\n    strs = []\n\n    if weeks:\n        strs.append(\n            ungettext('%(weeks)d week', '%(weeks)d weeks', weeks) % {'weeks': weeks}\n        )\n\n    if days:\n        strs.append(ungettext('%(days)d day', '%(days)d days', days) % {'days': days})\n\n    if hours:\n        strs.append(\n            ungettext('%(hours)d hour', '%(hours)d hours', hours) % {'hours': hours}\n        )\n\n    return ', '.join(strs)\n", "path": "mygpo/web/utils.py"}], "after_files": [{"content": "import re\nimport math\nimport string\nimport collections\nfrom datetime import datetime\n\nfrom django.utils.translation import ungettext\nfrom django.views.decorators.cache import never_cache\nfrom django.utils.html import strip_tags\nfrom django.urls import reverse\nfrom django.shortcuts import render\nfrom django.http import Http404\n\nfrom babel import Locale, UnknownLocaleError\n\nfrom mygpo.podcasts.models import Podcast\n\n\ndef get_accepted_lang(request):\n    \"\"\" returns a list of language codes accepted by the HTTP request \"\"\"\n\n    lang_str = request.META.get('HTTP_ACCEPT_LANGUAGE', '')\n    lang_str = ''.join([c for c in lang_str if c in string.ascii_letters + ','])\n    langs = lang_str.split(',')\n    langs = [s[:2] for s in langs]\n    langs = list(map(str.strip, langs))\n    langs = [_f for _f in langs if _f]\n    return list(set(langs))\n\n\nRE_LANG = re.compile('^[a-zA-Z]{2}[-_]?.*$')\n\n\ndef sanitize_language_code(lang):\n    return lang[:2].lower()\n\n\ndef sanitize_language_codes(ls):\n    \"\"\"\n    expects a list of language codes and returns a unique lost of the first\n    part of all items. obviously invalid entries are skipped\n\n    >>> sanitize_language_codes(['de-at', 'de-ch'])\n    ['de']\n\n    >>> set(sanitize_language_codes(['de-at', 'en', 'en-gb', '(asdf', 'Deutsch'])) == {'de', 'en'}\n    True\n    \"\"\"\n\n    ls = [sanitize_language_code(l) for l in ls if l and RE_LANG.match(l)]\n    return list(set(ls))\n\n\ndef get_language_names(lang):\n    \"\"\"\n    Takes a list of language codes and returns a list of tuples\n    with (code, name)\n    \"\"\"\n    res = {}\n    for l in lang:\n        try:\n            locale = Locale(l)\n        except UnknownLocaleError:\n            continue\n\n        if locale.display_name:\n            res[l] = locale.display_name\n\n    return res\n\n\ndef get_page_list(start, total, cur, show_max):\n    \"\"\"\n    returns a list of pages to be linked for navigation in a paginated view\n\n    >>> get_page_list(1, 100, 1, 10)\n    [1, 2, 3, 4, 5, 6, '...', 98, 99, 100]\n\n    >>> get_page_list(1, 995/10, 1, 10)\n    [1, 2, 3, 4, 5, 6, '...', 98, 99, 100]\n\n    >>> get_page_list(1, 100, 50, 10)\n    [1, '...', 48, 49, 50, 51, '...', 98, 99, 100]\n\n    >>> get_page_list(1, 100, 99, 10)\n    [1, '...', 97, 98, 99, 100]\n\n    >>> get_page_list(1, 3, 2, 10)\n    [1, 2, 3]\n    \"\"\"\n\n    # if we get \"total\" as a float (eg from total_entries / entries_per_page)\n    # we round up\n    total = math.ceil(total)\n\n    if show_max >= (total - start):\n        return list(range(start, total + 1))\n\n    ps = []\n    if (cur - start) > show_max / 2:\n        ps.extend(list(range(start, int(show_max / 4))))\n        ps.append('...')\n        ps.extend(list(range(cur - int(show_max / 4), cur)))\n\n    else:\n        ps.extend(list(range(start, cur)))\n\n    ps.append(cur)\n\n    if (total - cur) > show_max / 2:\n        # for the first pages, show more pages at the beginning\n        add = math.ceil(show_max / 2 - len(ps))\n        ps.extend(list(range(cur + 1, cur + int(show_max / 4) + add)))\n        ps.append('...')\n        ps.extend(list(range(total - int(show_max / 4), total + 1)))\n\n    else:\n        ps.extend(list(range(cur + 1, total + 1)))\n\n    return ps\n\n\ndef process_lang_params(request):\n\n    lang = request.GET.get('lang', None)\n\n    if lang is None:\n        langs = get_accepted_lang(request)\n        lang = next(iter(langs), '')\n\n    return sanitize_language_code(lang)\n\n\ndef symbian_opml_changes(podcast):\n    podcast.description = podcast.display_title + '\\n' + (podcast.description or '')\n    return podcast\n\n\n@never_cache\ndef maintenance(request, *args, **kwargs):\n    resp = render(request, 'maintenance.html', {})\n    resp.status_code = 503\n    return resp\n\n\ndef get_podcast_link_target(podcast, view_name='podcast', add_args=[]):\n    \"\"\" Returns the link-target for a Podcast, preferring slugs over Ids \"\"\"\n\n    # we prefer slugs\n    if podcast.slug:\n        args = [podcast.slug]\n        view_name = '%s-slug' % view_name\n\n    # as a fallback we use UUIDs\n    else:\n        args = [podcast.id]\n        view_name = '%s-id' % view_name\n\n    return reverse(view_name, args=args + add_args)\n\n\ndef get_podcast_group_link_target(group, view_name, add_args=[]):\n    \"\"\" the link-target for a Podcast group, preferring slugs over Ids \"\"\"\n    args = [group.slug]\n    view_name = '%s-slug-id' % view_name\n    return reverse(view_name, args=args + add_args)\n\n\ndef get_episode_link_target(episode, podcast, view_name='episode', add_args=[]):\n    \"\"\" Returns the link-target for an Episode, preferring slugs over Ids \"\"\"\n\n    # prefer slugs\n    if episode.slug:\n        args = [podcast.slug, episode.slug]\n        view_name = '%s-slug' % view_name\n\n    # fallback: UUIDs\n    else:\n        podcast = podcast or episode.podcast\n        args = [podcast.id, episode.id]\n        view_name = '%s-id' % view_name\n\n    return strip_tags(reverse(view_name, args=args + add_args))\n\n\n# doesn't include the '@' because it's not stored as part of a twitter handle\nTWITTER_CHARS = string.ascii_letters + string.digits + '_'\n\n\ndef normalize_twitter(s):\n    \"\"\" normalize user input that is supposed to be a Twitter handle \"\"\"\n    return \"\".join(i for i in s if i in TWITTER_CHARS)\n\n\nCCLICENSE = re.compile(\n    r'https?://(www\\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?'\n)\nCCPUBLICDOMAIN = re.compile(\n    r'https?://(www\\.)?creativecommons.org/licenses/publicdomain/?'\n)\nLicenseInfo = collections.namedtuple('LicenseInfo', 'name version url')\n\n\ndef license_info(license_url):\n    \"\"\" Extracts license information from the license URL\n\n    >>> i = license_info('http://creativecommons.org/licenses/by/3.0/')\n    >>> i.name\n    'CC BY'\n    >>> i.version\n    '3.0'\n    >>> i.url\n    'http://creativecommons.org/licenses/by/3.0/'\n\n    >>> ihttps = license_info('https://creativecommons.org/licenses/by/3.0/')\n    >>> i.name == ihttps.name and i.version == ihttps.version\n    True\n\n    >>> iwww = license_info('http://www.creativecommons.org/licenses/by/3.0/')\n    >>> i.name == iwww.name and i.version == iwww.version\n    True\n\n    >>> iwww = license_info('https://www.creativecommons.org/licenses/by/3.0/')\n    >>> i.name == iwww.name and i.version == iwww.version\n    True\n\n    >>> i = license_info('http://www.creativecommons.org/licenses/publicdomain')\n    >>> i.name\n    'Public Domain'\n    >>> i.version is None\n    True\n\n    >>> ihttps = license_info('https://www.creativecommons.org/licenses/publicdomain')\n    >>> i.name == ihttps.name and i.version == ihttps.version\n    True\n\n    >>> i = license_info('http://example.com/my-own-license')\n    >>> i.name is None\n    True\n    >>> i.version is None\n    True\n    >>> i.url\n    'http://example.com/my-own-license'\n    \"\"\"\n    m = CCLICENSE.match(license_url)\n    if m:\n        _, name, version = m.groups()\n        return LicenseInfo('CC %s' % name.upper(), version, license_url)\n\n    m = CCPUBLICDOMAIN.match(license_url)\n    if m:\n        return LicenseInfo('Public Domain', None, license_url)\n\n    return LicenseInfo(None, None, license_url)\n\n\ndef check_restrictions(obj):\n    \"\"\" checks for known restrictions of the object \"\"\"\n\n    restrictions = obj.restrictions.split(',')\n    if \"hide\" in restrictions:\n        raise Http404\n\n    if \"hide-author\" in restrictions:\n        obj.author = None\n\n    return obj\n\n\ndef hours_to_str(hours_total):\n    \"\"\" returns a human-readable string representation of some hours\n\n    >>> hours_to_str(1)\n    '1 hour'\n\n    >>> hours_to_str(5)\n    '5 hours'\n\n    >>> hours_to_str(100)\n    '4 days, 4 hours'\n\n    >>> hours_to_str(960)\n    '5 weeks, 5 days'\n\n    >>> hours_to_str(961)\n    '5 weeks, 5 days, 1 hour'\n    \"\"\"\n\n    weeks = int(hours_total / 24 / 7)\n    days = int(hours_total / 24) % 7\n    hours = hours_total % 24\n\n    strs = []\n\n    if weeks:\n        strs.append(\n            ungettext('%(weeks)d week', '%(weeks)d weeks', weeks) % {'weeks': weeks}\n        )\n\n    if days:\n        strs.append(ungettext('%(days)d day', '%(days)d days', days) % {'days': days})\n\n    if hours:\n        strs.append(\n            ungettext('%(hours)d hour', '%(hours)d hours', hours) % {'hours': hours}\n        )\n\n    return ', '.join(strs)\n", "path": "mygpo/web/utils.py"}]}