Merge lp:~thumper/launchpad/recipe-syntax into lp:launchpad

Proposed by Tim Penhey
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 11995
Proposed branch: lp:~thumper/launchpad/recipe-syntax
Merge into: lp:launchpad
Prerequisite: lp:~thumper/launchpad/makefile-tweaks
Diff against target: 360 lines (+230/-15)
8 files modified
Makefile (+3/-0)
lib/lp/app/browser/configure.zcml (+8/-0)
lib/lp/app/browser/launchpadform.py (+32/-0)
lib/lp/app/browser/tests/test_launchpadform.py (+66/-0)
lib/lp/app/templates/launchpad-form.pt (+18/-12)
lib/lp/code/browser/sourcepackagerecipe.py (+11/-3)
lib/lp/code/help/recipe-syntax.html (+91/-0)
lib/lp/code/templates/sourcepackagerecipe-new.pt (+1/-0)
To merge this branch: bzr merge lp:~thumper/launchpad/recipe-syntax
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+41818@code.launchpad.net

This proposal supersedes a proposal from 2010-11-23.

Commit message

[r=sinzui][ui=none][bug=670475] Add a help link to the recipe text description when adding a new recipe.

Description of the change

Show a help link in the description of the recipe text widget, and have that bring up some meaningful help.

As discussed on the list I'm using an optional field modifier that gets the launchpad form infrastructure
to render the widget's description using the structured text, so html is rendered un-escaped.

tests:
   lp.app.browser.tests.test_launchpadform

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Hi Tim.

Your implementation looks good. I have a few concerns about markup and text that you may want to consider before you land.

@lib/lp/app/templates/launchpad-form.pt

launchpad-form does not look very DRY. I wonder if we could define a metal macro before the launchpad form macro based on the first definition and use it.

  <metal:help-hint metal:define-macro="help_hint">
    <tal:widget-help
        define="use_structure context/query:has-structured-doc"
        condition="context/hint">
     <p class="formHelp" tal:content="context/hint"
         tal:condition="not: use_structure">
        Some Help Text
      </p>
      <p class="formHelp" tal:content="structure context/hint"
         tal:condition="use_structure">
        Some Structured Help Text
      </p>
    </tal:widget-help>
  </metal:help-hint>

then

  <metal:help-hint use-macro="widget/@@launchpad_form/help_hint">

Writing macros is somewhat dodgy. Maybe we should report a bug in launchpad-web and set someone the task of cleaning up the rules. The clean up might reveal inconsistencies in the file.

@lib/lp/code/browser/sourcepackagerecipe.py

Some users will see odd leading and trailing underlines on hover because there is white space between the anchor tags and content. While HTML 3.0+ requires renders to strip the leanding and trailing space, few browsers have implemented the rule.

                <a href="/+help/recipe-syntax.html" target="help"
                  >Syntax help&nbsp;
                  <span class="sprite maybe">
                    <span class="invisible-link">Help</span>
                  </span></a>

@lib/lp/code/help/recipe-syntax.html

I believe we are using an > to indicate the user will leave the site/open a new window.
    <a href="https://help.launchpad.net/Place" target="_blank">place &gt;</a>

s/ndash/mdash/ ndash is for a range of numbers, mdash it used to construct a clause

review: Approve (code)
Revision history for this message
Tim Penhey (thumper) wrote :

Thanks Curtis.

I've updated the code following your recommendations.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2010-11-25 02:28:35 +0000
3+++ Makefile 2010-11-26 02:05:36 +0000
4@@ -139,6 +139,9 @@
5 touch $(CODEHOSTING_ROOT)/config/launchpad-lookup.txt
6
7 build: compile apidoc jsbuild css_combine
8+ mkdir -p $(CODEHOSTING_ROOT)/mirrors
9+ touch $(CODEHOSTING_ROOT)/rewrite.log
10+ chmod 777 $(CODEHOSTING_ROOT)/rewrite.log
11
12 css_combine: sprite_css bin/combine-css
13 ${SHHH} bin/combine-css
14
15=== modified file 'lib/lp/app/browser/configure.zcml'
16--- lib/lp/app/browser/configure.zcml 2010-11-09 04:24:09 +0000
17+++ lib/lp/app/browser/configure.zcml 2010-11-26 02:05:36 +0000
18@@ -551,6 +551,14 @@
19 name="fmt"
20 />
21
22+ <!-- TALES query: namespace -->
23+ <adapter
24+ for="zope.app.form.interfaces.IWidget"
25+ provides="zope.traversing.interfaces.IPathAdapter"
26+ factory="lp.app.browser.launchpadform.WidgetHasStructuredDoc"
27+ name="query"
28+ />
29+
30 <adapter
31 factory="lp.app.browser.tales.LaunchpadLayerToMainTemplateAdapter"
32 />
33
34=== modified file 'lib/lp/app/browser/launchpadform.py'
35--- lib/lp/app/browser/launchpadform.py 2010-11-23 23:22:27 +0000
36+++ lib/lp/app/browser/launchpadform.py 2010-11-26 02:05:36 +0000
37@@ -9,6 +9,7 @@
38 __all__ = [
39 'action',
40 'custom_widget',
41+ 'has_structured_doc',
42 'LaunchpadEditFormView',
43 'LaunchpadFormView',
44 'ReturnToReferrerMixin',
45@@ -32,9 +33,12 @@
46 from zope.formlib.form import action
47 from zope.interface import (
48 classImplements,
49+ implements,
50 providedBy,
51 )
52 from zope.interface.advice import addClassAdvisor
53+from zope.traversing.interfaces import ITraversable, TraversalError
54+
55
56 from canonical.launchpad.webapp.interfaces import (
57 IAlwaysSubmittedWidget,
58@@ -511,3 +515,31 @@
59
60 next_url = _return_url
61 cancel_url = _return_url
62+
63+
64+def has_structured_doc(field):
65+ """Set an annotation to mark that the field's doc should be structured."""
66+ field.setTaggedValue('has_structured_doc', True)
67+ return field
68+
69+
70+class WidgetHasStructuredDoc:
71+ """Check if widget has structured doc.
72+
73+ Example usage::
74+ tal:condition="widget/query:has-structured-doc"
75+ """
76+
77+ implements(ITraversable)
78+
79+ def __init__(self, widget):
80+ self.widget = widget
81+
82+ def traverse(self, name, furtherPath):
83+ if name != 'has-structured-doc':
84+ raise TraversalError("Unknown query %r" % name)
85+ if len(furtherPath) > 0:
86+ raise TraversalError(
87+ "There should be no further path segments after "
88+ "query:has-structured-doc")
89+ return self.widget.context.queryTaggedValue('has_structured_doc')
90
91=== added file 'lib/lp/app/browser/tests/test_launchpadform.py'
92--- lib/lp/app/browser/tests/test_launchpadform.py 1970-01-01 00:00:00 +0000
93+++ lib/lp/app/browser/tests/test_launchpadform.py 2010-11-26 02:05:36 +0000
94@@ -0,0 +1,66 @@
95+# Copyright 2010 Canonical Ltd. This software is licensed under the
96+# GNU Affero General Public License version 3 (see the file LICENSE).
97+
98+"""Tests for the lp.app.browser.launchpadform module."""
99+
100+__metaclass__ = type
101+
102+from zope.interface import Interface
103+from zope.schema import Text
104+
105+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
106+from canonical.testing.layers import DatabaseFunctionalLayer
107+from lp.app.browser.launchpadform import (
108+ has_structured_doc,
109+ LaunchpadFormView,
110+ )
111+from lp.testing import TestCase, test_tales
112+
113+
114+class TestInterface(Interface):
115+ """Test interface for the view below."""
116+
117+ normal = Text(title=u'normal', description=u'plain text')
118+
119+ structured = has_structured_doc(
120+ Text(title=u'structured',
121+ description=u'<strong>structured text</strong'))
122+
123+
124+class TestView(LaunchpadFormView):
125+ """A trivial view using the TestInterface."""
126+
127+ schema = TestInterface
128+
129+
130+class TestHasStructuredDoc(TestCase):
131+
132+ layer = DatabaseFunctionalLayer
133+
134+ def _widget_annotation(self, widget):
135+ return widget.context.queryTaggedValue('has_structured_doc')
136+
137+ def test_has_structured_doc_sets_attribute(self):
138+ # Test that has_structured_doc sets the field annotation.
139+ request = LaunchpadTestRequest()
140+ view = TestView(None, request)
141+ view.initialize()
142+ normal_widget, structured_widget = list(view.widgets)
143+ self.assertIs(None, self._widget_annotation(normal_widget))
144+ self.assertTrue(self._widget_annotation(structured_widget))
145+
146+
147+class TestQueryTalesForHasStructuredDoc(TestCase):
148+
149+ layer = DatabaseFunctionalLayer
150+
151+ def test_query_tales(self):
152+ # Test that query:has-structured-doc gets sets the field annotation.
153+ request = LaunchpadTestRequest()
154+ view = TestView(None, request)
155+ view.initialize()
156+ normal_widget, structured_widget = list(view.widgets)
157+ self.assertIs(None, test_tales(
158+ 'widget/query:has-structured-doc', widget=normal_widget))
159+ self.assertTrue(test_tales(
160+ 'widget/query:has-structured-doc', widget=structured_widget))
161
162=== modified file 'lib/lp/app/templates/launchpad-form.pt'
163--- lib/lp/app/templates/launchpad-form.pt 2010-10-15 01:27:04 +0000
164+++ lib/lp/app/templates/launchpad-form.pt 2010-11-26 02:05:36 +0000
165@@ -123,10 +123,7 @@
166 </div>
167 <div class="message" tal:condition="error"
168 tal:content="structure error">Error message</div>
169- <p class="formHelp"
170- tal:condition="widget/hint"
171- tal:content="widget/hint">Some Help Text
172- </p>
173+ <metal:help-hint use-macro="widget/@@launchpad_form/help_hint" />
174 </div>
175 </td>
176 </tr>
177@@ -152,10 +149,7 @@
178 <div tal:content="structure widget">
179 <input type="text" />
180 </div>
181- <p class="formHelp"
182- tal:condition="widget/hint"
183- tal:content="widget/hint">Some Help Text
184- </p>
185+ <metal:help-hint use-macro="widget/@@launchpad_form/help_hint" />
186 </div>
187 </td>
188 </tr>
189@@ -171,10 +165,7 @@
190 tal:content="structure error"
191 class="message"
192 >Error message</div>
193- <p class="formHelp"
194- tal:condition="widget/hint"
195- tal:content="widget/hint">Some Help Text
196- </p>
197+ <metal:help-hint use-macro="widget/@@launchpad_form/help_hint" />
198 </div>
199 </td>
200 </tr>
201@@ -193,4 +184,19 @@
202 </table>
203 </metal:macro>
204
205+ <metal:help-hint metal:define-macro="help_hint">
206+ <tal:widget-help
207+ define="use_structure widget/query:has-structured-doc"
208+ condition="widget/hint">
209+ <p class="formHelp" tal:content="widget/hint"
210+ tal:condition="not: use_structure">
211+ Some Help Text
212+ </p>
213+ <p class="formHelp" tal:content="structure widget/hint"
214+ tal:condition="use_structure">
215+ Some Structured Help Text
216+ </p>
217+ </tal:widget-help>
218+ </metal:help-hint>
219+
220 </div>
221
222=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
223--- lib/lp/code/browser/sourcepackagerecipe.py 2010-11-24 08:05:42 +0000
224+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-11-26 02:05:36 +0000
225@@ -58,6 +58,7 @@
226 from lp.app.browser.launchpadform import (
227 action,
228 custom_widget,
229+ has_structured_doc,
230 LaunchpadEditFormView,
231 LaunchpadFormView,
232 )
233@@ -287,9 +288,16 @@
234 description=(
235 u'If built daily, these are the distribution versions that '
236 u'the recipe will be built for.'))
237- recipe_text = Text(
238- title=u'Recipe text', required=True,
239- description=u'The text of the recipe.')
240+ recipe_text = has_structured_doc(
241+ Text(
242+ title=u'Recipe text', required=True,
243+ description=u"""The text of the recipe.
244+ <a href="/+help/recipe-syntax.html" target="help"
245+ >Syntax help&nbsp;
246+ <span class="sprite maybe">
247+ <span class="invisible-link">Help</span>
248+ </span></a>
249+ """))
250
251
252 class RecipeTextValidatorMixin:
253
254=== added file 'lib/lp/code/help/recipe-syntax.html'
255--- lib/lp/code/help/recipe-syntax.html 1970-01-01 00:00:00 +0000
256+++ lib/lp/code/help/recipe-syntax.html 2010-11-26 02:05:36 +0000
257@@ -0,0 +1,91 @@
258+<html>
259+ <head>
260+ <title>Source package recipe syntax</title>
261+ <link rel="stylesheet" type="text/css"
262+ href="/+icing/yui/cssreset/reset.css" />
263+ <link rel="stylesheet" type="text/css"
264+ href="/+icing/yui/cssfonts/fonts.css" />
265+ <link rel="stylesheet" type="text/css"
266+ href="/+icing/yui/cssbase/base.css" />
267+ </head>
268+ <body>
269+ <h1>Source package recipe syntax</h1>
270+
271+ <p>A recipe is just text that starts with a line such as:</p>
272+ <dl>
273+ <dd>
274+ <tt># bzr-builder format 0.2 deb-version {debupstream}-0~{revno}</tt>
275+ </dd>
276+ </dl>
277+
278+ <p>
279+ The format specifier is there to allow the syntax to be changed in later
280+ versions, and the meaning of "deb-version" will be explained below.
281+ </p>
282+
283+ <p>
284+ The next line specifies the base branch, this is the branch that will
285+ be used to define the initial files and directory structure.
286+ </p>
287+
288+ <p>
289+ Next comes any number of lines of other branches to be merged in, but
290+ using a slightly different format. To merge a branch in to the base
291+ specify something like:
292+ </p>
293+
294+ <dl>
295+ <dd>
296+ <tt>merge packaging lp:~foo-dev/foo/packaging</tt>
297+ </dd>
298+ </dl>
299+
300+ <p>
301+ which specifies we are merging a branch we will refer to as "packaging",
302+ which can be found at the given URI. The name you give to the branch as
303+ the second item doesn't have to match anything else, it's just an
304+ identifier specific to the recipe.
305+ </p>
306+
307+ <p>
308+ <a href="https://help.launchpad.net/Packaging/SourceBuilds/Recipes"
309+ target="_blank">Read more &gt;</a> on recipe syntax for other commands and
310+ specifying revisions for the branches.
311+ </p>
312+
313+ <h2>deb-version</h2>
314+
315+ <p>
316+ To build Debian source package that you desire you should make sure that
317+ "deb-version" is set to an appropriate value on the first line of your
318+ recipe. This will be used as the version number of the package. The
319+ value you put there also allows for substitution of values in to it
320+ based on various things when the recipe is processed:
321+ </p>
322+
323+ <ul class="bulleted">
324+ <li><tt>{date}</tt> &mdash; will be substituted with just the current
325+ date, such as <tt>20090819</tt></li>
326+ <li><tt>{debupstream}</tt> &mdash; will be replaced by the upstream
327+ portion of the version number taken from debian/changelog in the final
328+ tree. If when the tree is built the top of debian/changelog has a
329+ version number of "<tt>1.0-1</tt>" then this would evaluate to
330+ "<tt>1.0</tt>".
331+ </li>
332+ <li><tt>{revno}</tt> &mdash; will be the revno of the base branch (the
333+ first specified)</li>
334+ <li><tt>{revno:&lt;branch name&gt;}</tt> &mdash; will be substituted
335+ with the revno for the branch named &lt;branch name&gt; in the
336+ recipe</li>
337+ <li><tt>{time}</tt> &mdash; will be substituted with the current date
338+ and time, such as <tt>200908191512</tt></li>
339+ </ul>
340+
341+ <p>
342+ <a href="https://help.launchpad.net/Packaging/SourceBuilds/Recipes"
343+ target="_blank">Read more &gt;</a> on recipe syntax for other commands and
344+ specifying revisions for the branches.
345+ </p>
346+
347+ </body>
348+</html>
349
350=== modified file 'lib/lp/code/templates/sourcepackagerecipe-new.pt'
351--- lib/lp/code/templates/sourcepackagerecipe-new.pt 2010-11-03 01:20:17 +0000
352+++ lib/lp/code/templates/sourcepackagerecipe-new.pt 2010-11-26 02:05:36 +0000
353@@ -24,6 +24,7 @@
354 </div>
355
356 <div metal:use-macro="context/@@launchpad_form/form" />
357+
358 </div>
359 </body>
360 </html>