add motd generator script
[freebsd-maintainer-scripts/.git] / getpatch
CommitLineData
efd73192
SB
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2012 Sofian Brabez <sbz@FreeBSD.org>
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer
12# 2. Redistributions in binary form must reproduce the above copyright
13# notice, this list of conditions and the following disclaimer in the
14# documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26#
c34d67af 27# $FreeBSD$
efd73192
SB
28#
29# MAINTAINER= sbz@FreeBSD.org
30
31import argparse
32import codecs
2b746d10 33import os
efd73192 34import re
2b746d10 35import ssl
efd73192
SB
36import sys
37if sys.version_info.major == 3:
38 import urllib.request as urllib2
39else:
40 import urllib2
41
42"""
43FreeBSD getpatch handles Gnats and Bugzilla patch attachments
44"""
45
2b746d10
SB
46
47def create_ssl_context(cafile):
48 if os.path.exists(cafile):
49 return ssl.create_default_context(cafile=cafile)
50 else:
51 return ssl._create_unverified_context()
52
53
efd73192
SB
54class GetPatch(object):
55
56 def __init__(self, pr, category='ports'):
57 self.pr = pr
58 self.category = category
59 self.patchs = []
60 self.url = ""
61 self.patch = ""
62 self.output_stdout = False
63 self.default_locale = sys.getdefaultencoding()
2b746d10 64 self.ssl_context = create_ssl_context('/usr/local/etc/ssl/cert.pem')
efd73192
SB
65
66 def fetch(self, *largs, **kwargs):
67 raise NotImplementedError()
68
69 def write(self, filename, data):
70 if filename.endswith(('.patch', '.txt')):
f772ae3b 71 filename = "{}.diff".format(filename[:filename.rindex('.')])
e265cb60 72 f = codecs.open(filename, encoding=self.default_locale, mode='w')
efd73192
SB
73 f.write(data.decode(self.default_locale))
74 f.close()
f772ae3b 75 self.out("[+] {} created".format(filename))
efd73192 76
e265cb60 77 def get(self, only_last=False, output_stdout=False):
efd73192
SB
78 self.output_stdout = output_stdout
79 self.fetch(self.pr, category=self.category)
80
81 if len(self.patchs) == 0:
82 self.out("[-] No patch found")
f772ae3b 83 sys.exit(os.EX_UNAVAILABLE)
efd73192
SB
84
85 if only_last:
86 self.patchs = [self.patchs.pop()]
87
88 for patch in self.patchs:
89 url = patch['url']
90 p = patch['name']
91
2b746d10 92 data = urllib2.urlopen(url, context=self.ssl_context).read()
efd73192
SB
93
94 if self.output_stdout:
95 sys.stdout.write(data.decode(self.default_locale))
96 else:
97 self.write(p, data)
98
99 def add_patch(self, url, name):
100 self.patchs.append({'url': url, 'name': name})
101
102 def out(self, s):
103 if not self.output_stdout:
104 print(s)
105
e265cb60 106
efd73192
SB
107class GnatsGetPatch(GetPatch):
108
109 URL_BASE = 'https://www.freebsd.org/cgi'
f772ae3b 110 URL = '{}/query-pr.cgi?pr='.format(URL_BASE)
efd73192
SB
111 REGEX = r'<b>Download <a href="([^"]*)">([^<]*)</a>'
112
113 def __init__(self, pr, category):
114 GetPatch.__init__(self, pr, category)
115
116 def fetch(self, *largs, **kwargs):
117 category = kwargs['category']
f772ae3b
SB
118 target = ("{}/{}".format(category, self.pr),
119 "{}".format(self.pr))[category == '']
120 self.out("[+] Fetching patch for pr {}".format(target))
efd73192 121 pattern = re.compile(self.REGEX)
f772ae3b
SB
122 u = urllib2.urlopen("{}{}".format(self.URL, target),
123 context=self.ssl_context)
efd73192 124 data = u.read()
e265cb60 125 if data is None:
efd73192 126 self.out("[-] No patch found")
f772ae3b 127 sys.exit(os.EX_UNAVAILABLE)
efd73192
SB
128
129 for patchs in re.findall(pattern, str(data)):
130 self.add_patch(patchs[0], patchs[1])
131
e265cb60 132
efd73192
SB
133class BzGetPatch(GetPatch):
134
e265cb60 135 URL_BASE = 'https://bugs.freebsd.org/bugzilla/'
f772ae3b 136 URL_SHOW = '{}/show_bug.cgi?id='.format(URL_BASE)
86249dc1
SB
137 REGEX_ATTACHMENTS_TABLE = r'<table id="attachment_table">(.*?)</table>'
138 REGEX_ATTACHMENT_TR = r'(<tr id="a\d+"[^<]+>.*?</tr>)'
efd73192
SB
139 REGEX_URL = r'<a href="([^<]+)">Details</a>'
140 REGEX = r'<div class="details">([^ ]+) \(text/plain(?:; charset=[-\w]+)?\)'
141
142 def __init__(self, pr, category):
143 GetPatch.__init__(self, pr, category)
144
145 def _get_patch_name(self, url):
f772ae3b
SB
146 data = urllib2.urlopen(url, context=self.ssl_context).read()
147 match = re.search(self.REGEX, str(data))
efd73192
SB
148 if match is None:
149 return None
150 return match.group(1)
151
86249dc1 152 def _get_patch_url(self, data):
f772ae3b
SB
153 for url in re.findall(self.REGEX_URL, str(data)):
154 url = '{}{}'.format(self.URL_BASE, url)
efd73192
SB
155 file_name = self._get_patch_name(url)
156 if file_name is None:
f772ae3b
SB
157 msg = "[-] Could not determine the patch file name in {}." \
158 "Skipping."
159 self.out(msg.format(url))
efd73192
SB
160 continue
161 download_url = url[:url.find('&')]
86249dc1
SB
162 return download_url, file_name
163
164 def _get_patch_urls(self, data):
165 patch_urls = {}
166 match = re.search(self.REGEX_ATTACHMENTS_TABLE, str(data), re.DOTALL)
167 if match is None:
168 return patch_urls
169 table = match.group(1)
170 for tr in re.findall(self.REGEX_ATTACHMENT_TR, str(data), re.DOTALL):
171 if (tr.find('bz_tr_obsolete') >= 0):
172 continue
173 download_url, file_name = self._get_patch_url(tr)
efd73192 174 patch_urls[download_url] = file_name
86249dc1 175
efd73192
SB
176 return patch_urls
177
178 def fetch(self, *largs, **kwargs):
179 category = kwargs['category']
f772ae3b
SB
180 target = ("{}/{}".format(category, self.pr),
181 "{}".format(self.pr))[category == '']
182 self.out("[+] Fetching patch for pr {}".format(target))
183 u = urllib2.urlopen("{}{}".format(self.URL_SHOW, self.pr),
184 context=self.ssl_context)
efd73192
SB
185 data = u.read()
186
e265cb60 187 if data is None:
efd73192 188 self.out("[-] No patch found")
f772ae3b 189 sys.exit(os.EX_UNAVAILABLE)
efd73192
SB
190
191 patch_urls = self._get_patch_urls(data)
192 if not patch_urls:
193 self.out("[-] No patch found")
f772ae3b 194 sys.exit(os.EX_UNAVAILABLE)
efd73192 195
f772ae3b 196 for url, file_name in patch_urls.items():
efd73192
SB
197 self.add_patch(url, file_name)
198
e265cb60 199
efd73192
SB
200def main():
201
202 parser = argparse.ArgumentParser(
203 description='Gets patch attachments from a Bug Tracking System'
204 )
205 parser.add_argument('pr', metavar='pr', type=str, nargs=1,
f772ae3b 206 help='Pr id number')
e265cb60 207 parser.add_argument('--mode', type=str, choices=['gnats', 'bz'],
f772ae3b 208 default='bz', help='available modes to retrieve patch')
efd73192 209 parser.add_argument('--last', action='store_true',
f772ae3b 210 help='only retrieve the latest iteration of a patch')
efd73192 211 parser.add_argument('--stdout', action='store_true',
f772ae3b 212 help='dump patch on stdout')
efd73192
SB
213
214 if len(sys.argv) == 1:
215 parser.print_help()
f772ae3b 216 sys.exit(os.EX_USAGE)
efd73192
SB
217
218 args = parser.parse_args()
219
220 category = ""
221 pr = str(args.pr[0])
222
e265cb60 223 if pr and '/' in pr:
efd73192
SB
224 category, pr = pr.split('/')
225
226 Clazz = globals()['%sGetPatch' % args.mode.capitalize()]
227 gp = Clazz(pr, category)
228 gp.get(only_last=args.last, output_stdout=args.stdout)
229
f772ae3b
SB
230 return os.EX_OK
231
efd73192 232if __name__ == '__main__':
f772ae3b 233 sys.exit(main())