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
=== modified file 'HACKING'
--- HACKING 2015-03-31 12:27:56 +0000
+++ HACKING 2015-05-13 13:46:52 +0000
@@ -86,7 +86,7 @@
8686
87 sudo apt install python-pelican po4a make bzrtools \87 sudo apt install python-pelican po4a make bzrtools \
88 ubuntu-html5-ui-toolkit python3-polib python3-magic \88 ubuntu-html5-ui-toolkit python3-polib python3-magic \
89 python3-markdown pep8 pyflakes89 python3-markdown pep8 pyflakes python-magic python-polib
9090
91This will install the necessary files, so you can build the app or web build91This will install the necessary files, so you can build the app or web build
92locally and check if your changes actually make sense and look and work well.92locally and check if your changes actually make sense and look and work well.
9393
=== modified file 'debian/control'
--- debian/control 2015-03-25 10:14:27 +0000
+++ debian/control 2015-05-13 13:46:52 +0000
@@ -8,6 +8,8 @@
8 po4a,8 po4a,
9 pyflakes,9 pyflakes,
10 python-pelican (>= 3.5.0~),10 python-pelican (>= 3.5.0~),
11 python-magic,
12 python-polib,
11 python3-magic,13 python3-magic,
12 python3-markdown,14 python3-markdown,
13 python3-polib,15 python3-polib,
1416
=== added file 'internals/local/external-links.py'
--- internals/local/external-links.py 1970-01-01 00:00:00 +0000
+++ internals/local/external-links.py 2015-05-13 13:46:52 +0000
@@ -0,0 +1,26 @@
1from __future__ import absolute_import
2from __future__ import unicode_literals
3from markdown import Extension
4from markdown.treeprocessors import Treeprocessor
5
6from translations.utils import link_is_local
7
8
9class ExternalLinksProcessor(Treeprocessor):
10 def run(self, doc):
11 for elem in doc.iter('a'):
12 if 'href' in elem.attrib and \
13 not link_is_local(elem.get('href')):
14 elem.set('target', '_blank')
15
16
17class ExternalLinksExtension(Extension):
18 def extendMarkdown(self, md, md_globals):
19 md.treeprocessors.add('external-links',
20 ExternalLinksProcessor(md.parser),
21 '_end')
22 md.registerExtension(self)
23
24
25def makeExtension(*args, **kwargs):
26 return ExternalLinksExtension(*args, **kwargs)
027
=== modified file 'internals/pelicanconf.py'
--- internals/pelicanconf.py 2015-03-31 08:18:07 +0000
+++ internals/pelicanconf.py 2015-05-13 13:46:52 +0000
@@ -54,7 +54,7 @@
54TAG_SAVE_AS = ''54TAG_SAVE_AS = ''
55THEME = TOP_LEVEL_DIR+'static/themes/app/'55THEME = TOP_LEVEL_DIR+'static/themes/app/'
5656
57MD_EXTENSIONS = ['local.q-and-a', 'attr_list', 'toc']57MD_EXTENSIONS = ['local.q-and-a', 'local.external-links', 'attr_list', 'toc']
5858
59META_TAGS = [59META_TAGS = [
60 '[TOC]',60 '[TOC]',
6161
=== modified file 'internals/tests/test_links.py'
--- internals/tests/test_links.py 2015-03-31 06:25:44 +0000
+++ internals/tests/test_links.py 2015-05-13 13:46:52 +0000
@@ -5,22 +5,12 @@
5import subprocess5import subprocess
6import tempfile6import tempfile
7from unittest import TestCase7from unittest import TestCase
8import urllib.parse8
99from translations.utils import (
10from translations.utils import use_top_level_dir10 link_is_anchor,
1111 link_is_local,
1212 use_top_level_dir,
13def require_build(build):13)
14 tempdir = tempfile.mkdtemp()
15 env = {}
16 if build == 'app':
17 env = {'OUTPUTDIR_APP': tempdir}
18 if build == 'web':
19 env = {'OUTPUTDIR_WEB': tempdir}
20 pwd = use_top_level_dir()
21 ret = subprocess.call(['make', '-es', build], env=env)
22 os.chdir(pwd)
23 return (ret, tempdir)
2414
2515
26def clean_tempdir(tempdir):16def clean_tempdir(tempdir):
@@ -28,53 +18,165 @@
28 shutil.rmtree(tempdir)18 shutil.rmtree(tempdir)
2919
3020
21class BuildRunner():
22 def __init__(self, build):
23 self.tempdir = tempfile.mkdtemp()
24 self.env = {}
25 self.build = build
26 self.html_files = []
27 self.rc = None
28 if self.build == 'app':
29 self.env = {'APP_DIR': self.tempdir}
30 if self.build == 'web':
31 self.env = {'OUTPUTDIR_WEB': self.tempdir}
32 self.pwd = use_top_level_dir()
33 self.run_build()
34 self.find_html_files()
35
36 def run_build(self):
37 self.rc = subprocess.call(['make', '-es', self.build], env=self.env)
38 os.chdir(self.pwd)
39
40 def find_html_files(self):
41 for dirpath, dirnames, filenames in os.walk(self.tempdir):
42 self.html_files.extend([os.path.join(dirpath, fn)
43 for fn in filenames
44 if fn.endswith('.html')])
45
46 def __del__(self):
47 os.chdir(self.pwd)
48 clean_tempdir(self.tempdir)
49
50
31class MyHTMLParser(HTMLParser):51class MyHTMLParser(HTMLParser):
32 links = []52 links = []
3353
34 def handle_starttag(self, tag, attrs):54 def handle_starttag(self, tag, attrs):
35 if tag == "a":55 if tag == 'a':
56 found = {}
36 for name, value in attrs:57 for name, value in attrs:
37 if name == "href":58 if name == 'href' and \
38 url = urllib.parse.urlparse(value)59 not link_is_anchor(value) and \
39 if not value.startswith('#') and \60 value != '/':
40 url.netloc in [None, '', 'localhost']:61 found['href'] = value
41 self.links += [value]62 if name == 'target':
63 found['target'] = value
64 if found != {}:
65 self.links += [found]
4266
43 def reload(self):67 def feed(self, data):
44 links = self.links
45 self.links = []68 self.links = []
46 return links69 super(MyHTMLParser, self).feed(data)
70
71
72class HTMLLinkChecker():
73 def __init__(self, html_files):
74 self.local_link_results = []
75 self.htmlparser = MyHTMLParser()
76 self.links = {}
77 for fn in html_files:
78 html = codecs.open(fn, encoding='utf-8').read()
79 self.links[fn] = self._read_links(html)
80
81 def _read_links(self, html):
82 self.htmlparser.feed(html)
83 return self.htmlparser.links
84
85 def check_local_links(self):
86 self.local_link_results = []
87 pwd = os.getcwd()
88 for filename in self.links:
89 for link in [a['href'] for a
90 in self.links[filename]
91 if link_is_local(a['href'])]:
92 os.chdir(os.path.dirname(filename))
93 full_link_fn = os.path.join(os.path.dirname(filename), link)
94 rel_path = os.path.relpath(full_link_fn, filename)
95 path = os.path.normpath(os.path.join(filename, rel_path))
96 self.local_link_results += [os.path.exists(path)]
97 os.chdir(pwd)
98
99 def check_external_links(self):
100 self.external_link_results = []
101 for filename in self.links:
102 for link in [a for a
103 in self.links[filename]
104 if not link_is_local(a['href'])]:
105 self.external_link_results += [('target' in link and
106 link['target'] == '_blank')]
47107
48108
49class HelpTestCase(TestCase):109class HelpTestCase(TestCase):
50 def __init__(self, *args):110 def __init__(self, *args):
51 self.pwd = os.getcwd()111 self.pwd = os.getcwd()
52 self.htmlparser = MyHTMLParser()112 self.build_runner = None
53 TestCase.__init__(self, *args)113 TestCase.__init__(self, *args)
114 self.html_link_checker = None
54115
55 def __del__(self):116 def __del__(self):
56 os.chdir(self.pwd)117 os.chdir(self.pwd)
57118
119 def _require_build(self, build):
120 if self.build_runner is None or \
121 self.build_runner.build != build:
122 self.build_runner = BuildRunner(build)
123
58 def _test_local_links(self, build):124 def _test_local_links(self, build):
59 (ret, tempdir) = require_build(build)125 self._require_build(build)
60 self.assertEqual(ret, 0)126 self.html_link_checker = HTMLLinkChecker(self.build_runner.html_files)
61 for dirpath, dirnames, filenames in os.walk(tempdir):127 self.html_link_checker.check_local_links()
62 for fn in filenames:128
63 full_fn = os.path.join(dirpath, fn)129 def _test_external_links(self, build):
64 os.chdir(dirpath)130 self._require_build(build)
65 if full_fn.endswith('.html'):131 self.html_link_checker = HTMLLinkChecker(self.build_runner.html_files)
66 html = codecs.open(full_fn, encoding='utf-8').read()132 self.html_link_checker.check_external_links()
67 self.htmlparser.feed(html)133
68 links = self.htmlparser.reload()134 def test_simple_local_broken_link(self):
69 for link in links:135 dirname = tempfile.mkdtemp()
70 rel_path = os.path.relpath(link, full_fn)136 filename = os.path.join(dirname, 'first-test.html')
71 path = os.path.normpath(os.path.join(full_fn, rel_path))137 with open(filename, 'w') as f:
72 self.assertTrue(os.path.exists(path))138 f.write('<html><body>' +
73 clean_tempdir(tempdir)139 '<a href="nonexisting-test.html">broken test link</a>' +
74 return True140 '</body><html>')
75141 self.html_link_checker = HTMLLinkChecker([filename])
76 def test_local_phone_links(self):142 self.html_link_checker.check_local_links()
77 return self._test_local_links('html')143 self.assertNotIn(True, self.html_link_checker.local_link_results)
78144 clean_tempdir(dirname)
79 def test_local_web_links(self):145
80 return self._test_local_links('web')146 def test_simple_local_working_link(self):
147 html = '<html><body>' + \
148 '<a href="second-test.html">broken test link</a>' + \
149 '</body><html>'
150 dirname = tempfile.mkdtemp()
151 filename1 = os.path.join(dirname, 'first-test.html')
152 with open(filename1, 'w') as f:
153 f.write(html)
154 filename2 = os.path.join(dirname, 'second-test.html')
155 with open(filename2, 'w') as f:
156 f.write(html)
157 self.html_link_checker = HTMLLinkChecker([filename1])
158 self.html_link_checker.check_local_links()
159 self.assertEqual([True], self.html_link_checker.local_link_results)
160 clean_tempdir(dirname)
161
162 def test_non_existing_build(self):
163 self._require_build('not-existing')
164 self.assertNotEqual(self.build_runner.rc, 0)
165
166 def test_local_links_in_phone_build(self):
167 self._test_local_links('app')
168 self.assertEqual(self.build_runner.rc, 0)
169 self.assertNotEqual(self.build_runner.html_files, [])
170 self.assertNotIn(False, self.html_link_checker.local_link_results)
171
172 def test_external_links_in_phone_build(self):
173 self._test_external_links('app')
174 self.assertEqual(self.build_runner.rc, 0)
175 self.assertNotEqual(self.build_runner.html_files, [])
176 self.assertNotIn(False, self.html_link_checker.external_link_results)
177
178 def test_local_links_in_web_build(self):
179 self._test_local_links('web')
180 self.assertEqual(self.build_runner.rc, 0)
181 self.assertNotEqual(self.build_runner.html_files, [])
182 self.assertNotIn(False, self.html_link_checker.local_link_results)
81183
=== modified file 'internals/translations/build.py'
--- internals/translations/build.py 2015-03-31 11:38:30 +0000
+++ internals/translations/build.py 2015-05-13 13:46:52 +0000
@@ -20,7 +20,7 @@
20try:20try:
21 import polib21 import polib
22except ImportError:22except ImportError:
23 require('python3-polib')23 require('python3-polib pyton-polib')
2424
25from pelicanconf import (25from pelicanconf import (
26 DOCS_PATH,26 DOCS_PATH,
2727
=== modified file 'internals/translations/utils.py'
--- internals/translations/utils.py 2015-03-31 07:57:56 +0000
+++ internals/translations/utils.py 2015-05-13 13:46:52 +0000
@@ -1,6 +1,10 @@
1import os1import os
2import sys2import sys
3import tempfile3import tempfile
4if sys.version_info.major == 2:
5 import urlparse
6else:
7 import urllib.parse as urlparse
48
59
6def require(package):10def require(package):
@@ -11,7 +15,7 @@
11try:15try:
12 import magic16 import magic
13except:17except:
14 require('python3-magic')18 require('python3-magic python-magic')
1519
16try:20try:
17 import markdown21 import markdown
@@ -37,6 +41,15 @@
37]41]
3842
3943
44def link_is_anchor(link):
45 return link.startswith('#')
46
47
48def link_is_local(link):
49 url = urlparse.urlparse(link)
50 return url.netloc in [None, '', 'localhost']
51
52
40def normalise_path(path):53def normalise_path(path):
41 return os.path.relpath(path, TOP_LEVEL_DIR)54 return os.path.relpath(path, TOP_LEVEL_DIR)
4255
4356
=== modified file 'static/themes/app/static/css/app.css'
--- static/themes/app/static/css/app.css 2015-03-18 12:06:41 +0000
+++ static/themes/app/static/css/app.css 2015-05-13 13:46:52 +0000
@@ -47,13 +47,6 @@
47 display: none;47 display: none;
48}48}
4949
50/* Disable links until they work
51 properly */
52a {
53 pointer-events: none;
54 cursor: default;
55}
56
57/* Disable this when the app container50/* Disable this when the app container
58 properly supports tab scrolling.51 properly supports tab scrolling.
59 We hardcode support for showing52 We hardcode support for showing

Subscribers

People subscribed via source and target branches