##// END OF EJS Templates
don't do anything if add_anchor fails
MinRK -
Show More
@@ -1,204 +1,212 b''
1 # coding: utf-8
1 # coding: utf-8
2 """String filters.
2 """String filters.
3
3
4 Contains a collection of useful string manipulation filters for use in Jinja
4 Contains a collection of useful string manipulation filters for use in Jinja
5 templates.
5 templates.
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2013, the IPython Development Team.
8 # Copyright (c) 2013, the IPython Development Team.
9 #
9 #
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11 #
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 import re
20 import re
21 import textwrap
21 import textwrap
22 try:
22 try:
23 from urllib.parse import quote # Py 3
23 from urllib.parse import quote # Py 3
24 except ImportError:
24 except ImportError:
25 from urllib2 import quote # Py 2
25 from urllib2 import quote # Py 2
26 from xml.etree import ElementTree
26 from xml.etree import ElementTree
27
27
28 from IPython.core.interactiveshell import InteractiveShell
28 from IPython.core.interactiveshell import InteractiveShell
29 from IPython.utils import py3compat
29 from IPython.utils import py3compat
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Functions
32 # Functions
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 __all__ = [
35 __all__ = [
36 'wrap_text',
36 'wrap_text',
37 'html2text',
37 'html2text',
38 'add_anchor',
38 'add_anchor',
39 'strip_dollars',
39 'strip_dollars',
40 'strip_files_prefix',
40 'strip_files_prefix',
41 'comment_lines',
41 'comment_lines',
42 'get_lines',
42 'get_lines',
43 'ipython2python',
43 'ipython2python',
44 'posix_path',
44 'posix_path',
45 'path2url',
45 'path2url',
46 'add_prompts'
46 'add_prompts'
47 ]
47 ]
48
48
49
49
50 def wrap_text(text, width=100):
50 def wrap_text(text, width=100):
51 """
51 """
52 Intelligently wrap text.
52 Intelligently wrap text.
53 Wrap text without breaking words if possible.
53 Wrap text without breaking words if possible.
54
54
55 Parameters
55 Parameters
56 ----------
56 ----------
57 text : str
57 text : str
58 Text to wrap.
58 Text to wrap.
59 width : int, optional
59 width : int, optional
60 Number of characters to wrap to, default 100.
60 Number of characters to wrap to, default 100.
61 """
61 """
62
62
63 split_text = text.split('\n')
63 split_text = text.split('\n')
64 wrp = map(lambda x:textwrap.wrap(x,width), split_text)
64 wrp = map(lambda x:textwrap.wrap(x,width), split_text)
65 wrpd = map('\n'.join, wrp)
65 wrpd = map('\n'.join, wrp)
66 return '\n'.join(wrpd)
66 return '\n'.join(wrpd)
67
67
68
68
69 def html2text(element):
69 def html2text(element):
70 """extract inner text from html
70 """extract inner text from html
71
71
72 Analog of jQuery's $(element).text()
72 Analog of jQuery's $(element).text()
73 """
73 """
74 if isinstance(element, py3compat.string_types):
74 if isinstance(element, py3compat.string_types):
75 element = ElementTree.fromstring(element)
75 try:
76 element = ElementTree.fromstring(element)
77 except Exception:
78 # failed to parse, just return it unmodified
79 return element
76
80
77 text = element.text or ""
81 text = element.text or ""
78 for child in element:
82 for child in element:
79 text += html2text(child)
83 text += html2text(child)
80 text += (element.tail or "")
84 text += (element.tail or "")
81 return text
85 return text
82
86
83
87
84 def add_anchor(html):
88 def add_anchor(html):
85 """Add an anchor-link to an html header tag
89 """Add an anchor-link to an html header tag
86
90
87 For use in heading cells
91 For use in heading cells
88 """
92 """
89 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html, encoding='utf-8'))
93 try:
94 h = ElementTree.fromstring(py3compat.cast_bytes_py2(html, encoding='utf-8'))
95 except Exception:
96 # failed to parse, just return it unmodified
97 return html
90 link = html2text(h).replace(' ', '-')
98 link = html2text(h).replace(' ', '-')
91 h.set('id', link)
99 h.set('id', link)
92 a = ElementTree.Element("a", {"class" : "anchor-link", "href" : "#" + link})
100 a = ElementTree.Element("a", {"class" : "anchor-link", "href" : "#" + link})
93 a.text = u'ΒΆ'
101 a.text = u'ΒΆ'
94 h.append(a)
102 h.append(a)
95
103
96 # Known issue of Python3.x, ElementTree.tostring() returns a byte string
104 # Known issue of Python3.x, ElementTree.tostring() returns a byte string
97 # instead of a text string. See issue http://bugs.python.org/issue10942
105 # instead of a text string. See issue http://bugs.python.org/issue10942
98 # Workaround is to make sure the bytes are casted to a string.
106 # Workaround is to make sure the bytes are casted to a string.
99 return py3compat.decode(ElementTree.tostring(h), 'utf-8')
107 return py3compat.decode(ElementTree.tostring(h), 'utf-8')
100
108
101
109
102 def add_prompts(code, first='>>> ', cont='... '):
110 def add_prompts(code, first='>>> ', cont='... '):
103 """Add prompts to code snippets"""
111 """Add prompts to code snippets"""
104 new_code = []
112 new_code = []
105 code_list = code.split('\n')
113 code_list = code.split('\n')
106 new_code.append(first + code_list[0])
114 new_code.append(first + code_list[0])
107 for line in code_list[1:]:
115 for line in code_list[1:]:
108 new_code.append(cont + line)
116 new_code.append(cont + line)
109 return '\n'.join(new_code)
117 return '\n'.join(new_code)
110
118
111
119
112 def strip_dollars(text):
120 def strip_dollars(text):
113 """
121 """
114 Remove all dollar symbols from text
122 Remove all dollar symbols from text
115
123
116 Parameters
124 Parameters
117 ----------
125 ----------
118 text : str
126 text : str
119 Text to remove dollars from
127 Text to remove dollars from
120 """
128 """
121
129
122 return text.strip('$')
130 return text.strip('$')
123
131
124
132
125 files_url_pattern = re.compile(r'(src|href)\=([\'"]?)files/')
133 files_url_pattern = re.compile(r'(src|href)\=([\'"]?)files/')
126
134
127 def strip_files_prefix(text):
135 def strip_files_prefix(text):
128 """
136 """
129 Fix all fake URLs that start with `files/`,
137 Fix all fake URLs that start with `files/`,
130 stripping out the `files/` prefix.
138 stripping out the `files/` prefix.
131
139
132 Parameters
140 Parameters
133 ----------
141 ----------
134 text : str
142 text : str
135 Text in which to replace 'src="files/real...' with 'src="real...'
143 Text in which to replace 'src="files/real...' with 'src="real...'
136 """
144 """
137 return files_url_pattern.sub(r"\1=\2", text)
145 return files_url_pattern.sub(r"\1=\2", text)
138
146
139
147
140 def comment_lines(text, prefix='# '):
148 def comment_lines(text, prefix='# '):
141 """
149 """
142 Build a Python comment line from input text.
150 Build a Python comment line from input text.
143
151
144 Parameters
152 Parameters
145 ----------
153 ----------
146 text : str
154 text : str
147 Text to comment out.
155 Text to comment out.
148 prefix : str
156 prefix : str
149 Character to append to the start of each line.
157 Character to append to the start of each line.
150 """
158 """
151
159
152 #Replace line breaks with line breaks and comment symbols.
160 #Replace line breaks with line breaks and comment symbols.
153 #Also add a comment symbol at the beginning to comment out
161 #Also add a comment symbol at the beginning to comment out
154 #the first line.
162 #the first line.
155 return prefix + ('\n'+prefix).join(text.split('\n'))
163 return prefix + ('\n'+prefix).join(text.split('\n'))
156
164
157
165
158 def get_lines(text, start=None,end=None):
166 def get_lines(text, start=None,end=None):
159 """
167 """
160 Split the input text into separate lines and then return the
168 Split the input text into separate lines and then return the
161 lines that the caller is interested in.
169 lines that the caller is interested in.
162
170
163 Parameters
171 Parameters
164 ----------
172 ----------
165 text : str
173 text : str
166 Text to parse lines from.
174 Text to parse lines from.
167 start : int, optional
175 start : int, optional
168 First line to grab from.
176 First line to grab from.
169 end : int, optional
177 end : int, optional
170 Last line to grab from.
178 Last line to grab from.
171 """
179 """
172
180
173 # Split the input into lines.
181 # Split the input into lines.
174 lines = text.split("\n")
182 lines = text.split("\n")
175
183
176 # Return the right lines.
184 # Return the right lines.
177 return "\n".join(lines[start:end]) #re-join
185 return "\n".join(lines[start:end]) #re-join
178
186
179 def ipython2python(code):
187 def ipython2python(code):
180 """Transform IPython syntax to pure Python syntax
188 """Transform IPython syntax to pure Python syntax
181
189
182 Parameters
190 Parameters
183 ----------
191 ----------
184
192
185 code : str
193 code : str
186 IPython code, to be transformed to pure Python
194 IPython code, to be transformed to pure Python
187 """
195 """
188 shell = InteractiveShell.instance()
196 shell = InteractiveShell.instance()
189 return shell.input_transformer_manager.transform_cell(code)
197 return shell.input_transformer_manager.transform_cell(code)
190
198
191 def posix_path(path):
199 def posix_path(path):
192 """Turn a path into posix-style path/to/etc
200 """Turn a path into posix-style path/to/etc
193
201
194 Mainly for use in latex on Windows,
202 Mainly for use in latex on Windows,
195 where native Windows paths are not allowed.
203 where native Windows paths are not allowed.
196 """
204 """
197 if os.path.sep != '/':
205 if os.path.sep != '/':
198 return path.replace(os.path.sep, '/')
206 return path.replace(os.path.sep, '/')
199 return path
207 return path
200
208
201 def path2url(path):
209 def path2url(path):
202 """Turn a file path into a URL"""
210 """Turn a file path into a URL"""
203 parts = path.split(os.path.sep)
211 parts = path.split(os.path.sep)
204 return '/'.join(quote(part) for part in parts)
212 return '/'.join(quote(part) for part in parts)
@@ -1,135 +1,140 b''
1 """
1 """
2 Module with tests for Strings
2 Module with tests for Strings
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 import os
16 import os
17
17
18 from ...tests.base import TestsBase
18 from ...tests.base import TestsBase
19 from ..strings import (wrap_text, html2text, add_anchor, strip_dollars,
19 from ..strings import (wrap_text, html2text, add_anchor, strip_dollars,
20 strip_files_prefix, get_lines, comment_lines, ipython2python, posix_path,
20 strip_files_prefix, get_lines, comment_lines, ipython2python, posix_path,
21 add_prompts
21 add_prompts
22 )
22 )
23
23
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Class
26 # Class
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 class TestStrings(TestsBase):
29 class TestStrings(TestsBase):
30
30
31 def test_wrap_text(self):
31 def test_wrap_text(self):
32 """wrap_text test"""
32 """wrap_text test"""
33 test_text = """
33 test_text = """
34 Tush! never tell me; I take it much unkindly
34 Tush! never tell me; I take it much unkindly
35 That thou, Iago, who hast had my purse
35 That thou, Iago, who hast had my purse
36 As if the strings were thine, shouldst know of this.
36 As if the strings were thine, shouldst know of this.
37 """
37 """
38 for length in [30,5,1]:
38 for length in [30,5,1]:
39 self._confirm_wrap_text(test_text, length)
39 self._confirm_wrap_text(test_text, length)
40
40
41
41
42 def _confirm_wrap_text(self, text, length):
42 def _confirm_wrap_text(self, text, length):
43 for line in wrap_text(text, length).split('\n'):
43 for line in wrap_text(text, length).split('\n'):
44 assert len(line) <= length
44 assert len(line) <= length
45
45
46
46
47 def test_html2text(self):
47 def test_html2text(self):
48 """html2text test"""
48 """html2text test"""
49 #TODO: More tests
49 #TODO: More tests
50 self.assertEqual(html2text('<name>joe</name>'), 'joe')
50 self.assertEqual(html2text('<name>joe</name>'), 'joe')
51
51
52
52
53 def test_add_anchor(self):
53 def test_add_anchor(self):
54 """add_anchor test"""
54 """add_anchor test"""
55 #TODO: More tests
55 #TODO: More tests
56 results = add_anchor('<b>Hello World!</b>')
56 results = add_anchor('<b>Hello World!</b>')
57 assert 'Hello World!' in results
57 assert 'Hello World!' in results
58 assert 'id="' in results
58 assert 'id="' in results
59 assert 'class="anchor-link"' in results
59 assert 'class="anchor-link"' in results
60 assert '<b' in results
60 assert '<b' in results
61 assert '</b>' in results
61 assert '</b>' in results
62
62
63
63 def test_add_anchor_fail(self):
64 """add_anchor does nothing when it fails"""
65 html = '<h1>Hello <br>World!</h1>'
66 results = add_anchor(html)
67 self.assertEqual(html, results)
68
64 def test_strip_dollars(self):
69 def test_strip_dollars(self):
65 """strip_dollars test"""
70 """strip_dollars test"""
66 tests = [
71 tests = [
67 ('', ''),
72 ('', ''),
68 ('$$', ''),
73 ('$$', ''),
69 ('$H$', 'H'),
74 ('$H$', 'H'),
70 ('$He', 'He'),
75 ('$He', 'He'),
71 ('H$el', 'H$el'),
76 ('H$el', 'H$el'),
72 ('Hell$', 'Hell'),
77 ('Hell$', 'Hell'),
73 ('Hello', 'Hello'),
78 ('Hello', 'Hello'),
74 ('W$o$rld', 'W$o$rld')]
79 ('W$o$rld', 'W$o$rld')]
75 for test in tests:
80 for test in tests:
76 self._try_strip_dollars(test[0], test[1])
81 self._try_strip_dollars(test[0], test[1])
77
82
78
83
79 def _try_strip_dollars(self, test, result):
84 def _try_strip_dollars(self, test, result):
80 self.assertEqual(strip_dollars(test), result)
85 self.assertEqual(strip_dollars(test), result)
81
86
82
87
83 def test_strip_files_prefix(self):
88 def test_strip_files_prefix(self):
84 """strip_files_prefix test"""
89 """strip_files_prefix test"""
85 tests = [
90 tests = [
86 ('', ''),
91 ('', ''),
87 ('/files', '/files'),
92 ('/files', '/files'),
88 ('test="/files"', 'test="/files"'),
93 ('test="/files"', 'test="/files"'),
89 ('My files are in `files/`', 'My files are in `files/`'),
94 ('My files are in `files/`', 'My files are in `files/`'),
90 ('<a href="files/test.html">files/test.html</a>', '<a href="test.html">files/test.html</a>')]
95 ('<a href="files/test.html">files/test.html</a>', '<a href="test.html">files/test.html</a>')]
91 for test in tests:
96 for test in tests:
92 self._try_files_prefix(test[0], test[1])
97 self._try_files_prefix(test[0], test[1])
93
98
94
99
95 def _try_files_prefix(self, test, result):
100 def _try_files_prefix(self, test, result):
96 self.assertEqual(strip_files_prefix(test), result)
101 self.assertEqual(strip_files_prefix(test), result)
97
102
98
103
99 def test_comment_lines(self):
104 def test_comment_lines(self):
100 """comment_lines test"""
105 """comment_lines test"""
101 for line in comment_lines('hello\nworld\n!').split('\n'):
106 for line in comment_lines('hello\nworld\n!').split('\n'):
102 assert line.startswith('# ')
107 assert line.startswith('# ')
103 for line in comment_lines('hello\nworld\n!', 'beep').split('\n'):
108 for line in comment_lines('hello\nworld\n!', 'beep').split('\n'):
104 assert line.startswith('beep')
109 assert line.startswith('beep')
105
110
106
111
107 def test_get_lines(self):
112 def test_get_lines(self):
108 """get_lines test"""
113 """get_lines test"""
109 text = "hello\nworld\n!"
114 text = "hello\nworld\n!"
110 self.assertEqual(get_lines(text, start=1), "world\n!")
115 self.assertEqual(get_lines(text, start=1), "world\n!")
111 self.assertEqual(get_lines(text, end=2), "hello\nworld")
116 self.assertEqual(get_lines(text, end=2), "hello\nworld")
112 self.assertEqual(get_lines(text, start=2, end=5), "!")
117 self.assertEqual(get_lines(text, start=2, end=5), "!")
113 self.assertEqual(get_lines(text, start=-2), "world\n!")
118 self.assertEqual(get_lines(text, start=-2), "world\n!")
114
119
115
120
116 def test_ipython2python(self):
121 def test_ipython2python(self):
117 """ipython2python test"""
122 """ipython2python test"""
118 #TODO: More tests
123 #TODO: More tests
119 results = ipython2python(u'%%pylab\nprint("Hello-World")').replace("u'", "'")
124 results = ipython2python(u'%%pylab\nprint("Hello-World")').replace("u'", "'")
120 self.fuzzy_compare(results, u"get_ipython().run_cell_magic('pylab', '', 'print(\"Hello-World\")')",
125 self.fuzzy_compare(results, u"get_ipython().run_cell_magic('pylab', '', 'print(\"Hello-World\")')",
121 ignore_spaces=True, ignore_newlines=True)
126 ignore_spaces=True, ignore_newlines=True)
122
127
123 def test_posix_path(self):
128 def test_posix_path(self):
124 """posix_path test"""
129 """posix_path test"""
125 path_list = ['foo', 'bar']
130 path_list = ['foo', 'bar']
126 expected = '/'.join(path_list)
131 expected = '/'.join(path_list)
127 native = os.path.join(*path_list)
132 native = os.path.join(*path_list)
128 filtered = posix_path(native)
133 filtered = posix_path(native)
129 self.assertEqual(filtered, expected)
134 self.assertEqual(filtered, expected)
130
135
131 def test_add_prompts(self):
136 def test_add_prompts(self):
132 """add_prompts test"""
137 """add_prompts test"""
133 text1 = """for i in range(10):\n i += 1\n print i"""
138 text1 = """for i in range(10):\n i += 1\n print i"""
134 text2 = """>>> for i in range(10):\n... i += 1\n... print i"""
139 text2 = """>>> for i in range(10):\n... i += 1\n... print i"""
135 self.assertEqual(text2, add_prompts(text1))
140 self.assertEqual(text2, add_prompts(text1))
General Comments 0
You need to be logged in to leave comments. Login now