Merge lp:~jamesh/unity-scope-yelp/preview into lp:unity-scope-yelp

Proposed by James Henstridge
Status: Merged
Approved by: James Henstridge
Approved revision: 29
Merged at revision: 28
Proposed branch: lp:~jamesh/unity-scope-yelp/preview
Merge into: lp:unity-scope-yelp
Diff against target: 262 lines (+122/-70)
2 files modified
src/unity_yelp_daemon.py (+93/-59)
tests/test_yelp.py (+29/-11)
To merge this branch: bzr merge lp:~jamesh/unity-scope-yelp/preview
Reviewer Review Type Date Requested Status
David Callé Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+163470@code.launchpad.net

Commit message

Add a simple preview to the scope, and correctly discover documentation in the fallback "C" directory when there are localised manuals.

Description of the change

Add a preview to the Yelp scope.

I also updated the code that discovers installed documentation to not completely ignore the "C" fallback directory if there is any localised documentation (it now picks up any manuals from there that haven't been localised).

I also switched to using cElementTree for the parsing to speed things up a bit.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
David Callé (davidc3) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/unity_yelp_daemon.py'
2--- src/unity_yelp_daemon.py 2013-04-02 11:09:28 +0000
3+++ src/unity_yelp_daemon.py 2013-05-13 07:19:26 +0000
4@@ -14,12 +14,14 @@
5 # You should have received a copy of the GNU General Public License along
6 # with this program. If not, see <http://www.gnu.org/licenses/>.
7
8-from gi.repository import GLib, Gio
9-from gi.repository import Unity
10 import gettext
11+import html
12 import locale
13 import os
14-from xml.dom import minidom
15+from xml.etree import cElementTree as ET
16+
17+from gi.repository import GLib, Gio
18+from gi.repository import Unity
19
20 APP_NAME = 'unity-scope-yelp'
21 LOCAL_PATH = '/usr/share/locale/'
22@@ -55,51 +57,71 @@
23 EXTRA_METADATA = []
24
25
26+def _get_manuals_in_dir(dir, manuals):
27+ """Yield the manuals found in the given directory, omitting those
28+ that have already been discovered."""
29+ if not os.path.isdir(dir):
30+ return
31+ for manual in os.listdir(dir):
32+ if manual in manuals:
33+ continue
34+ manualdir = os.path.join(dir, manual)
35+ if os.path.isdir(manualdir):
36+ manuals.add(manual)
37+ yield manualdir
38+
39+
40+def get_manuals(helpdir):
41+ """Get the manuals found in the given directory according to the
42+ user's locale."""
43+ language, encoding = locale.getlocale()
44+ manuals = set()
45+ if language is not None:
46+ languagedir = os.path.join(helpdir, language)
47+ yield from _get_manuals_in_dir(languagedir, manuals)
48+ # If the locale includes a country, look for manuals
49+ if language[:2] != language:
50+ languagedir = os.path.join(helpdir, language[:2])
51+ yield from _get_manuals_in_dir(languagedir, manuals)
52+ # Now return untranslated versions of remaining manuals.
53+ languagedir = os.path.join(helpdir, 'C')
54+ yield from _get_manuals_in_dir(languagedir, manuals)
55+
56+
57 def get_yelp_documents():
58 '''
59 Parses local help files for <desc> and 1st <title> tags and associates
60 them with the page's uri in self.data as: {uri, page description, page title}
61 If a help file has no <desc> or <title> tag, it is excluded.
62 '''
63- language, encoding = locale.getlocale()
64- if language is None:
65- language = 'C'
66-
67 data = []
68- help_location = '%s%s' % (HELP_DIR, language)
69- if not os.path.exists(help_location):
70- help_location = '%s%s' % (HELP_DIR, language[:2])
71- if not os.path.exists(help_location):
72- help_location = '%s%s' % (HELP_DIR, "C")
73- for dirname in os.listdir(help_location):
74- directory = help_location + "/" + dirname
75- for filename in os.listdir(directory):
76- filename = directory + "/" + filename
77- if not os.path.isdir(filename):
78- if os.path.isfile(filename):
79- xmldoc = minidom.parse(filename)
80- try:
81- pagenode = xmldoc.getElementsByTagName('page')[0]
82- if pagenode.attributes["type"].value == "topic":
83- desc = ""
84- nodes = xmldoc.getElementsByTagName('desc').item(0).childNodes
85- for node in nodes:
86- try:
87- desc += str(node.wholeText)
88- except:
89- desc += str(node.childNodes[0].wholeText)
90- desc = desc.strip(' \t\n\r')
91- title = xmldoc.getElementsByTagName('title').item(0).childNodes[0].data
92- if desc == "":
93- desc = title
94- record = []
95- record.append(filename)
96- record.append(desc)
97- record.append(title)
98- record.append(dirname)
99- data.append(record)
100- except:
101- pass
102+ namespaces = {'m': 'http://projectmallard.org/1.0/'}
103+ for manualdir in get_manuals(HELP_DIR):
104+ for filename in os.listdir(manualdir):
105+ filename = os.path.join(manualdir, filename)
106+ if not (filename.endswith('page') and os.path.isfile(filename)):
107+ continue
108+ try:
109+ tree = ET.parse(filename)
110+ except ET.ParseError:
111+ # Not an XML file
112+ continue
113+ if (tree.getroot().tag != '{http://projectmallard.org/1.0/}page' or
114+ tree.getroot().get('type') != 'topic'):
115+ # Not a Mallard documentation file.
116+ continue
117+ title = desc = ""
118+ node = tree.find('m:title', namespaces)
119+ if node is not None:
120+ title = node.text
121+ node = tree.find('m:info/m:desc', namespaces)
122+ if node is not None:
123+ desc = ''.join(node.itertext())
124+ desc = desc.strip(' \t\n\r')
125+ if desc == "":
126+ desc = title
127+
128+ data.append((filename, title, desc, os.path.basename(manualdir)))
129 return data
130
131
132@@ -116,34 +138,40 @@
133 YELP_CACHE = get_yelp_documents()
134 help_data = YELP_CACHE
135
136- for data in help_data:
137+ search = search.lower()
138+
139+ for (filename, title, desc, manual) in help_data:
140 if len(results) >= MAX_RESULTS:
141- return results
142+ break
143 try:
144- if data[3] == "ubuntu-help":
145+ if manual == "ubuntu-help":
146 icon_hint = Gio.ThemedIcon.new("distributor-logo").to_string()
147 else:
148- icon_hint = Gio.ThemedIcon.new(data[3]).to_string()
149+ icon_hint = Gio.ThemedIcon.new(manual).to_string()
150 except:
151 icon_hint = Gio.ThemedIcon.new("help").to_string()
152
153- if search.lower() in data[1].lower():
154- results.append({'uri': data[0],
155- 'icon': icon_hint,
156- 'title': data[1]})
157- elif search.lower() in data[2].lower():
158- results.append({'uri': data[0],
159- 'icon': icon_hint,
160- 'title': data[2]})
161- elif search.lower() in data[3].lower():
162- results.append({'uri': data[0],
163- 'icon': icon_hint,
164- 'title': data[1]})
165- else:
166- continue
167+ if (search in title.lower() or
168+ search in desc.lower() or
169+ search in manual.lower()):
170+ results.append({'uri': filename,
171+ 'icon': icon_hint,
172+ 'title': title,
173+ 'comment': desc})
174 return results
175
176
177+class Previewer(Unity.ResultPreviewer):
178+
179+ def do_run(self):
180+ image = Gio.ThemedIcon.new(self.result.icon_hint)
181+ preview = Unity.GenericPreview.new(
182+ self.result.title, html.escape(self.result.comment), image)
183+ action = Unity.PreviewAction.new("open", _("Open"), None)
184+ preview.add_action(action)
185+ return preview
186+
187+
188 # Classes below this point establish communication
189 # with Unity, you probably shouldn't modify them.
190
191@@ -245,6 +273,12 @@
192 se = MySearch(search_context)
193 return se
194
195+ def do_create_previewer(self, result, metadata):
196+ rp = Previewer()
197+ rp.set_scope_result(result)
198+ rp.set_search_metadata(metadata)
199+ return rp
200+
201 def do_activate(self, result, metadata, id):
202 parameters = [YELP_EXECUTABLE, result.uri]
203 GLib.spawn_async(parameters)
204
205=== renamed file 'tests/data/C/sample_help/mock_yelp_file' => 'tests/data/C/sample_help/mock_yelp_file.page'
206=== modified file 'tests/test_yelp.py'
207--- tests/test_yelp.py 2013-04-02 11:09:28 +0000
208+++ tests/test_yelp.py 2013-05-13 07:19:26 +0000
209@@ -44,24 +44,42 @@
210
211 def test_questions_search(self):
212 self.scope_module.HELP_DIR = 'tests/data/'
213- expected_results = ["tests/data/C/sample_help/mock_yelp_file",
214- "A visual introduction to the Unity desktop."]
215+ expected_results = ["tests/data/C/sample_help/mock_yelp_file.page",
216+ "Welcome to Ubuntu"]
217 results = []
218- for s in ['unity']:
219- result_set = self.perform_query(s)
220- results.append(result_set.results[0]['uri'])
221- results.append(result_set.results[0]['title'])
222- self.assertEqual(results, expected_results)
223+ result_set = self.perform_query('unity')
224+ self.assertEqual(len(result_set.results), 1)
225+ result = result_set.results[0]
226+ self.assertEqual(
227+ result['uri'], "tests/data/C/sample_help/mock_yelp_file.page")
228+ self.assertEqual(result['title'], "Welcome to Ubuntu")
229+ self.assertEqual(
230+ result['comment'], "A visual introduction to the Unity desktop.")
231+ self.assertEqual(result['icon'], 'sample_help')
232
233 def test_questions_failing_search(self):
234 self.scope_module.HELP_DIR = 'tests/data/'
235- for s in ['upnriitnyt']:
236- result_set = self.perform_query(s)
237- self.assertEqual(len(result_set.results), 0)
238+ result_set = self.perform_query('upnriitnyt')
239+ self.assertEqual(len(result_set.results), 0)
240+
241+ def test_preview(self):
242+ result = Unity.ScopeResult()
243+ result.uri = "tests/data/C/sample_help/mock_yelp_file.page"
244+ result.title = "Welcome to Ubuntu"
245+ result.comment = "A visual introduction to the Unity desktop."
246+ result.icon_hint = 'sample_help'
247+
248+ previewer = self.scope.create_previewer(result, Unity.SearchMetadata())
249+ preview = previewer.run()
250+ self.assertEqual(preview.props.title, "Welcome to Ubuntu")
251+ self.assertEqual(preview.props.description_markup,
252+ "A visual introduction to the Unity desktop.")
253+ self.assertNotEqual(preview.props.image, None)
254+ self.assertEqual(preview.props.image.get_names(), ['sample_help'])
255
256 def test_activation(self):
257 result = Unity.ScopeResult()
258- result.uri = "tests/data/C/sample_help/mock_yelp_file"
259+ result.uri = "tests/data/C/sample_help/mock_yelp_file.page"
260 activation = self.scope.activate(result, Unity.SearchMetadata(), None)
261 self.assertEqual(activation.props.goto_uri, None)
262 self.assertEqual(activation.props.handled, Unity.HandledType.HIDE_DASH)

Subscribers

People subscribed via source and target branches

to all changes: