Merge lp:~dholbach/help-app/1433525 into lp:help-app

Proposed by Daniel Holbach
Status: Merged
Approved by: Daniel Holbach
Approved revision: 141
Merged at revision: 135
Proposed branch: lp:~dholbach/help-app/1433525
Merge into: lp:help-app
Diff against target: 371 lines (+195/-59)
8 files modified
HACKING (+1/-1)
debian/control (+2/-0)
internals/local/external-links.py (+26/-0)
internals/pelicanconf.py (+1/-1)
internals/tests/test_links.py (+150/-48)
internals/translations/build.py (+1/-1)
internals/translations/utils.py (+14/-1)
static/themes/app/static/css/app.css (+0/-7)
To merge this branch: bzr merge lp:~dholbach/help-app/1433525
Reviewer Review Type Date Requested Status
Nicholas Skaggs (community) Approve
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Review via email: mp+258866@code.launchpad.net

Commit message

Add markdown extension to make external links use target="_blank".

Description of the change

Add markdown extension to make external links use target="_blank".

To post a comment you must log in.
lp:~dholbach/help-app/1433525 updated
137. By Daniel Holbach

simplify python2/3 urlparse code

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
lp:~dholbach/help-app/1433525 updated
138. By Daniel Holbach

simplify code in link checking somewhat, this will allow us to add more tests to check links, add more atomic tests test_simple_local_broken_link, test_simple_local_working_link, test_non_existing_build

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
lp:~dholbach/help-app/1433525 updated
139. By Daniel Holbach

fix tests run during package build, by fixing override of build location (temporary dir to avoid changing live data)

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
lp:~dholbach/help-app/1433525 updated
140. By Daniel Holbach

extend and generalise MyHTMLParser and HTMLLinkChecker so we can run other checks on links as well - added a test to see if the markdown extension for external links was run

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
lp:~dholbach/help-app/1433525 updated
141. By Daniel Holbach

fix links in the CSS

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

LGTM. Couldn't check on device; will do before release.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'HACKING'
2--- HACKING 2015-03-31 12:27:56 +0000
3+++ HACKING 2015-05-13 13:46:52 +0000
4@@ -86,7 +86,7 @@
5
6 sudo apt install python-pelican po4a make bzrtools \
7 ubuntu-html5-ui-toolkit python3-polib python3-magic \
8- python3-markdown pep8 pyflakes
9+ python3-markdown pep8 pyflakes python-magic python-polib
10
11 This will install the necessary files, so you can build the app or web build
12 locally and check if your changes actually make sense and look and work well.
13
14=== modified file 'debian/control'
15--- debian/control 2015-03-25 10:14:27 +0000
16+++ debian/control 2015-05-13 13:46:52 +0000
17@@ -8,6 +8,8 @@
18 po4a,
19 pyflakes,
20 python-pelican (>= 3.5.0~),
21+ python-magic,
22+ python-polib,
23 python3-magic,
24 python3-markdown,
25 python3-polib,
26
27=== added file 'internals/local/external-links.py'
28--- internals/local/external-links.py 1970-01-01 00:00:00 +0000
29+++ internals/local/external-links.py 2015-05-13 13:46:52 +0000
30@@ -0,0 +1,26 @@
31+from __future__ import absolute_import
32+from __future__ import unicode_literals
33+from markdown import Extension
34+from markdown.treeprocessors import Treeprocessor
35+
36+from translations.utils import link_is_local
37+
38+
39+class ExternalLinksProcessor(Treeprocessor):
40+ def run(self, doc):
41+ for elem in doc.iter('a'):
42+ if 'href' in elem.attrib and \
43+ not link_is_local(elem.get('href')):
44+ elem.set('target', '_blank')
45+
46+
47+class ExternalLinksExtension(Extension):
48+ def extendMarkdown(self, md, md_globals):
49+ md.treeprocessors.add('external-links',
50+ ExternalLinksProcessor(md.parser),
51+ '_end')
52+ md.registerExtension(self)
53+
54+
55+def makeExtension(*args, **kwargs):
56+ return ExternalLinksExtension(*args, **kwargs)
57
58=== modified file 'internals/pelicanconf.py'
59--- internals/pelicanconf.py 2015-03-31 08:18:07 +0000
60+++ internals/pelicanconf.py 2015-05-13 13:46:52 +0000
61@@ -54,7 +54,7 @@
62 TAG_SAVE_AS = ''
63 THEME = TOP_LEVEL_DIR+'static/themes/app/'
64
65-MD_EXTENSIONS = ['local.q-and-a', 'attr_list', 'toc']
66+MD_EXTENSIONS = ['local.q-and-a', 'local.external-links', 'attr_list', 'toc']
67
68 META_TAGS = [
69 '[TOC]',
70
71=== modified file 'internals/tests/test_links.py'
72--- internals/tests/test_links.py 2015-03-31 06:25:44 +0000
73+++ internals/tests/test_links.py 2015-05-13 13:46:52 +0000
74@@ -5,22 +5,12 @@
75 import subprocess
76 import tempfile
77 from unittest import TestCase
78-import urllib.parse
79-
80-from translations.utils import use_top_level_dir
81-
82-
83-def require_build(build):
84- tempdir = tempfile.mkdtemp()
85- env = {}
86- if build == 'app':
87- env = {'OUTPUTDIR_APP': tempdir}
88- if build == 'web':
89- env = {'OUTPUTDIR_WEB': tempdir}
90- pwd = use_top_level_dir()
91- ret = subprocess.call(['make', '-es', build], env=env)
92- os.chdir(pwd)
93- return (ret, tempdir)
94+
95+from translations.utils import (
96+ link_is_anchor,
97+ link_is_local,
98+ use_top_level_dir,
99+)
100
101
102 def clean_tempdir(tempdir):
103@@ -28,53 +18,165 @@
104 shutil.rmtree(tempdir)
105
106
107+class BuildRunner():
108+ def __init__(self, build):
109+ self.tempdir = tempfile.mkdtemp()
110+ self.env = {}
111+ self.build = build
112+ self.html_files = []
113+ self.rc = None
114+ if self.build == 'app':
115+ self.env = {'APP_DIR': self.tempdir}
116+ if self.build == 'web':
117+ self.env = {'OUTPUTDIR_WEB': self.tempdir}
118+ self.pwd = use_top_level_dir()
119+ self.run_build()
120+ self.find_html_files()
121+
122+ def run_build(self):
123+ self.rc = subprocess.call(['make', '-es', self.build], env=self.env)
124+ os.chdir(self.pwd)
125+
126+ def find_html_files(self):
127+ for dirpath, dirnames, filenames in os.walk(self.tempdir):
128+ self.html_files.extend([os.path.join(dirpath, fn)
129+ for fn in filenames
130+ if fn.endswith('.html')])
131+
132+ def __del__(self):
133+ os.chdir(self.pwd)
134+ clean_tempdir(self.tempdir)
135+
136+
137 class MyHTMLParser(HTMLParser):
138 links = []
139
140 def handle_starttag(self, tag, attrs):
141- if tag == "a":
142+ if tag == 'a':
143+ found = {}
144 for name, value in attrs:
145- if name == "href":
146- url = urllib.parse.urlparse(value)
147- if not value.startswith('#') and \
148- url.netloc in [None, '', 'localhost']:
149- self.links += [value]
150+ if name == 'href' and \
151+ not link_is_anchor(value) and \
152+ value != '/':
153+ found['href'] = value
154+ if name == 'target':
155+ found['target'] = value
156+ if found != {}:
157+ self.links += [found]
158
159- def reload(self):
160- links = self.links
161+ def feed(self, data):
162 self.links = []
163- return links
164+ super(MyHTMLParser, self).feed(data)
165+
166+
167+class HTMLLinkChecker():
168+ def __init__(self, html_files):
169+ self.local_link_results = []
170+ self.htmlparser = MyHTMLParser()
171+ self.links = {}
172+ for fn in html_files:
173+ html = codecs.open(fn, encoding='utf-8').read()
174+ self.links[fn] = self._read_links(html)
175+
176+ def _read_links(self, html):
177+ self.htmlparser.feed(html)
178+ return self.htmlparser.links
179+
180+ def check_local_links(self):
181+ self.local_link_results = []
182+ pwd = os.getcwd()
183+ for filename in self.links:
184+ for link in [a['href'] for a
185+ in self.links[filename]
186+ if link_is_local(a['href'])]:
187+ os.chdir(os.path.dirname(filename))
188+ full_link_fn = os.path.join(os.path.dirname(filename), link)
189+ rel_path = os.path.relpath(full_link_fn, filename)
190+ path = os.path.normpath(os.path.join(filename, rel_path))
191+ self.local_link_results += [os.path.exists(path)]
192+ os.chdir(pwd)
193+
194+ def check_external_links(self):
195+ self.external_link_results = []
196+ for filename in self.links:
197+ for link in [a for a
198+ in self.links[filename]
199+ if not link_is_local(a['href'])]:
200+ self.external_link_results += [('target' in link and
201+ link['target'] == '_blank')]
202
203
204 class HelpTestCase(TestCase):
205 def __init__(self, *args):
206 self.pwd = os.getcwd()
207- self.htmlparser = MyHTMLParser()
208+ self.build_runner = None
209 TestCase.__init__(self, *args)
210+ self.html_link_checker = None
211
212 def __del__(self):
213 os.chdir(self.pwd)
214
215+ def _require_build(self, build):
216+ if self.build_runner is None or \
217+ self.build_runner.build != build:
218+ self.build_runner = BuildRunner(build)
219+
220 def _test_local_links(self, build):
221- (ret, tempdir) = require_build(build)
222- self.assertEqual(ret, 0)
223- for dirpath, dirnames, filenames in os.walk(tempdir):
224- for fn in filenames:
225- full_fn = os.path.join(dirpath, fn)
226- os.chdir(dirpath)
227- if full_fn.endswith('.html'):
228- html = codecs.open(full_fn, encoding='utf-8').read()
229- self.htmlparser.feed(html)
230- links = self.htmlparser.reload()
231- for link in links:
232- rel_path = os.path.relpath(link, full_fn)
233- path = os.path.normpath(os.path.join(full_fn, rel_path))
234- self.assertTrue(os.path.exists(path))
235- clean_tempdir(tempdir)
236- return True
237-
238- def test_local_phone_links(self):
239- return self._test_local_links('html')
240-
241- def test_local_web_links(self):
242- return self._test_local_links('web')
243+ self._require_build(build)
244+ self.html_link_checker = HTMLLinkChecker(self.build_runner.html_files)
245+ self.html_link_checker.check_local_links()
246+
247+ def _test_external_links(self, build):
248+ self._require_build(build)
249+ self.html_link_checker = HTMLLinkChecker(self.build_runner.html_files)
250+ self.html_link_checker.check_external_links()
251+
252+ def test_simple_local_broken_link(self):
253+ dirname = tempfile.mkdtemp()
254+ filename = os.path.join(dirname, 'first-test.html')
255+ with open(filename, 'w') as f:
256+ f.write('<html><body>' +
257+ '<a href="nonexisting-test.html">broken test link</a>' +
258+ '</body><html>')
259+ self.html_link_checker = HTMLLinkChecker([filename])
260+ self.html_link_checker.check_local_links()
261+ self.assertNotIn(True, self.html_link_checker.local_link_results)
262+ clean_tempdir(dirname)
263+
264+ def test_simple_local_working_link(self):
265+ html = '<html><body>' + \
266+ '<a href="second-test.html">broken test link</a>' + \
267+ '</body><html>'
268+ dirname = tempfile.mkdtemp()
269+ filename1 = os.path.join(dirname, 'first-test.html')
270+ with open(filename1, 'w') as f:
271+ f.write(html)
272+ filename2 = os.path.join(dirname, 'second-test.html')
273+ with open(filename2, 'w') as f:
274+ f.write(html)
275+ self.html_link_checker = HTMLLinkChecker([filename1])
276+ self.html_link_checker.check_local_links()
277+ self.assertEqual([True], self.html_link_checker.local_link_results)
278+ clean_tempdir(dirname)
279+
280+ def test_non_existing_build(self):
281+ self._require_build('not-existing')
282+ self.assertNotEqual(self.build_runner.rc, 0)
283+
284+ def test_local_links_in_phone_build(self):
285+ self._test_local_links('app')
286+ self.assertEqual(self.build_runner.rc, 0)
287+ self.assertNotEqual(self.build_runner.html_files, [])
288+ self.assertNotIn(False, self.html_link_checker.local_link_results)
289+
290+ def test_external_links_in_phone_build(self):
291+ self._test_external_links('app')
292+ self.assertEqual(self.build_runner.rc, 0)
293+ self.assertNotEqual(self.build_runner.html_files, [])
294+ self.assertNotIn(False, self.html_link_checker.external_link_results)
295+
296+ def test_local_links_in_web_build(self):
297+ self._test_local_links('web')
298+ self.assertEqual(self.build_runner.rc, 0)
299+ self.assertNotEqual(self.build_runner.html_files, [])
300+ self.assertNotIn(False, self.html_link_checker.local_link_results)
301
302=== modified file 'internals/translations/build.py'
303--- internals/translations/build.py 2015-03-31 11:38:30 +0000
304+++ internals/translations/build.py 2015-05-13 13:46:52 +0000
305@@ -20,7 +20,7 @@
306 try:
307 import polib
308 except ImportError:
309- require('python3-polib')
310+ require('python3-polib pyton-polib')
311
312 from pelicanconf import (
313 DOCS_PATH,
314
315=== modified file 'internals/translations/utils.py'
316--- internals/translations/utils.py 2015-03-31 07:57:56 +0000
317+++ internals/translations/utils.py 2015-05-13 13:46:52 +0000
318@@ -1,6 +1,10 @@
319 import os
320 import sys
321 import tempfile
322+if sys.version_info.major == 2:
323+ import urlparse
324+else:
325+ import urllib.parse as urlparse
326
327
328 def require(package):
329@@ -11,7 +15,7 @@
330 try:
331 import magic
332 except:
333- require('python3-magic')
334+ require('python3-magic python-magic')
335
336 try:
337 import markdown
338@@ -37,6 +41,15 @@
339 ]
340
341
342+def link_is_anchor(link):
343+ return link.startswith('#')
344+
345+
346+def link_is_local(link):
347+ url = urlparse.urlparse(link)
348+ return url.netloc in [None, '', 'localhost']
349+
350+
351 def normalise_path(path):
352 return os.path.relpath(path, TOP_LEVEL_DIR)
353
354
355=== modified file 'static/themes/app/static/css/app.css'
356--- static/themes/app/static/css/app.css 2015-03-18 12:06:41 +0000
357+++ static/themes/app/static/css/app.css 2015-05-13 13:46:52 +0000
358@@ -47,13 +47,6 @@
359 display: none;
360 }
361
362-/* Disable links until they work
363- properly */
364-a {
365- pointer-events: none;
366- cursor: default;
367-}
368-
369 /* Disable this when the app container
370 properly supports tab scrolling.
371 We hardcode support for showing

Subscribers

People subscribed via source and target branches