PageRenderTime 61ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/atom.xml

https://github.com/treyhunner/treyhunner.github.com
XML | 1170 lines | 866 code | 304 blank | 0 comment | 0 complexity | f5fc078b550c625035b3083532e1990f MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <feed xmlns="http://www.w3.org/2005/Atom">
  3. <title><![CDATA[Trey Hunner]]></title>
  4. <link href="http://treyhunner.com/atom.xml" rel="self"/>
  5. <link href="http://treyhunner.com/"/>
  6. <updated>2014-06-16T18:24:10-07:00</updated>
  7. <id>http://treyhunner.com/</id>
  8. <author>
  9. <name><![CDATA[Trey Hunner]]></name>
  10. </author>
  11. <generator uri="http://octopress.org/">Octopress</generator>
  12. <entry>
  13. <title type="html"><![CDATA[CLI for Finding DRM-free Audiobooks]]></title>
  14. <link href="http://treyhunner.com/2014/05/cli-for-finding-drm-free-audiobooks/"/>
  15. <updated>2014-05-14T12:31:00-07:00</updated>
  16. <id>http://treyhunner.com/2014/05/cli-for-finding-drm-free-audiobooks</id>
  17. <content type="html"><![CDATA[<p>I recently acquired an appreciation for audiobooks. I listen to multiple
  18. audiobooks every month and I prefer DRM-free audiobooks so that I can listen
  19. through my favorite audiobook reader on each of my devices.</p>
  20. <p><a href="http://www.downpour.com/">Downpour</a> and <a href="http://www.emusic.com/">eMusic</a> are the only services I know that provide a large
  21. selection of DRM-free audiobooks. Unfortunately not all audiobooks are
  22. available on either of these sites, so I often end up searching both sites for
  23. each book to discover what versions (if any) are available from each. I got
  24. sick of searching both websites all the time, so I just created a Python script
  25. to do that work for me.</p>
  26. <div><script src='https://gist.github.com/21f307b975027be5162d.js'></script>
  27. <noscript><pre><code>#!/usr/bin/env python
  28. &quot;&quot;&quot;
  29. Search downpour.com and emusic.com for DRM-free audiobooks
  30. Usage::
  31. ./find_audiobooks.py &lt;title&gt;...
  32. File released to the public domain under CC0 license:
  33. http://creativecommons.org/publicdomain/zero/1.0/deed
  34. &quot;&quot;&quot;
  35. import sys
  36. from itertools import chain, izip_longest
  37. import urllib2
  38. from bs4 import BeautifulSoup
  39. from purl import URL
  40. def unescape(text):
  41. &quot;&quot;&quot;Return string without smart apostrophes&quot;&quot;&quot;
  42. return text.replace(u'\u2019', &quot;'&quot;)
  43. def get_downpour_url(book_name):
  44. &quot;&quot;&quot;Return search URL for downpour.com&quot;&quot;&quot;
  45. base = URL(&quot;http://www.downpour.com/catalogsearch/result/&quot;)
  46. return base.query_param('q', book_name).as_string()
  47. def get_emusic_url(book_name):
  48. &quot;&quot;&quot;Return search URL for emusic.com&quot;&quot;&quot;
  49. base_url = URL(&quot;http://www.emusic.com/search/book/&quot;)
  50. return base_url.query_param('s', book_name).as_string()
  51. def search_downpour(book_name):
  52. &quot;&quot;&quot;Search Downpour and return list of parsed results&quot;&quot;&quot;
  53. response = urllib2.urlopen(get_downpour_url(book_name))
  54. page = BeautifulSoup(response)
  55. books = page.find_all('li', attrs={'class': &quot;item&quot;})
  56. results = []
  57. for book in books:
  58. header = book.find(attrs={'class': &quot;product-name&quot;})
  59. link_tag = header.find('a')
  60. title = ' '.join(unescape(x)
  61. for x in link_tag.stripped_strings)
  62. link = link_tag['href']
  63. for author in book.find_all(attrs={'class': 'author'}):
  64. author_text = author.text
  65. if author_text.startswith('By '):
  66. author = author_text[3:]
  67. results.append({
  68. 'title': title,
  69. 'link': link,
  70. 'author': author,
  71. })
  72. return results
  73. def search_emusic(book_name):
  74. &quot;&quot;&quot;Search eMusic and return list of parsed results&quot;&quot;&quot;
  75. response = urllib2.urlopen(get_emusic_url(book_name))
  76. page = BeautifulSoup(response)
  77. books = page.find_all('li', attrs={'class': &quot;bundle&quot;})
  78. results = []
  79. for book in books:
  80. link_tag = book.find('h4').find('a')
  81. author_tag = book.find('h5')
  82. results.append({
  83. 'title': link_tag.text,
  84. 'link': link_tag['href'],
  85. 'author': author_tag.text,
  86. })
  87. return results
  88. def print_result(result):
  89. &quot;&quot;&quot;Print title, author, and link for audiobook result&quot;&quot;&quot;
  90. print &quot;Title: {}&quot;.format(result['title'])
  91. print &quot;Author: {}&quot;.format(result['author'])
  92. print &quot;Link: {}&quot;.format(result['link'])
  93. print
  94. def merge_lists(*lists):
  95. &quot;&quot;&quot;Return merge of lists by alternating elements of each&quot;&quot;&quot;
  96. combined_lists = chain.from_iterable(izip_longest(*lists))
  97. return list(filter(bool, combined_lists))
  98. def main(*args):
  99. &quot;&quot;&quot;Search audiobook sites and return search results&quot;&quot;&quot;
  100. for book_name in args:
  101. results1 = search_emusic(book_name)
  102. results2 = search_downpour(book_name)
  103. results = merge_lists(results1[:3], results2[:3])
  104. for result in results:
  105. print_result(result)
  106. if __name__ == &quot;__main__&quot;:
  107. main(*sys.argv[1:])
  108. </code></pre></noscript></div>
  109. <h3>Future Improvements</h3>
  110. <p>Some ideas for future improvements:</p>
  111. <ul>
  112. <li>Add <a href="http://www.audible.com/">Audible</a> results when the book cannot be found in a DRM-free format</li>
  113. <li>Rewrite the script in JavaScript and create a Chrome extension out of it</li>
  114. <li>Use <a href="https://github.com/kennethreitz/clint">clint</a> to colorize the command-line output</li>
  115. </ul>
  116. ]]></content>
  117. </entry>
  118. <entry>
  119. <title type="html"><![CDATA[Supporting Both Django 1.7 and South]]></title>
  120. <link href="http://treyhunner.com/2014/03/migrating-to-django-1-dot-7/"/>
  121. <updated>2014-03-27T13:05:00-07:00</updated>
  122. <id>http://treyhunner.com/2014/03/migrating-to-django-1-dot-7</id>
  123. <content type="html"><![CDATA[<p>Have an open source Django app with South migrations? Adding support for Django 1.7 might be a little painful. In this post I will discuss the difficulty of supporting Django 1.7 while maintaining South migrations for users of Django 1.6 and below.</p>
  124. <p>Django 1.7 uses the <code>migrations</code> sub-package in your app for database migrations and South relies on the same package. Unfortunately, you can&rsquo;t store both packages in the same place. At first glance, it seems we cannot support both Django 1.7 and previous versions of Django using South. However, as I explain below, we can support both at once.</p>
  125. <h2>Assessing your options</h2>
  126. <p>In order to support both Django 1.7 and Django 1.6 with South we can rename the <code>migrations</code> package and instruct users to reference the new package in their settings module. We can do this with the <a href="https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-MIGRATION_MODULES">MIGRATION_MODULES</a> or <a href="http://south.readthedocs.org/en/latest/settings.html#south-migration-modules">SOUTH_MIGRATION_MODULES</a> settings. There are three options:</p>
  127. <ol>
  128. <li>Move existing <code>migrations</code> directory to <code>south_migrations</code> and create Django 1.7 migrations in <code>migrations</code> package</li>
  129. <li>Create new Django 1.7 migrations package in <code>django_migrations</code> directory and leave existing South migrations package</li>
  130. <li>Move existing <code>migrations</code> directory to <code>south_migrations</code> and create Django 1.7 migrations in <code>django_migrations</code> directory</li>
  131. </ol>
  132. <p>The first option requires existing users either switch to Django 1.7 or update their settings module before upgrading to the new version of your app. The second option requires all Django 1.7 users to customize their settings module to properly install your app. The third option requires everyone (both Django 1.7 and South users) to update their settings module.</p>
  133. <p>Out of those options I prefer the first one. When you eventually drop support for South, you will probably want your Django 1.7 migrations to live in the <code>migrations</code> directory. If you don&rsquo;t force that switch now, you would eventually need to break backwards-compatibility or maintain two duplicate migrations directories.</p>
  134. <p>So our plan is to move the South migrations to <code>south_migrations</code> and create Django 1.7 migrations. An example with the <a href="https://github.com/treyhunner/django-email-log">django-email-log</a> app:</p>
  135. <figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
  136. <span class='line-number'>2</span>
  137. <span class='line-number'>3</span>
  138. </pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>git mv email_log/migrations email_log/south_migrations
  139. </span><span class='line'><span class="nv">$ </span>python manage.py makemigrations email_log
  140. </span><span class='line'><span class="nv">$ </span>git add email_log/migrations
  141. </span></code></pre></td></tr></table></div></figure>
  142. <h2>Breaking South support</h2>
  143. <p>If you move <code>migrations</code> to <code>south_migrations</code> and make a Django 1.7 <code>migrations</code> package, what happens to existing users with South?</p>
  144. <p>Your new app upgrade will break backwards compatibility for South users and you want to make sure they <em>know</em> they need to make a change immediately after upgrading. Users should see a loud and clear error message instructing them what they need to do. This can be done by hijacking their use of the <strong>migrate</strong> command with South.</p>
  145. <p>Existing users will run <strong>migrate</strong> when upgrading your app. If they don&rsquo;t migrate immediately, they will when they notice a problem and realize they need to run <strong>migrate</strong>. Upon migrating, we want to show a clear error message telling the user what to do.</p>
  146. <h2>Failing loudly and with a clear error message</h2>
  147. <p>When South looks for app migrations it will import our <code>migrations</code> package. Our <code>migrations</code> package contains Django 1.7 migrations, which South won&rsquo;t understand. So we want to make sure that if our <code>migrations</code> package is imported either Django 1.7 is installed or a proper error message is displayed. Upon importing this package, we can check for the presence of the new <code>django.db.migrations</code> module and if not found we will raise an exception with a descriptive error message.</p>
  148. <p>For example, this is the code I plan to add to the <code>email_log/migrations/__init__.py</code> file for <a href="https://github.com/treyhunner/django-email-log">django-email-log</a> to add Django 1.7 support:</p>
  149. <figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
  150. <span class='line-number'>2</span>
  151. <span class='line-number'>3</span>
  152. <span class='line-number'>4</span>
  153. <span class='line-number'>5</span>
  154. <span class='line-number'>6</span>
  155. <span class='line-number'>7</span>
  156. <span class='line-number'>8</span>
  157. <span class='line-number'>9</span>
  158. <span class='line-number'>10</span>
  159. <span class='line-number'>11</span>
  160. <span class='line-number'>12</span>
  161. <span class='line-number'>13</span>
  162. <span class='line-number'>14</span>
  163. <span class='line-number'>15</span>
  164. <span class='line-number'>16</span>
  165. <span class='line-number'>17</span>
  166. <span class='line-number'>18</span>
  167. <span class='line-number'>19</span>
  168. <span class='line-number'>20</span>
  169. <span class='line-number'>21</span>
  170. </pre></td><td class='code'><pre><code class='python'><span class='line'><span class="sd">&quot;&quot;&quot;</span>
  171. </span><span class='line'><span class="sd">Django migrations for email_log app</span>
  172. </span><span class='line'>
  173. </span><span class='line'><span class="sd">This package does not contain South migrations. South migrations can be found</span>
  174. </span><span class='line'><span class="sd">in the ``south_migrations`` package.</span>
  175. </span><span class='line'><span class="sd">&quot;&quot;&quot;</span>
  176. </span><span class='line'>
  177. </span><span class='line'><span class="n">SOUTH_ERROR_MESSAGE</span> <span class="o">=</span> <span class="s">&quot;&quot;&quot;</span><span class="se">\n</span><span class="s"></span>
  178. </span><span class='line'><span class="s">For South support, customize the SOUTH_MIGRATION_MODULES setting like so:</span>
  179. </span><span class='line'>
  180. </span><span class='line'><span class="s"> SOUTH_MIGRATION_MODULES = {</span>
  181. </span><span class='line'><span class="s"> &#39;email_log&#39;: &#39;email_log.south_migrations&#39;,</span>
  182. </span><span class='line'><span class="s"> }</span>
  183. </span><span class='line'><span class="s">&quot;&quot;&quot;</span>
  184. </span><span class='line'>
  185. </span><span class='line'><span class="c"># Ensure the user is not using Django 1.6 or below with South</span>
  186. </span><span class='line'><span class="k">try</span><span class="p">:</span>
  187. </span><span class='line'> <span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">migrations</span> <span class="c"># noqa</span>
  188. </span><span class='line'><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
  189. </span><span class='line'> <span class="kn">from</span> <span class="nn">django.core.exceptions</span> <span class="kn">import</span> <span class="n">ImproperlyConfigured</span>
  190. </span><span class='line'> <span class="k">raise</span> <span class="n">ImproperlyConfigured</span><span class="p">(</span><span class="n">SOUTH_ERROR_MESSAGE</span><span class="p">)</span>
  191. </span></code></pre></td></tr></table></div></figure>
  192. <p>Now when we run <strong>migrate</strong> with Django 1.6 and South, we&rsquo;ll see the following exception raised:</p>
  193. <figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
  194. <span class='line-number'>2</span>
  195. <span class='line-number'>3</span>
  196. <span class='line-number'>4</span>
  197. <span class='line-number'>5</span>
  198. <span class='line-number'>6</span>
  199. <span class='line-number'>7</span>
  200. </pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">django</span><span class="o">.</span><span class="n">core</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">ImproperlyConfigured</span><span class="p">:</span>
  201. </span><span class='line'>
  202. </span><span class='line'><span class="n">For</span> <span class="n">South</span> <span class="n">support</span><span class="p">,</span> <span class="n">customize</span> <span class="n">the</span> <span class="n">SOUTH_MIGRATION_MODULES</span> <span class="n">setting</span> <span class="n">like</span> <span class="n">so</span><span class="p">:</span>
  203. </span><span class='line'>
  204. </span><span class='line'> <span class="n">SOUTH_MIGRATION_MODULES</span> <span class="o">=</span> <span class="p">{</span>
  205. </span><span class='line'> <span class="s">&#39;email_log&#39;</span><span class="p">:</span> <span class="s">&#39;email_log.south_migrations&#39;</span><span class="p">,</span>
  206. </span><span class='line'> <span class="p">}</span>
  207. </span></code></pre></td></tr></table></div></figure>
  208. <h2>Conclusion</h2>
  209. <p>This breaks backwards compatibility, but our users should immediately understand what has broken and how to fix it. Remember to upgrade the major number of your package version to note this backwards-incompatible change.</p>
  210. <p>I would love to hear your thoughts about this approach in the comments below. Let me know if you have other ideas about how to handle supporting Django 1.7 migrations and South at the same time.</p>
  211. ]]></content>
  212. </entry>
  213. <entry>
  214. <title type="html"><![CDATA[TDD With Django Tutorial]]></title>
  215. <link href="http://treyhunner.com/2013/11/tdd-with-django-workshop/"/>
  216. <updated>2013-11-04T00:00:00-08:00</updated>
  217. <id>http://treyhunner.com/2013/11/tdd-with-django-workshop</id>
  218. <content type="html"><![CDATA[<p>I helped host a free Test-Driven Django Web Development workshop on <time date="2013-11-02">Saturday November 2</time> with <a href="http://pythonsd.org/">San Diego Python</a>. We created a series of tutorials demonstrating how to create a Django-powered blog while practicing test-driven development. The <a href="http://python.org/psf/">Python Software Foundation</a> sponsored the event and the <a href="http://aicenterca.com/">Ansir Innovation Center</a> provided a venue.</p>
  219. <p>You can find the tutorials at <a href="http://bit.ly/pysd-tdd">http://bit.ly/pysd-tdd</a> . The tutorials are provided under a <a href="https://creativecommons.org/licenses/by-sa/3.0/">CC BY-SA license</a> so you can reuse and modify them for your own purposes.</p>
  220. <p>Tutorial markdown files and working source code may be found on <a href="https://github.com/pythonsd/test-driven-django-development">Github</a>. We plan to improve and extend these tutorials for a future workshop. If you have ideas for improvements/additions or if you notice a bug, please submit an issue or open a pull request.</p>
  221. ]]></content>
  222. </entry>
  223. <entry>
  224. <title type="html"><![CDATA[Visual Integration Tests for Django]]></title>
  225. <link href="http://treyhunner.com/2013/10/visual-integration-tests-for-django/"/>
  226. <updated>2013-10-03T15:19:00-07:00</updated>
  227. <id>http://treyhunner.com/2013/10/visual-integration-tests-for-django</id>
  228. <content type="html"><![CDATA[<p>I recently added a new type of test to my testing arsenal: visual tests. Visual tests ensure the CSS, markup, and JavaScript produce a webpage that looks right.</p>
  229. <h2>Visual testing frameworks</h2>
  230. <p>Visual testing tools compare screenshots to ensure tested webpages look pixel perfect. Capturing webpage screenshots requires a full-featured web browser to render CSS and execute JavaScript. All three of the visual testing tools I found rely on Selenium or PhantomJS for rendering.</p>
  231. <h3>PhantomCSS</h3>
  232. <p><a href="https://github.com/Huddle/PhantomCSS">PhantomCSS</a> uses PhantomJS for screenshot differencing. PhantomCSS won&rsquo;t integrate directly with the Django live server or your Python test suite, so if you want to run a visual integration test, you&rsquo;d need to manually start and stop the test server between tests. I might eventually try out PhantomCSS for CSS unit tests, but I wanted to visually test my full website so I needed integration with the Django live server.</p>
  233. <h3>Django-casper</h3>
  234. <p><a href="https://github.com/dobarkod/django-casper">Django-casper</a> uses Django live server tests to execute CasperJS test files (which use PhantomJS) to compare screenshots. Each test requires an additional Python test which references a JavaScript file that executes the navigation and screenshotting code. I found this approach messy and difficult to setup.</p>
  235. <h3>Needle</h3>
  236. <p>The <a href="https://github.com/bfirsh/needle">needle</a> Python library uses Selenium to navigate your website and screenshot rendered pages. Unfortunately needle has poor test coverage, a seemingly failing test suite, and no change log. Despite these shortcomings, I went with needle for my visual integration tests because it got the job done.</p>
  237. <h2>Django and Needle</h2>
  238. <p>I used the following mixin to integrate the Django live server with needle. I used PhantomJS, but Firefox or another Selenium web driver should work as well.</p>
  239. <figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
  240. <span class='line-number'>2</span>
  241. <span class='line-number'>3</span>
  242. <span class='line-number'>4</span>
  243. <span class='line-number'>5</span>
  244. <span class='line-number'>6</span>
  245. <span class='line-number'>7</span>
  246. <span class='line-number'>8</span>
  247. <span class='line-number'>9</span>
  248. <span class='line-number'>10</span>
  249. <span class='line-number'>11</span>
  250. <span class='line-number'>12</span>
  251. <span class='line-number'>13</span>
  252. <span class='line-number'>14</span>
  253. </pre></td><td class='code'><pre><code class='python'><span class='line'><span class="kn">from</span> <span class="nn">django.test</span> <span class="kn">import</span> <span class="n">LiveServerTestCase</span>
  254. </span><span class='line'><span class="kn">from</span> <span class="nn">needle.cases</span> <span class="kn">import</span> <span class="n">NeedleTestCase</span>
  255. </span><span class='line'><span class="kn">from</span> <span class="nn">selenium.webdriver</span> <span class="kn">import</span> <span class="n">PhantomJS</span>
  256. </span><span class='line'>
  257. </span><span class='line'>
  258. </span><span class='line'><span class="k">class</span> <span class="nc">DjangoNeedleTestCase</span><span class="p">(</span><span class="n">NeedleTestCase</span><span class="p">,</span> <span class="n">LiveServerTestCase</span><span class="p">):</span>
  259. </span><span class='line'>
  260. </span><span class='line'> <span class="sd">&quot;&quot;&quot;Needle test case for use with Django live server&quot;&quot;&quot;</span>
  261. </span><span class='line'>
  262. </span><span class='line'> <span class="n">driver</span> <span class="o">=</span> <span class="n">PhantomJS</span>
  263. </span><span class='line'>
  264. </span><span class='line'> <span class="nd">@classmethod</span>
  265. </span><span class='line'> <span class="k">def</span> <span class="nf">get_web_driver</span><span class="p">(</span><span class="n">cls</span><span class="p">):</span>
  266. </span><span class='line'> <span class="k">return</span> <span class="nb">type</span><span class="p">(</span><span class="s">&#39;NeedleWebDriver&#39;</span><span class="p">,</span> <span class="p">(</span><span class="n">NeedleWebDriverMixin</span><span class="p">,</span> <span class="n">cls</span><span class="o">.</span><span class="n">driver</span><span class="p">),</span> <span class="p">{})()</span>
  267. </span></code></pre></td></tr></table></div></figure>
  268. <p>Unfortunately the above code only works with the version of needle on Github. The PyPI version does not yet include the <code>NeedleWebDriverMixin</code> (which I contributed recently for Django support). I have created <a href="https://github.com/bfirsh/needle/issues/13">an issue</a> suggesting a new PyPI release be made to resolve this problem.</p>
  269. <h2>Room for improvement</h2>
  270. <p>Currently I only run my visual tests manually. Visual tests are very brittle and occasionally they just break without any changes. If I manage to stabilize my visual tests so that they pass consistently on different platforms, I may run them during continuous integration.</p>
  271. <p>Do you have another solution for visual integration testing? Let me know in the comments.</p>
  272. ]]></content>
  273. </entry>
  274. <entry>
  275. <title type="html"><![CDATA[Extensible JSON Encoder Using Single-dispatch Functions]]></title>
  276. <link href="http://treyhunner.com/2013/09/singledispatch-json-serializer/"/>
  277. <updated>2013-09-27T00:00:00-07:00</updated>
  278. <id>http://treyhunner.com/2013/09/singledispatch-json-serializer</id>
  279. <content type="html"><![CDATA[<p>Single-dispatch generic functions will be added to Python 3.4 (as proposed in <a href="http://www.python.org/dev/peps/pep-0443/">PEP 443</a>). When reading about single-dispatch functions I immediately thought of the difficulties I&rsquo;ve had with custom JSON encoders. Below I explain why custom JSON encoders can complicate your application and how single-dispatch functions could be used to create a simpler JSON encoder.</p>
  280. <h2>JSON Encoding using the json library</h2>
  281. <p>With the current Python <code>json</code> library, using an extensible JSON encoder in your generic application may require some/all of the following:</p>
  282. <ul>
  283. <li>Allowing specification of custom encoder class by client applications</li>
  284. <li>Overriding the default JSON encoder class (or a client-specified one) for any further extensions</li>
  285. <li>Passing JSON encoder classes into other serialization libraries used by your application</li>
  286. </ul>
  287. <h3>Combining JSON encoders</h3>
  288. <p>If you need to compose two custom JSON encoders specified in two different packages, you may need to:</p>
  289. <ul>
  290. <li>Use multiple inheritance and hope the encoders play nicely together</li>
  291. <li>Duplicate code from one of the packages and create a new serializer with single inheritance</li>
  292. <li>Monkey patch one or both of the libraries</li>
  293. </ul>
  294. <h2>JSON encoder using single-dispatch generic functions</h2>
  295. <p>I created a wrapper around the <code>json</code> library to make a JSON encoder using single-dispatch generic functions. Here&rsquo;s how to use it:</p>
  296. <div><script src='https://gist.github.com/6734816.js?file=example.py'></script>
  297. <noscript><pre><code>from decimal import Decimal
  298. from json_singledispatch import encode
  299. @encode.register(set)
  300. def encode_set(obj):
  301. return encode(list(obj))
  302. @encode.register(Decimal)
  303. def encode_dict(obj):
  304. return encode(str(obj))
  305. print encode({'key': &quot;value&quot;})
  306. print encode({5, 6})
  307. print encode(Decimal(&quot;5.6&quot;))</code></pre></noscript></div>
  308. <p>As you can see, it&rsquo;s fairly easy to extend the encoder to understand serialization rules for new data types.</p>
  309. <p>The impementation is fairly simple, albeit a hack:</p>
  310. <div><script src='https://gist.github.com/6734816.js?file=json_singledispatch.py'></script>
  311. <noscript><pre><code>import json
  312. from singledispatch import singledispatch
  313. class _CustomEncoder(json.JSONEncoder):
  314. def default(self, obj):
  315. for obj_type, handler in encode.registry.items():
  316. if isinstance(obj, obj_type) and obj_type is not object:
  317. return handler(obj)
  318. return super(_CustomEncoder, self).default(obj)
  319. @singledispatch
  320. def encode(obj, **kwargs):
  321. return json.dumps(obj, cls=_CustomEncoder, **kwargs)</code></pre></noscript></div>
  322. <p>This code is intended as a proof-of-concept to demonstrate the power of single-dispatch generic functions. Feel free to use it however you like.</p>
  323. <h2>Related Links</h2>
  324. <ul>
  325. <li><a href="http://lukasz.langa.pl/8/single-dispatch-generic-functions/">What single-dispatch generic functios mean for you</a></li>
  326. <li><a href="http://julien.danjou.info/blog/2013/python-3.4-single-dispatch-generic-function">Python 3.4 single dispatch, a step into generic functions</a></li>
  327. </ul>
  328. ]]></content>
  329. </entry>
  330. <entry>
  331. <title type="html"><![CDATA[Test-Inspired Development]]></title>
  332. <link href="http://treyhunner.com/2013/07/test-inspired-development/"/>
  333. <updated>2013-07-28T00:00:00-07:00</updated>
  334. <id>http://treyhunner.com/2013/07/test-inspired-development</id>
  335. <content type="html"><![CDATA[<p>You need to write tests for your code to demonstrate that it works as expected. In this post I will note the method I usually use for writing code and writing tests.</p>
  336. <p>I follow a method of development similar to test-driven development, which I call &ldquo;test-inspired development&rdquo;. Here&rsquo;s roughly what I do:</p>
  337. <ol>
  338. <li>Write a bit of code</li>
  339. <li>Write a bit of a unit test for your code</li>
  340. <li>Ensure the test succeeds, but fails when the code is commented out or git stash-ed</li>
  341. <li>Repeat steps 1, 2, and 3 as needed</li>
  342. <li>Start writing an integration test, fleshing out as far as you can</li>
  343. <li>Make sure the integration test fails at the correct point</li>
  344. <li>Repeat steps 5 and 6 as needed</li>
  345. <li>Make sure no commit you make along the way contains both tests and code</li>
  346. <li>Use git rebase if necessary to ensure the test commits come before the code commits</li>
  347. </ol>
  348. <p>Ideally I would write all of my test code first and then write my application code and ensure my tests pass. That isn&rsquo;t what usually happens though. I often find it tricky to write full tests before writing any code. Even when writing code in small parts, sometimes my code gets written before the corresponding test code.</p>
  349. <p>I write all of my code in feature branches (short-lived branches forked from master) and I often create a pull request (even against my own repository) when creating a new feature. Because I commit the tests first, a code reviewer can easily confirm that all the new tests actually fail correctly without the code present by simply rolling back to the last test commit and running the tests.</p>
  350. <p>I usually write my unit tests first and at or near the same time as my code because they help often help shape my code heavily. The functional/integration tests are important for ensuring that the application actually works all the way through (working parts are useless on their own).</p>
  351. <p>Have questions about my testing methodology? Do you use a different technique? Feel free to share your thoughts in the comments below.</p>
  352. ]]></content>
  353. </entry>
  354. <entry>
  355. <title type="html"><![CDATA[Maintaining an Open Source Project]]></title>
  356. <link href="http://treyhunner.com/2013/07/maintaining-an-open-source-project/"/>
  357. <updated>2013-07-21T00:00:00-07:00</updated>
  358. <id>http://treyhunner.com/2013/07/maintaining-an-open-source-project</id>
  359. <content type="html"><![CDATA[<p>This post contains some of my opinions on how to maintain an open source project well. I&rsquo;ve built up my opinions on this topic from maintaining and contributing to open source projects. Keep in mind that I am not an expert on this topic.</p>
  360. <h2>Structure your project logically</h2>
  361. <p>You shouldn&rsquo;t need to worry about structuring your project because others have done that work for you. <strong>Base your project&rsquo;s design on other well-organized projects.</strong></p>
  362. <p>I compiled some tips I try to follow for open source Django projects I maintain in my <a href="https://github.com/treyhunner/django-skeleton-app">django-skeleton-app</a> repository. Check it out if you&rsquo;re starting a new Python project.</p>
  363. <h2>Make it very easy to contribute</h2>
  364. <p>Document your process for contributing code, filing bugs, and maintaining other feedback. Reference this process in your README file, your documentation, and your <a href="https://github.com/blog/1184-contributing-guidelines">CONTRIBUTING</a> file. If you need a place to discuss project development outside of the issue tracker, consider creating a mailing list.</p>
  365. <h2>Be kind</h2>
  366. <p>Thank your contributors, appologize if you make a big mistake or say something rude, and be very courteous. Add all your contributors to the AUTHORS file and grant push access to active contributors.</p>
  367. <h2>Be Responsive</h2>
  368. <p>Don&rsquo;t have time to look over a new pull request this week? Make a comment noting when you&rsquo;ll give your feedback. This way your contributor can add a friendly reminder later without feeling rude.</p>
  369. <p>Realized you forgot to respond to a 12 month old pull request? Respond right now and ask whether the code or problem is still relevant. A late response is better than none at all.</p>
  370. <p>Don&rsquo;t know if you&rsquo;ll ever have time to look at an issue? Say so! <strong>Do not leave your contributors in the dark!</strong></p>
  371. <h2>Automate all the things</h2>
  372. <p>Use:</p>
  373. <ul>
  374. <li><a href="https://travis-ci.org/">Travis CI</a>: run continuous integration tests for all pushes and pull requests</li>
  375. <li><a href="https://coveralls.io/">Coveralls</a>: measure code coverage while running your CI tests</li>
  376. <li>A documentation site like <a href="https://readthedocs.org/">Read The Docs</a> which auto-updates on pushes</li>
  377. </ul>
  378. <h2>Learn from others</h2>
  379. <p>I&rsquo;m not the only one with opinions about open source.</p>
  380. <p>Read more opinions on maintaining an open source project:</p>
  381. <ul>
  382. <li><a href="https://segment.io/blog/tips-for-maintaining-an-open-source-library/">Tips for maintaining an Open-Source library</a></li>
  383. <li><a href="http://www.codesimplicity.com/post/open-source-community-simplified/">Open Source Community, Simplified</a></li>
  384. <li><a href="https://hacks.mozilla.org/2013/05/how-to-spread-the-word-about-your-code/">How to Spead The Word About Your Code</a></li>
  385. </ul>
  386. <p>Watch some videos about maintaining an open source project:</p>
  387. <ul>
  388. <li><a href="http://www.youtube.com/watch?v=xgWFTrXn0_U">Maintaining Your Sanity While Maintaining Your Open Source App</a></li>
  389. <li><a href="http://pyvideo.org/video/1795/write-the-docs">Write the Docs</a></li>
  390. </ul>
  391. ]]></content>
  392. </entry>
  393. <entry>
  394. <title type="html"><![CDATA[Log All Outgoing Emails in Django]]></title>
  395. <link href="http://treyhunner.com/2013/05/django-email-log/"/>
  396. <updated>2013-05-20T00:00:00-07:00</updated>
  397. <id>http://treyhunner.com/2013/05/django-email-log</id>
  398. <content type="html"><![CDATA[<p>Ever needed to determine whether an email was sent from a Django project? I
  399. made a Django application that does exactly that: <a href="https://github.com/treyhunner/django-email-log">django-email-log</a>.</p>
  400. <p>I got the idea from <a href="http://stackoverflow.com/a/7553759/98187">a StackOverflow answer</a> and I decided to make a real
  401. application out of it. All emails are stored in a single model which can
  402. easily be viewed, searched, sorted, and filtered from the admin site.</p>
  403. <p>I used test-driven development when making the app and I baked in Python 3
  404. support from the beginning. I found the process of TDD for a standalone
  405. Python package fairly easy and enjoyable.</p>
  406. ]]></content>
  407. </entry>
  408. <entry>
  409. <title type="html"><![CDATA[Django-simple-history Is Back]]></title>
  410. <link href="http://treyhunner.com/2013/05/django-simple-history/"/>
  411. <updated>2013-05-01T00:00:00-07:00</updated>
  412. <id>http://treyhunner.com/2013/05/django-simple-history</id>
  413. <content type="html"><![CDATA[<p>I wrote <a href="http://treyhunner.com/2011/09/django-and-model-history/">a post</a> over a year ago about recording a history of changes for Django model instances. I evaluated three different Django packages to record model history. My favorite of the options, django-simple-history, was abandoned and development continued through multiple forks.</p>
  414. <p>I recently attempted to revive <a href="https://github.com/treyhunner/django-simple-history">django-simple-history</a>. I added tests, put it <a href="https://pypi.python.org/pypi/django-simple-history/">on PyPI</a>, and made it easier to use with newer versions of Django. I moved my fork of the project to git and Github, added Travis and Coveralls support for continuous integration and code coverage tracking, and noted future features on the issue tracker.</p>
  415. <p>Soon after I started writing tests for the project I received feature requests, issues, pull requests, and emails with words of encouragement. I appreciate all of the help I&rsquo;ve had while reviving the project. I plan to remain responsive to the suggestions for my fork of the code. If you&rsquo;d like to help out with the project please feel free to submit an issue, make a pull request, or comment on the code commits on the <a href="https://github.com/treyhunner/django-simple-history">Github page</a>.</p>
  416. ]]></content>
  417. </entry>
  418. <entry>
  419. <title type="html"><![CDATA[Random Name Generator Website]]></title>
  420. <link href="http://treyhunner.com/2013/03/pseudorandom.name/"/>
  421. <updated>2013-03-10T00:00:00-08:00</updated>
  422. <id>http://treyhunner.com/2013/03/pseudorandom.name</id>
  423. <content type="html"><![CDATA[<p>In my <a href="http://treyhunner.com/2013/02/random-name-generator/">last post</a> I discussed the <strong>names</strong> Python library that generates
  424. random names. I&rsquo;ve now used this Python library to make a basic Flask website
  425. that generates random names. You can visit it at:
  426. <a href="http://www.pseudorandom.name">http://www.pseudorandom.name</a></p>
  427. <h2>It&rsquo;s responsive</h2>
  428. <p>The font size and margin on pseudorandom.name change based on the web browser
  429. width and height. I determined the font sizes I wanted to use for each screen
  430. width by using the longest first and last name in the name files I&rsquo;m using (11
  431. and 13 characters respectively). The website looks reasonable on various
  432. desktop resolutions and on phone screens.</p>
  433. <h2>It&rsquo;s an HTTP API</h2>
  434. <p>If curl is used to access the site, plain text is returned instead of an HTML
  435. webpage. For example:</p>
  436. <pre><code>curl www.pseudorandom.name
  437. </code></pre>
  438. <p>The site is hosted on Heroku which <a href="https://devcenter.heroku.com/articles/custom-domains#apex-domains-examplecom">doesn&rsquo;t support bare domains</a>.
  439. Currently <a href="http://pseudorandom.name">http://pseudorandom.name</a> redirects to <a href="http://www.pseudorandom.name">http://www.pseudorandom.name</a> so
  440. using the bare domain requires telling curl to follow redirects with <code>-L</code>:</p>
  441. <pre><code>curl -L pseudorandom.name
  442. </code></pre>
  443. <h2>More to come?</h2>
  444. <p>It might be nice to allow generating multiple names at once, generating
  445. gender-specific names, and maybe providing other content types (JSON). The
  446. HTML version could also use a cleaner, more feature-full design.</p>
  447. <p>Also it would probably be more efficient to use a SQL database for querying
  448. random names so I may eventually abandon the names library and use a database
  449. for querying.</p>
  450. <p>The website&rsquo;s source code is <a href="https://github.com/treyhunner/pseudorandom.name">hosted on Github</a> and provided under an
  451. <a href="http://th.mit-license.org/2013">MIT license</a>. Feel free to fork it or submit an issue.</p>
  452. ]]></content>
  453. </entry>
  454. <entry>
  455. <title type="html"><![CDATA[Random Name Generator in Python]]></title>
  456. <link href="http://treyhunner.com/2013/02/random-name-generator/"/>
  457. <updated>2013-02-17T00:00:00-08:00</updated>
  458. <id>http://treyhunner.com/2013/02/random-name-generator</id>
  459. <content type="html"><![CDATA[<p>I&rsquo;ve used multiple websites to generate random names for my test data when
  460. running manual or automated QA tests.</p>
  461. <p>Since discovering DuckDuckGo&rsquo;s <a href="https://duckduckgo.com/?q=random+word">random word</a> generation, I&rsquo;ve
  462. <a href="https://duckduckhack.uservoice.com/forums/5168-ideas-for-duckduckgo-instant-answer-plugins/suggestions/2850418-random-name">hoped</a> that someone would make a DuckDuckGo plugin to generate
  463. random names also.</p>
  464. <p>I didn&rsquo;t want to write my own DuckDuckGo plugin yet so I made a Python-powered
  465. command line tool instead using <a href="https://www.census.gov/genealogy/www/data/1990surnames/index.html">1990 Census data</a>.</p>
  466. <p>The program is called <code>names</code> and can be found on <a href="https://github.com/treyhunner/names">Github</a> and <a href="http://pypi.python.org/pypi/names/0.1">PyPI</a>.</p>
  467. <h3>It&rsquo;s really simple</h3>
  468. <p>It&rsquo;s basically just one file currently that&rsquo;s <a href="https://github.com/treyhunner/names/blob/f99542dc21f48aa82da4406f8ce408e92639430d/names/__init__.py">about 40 lines long</a>.
  469. There is only one feature available from the command line currently: generate a
  470. single random full name. There&rsquo;s a few more features if importing as a Python
  471. package: generate random last name or generate random first name (with or
  472. without specifying gender), generate random full name (also without or without
  473. gender).</p>
  474. <p>The random name picker relies on the cumulative frequencies listed in the
  475. included Census data files. Here&rsquo;s the steps that are taken:
  476. 1. A random floating point number is chosen between 0.0 and 90.0
  477. 2. Name file lines are iterated through until a cumulative frequency is found
  478. that is less than the randomly generated number
  479. 3. The name on that line is chosen and returned (or printed out)</p>
  480. <h3>Examples</h3>
  481. <p>Here&rsquo;s how you use it from the command line:</p>
  482. <pre><code>$ names
  483. Kara Lopes
  484. </code></pre>
  485. <p>Here&rsquo;s how you use it as a Python package:</p>
  486. <pre><code>&gt;&gt;&gt; import names
  487. &gt;&gt;&gt; names.get_full_name()
  488. u'Patricia Halford'
  489. &gt;&gt;&gt; names.get_full_name(gender='male')
  490. u'Patrick Keating'
  491. &gt;&gt;&gt; names.get_first_name()
  492. 'Bernard'
  493. &gt;&gt;&gt; names.get_first_name(gender='female')
  494. 'Christina'
  495. &gt;&gt;&gt; names.get_last_name()
  496. 'Szczepanek'
  497. </code></pre>
  498. ]]></content>
  499. </entry>
  500. <entry>
  501. <title type="html"><![CDATA[Tmuxstart]]></title>
  502. <link href="http://treyhunner.com/2012/12/tmuxstart/"/>
  503. <updated>2012-12-15T00:00:00-08:00</updated>
  504. <id>http://treyhunner.com/2012/12/tmuxstart</id>
  505. <content type="html"><![CDATA[<p>I&rsquo;ve been using a helper script to manage all of my tmux sessions for the last
  506. few months (nearly since the time I switched from screen to tmux).</p>
  507. <h3>A tmuxinator alternative</h3>
  508. <p>I use a separate session for each project I work on and I wanted a way to
  509. easily connect to project sessions with the same default windows and
  510. environment variables each time. I tried using <a href="https://github.com/aziz/tmuxinator">tmuxinator</a>, but I found it
  511. to be too complicated for my needs. I had trouble working within the
  512. limitations of the yaml files and I found the installation to be overly
  513. complicated (especially when working on a new server that didn&rsquo;t have ruby
  514. installed). After I realized how simple it would be to solve my own problem I
  515. made the tmuxstart script.</p>
  516. <h3>Now available on Github</h3>
  517. <p>While updating my <a href="https://github.com/treyhunner/dotfiles">dotfiles</a> repository I realized I hadn&rsquo;t yet incorporated
  518. my tmuxstart workflow into my <code>.tmux.conf</code> file. Yesterday I decided to make
  519. README and LICENSE files for my script and put it under version control. I
  520. also added some helper functions to make the script even easier to use. It&rsquo;s
  521. now available in my <a href="https://github.com/treyhunner/tmuxstart">tmuxstart</a> repository on Github.</p>
  522. <h3>Example usage</h3>
  523. <p>Below is an example of how to use tmuxstart.</p>
  524. <ol>
  525. <li>Add the following line to your <code>~/.tmux.conf</code> file:</li>
  526. </ol>
  527. <figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
  528. </pre></td><td class='code'><pre><code class=''><span class='line'>bind S command-prompt -p "Make/attach session:" "new-window 'tmuxstart \'%%\''"</span></code></pre></td></tr></table></div></figure>
  529. <ol>
  530. <li>Create a <code>~/.tmuxstart</code> directory:</li>
  531. </ol>
  532. <figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
  533. </pre></td><td class='code'><pre><code class=''><span class='line'>mkdir ~/.tmuxstart</span></code></pre></td></tr></table></div></figure>
  534. <ol>
  535. <li>Create a file <code>~/.tmuxstart/main</code> with the following contents:</li>
  536. </ol>
  537. <figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
  538. <span class='line-number'>2</span>
  539. </pre></td><td class='code'><pre><code class=''><span class='line'>new_session htop
  540. </span><span class='line'>new_window</span></code></pre></td></tr></table></div></figure>
  541. <p>Now you can create a tmux session called &ldquo;main&rdquo; with <a href="http://htop.sourceforge.net/">htop</a> in the first
  542. window and a shell in the second window by executing:</p>
  543. <pre><code>tmuxstart main
  544. </code></pre>
  545. <p>Or from an existing tmux server type <code>&lt;PREFIX&gt; S</code> (<code>&lt;PREFIX&gt;</code> is Ctrl-B by
  546. default), type <code>main</code> and hit Enter.</p>
  547. <p>When using either of these methods if a session called &ldquo;main&rdquo; already exists
  548. the existing session will be used instead.</p>
  549. <h3>How I use this</h3>
  550. <p>I have a separate tmuxstart session file for each of my Django projects and
  551. some of my open source projects also. I use a &ldquo;main&rdquo; session file similar to
  552. the one above and I have Gnome Terminal set to start with the custom command
  553. &ldquo;tmuxstart main&rdquo; instead of a shell.</p>
  554. ]]></content>
  555. </entry>
  556. <entry>
  557. <title type="html"><![CDATA[Maintaining Consistent Coding Conventions With EditorConfig]]></title>
  558. <link href="http://treyhunner.com/2012/02/editorconfig/"/>
  559. <updated>2012-02-17T00:00:00-08:00</updated>
  560. <id>http://treyhunner.com/2012/02/editorconfig</id>
  561. <content type="html"><![CDATA[<p>I often have indentation issues when coding because I work with many open
  562. source (and closed source) projects that have different identation styles. For
  563. example, my <a href="https://github.com/treyhunner/jquery-formrestrict">jQuery formrestrict</a> uses 4 spaces for indentation,
  564. <a href="https://github.com/kswedberg/jquery-expander">jQuery expander</a> uses 2 spaces, and <a href="https://github.com/gbirke/jquery_pagination">jQuery pagination</a> uses hard tabs.
  565. I don&rsquo;t want to change my text editor&rsquo;s indentation settings every time I open
  566. one of these files and using a plugin to auto-detect indentation is only a
  567. partial solution. To solve this problem I started a project I call
  568. EditorConfig.</p>
  569. <p><a href="http://editorconfig.org">EditorConfig</a> defines coding conventions for groups of files and instructs
  570. text editors to adhere to these conventions. To solve my problem, I just
  571. create a file called <code>.editorconfig</code> that sets indentation for my files and
  572. with my EditorConfig plugin installed Vim takes care of the rest. Here&rsquo;s an
  573. example <code>.editorconfig</code> file I could add to my project that uses
  574. <a href="https://github.com/kswedberg/jquery-expander">jQuery expander</a> and <a href="https://github.com/gbirke/jquery_pagination">jQuery pagination</a>:</p>
  575. <figure class='code'><figcaption><span>.editorconfig</span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
  576. <span class='line-number'>2</span>
  577. <span class='line-number'>3</span>
  578. <span class='line-number'>4</span>
  579. <span class='line-number'>5</span>
  580. <span class='line-number'>6</span>
  581. <span class='line-number'>7</span>
  582. <span class='line-number'>8</span>
  583. <span class='line-number'>9</span>
  584. <span class='line-number'>10</span>
  585. <span class='line-number'>11</span>
  586. </pre></td><td class='code'><pre><code class='ini'><span class='line'><span class="k">[*.js]</span>
  587. </span><span class='line'><span class="na">indent_style</span> <span class="o">=</span> <span class="s">space</span>
  588. </span><span class='line'><span class="na">indent_size</span> <span class="o">=</span> <span class="s">4</span>
  589. </span><span class='line'>
  590. </span><span class='line'><span class="k">[jquery.expander.js]</span>
  591. </span><span class='line'><span class="na">indent_style</span> <span class="o">=</span> <span class="s">space</span>
  592. </span><span class='line'><span class="na">indent_size</span> <span class="o">=</span> <span class="s">2</span>
  593. </span><span class='line'>
  594. </span><span class='line'><span class="k">[jquery.pagination.js]</span>
  595. </span><span class='line'><span class="na">indent_style</span> <span class="o">=</span> <span class="s">tab</span>
  596. </span><span class='line'><span class="na">indent_size</span> <span class="o">=</span> <span class="s">4</span>
  597. </span></code></pre></td></tr></table></div></figure>
  598. <p>With this <code>.editorconfig</code> file, all JavaScript files use 4 spaces for
  599. indentation except for <code>jquery.expander.js</code> which uses 2 spaces and
  600. <code>jquery.pagination.js</code> which uses hard tabs with a column width of 4. If I put
  601. my <code>.editorconfig</code> file under version control, other developers working on my
  602. project can see the coding conventions I defined and if their text editor has
  603. an EditorConfig plugin installed their editor will automatically use the
  604. correct indentation as well.</p>
  605. <p>Example EditorConfig files can be seen in my own projects (such as
  606. <a href="https://github.com/treyhunner/dotfiles/blob/master/.editorconfig">in my dotfiles repo</a>) and in the
  607. <a href="https://github.com/treyhunner/dotfiles/blob/master/.editorconfig">various EditorConfig plugin codebases</a>. More
  608. information on EditorConfig can be found at the
  609. <a href="http://editorconfig.org">EditorConfig website</a>.</p>
  610. <p>EditorConfig plugins are <a href="http://editorconfig.org/#download">available for 6 different editors</a>
  611. now. If you like the idea, <a href="http://editorconfig.org">try out EditorConfig</a> for your
  612. favorite editor and <a href="https://groups.google.com/forum/?fromgroups#!forum/editorconfig">tell us what you think</a>. If you don&rsquo;t like
  613. the idea or have a problem with EditorConfig please <a href="https://github.com/editorconfig/editorconfig/issues">submit an issue</a>,
  614. add a thread to <a href="https://groups.google.com/forum/?fromgroups#!forum/editorconfig">the mailing list</a>, or send me an email voicing
  615. your concern.</p>
  616. ]]></content>
  617. </entry>
  618. <entry>
  619. <title type="html"><![CDATA[Migrating From Subversion to Git]]></title>
  620. <link href="http://treyhunner.com/2011/11/migrating-from-subversion-to-git/"/>
  621. <updated>2011-11-17T00:00:00-08:00</updated>
  622. <id>http://treyhunner.com/2011/11/migrating-from-subversion-to-git</id>
  623. <content type="html"><![CDATA[<p>I recently migrated multiple Subversion repositories to Git. I found
  624. <a href="http://blog.woobling.org/2009/06/git-svn-abandon.html" title="Migrating from Subversion to Git">this blog post</a> very helpful during the process. Here are
  625. some tips I found helpful that were not mentioned in that post.</p>
  626. <h3>Generating an authors file</h3>
  627. <p>Subversion denotes authors by usernames while Git uses name and email. An
  628. authors file can be used to map subversion usernames to Git authors when
  629. creating the Git repository, like so:</p>
  630. <pre><code>git-svn clone --stdlayout --authors-file=authors.txt http://your/svn/repo/url
  631. </code></pre>
  632. <p>The trickest part about <a href="http://triptico.com/n…

Large files files are truncated, but you can click here to view the full file