<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jamie Read</title>
    <description>The latest articles on Forem by Jamie Read (@jamoyjamie).</description>
    <link>https://forem.com/jamoyjamie</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F20271%2F91764d3c-32c6-41d7-a974-d133446eb23d.jpg</url>
      <title>Forem: Jamie Read</title>
      <link>https://forem.com/jamoyjamie</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jamoyjamie"/>
    <language>en</language>
    <item>
      <title>Introducing Japr - The Project Linter</title>
      <dc:creator>Jamie Read</dc:creator>
      <pubDate>Sun, 21 Jan 2024 20:59:55 +0000</pubDate>
      <link>https://forem.com/jamoyjamie/introducing-japr-the-project-linter-5fmj</link>
      <guid>https://forem.com/jamoyjamie/introducing-japr-the-project-linter-5fmj</guid>
      <description>&lt;p&gt;Japr is a cross-language tool for rating and enforcing the overall quality of projects by looking at tool &amp;amp; language setup&lt;/p&gt;

&lt;p&gt;Over summarized: It's a linter that makes sure you install linters (and some other stuff)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/JamJar00/japr"&gt;GitHub&lt;/a&gt; | &lt;a href="https://pypi.org/project/Japr/"&gt;PyPi&lt;/a&gt; | &lt;a href="https://hub.docker.com/r/jamoyjamie/japr"&gt;Docker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hm6alkr11c4pn2wzmjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hm6alkr11c4pn2wzmjq.png" alt="Screenshot of a report" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Japr
&lt;/h2&gt;

&lt;p&gt;Japr is a linter to help you make sure your project is setup as best as it can be. It checks things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you have a readme? Does it have a getting started section or some installation docs?&lt;/li&gt;
&lt;li&gt;Have you setup a linter for your language? Are you following the best practices for setting up those languages?&lt;/li&gt;
&lt;li&gt;Is git setup correctly? Using &lt;code&gt;main&lt;/code&gt; instead of &lt;code&gt;master&lt;/code&gt; and excluding the right files?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Obviously project setup depends highly on the project's purpose so Japr has four configurations available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personal - A lightweight ruleset designed to tidy up personal projects&lt;/li&gt;
&lt;li&gt;Team - A balanced ruleset designed for projects that belong to a single team&lt;/li&gt;
&lt;li&gt;Inner Source - A comprehensive ruleset for projects that are accessible across an organisation and may be used by other teams (often referred to as 'inner source' projects&lt;/li&gt;
&lt;li&gt;Open Source - A comprehensive ruleset for open source projects that are for anyone and everyone that might stumble upon the code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Japr is a very strict linter, it's opinionated and hard to satisfy so don't be upset if your project doesn't pass straight away! I don't have a single project that meets all of Japr's demands (even, ironically, Japr itself). Make use of the auto fixes and don't be afraid to suppress things if you disagree!&lt;/p&gt;

&lt;h2&gt;
  
  
  Rationale
&lt;/h2&gt;

&lt;p&gt;Think about your favourite open source project. Chances are it's very easy to get started with because you just looked at the readme and followed the steps. Perhaps you've raised an issue and they'd setup issue templates to help you help them. Perhaps you committed some code, ran their linters and their CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;Now think about a time when the codebase hurt you, maybe that proof-of-concept code you inherited from a coworker full of &lt;code&gt;.DS_Store&lt;/code&gt; files or that GitHub project that might have been perfect for you if you could actually build it.&lt;/p&gt;

&lt;p&gt;A lot of the differences between those two projects probably seem like common sense. Create a readme, setup CI/CD, create a &lt;code&gt;.gitignore&lt;/code&gt; but there's plenty more things we take for granted in the best projects.&lt;/p&gt;

&lt;p&gt;We lint basic things like our code formatting which could also be fixed with common sense, why not lint our project setup?&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;You can install Japr using pip and run as you expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;japr
japr &amp;lt;path&amp;gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;project-type&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just run it in Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/app jamoyjamie/japr:v1.0.1 &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;project-type&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Project type should be one of &lt;code&gt;personal&lt;/code&gt;, &lt;code&gt;team&lt;/code&gt;, &lt;code&gt;inner-source&lt;/code&gt; or &lt;code&gt;open-source&lt;/code&gt; depending on what best suits your project!&lt;/p&gt;

&lt;p&gt;There's also experimental auto fixing for some issues which you can try by adding &lt;code&gt;--fix&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftperz4ue49n0hjqdw020.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftperz4ue49n0hjqdw020.gif" alt="Animation of the tool usage" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Future
&lt;/h2&gt;

&lt;p&gt;I'll be using Japr on new projects and retrofitting old projects as I work on them again. I intend to add support for the other languages and tools I use as I go (e.g. terraform, helm) but I'll gladly accept advice, discussion and pull requests for other languages or general debate into the rulesets so that they represent a consensus.&lt;/p&gt;

&lt;p&gt;Please give it a try, feedback and let me know what your highest project scores are!&lt;/p&gt;

</description>
      <category>japr</category>
      <category>linting</category>
    </item>
    <item>
      <title>Pyash (and why I can't have nice things anymore)</title>
      <dc:creator>Jamie Read</dc:creator>
      <pubDate>Mon, 06 Jan 2020 23:47:10 +0000</pubDate>
      <link>https://forem.com/jamoyjamie/pyash-and-why-i-can-t-have-nice-things-anymore-26lo</link>
      <guid>https://forem.com/jamoyjamie/pyash-and-why-i-can-t-have-nice-things-anymore-26lo</guid>
      <description>&lt;p&gt;A few months back an innocent thought came into my mind:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can you make Python run this bash statement...?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cat "kittens.txt" | grep "fluff" &amp;gt; fluffy_kittens.txt&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes. Well nearly. But, mostly yes.&lt;/p&gt;

&lt;h1&gt;
  
  
  An Adventure Begins
&lt;/h1&gt;

&lt;p&gt;The Saturday after my innocent thought was spent trying to get as close as possible to making Python run the above line.&lt;/p&gt;

&lt;p&gt;Most users probably see &lt;code&gt;cat "kittens.txt"&lt;/code&gt; and think it isn't in the slightest syntactically correct and would be completely impossible to get to compile. You would be right actually. I never worked this one out.&lt;/p&gt;

&lt;p&gt;So I lowered my goal a little:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kittens.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fluff"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fluffy_kittens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is a little more reasonable! Now we have simple, Pythonic function calls surrounded by errors!&lt;/p&gt;

&lt;p&gt;Let's start.&lt;/p&gt;

&lt;p&gt;The first thing to do is to make &lt;code&gt;cat&lt;/code&gt; and &lt;code&gt;grep&lt;/code&gt; functions that we can call here, since we're obviously making function calls. I really wanted the ability to run &lt;em&gt;any&lt;/em&gt; executable on my PC, just like you can in bash, because I really like that aspect of shell scripting, so I wrote something along the lines of this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"PATH"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;";"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;executable_path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;*.exe"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;splitext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executable_path&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="nb"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;executable_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt; "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;(I work on a Windows machine with Git Bash, hence the search for &lt;code&gt;.exe&lt;/code&gt; files)&lt;/p&gt;

&lt;p&gt;It's janky, but this chunk of Python essentially searches through each path in the &lt;code&gt;PATH&lt;/code&gt; variable for executables and creates a lambda function that runs the executable when called. It then adds that to the dictionary of globals so that you can now do things like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kittens.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bed.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or even:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"world-domination.py"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Great!&lt;/p&gt;

&lt;p&gt;And it was at this point I realised that the &lt;code&gt;print&lt;/code&gt; function had been overwritten to invoke &lt;code&gt;print.exe&lt;/code&gt; instead...&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sigh&lt;/em&gt; 😒&lt;/p&gt;
&lt;h1&gt;
  
  
  Frankenstein's Python
&lt;/h1&gt;

&lt;p&gt;This is a great start. At this point I actually moved that code into a library of its own so that you can import the functions individually (it helped with the whole &lt;code&gt;print&lt;/code&gt; issue!)&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;shell&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kubectl&lt;/span&gt; &lt;span class="c1"&gt;# not you print
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's look at a harder example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"processes.txt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In bash, this would take the output of the &lt;code&gt;ps&lt;/code&gt; command and put it in the specified text file.&lt;/p&gt;

&lt;p&gt;You'll notice that I've enclosed the name of the file in quotes, it's still valid bash so it doesn't count as cheating... 👀&lt;/p&gt;

&lt;p&gt;This isn't so hard. In normal Python, this just gets evaluated as: &lt;em&gt;is the result of &lt;code&gt;ps()&lt;/code&gt; greater than the string "processes.txt"? True or False?&lt;/em&gt; If we return a custom object from &lt;code&gt;ps()&lt;/code&gt; we can then &lt;em&gt;overload&lt;/em&gt; the greater than operator to be able to compare itself with strings. (&lt;em&gt;Overloading&lt;/em&gt; operators is essentially just a fancy term for redefining them with a custom implementation, so Python will use our implemention for performing greater than comparisons rather than its normal one.)&lt;/p&gt;

&lt;p&gt;Here's a simple class we can return from &lt;code&gt;ps()&lt;/code&gt; that does it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EvaluationResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__gt__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this case we've overridden the greater than operator to write to a file of the name taken from the string it's comparing against.&lt;/p&gt;

&lt;p&gt;I won't show my full code, because at this point I switched to using &lt;code&gt;subprocess.Popen&lt;/code&gt; instead of &lt;code&gt;os.system&lt;/code&gt; to run the executables, and that's a whole other ropic I don't want to explain here.&lt;/p&gt;

&lt;p&gt;Let's throw in a different example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here we want to output the result of &lt;code&gt;ps&lt;/code&gt; to the console but with the above code you'll just get the memory address of our &lt;code&gt;EvaluationResult&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;__main__.EvaluationResult object at 0x03783030&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;An easy fix, we just need to overload &lt;code&gt;__str__&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EvaluationResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__gt__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;(&lt;code&gt;__str__&lt;/code&gt; is called when Python wants a string representation of an object, you can override this to provide better visibility into your classes while debuging if you like!)&lt;/p&gt;

&lt;p&gt;So how about our original example then?&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kittens.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fluff"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fluffy_kittens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The pipe operator (&lt;code&gt;|&lt;/code&gt;, normally or) is also available to overload in Python, so all we need to do is overload it to pass the output of our first executable to the input of the second.&lt;/p&gt;

&lt;p&gt;Unfortunately, Python, like most languages, executes &lt;code&gt;cat("kittens.txt")&lt;/code&gt; first, then executes &lt;code&gt;grep("fluff")&lt;/code&gt;, and then finally executes the or. By the time we get to our overloaded or operator we'll have already executed the second command and wont be able to pass it the output from the first (in actual fact, &lt;code&gt;grep&lt;/code&gt; will have hung waiting for input, so the or overload we write won't ever be called here anyway, &lt;em&gt;doh&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;The only solution I came up with for this was to lazily run the executables underneath the functions, that is to say that when you run &lt;code&gt;cat("kittens.txt")&lt;/code&gt; it doesn't actually run the &lt;code&gt;cat&lt;/code&gt; executable until the or gets evaluated later, at which point we know where it's output goes.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LazyInvocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__gt__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__or__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_output&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# actually run the command, passing self._input in and assigning self._output
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It makes everything a little wierd to run now though, because:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Won't actually run &lt;code&gt;ps&lt;/code&gt; anymore...&lt;/p&gt;
&lt;h1&gt;
  
  
  Pyash
&lt;/h1&gt;

&lt;p&gt;By the end of my Saturday, I had a nice set of bash syntax implemented!&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyash&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rm&lt;/span&gt;

&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"animals.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"caterpillar"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"nice_things.txt"&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"*sunset*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"nice_things.txt"&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"22"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"nice_things.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;rm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nice_things.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So I present to you my first PyPI repository, Pyash!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/JamJar00"&gt;
        JamJar00
      &lt;/a&gt; / &lt;a href="https://github.com/JamJar00/pyash"&gt;
        pyash
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Shell scripting... but in glorious Python!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Pyash&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://badge.fury.io/py/pyash" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/c2d16d5c1a4f40046c0e0ec22c8f17eeade28d92172edd90f70dbac84b7cc91b/68747470733a2f2f62616467652e667572792e696f2f70792f70796173682e737667" alt="PyPI version"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hate shell scripting? Love Python! Meet the horrible Frankenstein of the two!&lt;/p&gt;
&lt;p&gt;Pyash takes the best part of shell scripting, the ability to stream data through great chains of shell programs, and adds that ability to Python. In the process it retains the same familiar syntax but in a more Pythonic form!&lt;/p&gt;
&lt;div class="highlight highlight-source-python notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;pyash&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;cat&lt;/span&gt;, &lt;span class="pl-s1"&gt;grep&lt;/span&gt;
&lt;span class="pl-en"&gt;cat&lt;/span&gt;(&lt;span class="pl-s"&gt;".gitignore"&lt;/span&gt;) &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-en"&gt;grep&lt;/span&gt;(&lt;span class="pl-s"&gt;"env"&lt;/span&gt;) &lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt; &lt;span class="pl-s"&gt;"out.txt"&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
Importing Shell Programs&lt;/h2&gt;
&lt;p&gt;Importing shell programs is as easy as&lt;/p&gt;
&lt;div class="highlight highlight-source-python notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;pyash&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;grep&lt;/span&gt;, &lt;span class="pl-s1"&gt;find&lt;/span&gt;, &lt;span class="pl-s1"&gt;bash&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Pyash automatically searches your &lt;code&gt;PATH&lt;/code&gt; variable and loads any executable programs it finds, making them available through import.&lt;/p&gt;
&lt;p&gt;Try to avoid importing &lt;code&gt;*&lt;/code&gt; as you'll find basic builtin functions like &lt;code&gt;print()&lt;/code&gt; suddenly don't work!&lt;/p&gt;
&lt;h2&gt;
Running Shell Programs&lt;/h2&gt;
&lt;p&gt;Once you've imported the programs you want to import you can use them as you would any normal Python function splitting arguments or keeping them…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/JamJar00/pyash"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
('pyash' being 'python-bash', if you didn't get that... 😬)&lt;/p&gt;

&lt;p&gt;I don't recommend you use it; I can't imagine your coworkers would be best pleased with you using it either, and frankly it fails a number of PEP-8 style guidelines. However, it does go to show how much you can abuse Python and what you can do with a little creativity!&lt;/p&gt;

&lt;p&gt;It's not nice, nor is it a good idea and I feel like I've made a very beautiful language into a monster. This is why I can't have nice things.&lt;/p&gt;

&lt;p&gt;I hope you've enjoyed this story, if you have any of your own thoughts of things that could be added to Pyash or ways in which it can abuse Python further please do comment below and/or give them a go yourself!&lt;/p&gt;

</description>
      <category>python</category>
      <category>bash</category>
      <category>showdev</category>
    </item>
    <item>
      <title>GIVEN my article WHEN a reader reads it THEN the reader writes better tests</title>
      <dc:creator>Jamie Read</dc:creator>
      <pubDate>Fri, 22 Mar 2019 23:21:07 +0000</pubDate>
      <link>https://forem.com/jamoyjamie/given-my-article-when-a-reader-reads-it-then-the-reader-writes-better-tests-16df</link>
      <guid>https://forem.com/jamoyjamie/given-my-article-when-a-reader-reads-it-then-the-reader-writes-better-tests-16df</guid>
      <description>&lt;h1&gt;
  
  
  GIVEN a developer who hates writing tests
&lt;/h1&gt;

&lt;p&gt;I'm not one for testing, that's evidenced by my most used side project having around 5% test coverage. I just can't bring myself to slog through and write every test case of the code I'm writing particularly when I don't have much time to work on side projects anymore.&lt;/p&gt;

&lt;h1&gt;
  
  
  WHEN placed in a software house that likes testing
&lt;/h1&gt;

&lt;p&gt;However, in my current employment I'm expected to have 80% code coverage as part of the minimum to calling something 'stable' - hence I actually spend most of my working time writing unit, integration, and system tests.&lt;/p&gt;

&lt;h1&gt;
  
  
  AND he must write lots of tests in his job
&lt;/h1&gt;

&lt;p&gt;Testing is something my company are very good at. We have hundreds of thousands of lines of code spread across hundreds of different repositories across tens of different projects and on any given day I could be working in any part of it.&lt;/p&gt;

&lt;p&gt;The fact of the matter is that the tests save us countless headaches and make the insane amount of context switching significantly easier for us. Because of the high test coverage I know that I can hack and slash at code, and even perform massive refactors if I do so desire, and at the end of my rampage I can run the tests and know within minutes if there has been any changes to the code's behaviour.&lt;/p&gt;

&lt;p&gt;I just don't need to worry about breaking code while I edit it.&lt;/p&gt;

&lt;h1&gt;
  
  
  THEN he learns some neat tricks in testing
&lt;/h1&gt;

&lt;p&gt;This is the point where I tell you about the wonderous world of behaviour driven tests.&lt;/p&gt;

&lt;p&gt;We have a convention to write every test with the GIVEN-WHEN-THEN structure. Here's an example from my side project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestReadStringDeserializesTheDataCorrectly&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// GIVEN a buffer of serialized data&lt;/span&gt;
    &lt;span class="n"&gt;mockMessageBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="n"&gt;mockMessageBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Offset&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;mockMessageBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// WHEN I read a string from the reader&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// THEN the value is as expected&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AreEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ABC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I see this test and immediately know where to look for everything I need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the GIVEN section we setup any mocks we need, load in any test data from disk if we require it, and do any preparation we have to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the WHEN section we run the code being tested (and as best as we possibly can, &lt;em&gt;only&lt;/em&gt; the code being tested) and obtain a result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the THEN section we assert that the behaviour of the code was as we expect.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having this simple structure makes understanding what a test does trivial. I can look through hundreds of tests in development and code review and instantly grasp what a test is doing in a matter of seconds because every test in our company has this same, behaviour driven format.&lt;/p&gt;

&lt;p&gt;We also find this style creeps out into other parts of our tests as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestInitialize&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// GIVEN the object cache is disabled&lt;/span&gt;
    &lt;span class="n"&gt;ObjectCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ObjectCacheSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DontUseCache&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// AND a DarkRiftReader under test&lt;/span&gt;
    &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DarkRiftReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mockMessageBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can start to see how we build more complex tests as well here. It's encouraged that if you do multiple things in any section that you document them with an AND section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestExtraLargeMemoryBlocksArePooledCorrectly&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// GIVEN a memory pool with no previously pooled memory&lt;/span&gt;

    &lt;span class="c1"&gt;// WHEN I return an extra large memory block&lt;/span&gt;
    &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;oldBlock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;memoryPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReturnInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oldBlock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// AND I request a new extra large memory block&lt;/span&gt;
    &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;newBlock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memoryPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// THEN my memory block is the same&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AreSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oldBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newBlock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(I take a shortcut here, I should probably assign &lt;code&gt;oldBlock&lt;/code&gt; in a GIVEN, but then this is really just as understandable)&lt;/p&gt;

&lt;h1&gt;
  
  
  AND can apply them anywhere
&lt;/h1&gt;

&lt;p&gt;It also helps when writing tests!&lt;/p&gt;

&lt;p&gt;Thinking about behaviour is a lot easier in English - that's why things like pseudo code and rubber duck debugging exist - so why not write tests in your native language and then use that as a template?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestGetInstanceUsesPoolWhenHasInstances&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// GIVEN a pool with a single element&lt;/span&gt;

    &lt;span class="c1"&gt;// WHEN I get an instance from the pool&lt;/span&gt;

    &lt;span class="c1"&gt;// THEN a new instance is not generated&lt;/span&gt;

    &lt;span class="c1"&gt;// AND the returned instance is the pooled object&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestGetInstanceUsesPoolWhenHasInstances&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// GIVEN a pool with a single element&lt;/span&gt;
    &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;pooledObject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;objectPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReturnInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pooledObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// WHEN I get an instance from the pool&lt;/span&gt;
    &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objectPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// THEN a new instance is not generated&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newInstanceCaptor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// AND the returned instance is the pooled object&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AreSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pooledObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact order of the GIVEN-WHEN-THEN also doesn't matter, we often find that in the most complex of tests we need to assert behaviour in multiple places (THEN), or perform some step on the class under test (WHEN) before we can continue the setup (GIVEN):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="nt"&gt;@free&lt;/span&gt;
&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;I &lt;/span&gt;can disconnect from a server
    &lt;span class="nf"&gt;Given &lt;/span&gt;I have a running server
    &lt;span class="nf"&gt;And &lt;/span&gt;1 client connected
    &lt;span class="nf"&gt;Then &lt;/span&gt;all clients should be connected
    &lt;span class="nf"&gt;And &lt;/span&gt;the server should have 1 client
    &lt;span class="nf"&gt;When &lt;/span&gt;I disconnect client 0
    &lt;span class="nf"&gt;Then &lt;/span&gt;0 clients should be connected
    &lt;span class="nf"&gt;And &lt;/span&gt;1 client should be disconnected
    &lt;span class="nf"&gt;And &lt;/span&gt;the server should have 0 clients
    &lt;span class="nf"&gt;And &lt;/span&gt;there should be no recycling warnings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;You have no idea what my side project does, I never mentioned it, but you were hopefully still able to understand what these tests did, despite them covering some of the most complex and wacky optimisations I've had to add to it!&lt;/p&gt;

&lt;p&gt;Adopting this behaviour, for me, makes testing significantly less cumbersome. For the developers I work with, it makes understanding my tests faster and easier. And in the merge review we can review the test behaviour using the comments and then check the code against the comments to make reviews of large quantities of tests a little more manageable.&lt;/p&gt;

&lt;p&gt;I hope this article has made you think, and maybe you'll write a quick GIVEN-WHEN-THEN before you write your next test 🙂&lt;/p&gt;

</description>
      <category>testing</category>
      <category>documentation</category>
      <category>teamwork</category>
    </item>
    <item>
      <title>I'm a C# dev that needs to build a web app. Sell me a tech stack.</title>
      <dc:creator>Jamie Read</dc:creator>
      <pubDate>Wed, 22 Aug 2018 23:24:00 +0000</pubDate>
      <link>https://forem.com/jamoyjamie/im-a-c-dev-that-needs-to-build-a-web-app-sell-me-a-tech-stack-2nmm</link>
      <guid>https://forem.com/jamoyjamie/im-a-c-dev-that-needs-to-build-a-web-app-sell-me-a-tech-stack-2nmm</guid>
      <description>&lt;p&gt;I'm interested in building myself a little web app. That's probably a simple task for most people here, but unfortunately when I left the world of web development jQuery was still considered 'pretty neat'.&lt;/p&gt;

&lt;p&gt;So sell me your dream tech stack and tell me why I should build my web app with it.&lt;/p&gt;

&lt;p&gt;For some background into the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Platform-as-a-service for another of my side projects&lt;/li&gt;
&lt;li&gt;Will need to interface into Digital Ocean's API to start and stop servers on the fly&lt;/li&gt;
&lt;li&gt;Will open up to a moderately simple dashboard page showing current resources and deployments&lt;/li&gt;
&lt;li&gt;Will need to have various step-by-step wizards for creating deployments&lt;/li&gt;
&lt;li&gt;Will need to be able to authenticate users, possibly with 2FA in the future&lt;/li&gt;
&lt;li&gt;Will need to interface with some form of payment provider (bonus points for recommendations!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And background on myself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Primarily a C# developer&lt;/li&gt;
&lt;li&gt;Secondarily Python and Java&lt;/li&gt;
&lt;li&gt;Some experience with devops&lt;/li&gt;
&lt;li&gt;Some HTML, JS &amp;amp; CSS experience&lt;/li&gt;
&lt;li&gt;Lots of relational database experience&lt;/li&gt;
&lt;li&gt;Some NoSQL experience (specifically Firebase)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I look forward to reading any suggestions and recommendations people have! Feel free to ask for any more details etc. if you want!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>help</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Hazel Networking</title>
      <dc:creator>Jamie Read</dc:creator>
      <pubDate>Mon, 21 Aug 2017 19:06:35 +0000</pubDate>
      <link>https://forem.com/jamoyjamie/hazel-networking</link>
      <guid>https://forem.com/jamoyjamie/hazel-networking</guid>
      <description>&lt;p&gt;It's been a while since I've done much on it but I feel if there's anything I should showcase here it should be this!&lt;/p&gt;

&lt;h1&gt;
  
  
  Hazel
&lt;/h1&gt;

&lt;p&gt;Hazel is networking library for C# that aims to simplify networking and take away some of the unneeded complexities and poor interfaces of sockets.&lt;/p&gt;

&lt;p&gt;While I've only really advertised it on the Unity game engine forums it's fully compatible with .NET 3.5 and above (though not .NET core yet) so anyone who needs networking in .NET should be able to use it straight off.&lt;/p&gt;

&lt;p&gt;It supports communication over TCP, UDP and its own RUDP and provides 3 major guarantees/abstractions for all of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connection oriented&lt;/li&gt;
&lt;li&gt;Message based&lt;/li&gt;
&lt;li&gt;Thread safe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By providing a standard interface it becomes really trivial to switch protocol and I've aimed to make it as simple and lightweight to use as possible.&lt;/p&gt;

&lt;h1&gt;
  
  
  Code and Packages
&lt;/h1&gt;

&lt;p&gt;You can get the source on &lt;a href="https://github.com/DarkRiftNetworking/Hazel-Networking"&gt;GitHub&lt;/a&gt; or as a NuGet package &lt;a href="https://www.nuget.org/packages/DarkRiftNetworking.Hazel/"&gt;here&lt;/a&gt; (MIT license, yay!) &lt;/p&gt;

&lt;h1&gt;
  
  
  Future (and a little past)
&lt;/h1&gt;

&lt;p&gt;Hazel was a great little project for me and I had a great few weeks trying to invent and implement the RUDP algorithms and make them as efficient as possible. I really recommend it as a challenge to anyone who wants to learn more about networking or get a little experience in algorithmic thinking.&lt;/p&gt;

&lt;p&gt;I originally intended Hazel to be the core of one of my more commercial side projects but sadly I've since changed that to a TCP/UDP socket pair in order to simplify the code base a bit. I still plan on maintaining Hazel as it's pretty much complete, so if anyone wants to use it or submit pull requests/issues etc. then I'll make sure to put some extra time into it with you!&lt;/p&gt;

&lt;p&gt;One of the other visions I had for Hazel was to implement it in as many languages as possible in order to create a truly cross-platform networking library, hence the lightweightness of it, but that's perhaps something for the far future.&lt;/p&gt;

&lt;p&gt;Any discussion, improvements or thoughts are more than welcome, I'd love for more people to give it a try!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>networking</category>
      <category>dotnet</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
