tag:www.rhnh.net,2008:/rantRant - Xavier Shay's Blog2008-05-03T11:36:56ZEnkiXavier Shaynotreal@rhnh.nettag:www.rhnh.net,2008:Post/1162006-10-12T10:54:00Z2007-07-17T10:54:55ZRant 0.5.7 Released<p>Recently I have had the need to do some C# work. Not wanting to stray too far from ruby, I used <a href="http://make.rubyforge.org">Rant</a> to build my project. Initially, Rant wasn’t quite up to the task, so I rolled up my sleeves and did something about it. The culmination of my efforts comprise the primary component of today’s 0.5.7 release, so check it out for some white hot C# build file action.</p>tag:www.rhnh.net,2008:Post/2092006-09-21T02:10:00Z2007-11-03T01:14:01ZHack the Planet<ol>
<li>Vegetarian – battery farms lose!</li>
<li>Buy organic, fairtrade and/or local where possible</li>
<li>No car, use public transport and feet, except where not possible (Geelong)</li>
<li>Refuse plastic bags, although I think perversely our excessive number of green bags at home is soon to become an environmental risk</li>
<li>I plan to vote, haven’t had the opportunity yet</li>
<li>Spread the love. Bring politics into conversations. Getting people talking and thinking is the first step.</li>
</ol>
<p>The last one is important. Preaching at people will never work – global awareness must come from within. We must provide the support and encouragement. Lead by example. It can be tough sometimes. I almost hit intolerable despair last night. Startling, raw, realisations: The pope – the most important man in Christianity – is a political retard, the most powerful man in the world is widely regarded an idiot, and you couldn’t have pulled the recent Naomi Robson story from Frontline… Politics, Religion, Media, the triple crown. The world is loco.</p>
<p>In other news, I’ve just commited some C Sharp tools to <a href="http://make.rubyforge.org">Ruby Rant</a> , if you’re interested in a sweet build tool that lets you use ruby (XML loses!). I’m using it for a fairly decent project at work (multiple projects, resources, unit tests, etc) and find it a pleasure to work with. Note I’m talking about a replacement for the deprecated method described in the current documentation. I’m going to get that updated, but for now check the mailing list for info.</p>tag:www.rhnh.net,2008:Post/1212006-07-23T11:02:00Z2007-07-17T11:41:21ZBuilding Firefox Extensions<p>This article will introduce the basics of <a href="http://make.rubyforge.org">Ruby Rant</a> by creating a Rantfile to build <a href="http://www.mozilla.com/firefox/">Firefox</a> extensions. You don’t actually need to know anything about extensions to follow along, but if you are interested may I recommend this <a href="http://roachfiend.com/archives/2004/12/08/how-to-create-firefox-extensions/">tutorial by roachfiend</a>. You will note that that article (and many others on the same topic) use a batch file to build their extensions. While this is quick to set up for simple development, a build file saves time and effort in the long run, and gives more flexibility.</p>
<p>I assume you at least know what Rant is – a replacement for Rake – and have it installed and working. Please visit their website for more information on this topic. This is also not a build file tutorial – you should know what a task and a dependency are.</p>
<h3>Table of Contents</h3>
<ol>
<li><a href="#basics">Extension Basics</a></li>
<li><a href="#rant">Rant</a></li>
<li><a href="#jar">Making the <span class="caps">JAR</span></a></li>
<li><a href="#cleaning">Cleaning</a></li>
<li><a href="#xpi">Making the <span class="caps">XPI</span></a></li>
<li><a href="#final">Final Touches</a></li>
<li><a href="#completed">The Completed Rakefile</a></li>
</ol>
<h3 id="basics">Extension Basics</h3>
<p>The first step is to decide on directory structure for your project. Firefox extensions are comprised of two main portions – the install instructions, and the actual content of the extension. A Firefox extension (an <span class="caps">XPI</span> file) is really just a zip file with a different extension. You can open it up using your favourite archive manager and see the following structure:</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">myextension.xpi/<tt>
</tt> install.js<tt>
</tt> install.rdf<tt>
</tt> chrome/<tt>
</tt> myextension.jar/<tt>
</tt> ... myextension content ...<tt>
</tt></pre></td>
</tr></table>
<p>Likewise, the <span class="caps">JAR</span> file is also a zip file with an alternate extension. We can see that there are two major portions of the extension that need building, the <span class="caps">JAR</span> and the <span class="caps">XPI</span> (which contains the <span class="caps">JAR</span>). As such, we will use a source structure that looks like this (<a href="source">download the source code</a>):</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">myextension/<tt>
</tt> Rantfile<tt>
</tt> src/<tt>
</tt> install/<tt>
</tt> jar/<tt>
</tt></pre></td>
</tr></table>
<p>Clearly, the install folder will only contain our <code>install.js</code> and <code>install.rdf</code> files, and the jar folder will contain the contents of our jar.</p>
<h3 id="rant">Rant</h3>
<p>Enough introduction, let’s get started with Rant. Rant is a replacement for Rake. I won’t go into detail here, but one of the advantages for our purposes is portable zip creation without the need for external libraries. Rant is similar to Rake in that you define all your build tasks in a file in your root directory – the Rantfile. We will create 3 tasks – package, clean, and clobber. The first obviously packages up our extension into a zip file and gives it a <code>.xpi</code> extension. “clean” removes temporary files used to package the extension, and “clobber” removes all generated artefacts (basically the same as clean but also removes the <span class="caps">XPI</span> file).</p>
<h3 id="jar">Making the <span class="caps">JAR</span></h3>
<p>Baby steps steps though – first of all we want to create the <span class="caps">JAR</span> file for our extension. We can do this using the Archive::Zip generator provided by Rant:</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">import <span class="s"><span class="dl">"</span><span class="k">archive/zip</span><span class="dl">"</span></span><tt>
</tt>require <span class="s"><span class="dl">"</span><span class="k">archive_rootdir_fix</span><span class="dl">"</span></span><tt>
</tt><tt>
</tt>gen <span class="co">Archive</span>::<span class="co">Zip</span>, <span class="s"><span class="dl">"</span><span class="k">build/helloworld</span><span class="dl">"</span></span>, <tt>
</tt> <span class="sy">:files</span> => sys[<span class="s"><span class="dl">"</span><span class="k">src/jar/**/*</span><span class="dl">"</span></span>],<tt>
</tt> <span class="sy">:rootdir</span> => <span class="s"><span class="dl">"</span><span class="k">src/jar</span><span class="dl">"</span></span>,<tt>
</tt> <span class="sy">:extension</span> => <span class="s"><span class="dl">"</span><span class="k">.jar</span><span class="dl">"</span></span><tt>
</tt></pre></td>
</tr></table>
<p>This generator creates a task called “build/helloworld.jar” that creates exactly that archive, containing all the files from <code>src/jar</code>. “<code>**/*</code>” tells rant to recursively add all files. The <code>rootdir</code> parameter is necessary so that the generator knows where to start adding files. Without it, the created <span class="caps">JAR</span> will have the “<code>src/jar</code>” folders inside it, which is undesirable.</p>
<p>I draw your attention to the <code>archive_rootdir_fix</code> file that is being required. Support for the <code>rootdir</code> parameter is currently not in Rant. I’ve submitted a patch, but until it is accepted, you need this particular file. It is included in the <a href="source">example source code</a> for you convenience.</p>
<p>The generated task name is quite cumbersome, but it is quite trivial to create an alias to it using a blank task with a sole dependency. But what happens when we change our extension name or build directory? We also have to recode our alias task. Thankfully, the generator returns an object with information about the generated task, so that we can use it later in our Rantfile:</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">import <span class="s"><span class="dl">"</span><span class="k">archive/zip</span><span class="dl">"</span></span><tt>
</tt><tt>
</tt>jar_t = gen <span class="co">Archive</span>::<span class="co">Zip</span>, <span class="s"><span class="dl">"</span><span class="k">build/helloworld</span><span class="dl">"</span></span>, <tt>
</tt> <span class="sy">:files</span> => sys[<span class="s"><span class="dl">"</span><span class="k">src/jar/**/*</span><span class="dl">"</span></span>],<tt>
</tt> <span class="sy">:rootdir</span> => <span class="s"><span class="dl">"</span><span class="k">src/jar</span><span class="dl">"</span></span>,<tt>
</tt> <span class="sy">:extension</span> => <span class="s"><span class="dl">"</span><span class="k">.jar</span><span class="dl">"</span></span><tt>
</tt><tt>
</tt>task <span class="sy">:build_jar</span> => jar_t.path<tt>
</tt></pre></td>
</tr></table>
<h3 id="cleaning">Cleaning</h3>
<p>Before we proceed, let us quickly set up our clean and clobber tasks, as they are required for the next section. Rant makes this trivially easy, so I’m just going to show you some code and move on.</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">import <span class="s"><span class="dl">"</span><span class="k">clean</span><span class="dl">"</span></span><tt>
</tt><tt>
</tt>gen <span class="co">Clean</span>, <span class="sy">:clean</span><tt>
</tt>var[<span class="sy">:clean</span>] << <span class="s"><span class="dl">"</span><span class="k">build</span><span class="dl">"</span></span><tt>
</tt><tt>
</tt>gen <span class="co">Clean</span>, <span class="sy">:clobber</span><tt>
</tt>var[<span class="sy">:clobber</span>] << <span class="s"><span class="dl">"</span><span class="k">build</span><span class="dl">"</span></span><tt>
</tt>var[<span class="sy">:clobber</span>] << <span class="s"><span class="dl">"</span><span class="k">bin</span><span class="dl">"</span></span><tt>
</tt></pre></td>
</tr></table>
<h3 id="xpi">Making the <span class="caps">XPI</span></h3>
<p>As you can imagine, the next step – packaging up the <span class="caps">XPI</span> file – is more of the same. A small amount of trickery is required to get the <span class="caps">JAR</span> file into the chrome directory – we actually move files around and prepare the <span class="caps">XPI</span> file in the build directory, so that our zip task only has to zip the single directory. You can do this using methods of the <code>sys</code> object. Since it uses standard shell commands it is fairly self explanatory, as you’ll see in the following example. See that we can keep using the <code>jar_t</code> object through out build file.</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt>13<tt>
</tt>14<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">xpitask = gen <span class="co">Archive</span>::<span class="co">Zip</span>, <span class="s"><span class="dl">"</span><span class="k">bin/helloworld</span><span class="dl">"</span></span>,<tt>
</tt> <span class="sy">:version</span> => <span class="s"><span class="dl">"</span><span class="k">1.0.0</span><span class="dl">"</span></span>,<tt>
</tt> <span class="sy">:files</span> => sys[<span class="s"><span class="dl">"</span><span class="k">build/**/*</span><span class="dl">"</span></span>],<tt>
</tt> <span class="sy">:rootdir</span> => <span class="s"><span class="dl">"</span><span class="k">build</span><span class="dl">"</span></span>,<tt>
</tt> <span class="sy">:extension</span> => <span class="s"><span class="dl">"</span><span class="k">.xpi</span><span class="dl">"</span></span><tt>
</tt>task <span class="sy">:build_xpi</span> => xpitask.path <tt>
</tt><tt>
</tt>task <span class="sy">:prepare</span> => [<span class="sy">:build_jar</span>] <span class="r">do</span> |t|<tt>
</tt> sys.mkdir_p <span class="s"><span class="dl">"</span><span class="k">build/chrome</span><span class="dl">"</span></span><tt>
</tt> sys.mv jar_t.path, <span class="s"><span class="dl">"</span><span class="k">build/chrome/helloworld.jar</span><span class="dl">"</span></span><tt>
</tt> sys.cp sys[<span class="s"><span class="dl">"</span><span class="k">src/install/**/*</span><span class="dl">"</span></span>], <span class="s"><span class="dl">"</span><span class="k">build</span><span class="dl">"</span></span><tt>
</tt><span class="r">end</span><tt>
</tt><tt>
</tt>task <span class="sy">:package</span> => [<span class="sy">:prepare</span>, <span class="sy">:build_xpi</span>]<tt>
</tt></pre></td>
</tr></table>
<p>Note that we’ve added a version parameter to the zip task – this automatically appends a version string to our output file.</p>
<h3 id="final">Final Touches</h3>
<p>Now we just need to add the finishing touches to our build file. For maintainability, we will extract common names (such as the “helloworld” title and the “build” directory) into variables, so that changing them once will change them throughout the entire buildfile. You can use normal ruby variables for this, but it is preferable to use the “var” construct since it means you have the option of using them in <code>Command</code> generators later on (maybe I will cover it in another tutorial). It is more verbose, however, so you may choose not to use it in your own projects.</p>
<p>Finally, we move our public tasks to the top of file for readability and give them descriptions so they are displayed when executing “<code>rant -T</code>”. And there you have it folks, an automated build script for firefox extensions. Please <a href="source">download the source code</a> to peruse at your leisure.</p>
<h3 id="completed">The Completed Rantfile</h3><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt>13<tt>
</tt>14<tt>
</tt>15<tt>
</tt>16<tt>
</tt>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt>22<tt>
</tt>23<tt>
</tt>24<tt>
</tt>25<tt>
</tt>26<tt>
</tt>27<tt>
</tt>28<tt>
</tt>29<tt>
</tt><strong>30</strong><tt>
</tt>31<tt>
</tt>32<tt>
</tt>33<tt>
</tt>34<tt>
</tt>35<tt>
</tt>36<tt>
</tt>37<tt>
</tt>38<tt>
</tt>39<tt>
</tt><strong>40</strong><tt>
</tt>41<tt>
</tt>42<tt>
</tt>43<tt>
</tt>44<tt>
</tt>45<tt>
</tt>46<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="c"># Rantfile for building Firefox Extension</span><tt>
</tt><span class="c"># Xavier Shay (xshay@rhnh.net), July 2006</span><tt>
</tt><tt>
</tt>import <span class="s"><span class="dl">"</span><span class="k">archive/zip</span><span class="dl">"</span></span><tt>
</tt>require <span class="s"><span class="dl">"</span><span class="k">archive_rootdir_fix</span><span class="dl">"</span></span><tt>
</tt>import <span class="s"><span class="dl">"</span><span class="k">clean</span><span class="dl">"</span></span><tt>
</tt><tt>
</tt><span class="c"># Configuration</span><tt>
</tt>var <span class="sy">:title</span> => <span class="s"><span class="dl">"</span><span class="k">helloworld</span><span class="dl">"</span></span><tt>
</tt>var <span class="sy">:version</span> => <span class="s"><span class="dl">"</span><span class="k">1.0.0</span><span class="dl">"</span></span><tt>
</tt>var <span class="sy">:build_dir</span> => <span class="s"><span class="dl">"</span><span class="k">build</span><span class="dl">"</span></span><tt>
</tt>var <span class="sy">:bin_dir</span> => <span class="s"><span class="dl">"</span><span class="k">bin</span><span class="dl">"</span></span><tt>
</tt>var <span class="sy">:src_dir</span> => <span class="s"><span class="dl">"</span><span class="k">src</span><span class="dl">"</span></span><tt>
</tt><tt>
</tt><span class="c"># Primary tasks</span><tt>
</tt>desc <span class="s"><span class="dl">"</span><span class="k">Package up the XPI file for release</span><span class="dl">"</span></span><tt>
</tt>task <span class="sy">:package</span> => [<span class="sy">:prepare</span>, <span class="sy">:build_xpi</span>]<tt>
</tt><tt>
</tt>desc <span class="s"><span class="dl">"</span><span class="k">Cleanup temporary files</span><span class="dl">"</span></span><tt>
</tt>gen <span class="co">Clean</span>, <span class="sy">:clean</span><tt>
</tt>var[<span class="sy">:clean</span>] << <span class="s"><span class="dl">"</span><span class="k">build</span><span class="dl">"</span></span><tt>
</tt><tt>
</tt>desc <span class="s"><span class="dl">"</span><span class="k">Cleanup all generated artifacts</span><span class="dl">"</span></span><tt>
</tt>gen <span class="co">Clean</span>, <span class="sy">:clobber</span><tt>
</tt>var[<span class="sy">:clobber</span>] << <span class="s"><span class="dl">"</span><span class="k">build</span><span class="dl">"</span></span><tt>
</tt>var[<span class="sy">:clobber</span>] << <span class="s"><span class="dl">"</span><span class="k">bin</span><span class="dl">"</span></span><tt>
</tt><tt>
</tt><span class="c"># Support tasks</span><tt>
</tt>jar_t = gen <span class="co">Archive</span>::<span class="co">Zip</span>, <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:build_dir</span><span class="idl">}</span></span><span class="k">/</span><span class="il"><span class="idl">#{</span>var <span class="sy">:title</span><span class="idl">}</span></span><span class="dl">"</span></span>, <tt>
</tt> <span class="sy">:files</span> => sys[<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:src_dir</span><span class="idl">}</span></span><span class="k">/jar/**/*</span><span class="dl">"</span></span>],<tt>
</tt> <span class="sy">:rootdir</span> => <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:src_dir</span><span class="idl">}</span></span><span class="k">/jar</span><span class="dl">"</span></span>,<tt>
</tt> <span class="sy">:extension</span> => <span class="s"><span class="dl">"</span><span class="k">.jar</span><span class="dl">"</span></span><tt>
</tt>task <span class="sy">:build_jar</span> => jar_t.path<tt>
</tt><tt>
</tt>xpi_t = gen <span class="co">Archive</span>::<span class="co">Zip</span>, <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:bin_dir</span><span class="idl">}</span></span><span class="k">/</span><span class="il"><span class="idl">#{</span>var <span class="sy">:title</span><span class="idl">}</span></span><span class="dl">"</span></span>,<tt>
</tt> <span class="sy">:version</span> => <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:version</span><span class="idl">}</span></span><span class="dl">"</span></span>,<tt>
</tt> <span class="sy">:files</span> => sys[<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:build_dir</span><span class="idl">}</span></span><span class="k">/**/*</span><span class="dl">"</span></span>],<tt>
</tt> <span class="sy">:rootdir</span> => <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:build_dir</span><span class="idl">}</span></span><span class="dl">"</span></span>,<tt>
</tt> <span class="sy">:extension</span> => <span class="s"><span class="dl">"</span><span class="k">.xpi</span><span class="dl">"</span></span><tt>
</tt>task <span class="sy">:build_xpi</span> => xpi_t.path <tt>
</tt><tt>
</tt>task <span class="sy">:prepare</span> => [<span class="sy">:clean</span>, <span class="sy">:build_jar</span>] <span class="r">do</span> |t|<tt>
</tt> sys.mkdir_p <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:build_dir</span><span class="idl">}</span></span><span class="k">/chrome</span><span class="dl">"</span></span><tt>
</tt> sys.mv jar_t.path, <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:build_dir</span><span class="idl">}</span></span><span class="k">/chrome/</span><span class="il"><span class="idl">#{</span>var <span class="sy">:title</span><span class="idl">}</span></span><span class="k">.jar</span><span class="dl">"</span></span><tt>
</tt> sys.cp sys[<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:src_dir</span><span class="idl">}</span></span><span class="k">/install/**/*</span><span class="dl">"</span></span>], <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>var <span class="sy">:build_dir</span><span class="idl">}</span></span><span class="dl">"</span></span><tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
tag:www.rhnh.net,2008:Post/7692006-04-27T20:43:00Z2008-05-03T11:36:56ZLecture Etiquette<p>I love enterprise .NET, but nothing annoys me more than people slagging off microsoft in the lectures. Today on security “There’s no such thing as 100% security?” “Linux?”. Aaaargh. And it only got worse. <span class="caps">SHUTUP AND LEARN</span>. The lecture didn’t even have any MS specific material in it. People not liking the subject because they “don’t like the way microsoft does things”. 3-tier design? Security? Welcome to the enterprise world. <span class="caps">THESE ARE BEST PRACTICES</span>. We are just using microsoft products as a context for those practices. We can’t possibly cover all the options – this is a 12 week course. You knew we were going to be using microsoft products before you started the course.</p>
<p>Of course, this goes for all lectures.</p>
<p><strong>There is rarely a reason to speak in a lecture</strong></p>
<p>Save it for the tutes. No one else is interested in your opinion/question.</p>