<?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>DEV Community: Oliver Pham</title>
    <description>The latest articles on DEV Community by Oliver Pham (@oliverpham).</description>
    <link>https://dev.to/oliverpham</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%2F701166%2F88525a97-425b-4b00-a257-9af9c5f5f0b3.png</url>
      <title>DEV Community: Oliver Pham</title>
      <link>https://dev.to/oliverpham</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oliverpham"/>
    <language>en</language>
    <item>
      <title>How My View on Open Source Has Changed</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Sat, 11 Dec 2021 04:33:14 +0000</pubDate>
      <link>https://dev.to/oliverpham/how-my-view-on-open-source-has-changed-1bpc</link>
      <guid>https://dev.to/oliverpham/how-my-view-on-open-source-has-changed-1bpc</guid>
      <description>&lt;p&gt;In the first blog that I wrote about open source, there was one thing I wanted to know about it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I heard a lot about the glamorous evolution that most programmers experience while contributing to open source projects. Everything from their technical skills (e.g. programming knowledge, coding style) to their soft skills (e.g. collaboration and communication skills) is improved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without too much spoiling, I can say that I came looking for gold, but I found diamond instead. With only 14 weeks, I feel that there are still quite a lot of aspects about open source that I haven't experienced yet. Nevertheless, I won't let it stop me from sharing my personal experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supportive Community
&lt;/h2&gt;

&lt;p&gt;There is one thing I can say for sure: I love the spirit of open source. I've had a wonderful time working with open source communities of different sizes (e.g. &lt;a href="https://github.com/Seneca-CDOT"&gt;Seneca-CDOT&lt;/a&gt;, &lt;a href="https://stepzen.com/"&gt;StepZen&lt;/a&gt;, &lt;a href="https://appwrite.io/"&gt;Appwrite&lt;/a&gt;, and &lt;a href="https://zulip.com/"&gt;Zulip&lt;/a&gt;). Fortunately, all of them are fantastic and patient with beginners.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amazing opportunity to learn
&lt;/h2&gt;

&lt;p&gt;I've had several good opportunities to learn not only new technologies but also good software development practices. I helped &lt;a href="https://element.io/"&gt;Element&lt;/a&gt; fixed a CSS bug but made a few &lt;a href="https://dev.to/oliverpham/mistakes-to-avoid-before-submitting-your-pull-request-3nl8"&gt;mistakes when submitting my pull request&lt;/a&gt;. During Hacktoberfest, I picked up two completely new technologies (i.e. Ruby and GraphQL) and a nice tree for my 4 merged pull requests. I also had an opportunity to work with &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt; while auditing the content of IPC144 Course Notes. Finally, I will never forget my struggle to develop a new feature with jQuery and Handlebars for Zulip.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Open Source really is
&lt;/h2&gt;

&lt;p&gt;After all that, it's still hard to tell if my skills have improved any bit. Nevertheless, I realized &lt;a href="https://www.youtube.com/watch?v=eVTXPUF4Oz4"&gt;in the end it doesn't really matter&lt;/a&gt; (thanks Linkin Park for reminding that). In my opinion, &lt;strong&gt;&lt;em&gt;open source is all about publishing your "bad" code/project and collaborating with others to improve it&lt;/em&gt;&lt;/strong&gt;. I don't mean "bad" as in "worth being shamed and disrespected". It's the "bad" in "worth being improved/enhanced/optimized". Those are two different things.&lt;/p&gt;

&lt;p&gt;Therefore, my only advice for my past self and anyone who wants to get started with open source: get ready to learn. If you are interested in a project but clueless about how to get started, learn about it from the community (&lt;code&gt;git grep&lt;/code&gt; or VSCode search tool may be your &lt;em&gt;best buddy&lt;/em&gt;, but a supportive community is &lt;em&gt;your wife/husband&lt;/em&gt;). If you don't know how to fix a particular bug, learn how others approach(ed) it. If you want others to help you out with your open source project, communicate with other maintainers and learn how they treat new contributors. In open source, I think we are all here to grow not only as a developer but as a community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I wish open source could be applied to many other aspects in life. It's a bit embarrassing to admit how obsessed I am with open source. In fact, I was so used to filing a new issue on an open source project and requesting to work on it that I unconsciously did the same thing to Scotiabank the other day. The bank tellers must have thought I was high on drugs or something. Finally, I'd like to thank &lt;a href="https://github.com/humphd"&gt;David&lt;/a&gt;, who helped other students and me fall into this fascinating rabbit hole. If you are reading this (which is very likely), we really appreciate your amazing content and support, Dave! That was an incredible adventure from Release 0.1 to 0.4 and the labs. I'm looking forward to the my next chapter after this course.&lt;/p&gt;




&lt;p&gt;Cover photo by &lt;a href="https://unsplash.com/@lazizli?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Lala Azizli&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/teamwork?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Wrapping Zulip's Issue Up</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Sat, 11 Dec 2021 04:31:41 +0000</pubDate>
      <link>https://dev.to/oliverpham/wrapping-zulips-issue-up-5abi</link>
      <guid>https://dev.to/oliverpham/wrapping-zulips-issue-up-5abi</guid>
      <description>&lt;p&gt;As I was preparing for my finals and fixing some automation tests for my &lt;a href="https://github.com/zulip/zulip/pull/20494"&gt;Pull Request&lt;/a&gt; (PR), I realized I had made a serious mistake. I was clearly under no influence of alcohol, but I managed to write this comment:&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/zulip/zulip/pull/20494#issuecomment-989490678"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Comment for
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#20494&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/oliver-pham"&gt;
        &lt;img class="github-liquid-tag-img" src="https://res.cloudinary.com/practicaldev/image/fetch/s--PY4XewPm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://avatars.githubusercontent.com/u/55090719%3Fu%3D86d66668b53d52184a04b3d8225b86bcc71fe0fd%26v%3D4" alt="oliver-pham avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/oliver-pham"&gt;oliver-pham&lt;/a&gt;
        &lt;/strong&gt; commented on &lt;a href="https://github.com/zulip/zulip/pull/20494#issuecomment-989490678"&gt;&lt;time&gt;Dec 09, 2021&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;&lt;del&gt;That's strange. The tests actually passed when I ran them locally. Should I try to fix it, @timabbott?&lt;/del&gt; Sorry, I forgot to run the tests for the whole codebase.&lt;/p&gt;
&lt;p&gt;Edited: &lt;del&gt;I have no idea how to approach this, so it might take some more days&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;Edited 2: I think I know why now. I removed the use of &lt;a href="https://github.com/zulip/zulip/blob/6ec49951c68355e90c2ed7dad32a15556ea6368e/static/js/typeahead_helper.js#L50-L67"&gt;&lt;code&gt;make_query_highlighter()&lt;/code&gt;&lt;/a&gt; in &lt;code&gt;search_suggestion.js&lt;/code&gt;. It became obsolete since it was used there only. If I'm not mistaken, it was used to make every matching character of a suggestion bold. In this case, it's the full name of a user. Because the name is inside a pill now, I think the bold text doesn't stand out a lot like it used to. Should I add the feature back?&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/zulip/zulip/pull/20494#issuecomment-989490678"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;I'd like give you some background information first. &lt;a href="https://github.com/zulip/zulip/"&gt;Zulip&lt;/a&gt; strives to maintain 100% code coverage. In order for my PR to be merged, it must meet that requirement. If the code coverage is compromised, this message will be displayed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FQp50C93--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zq4uqid1052136agf3zw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FQp50C93--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zq4uqid1052136agf3zw.png" alt="Terminal message" width="741" height="460"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Believing that getting all my tests passed was enough, my PR failed the CI check at first. Unfortunately, that wasn't the worst part.&lt;/p&gt;

&lt;p&gt;Thanks to the test coverage, I noticed there was an unused function. It was used by some functions that I'd modified. This was a huge red flag, but it took me some time to realize that I removed an existing feature without asking the maintainers 🤦. Specifically, the feature highlights any substring in a suggestion that matches their query.&lt;/p&gt;

&lt;p&gt;In my defense, there was a complication with integrating the new feature (i.e. showing avatar and username in pill-shaped UI elements) with the one mentioned above. I quickly discussed the &lt;a href="https://chat.zulip.org/#narrow/stream/6-frontend/topic/avatars.20in.20search.20auto-complete.3F/near/1294812"&gt;issue&lt;/a&gt; with the maintainers. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xlaOeGot--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k7w8wjbedz5whifpmd94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xlaOeGot--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k7w8wjbedz5whifpmd94.png" alt="Discuss the issue" width="878" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My first attempt at solving it failed. Forgetting the test my code thoroughly, I made a silly mistake by double-escaping HTML expressions in every user's name:&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/zulip/zulip/pull/20494#issuecomment-991293978"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Comment for
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#20494&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/oliver-pham"&gt;
        &lt;img class="github-liquid-tag-img" src="https://res.cloudinary.com/practicaldev/image/fetch/s--PY4XewPm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://avatars.githubusercontent.com/u/55090719%3Fu%3D86d66668b53d52184a04b3d8225b86bcc71fe0fd%26v%3D4" alt="oliver-pham avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/oliver-pham"&gt;oliver-pham&lt;/a&gt;
        &lt;/strong&gt; commented on &lt;a href="https://github.com/zulip/zulip/pull/20494#issuecomment-991293978"&gt;&lt;time&gt;Dec 10, 2021&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Sure, &lt;a class="mentioned-user" href="https://dev.to/alya"&gt;@alya&lt;/a&gt;! Here you go:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/55090719/145640417-cf8a40cd-871d-47e4-83ff-b819bb17ac2d.mov" rel="nofollow"&gt;https://user-images.githubusercontent.com/55090719/145640417-cf8a40cd-871d-47e4-83ff-b819bb17ac2d.mov&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;... and thanks to you, I found something that looks a bit off. Right now, &lt;code&gt;'&lt;/code&gt; is currently considered a dangerous character (replaced by HTML entity &lt;code&gt;&amp;amp;#x27&lt;/code&gt;). Currently, it seems that we allow that character to be in a full name, so let me try to make a patch for it. Is there anything else I should change?&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/zulip/zulip/pull/20494#issuecomment-991293978"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Thankfully, I managed to patch my mistake instantly. Here's how it looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7_OqVX5e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zhq1l1dx96fdm0k1rfya.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7_OqVX5e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zhq1l1dx96fdm0k1rfya.gif" alt="Feature demo" width="600" height="291"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  What I've learnt
&lt;/h2&gt;

&lt;p&gt;Thanks to the test coverage report, I managed to discover my embarrassing flaw. Although it may be annoying to update the tests at times, I think I've learnt to appreciate a &lt;strong&gt;good&lt;/strong&gt; CI pipeline setup more than ever. I found &lt;a href="https://zulip.readthedocs.io/en/latest/testing/philosophy.html"&gt;Zulip's docs&lt;/a&gt; a good resource for how a developer should approach testing.&lt;/p&gt;

&lt;p&gt;I've also recognized the importance of sleep to keep my sanity. Looking back on my comments and code, I'm astonished by my audacity to have made such mistakes. I should have got started earlier to allow myself more time to get familiar with Zulip's codebase. &lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Since my PR is still under review by the time I'm writing this blog, I'll try to improve it until it's merged. There are not (hopefully) a lot of things left to do with it, so it should be done soon.&lt;/p&gt;




&lt;p&gt;Cover photo by &lt;a href="https://unsplash.com/@brookecagle?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Brooke Cagle&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/celebrate-computer?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>My Progress on Zulip's Issue</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Tue, 07 Dec 2021 20:23:30 +0000</pubDate>
      <link>https://dev.to/oliverpham/my-progress-on-zulips-issue-5h1</link>
      <guid>https://dev.to/oliverpham/my-progress-on-zulips-issue-5h1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;In preparing for battle I have always found that plans are useless, but planning is indispensable. - Dwight D. Eisenhower&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Indeed my &lt;a href="https://dev.to/oliverpham/initial-thoughts-on-a-zulips-issue-4job"&gt;initial approach&lt;/a&gt; to the &lt;a href="https://github.com/zulip/zulip/issues/20267"&gt;issue&lt;/a&gt; wasn't a good one, but it was not useless. Discussing it with several Zulip members &amp;amp; contributors helped me come up with a neater one and complete a prototype. After some refinement, I finally made a &lt;a href="https://github.com/zulip/zulip/pull/20494"&gt;pull request&lt;/a&gt; for the feature.&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/zulip/zulip/pull/20494"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        search_suggestion: Show profile pictures in search auto-complete suggestions
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#20494&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/oliver-pham"&gt;
        &lt;img class="github-liquid-tag-img" src="https://res.cloudinary.com/practicaldev/image/fetch/s--hQJ3u7_B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://avatars.githubusercontent.com/u/55090719%3Fv%3D4" alt="oliver-pham avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/oliver-pham"&gt;oliver-pham&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/zulip/zulip/pull/20494"&gt;&lt;time&gt;Dec 07, 2021&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      
&lt;p&gt;Fix #20267&lt;/p&gt;
&lt;p&gt;What I've basically changed is how &lt;a href="https://bootstrapdocs.com/v2.0.1/docs/javascript.html#typeahead" rel="nofollow"&gt;&lt;code&gt;bootstrap-typeahead&lt;/code&gt;&lt;/a&gt; should render the auto-complete suggestions for &lt;strong&gt;users only&lt;/strong&gt;. I've attempted to reuse the existing UI components (e.g. &lt;code&gt;input_pill&lt;/code&gt;) and styling, but it seems hard to make them work together as expected without adding a container and some CSS. If you find a better approach, I'm open to all recommendations.&lt;/p&gt;
&lt;p&gt;During the process of encapsulating the &lt;code&gt;typeahead&lt;/code&gt; list items inside flexboxes, I noticed the whitespace right after &lt;code&gt;prefix&lt;/code&gt; is not escaped in HTML. This can cause, for example, &lt;code&gt;"StreamDenmark"&lt;/code&gt; because the whitespace before &lt;code&gt;"Denmark"&lt;/code&gt;, which is wrapped with &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt; tags, is ignored in HTML.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Testing plan:&lt;/strong&gt; &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Manual Testing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;GIFs or screenshots:&lt;/strong&gt; 
On large screens:
&lt;a href="https://user-images.githubusercontent.com/55090719/145057916-64ea109b-3230-42a0-b7f8-f22019b480f4.png" rel="nofollow"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o50Oet-M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/55090719/145057916-64ea109b-3230-42a0-b7f8-f22019b480f4.png" alt="image"&gt;&lt;/a&gt;
&lt;a href="https://user-images.githubusercontent.com/55090719/145057941-19147939-75f0-4c72-9024-8fcf2cc10b87.png" rel="nofollow"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vk4JD5xV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/55090719/145057941-19147939-75f0-4c72-9024-8fcf2cc10b87.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On smaller screens:
&lt;a href="https://user-images.githubusercontent.com/55090719/145058064-e150d0f5-a077-4b20-849c-c5c18a9f5a55.png" rel="nofollow"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LphbFR4E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/55090719/145058064-e150d0f5-a077-4b20-849c-c5c18a9f5a55.png" alt="image"&gt;&lt;/a&gt;
&lt;a href="https://user-images.githubusercontent.com/55090719/145058080-c1e3197b-35dc-4756-be7e-3c20cbfd8901.png" rel="nofollow"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n5BDYc31--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/55090719/145058080-c1e3197b-35dc-4756-be7e-3c20cbfd8901.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/zulip/zulip/pull/20494"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;A few hours after my messages were sent, I got some helpful feedback to my initial thoughts. Basically, I was advised to modify a more relevant, lower-level function, &lt;a href="https://github.com/zulip/zulip/blob/58438c362f2eb0d5cc300543b501dd5cda457f16/static/js/search_suggestion.js#L222"&gt;&lt;code&gt;get_person_suggestions()&lt;/code&gt;&lt;/a&gt;. That should help me avoid breaking the existing features.&lt;/p&gt;

&lt;p&gt;My first prototype for the feature looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1q7YAV9V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/18hosww6fxgm2vank21a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1q7YAV9V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/18hosww6fxgm2vank21a.png" alt="A list of users with their avatars" width="880" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0xE-vt2G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1c8k5amiqyw4sxf6wj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0xE-vt2G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1c8k5amiqyw4sxf6wj3.png" alt="A list of search descriptions with a user's avatar and name" width="880" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's really ugly, wasn't it? I was then advised to use an existing UI component, &lt;code&gt;user_pill&lt;/code&gt;, to display a user's full name and avatar as a distinct block. Another refinement was to vertically align the text. I eventually implemented both, which significantly improved the design of my initial prototype.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VhKK4Qx8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p971iaftettj8ngr3jbr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VhKK4Qx8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p971iaftettj8ngr3jbr.png" alt="A list of users with their avatars in pill-shaped containers" width="869" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sUJq1yQl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9mtqgdivyzje9r07ip46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sUJq1yQl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9mtqgdivyzje9r07ip46.png" alt="A list of search descriptions with a user's avatar and name in a pill-shaped container" width="869" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I've learnt
&lt;/h2&gt;

&lt;p&gt;I should have made a draft/work-in-progress pull request early in my development process and improved on it iteratively. Keeping others posted of my prototype of the feature was a good idea, but I missed out another important deliverable: my nasty code. Working with an open source community means that I can get feedback for me code more easily, so I should've taken advantage of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Hopefully, the maintainers will review my code soon. In the meantime, I probably need to fix some broken automation tests.&lt;/p&gt;




&lt;p&gt;Cover photo by &lt;a href="https://unsplash.com/@afgprogrammer?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Mohammad Rahmani&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/coding?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Initial Thoughts on a Zulip's Issue</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Thu, 02 Dec 2021 03:48:37 +0000</pubDate>
      <link>https://dev.to/oliverpham/initial-thoughts-on-a-zulips-issue-4job</link>
      <guid>https://dev.to/oliverpham/initial-thoughts-on-a-zulips-issue-4job</guid>
      <description>&lt;p&gt;At the beginning of this week, I've found an interesting &lt;a href="https://github.com/zulip/zulip/issues/20162"&gt;issue&lt;/a&gt; on Zulip's &lt;a href="https://github.com/zulip/zulip"&gt;repo&lt;/a&gt;. &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--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/zulip"&gt;
        zulip
      &lt;/a&gt; / &lt;a href="https://github.com/zulip/zulip"&gt;
        zulip
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Zulip server and web application. Open-source team chat that helps teams stay productive and focused.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Zulip overview&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://zulip.com" rel="nofollow"&gt;Zulip&lt;/a&gt; is an open-source team collaboration tool with unique
&lt;a href="https://zulip.com/why-zulip/" rel="nofollow"&gt;topic-based threading&lt;/a&gt; that combines the best of email and chat to
make remote work productive and delightful. Fortune 500 companies, &lt;a href="https://zulip.com/case-studies/rust/" rel="nofollow"&gt;leading open
source projects&lt;/a&gt;, and thousands of other organizations use
Zulip every day. Zulip is the only &lt;a href="https://zulip.com/features/" rel="nofollow"&gt;modern team chat app&lt;/a&gt; that is
designed for both live and asynchronous conversations.&lt;/p&gt;
&lt;p&gt;Zulip is built by a distributed community of developers from all around the
world, with 74+ people who have each contributed 100+ commits. With
over 1000 contributors merging over 500 commits a month, Zulip is the
largest and fastest growing open source team chat project.&lt;/p&gt;
&lt;p&gt;Come find us on the &lt;a href="https://zulip.com/development-community/" rel="nofollow"&gt;development community chat&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/zulip/zulip/actions/workflows/zulip-ci.yml?query=branch%3Amain"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OCLP50fL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/zulip/zulip/actions/workflows/zulip-ci.yml/badge.svg" alt="GitHub Actions build status"&gt;&lt;/a&gt;
&lt;a href="https://codecov.io/gh/zulip/zulip" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/b5a475ccc9b54100370cc35adbd299bd41d74f7525b04af43e0d13df8866fc3f/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636f762f632f6769746875622f7a756c69702f7a756c69702f6d61696e2e737667" alt="coverage status"&gt;&lt;/a&gt;
&lt;a href="https://blog.zulip.org/2016/10/13/static-types-in-python-oh-mypy/" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/4094477d87cf5083282feddb3ff2a6bb6c7adf4f53023cc072ff0ded5ddf82a7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d7970792d3130302532352d677265656e2e737667" alt="Mypy coverage"&gt;&lt;/a&gt;
&lt;a href="https://github.com/charliermarsh/ruff"&gt;&lt;img src="https://camo.githubusercontent.com/10edd774caa15060c4ebcc0a0a14a45c4c76a39d627f164eb3ce23aaf34b4eca/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f75726c3d68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f636861726c6965726d617273682f727566662f6d61696e2f6173736574732f62616467652f76302e6a736f6e" alt="Ruff"&gt;&lt;/a&gt;
&lt;a href="https://github.com/psf/black"&gt;&lt;img src="https://camo.githubusercontent.com/d91ed7ac7abbd5a6102cbe988dd8e9ac21bde0a73d97be7603b891ad08ce3479/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f64652532307374796c652d626c61636b2d3030303030302e737667" alt="code style: black"&gt;&lt;/a&gt;
&lt;a href="https://github.com/prettier/prettier"&gt;&lt;img src="https://camo.githubusercontent.com/48a41f43affa2e6253d6a48e0ee662ec53ce13c46442ac815e81d36b6e6b434d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f64655f7374796c652d70726574746965722d6666363962342e737667" alt="code style: prettier"&gt;&lt;/a&gt;
&lt;a href="https://github.com/zulip/zulip/releases/latest"&gt;&lt;img src="https://camo.githubusercontent.com/2283441b16ae620fe5f32397813893d1ac1f577896d8face039f9f9fff8c5384/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652f7a756c69702f7a756c69702e737667" alt="GitHub release"&gt;&lt;/a&gt;
&lt;a href="https://zulip.readthedocs.io/en/latest/" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/3313ad27531ef3035228d8d1e47b20511555f63e37d87f00ffc470ef049fc7ca/68747470733a2f2f72656164746865646f63732e6f72672f70726f6a656374732f7a756c69702f62616467652f3f76657273696f6e3d6c6174657374" alt="docs"&gt;&lt;/a&gt;
&lt;a href="https://chat.zulip.org" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/11e6556bfe778e7cf7331cac9c44bd0616062722036cc0d9bb0b7909aaae8779/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7a756c69702d6a6f696e5f636861742d627269676874677265656e2e737667" alt="Zulip chat"&gt;&lt;/a&gt;
&lt;a href="https://twitter.com/zulip" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/76f56427e4b5c14e995f0b485bc148d1b1f0076d6eb186ae0ee75b60dab4ef75/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f747769747465722d407a756c69702d626c75652e7376673f7374796c653d666c6174" alt="Twitter"&gt;&lt;/a&gt;
&lt;a href="https://github.com/sponsors/zulip"&gt;&lt;img src="https://camo.githubusercontent.com/cbaa133426922cca528b85063205825e92b9ecdce1bce85d7ee260240ce89df7/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73706f6e736f72732f7a756c6970" alt="GitHub Sponsors"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Getting started&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Contributing code&lt;/strong&gt;. Check out our &lt;a href="https://zulip.readthedocs.io/en/latest/contributing/contributing.html" rel="nofollow"&gt;guide for new
contributors&lt;/a&gt;
to get started. We have invested in making Zulip’s code highly
readable, thoughtfully tested, and easy to modify. Beyond that, we
have…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/zulip/zulip"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://zulip.com/"&gt;Zulip&lt;/a&gt; is a popular open source communication platform built with Django (Python). Despite having spent some time one it, I eventually let another developer handle that issue&lt;sup id="fnref1"&gt;1&lt;/sup&gt; and claimed another &lt;a href="https://github.com/zulip/zulip/issues/20267"&gt;one&lt;/a&gt;. It's a feature request for showing profile pictures in search autocomplete suggestions.&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/zulip/zulip/issues/20267"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Show profile pictures in search auto-complete suggestions
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#20267&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/alya"&gt;
        &lt;img class="github-liquid-tag-img" src="https://res.cloudinary.com/practicaldev/image/fetch/s--JNRrQpzo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://avatars.githubusercontent.com/u/2090066%3Fv%3D4" alt="alya avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/alya"&gt;alya&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/zulip/zulip/issues/20267"&gt;&lt;time&gt;Nov 16, 2021&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;At present, we show profile pictures in @-mention auto-complete suggestions, but not in auto-complete suggestions for search. As a result, it's hard to quickly find the person you need in search auto-complete. Moreover, using the search suggestions is very awkward when multiple people have the same name.&lt;/p&gt;
&lt;p&gt;To address this, we should show avatars in the search auto-complete suggestions when a user is being suggested (sender, PMs with, etc.).&lt;/p&gt;
&lt;p&gt;From an implementation perspective, we would ideally reuse the send-PM-pills component rather than duplicating the logic.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://chat.zulip.org/#narrow/stream/6-frontend/topic/avatars.20in.20search.20auto-complete.3F" rel="nofollow"&gt;CZO discussion thread&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For reference, the @-mention UI:
&lt;a href="https://user-images.githubusercontent.com/2090066/141942458-a828bdc2-00e7-4a75-a715-a6189f3f58ba.png" rel="nofollow"&gt;&lt;img width="196" alt="Screen Shot 2021-11-15 at 5 07 42 PM" src="https://res.cloudinary.com/practicaldev/image/fetch/s--iqUfXU4o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/2090066/141942458-a828bdc2-00e7-4a75-a715-a6189f3f58ba.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Current search suggestions UI:
&lt;a href="https://user-images.githubusercontent.com/2090066/141942510-d04e1079-6022-43e7-b7e2-d174349b9ed4.png" rel="nofollow"&gt;&lt;img width="230" alt="Screen Shot 2021-11-15 at 5 08 29 PM" src="https://res.cloudinary.com/practicaldev/image/fetch/s--zxXvh1jF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/2090066/141942510-d04e1079-6022-43e7-b7e2-d174349b9ed4.png"&gt;&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/zulip/zulip/issues/20267"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What the feature involves
&lt;/h2&gt;

&lt;p&gt;I was overwhelmed with Zulip's gigantic codebase and technologies. Luckily, their documentation is really clear, concise, and helpful. Although setting up a local development environment for the project took more time than usual, the process was straightforward. I had a chance to learn a bit about Docker and &lt;a href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt;. Apart from that, I had an unexpected reunion with Git's pre-commit hooks, my old friend from OSD's lab.&lt;/p&gt;

&lt;p&gt;The next part was a nightmare: tracing the relevant code in a  codebase developed by 700+ contributors with 45000+ commits. After doing some research and receiving support from Zulip's community server, I've had some ideas for the feature.&lt;/p&gt;

&lt;p&gt;Based on an existing approach to a relatively similar feature, I need to customize the existing &lt;a href="https://twitter.github.io/typeahead.js/"&gt;typeahead.js&lt;/a&gt;'s layout to include an avatar for any suggested person or organization. As the author of the issue suggested, I could reuse a &lt;a href="https://handlebarsjs.com/"&gt;Handlebars&lt;/a&gt; partial for each suggestion.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I approach it
&lt;/h2&gt;

&lt;p&gt;Firstly, I need a way to render a user's profile picture from their name. After some code tracing, I found &lt;a href="https://github.com/zulip/zulip/blob/0a2649d9fe2b363c668b671c77fbab2fd0a554b3/static/js/typeahead_helper.js#L104"&gt;&lt;code&gt;render_person_or_user_group()&lt;/code&gt;&lt;/a&gt;, a typeahead's helper function that can help me with that task. It uses &lt;a href="https://github.com/zulip/zulip/blob/main/static/templates/typeahead_list_item.hbs"&gt;this Handlebars partial&lt;/a&gt; to render a suggestion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;&lt;span class="k"&gt;{{#if&lt;/span&gt; &lt;span class="nv"&gt;is_emoji&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{#if&lt;/span&gt; &lt;span class="nv"&gt;has_image&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"emoji"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;img_src&lt;/span&gt; &lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;{{else}}&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'emoji emoji-&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;emoji_code&lt;/span&gt; &lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;{{/if}}&lt;/span&gt;
    &lt;span class="ni"&gt;&amp;amp;nbsp;&amp;amp;nbsp;&lt;/span&gt;
&lt;span class="k"&gt;{{else}}&lt;/span&gt;
    &lt;span class="k"&gt;{{#if&lt;/span&gt; &lt;span class="nv"&gt;is_person&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
        &lt;span class="k"&gt;{{#if&lt;/span&gt; &lt;span class="nv"&gt;user_circle_class&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;user_circle_class&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s"&gt; user_circle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;{{/if}}&lt;/span&gt;
        &lt;span class="k"&gt;{{#if&lt;/span&gt; &lt;span class="nv"&gt;has_image&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"typeahead-image"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;img_src&lt;/span&gt; &lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;{{else}}&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'typeahead-image fa fa-bullhorn no-presence-circle'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;{{/if}}&lt;/span&gt;
    &lt;span class="k"&gt;{{else}}&lt;/span&gt;
        &lt;span class="k"&gt;{{#if&lt;/span&gt; &lt;span class="nv"&gt;is_user_group&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"typeahead-image icon fa fa-group no-presence-circle"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;{{/if}}&lt;/span&gt;
    &lt;span class="k"&gt;{{/if}}&lt;/span&gt;
&lt;span class="k"&gt;{{/if}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;{{~&lt;/span&gt; &lt;span class="nv"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;~}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;{{~#if&lt;/span&gt; &lt;span class="nv"&gt;has_secondary&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="ni"&gt;&amp;amp;nbsp;&amp;amp;nbsp;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;small&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"autocomplete_secondary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;{{~&lt;/span&gt; &lt;span class="nv"&gt;secondary&lt;/span&gt; &lt;span class="k"&gt;~}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/small&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;{{#if&lt;/span&gt; &lt;span class="nv"&gt;is_unsubscribed&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fa fa-exclamation-triangle unsubscribed_icon"&lt;/span&gt;
  &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;t&lt;/span&gt; &lt;span class="s1"&gt;'You are not currently subscribed to this stream.'&lt;/span&gt; &lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;{{/if}}&lt;/span&gt;
&lt;span class="k"&gt;{{~/if}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having said that, I still need more time to understand the behaviour of the function. It's likely that I have to write a similar one to exclude the irrelevant.&lt;/p&gt;

&lt;p&gt;Secondly, I need to find a way to customize a suggestion's layout. After some research, I noticed &lt;a href="https://github.com/zulip/zulip/blob/0a2649d9fe2b363c668b671c77fbab2fd0a554b3/static/js/search.js#L95-L98"&gt;&lt;code&gt;highlighter()&lt;/code&gt;&lt;/a&gt; is responsible for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;highlighter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;search_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&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;ul&gt;
&lt;li&gt;
&lt;code&gt;obj.description&lt;/code&gt; is some bizarre raw HTML with a par of &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt; tags, enclosing each character of a name, and another one that follows it and encloses absolutely nothing. For instance, the HTML for "Sent by &lt;strong&gt;Aayush Solanki&lt;/strong&gt;" looks like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sent by &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;A&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;a&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;y&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;u&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;s&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;h&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;S&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;o&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;l&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;a&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;n&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;k&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;/strong&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;i&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, I must come up with a way to connect the two functions seamlessly. This is the most complicated part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This seems like an impactful contribution to a large open source project. I'm still clueless about how to build upon my initial approach, but I'll try to keep you posted about my progress.&lt;/p&gt;




&lt;p&gt;Cover photo by &lt;a href="https://unsplash.com/@kaleidico?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Kaleidico&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/development?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;There was another developer who had worked on the &lt;a href="https://github.com/zulip/zulip/issues/20162"&gt;issue&lt;/a&gt; before me. He &lt;a href="https://github.com/zulip/zulip/issues/20162#issuecomment-976142115"&gt;abandoned the issue&lt;/a&gt; due to his college work. However, he asked to work on it again a day after I &lt;a href="https://chat.zulip.org/#narrow/stream/6-frontend/topic/Add.20summary.20statistics.20to.20.2Fstats.20page"&gt;discussed my approaches&lt;/a&gt; with the project maintainers.  Since he claimed to have some progress on it already, I decided to let him take over the issue. Do you think I've done the right thing in this situation? Let me know the comments. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>opensource</category>
      <category>devjournal</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Publish a Python Project in 5 Steps</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Sat, 27 Nov 2021 03:10:01 +0000</pubDate>
      <link>https://dev.to/oliverpham/publish-a-python-project-in-5-steps-12hm</link>
      <guid>https://dev.to/oliverpham/publish-a-python-project-in-5-steps-12hm</guid>
      <description>&lt;p&gt;This week, I've published &lt;a href="https://github.com/oliver-pham/silkie"&gt;Silkie&lt;/a&gt;, my Python static site generator (SSG), to &lt;a href="https://pypi.org/project/silkie/"&gt;PyPI&lt;/a&gt;. It may seem daunting at first, but you can publish your own Python package by following these 5 steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start
&lt;/h2&gt;

&lt;p&gt;To avoid most painful misconfiguration, I organized my project to follow a standard directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;silkie
├── LICENSE
├── README.md
├── pyproject.toml
├── requirements.txt
├── setup.cfg
├── silkie
│   ├── __init__.py
│   ├── __main__.py
│   ├── cli.py
│   ├── ...
├── tests
└── tox.ini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might want to use &lt;a href="https://cookiecutter.readthedocs.io/en/latest/README.html#a-pantry-full-of-cookiecutters"&gt;Cookiecutter&lt;/a&gt; to set up a template for your Python project if you are starting from scratch.&lt;/p&gt;

&lt;p&gt;We'll be uploading our package to PyPI, so we should have an account created on PyPI. In addition, you can also create another account on &lt;a href="https://test.pypi.org/"&gt;TestPyPI&lt;/a&gt; to test your package first before an official publication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://setuptools.pypa.io/en/latest/"&gt;Setuptools&lt;/a&gt;, a popular and extensible library for packaging Python projects, seems like a perfect choice for my project. Configuration steps for it includes writing two files: &lt;code&gt;pyproject.toml&lt;/code&gt; and &lt;code&gt;setup.cfg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pyproject.toml&lt;/code&gt; file simply declares the requirements for building our package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[build-system]&lt;/span&gt;
&lt;span class="py"&gt;requires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="py"&gt;"setuptools&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="s"&gt;"wheel"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;build-backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"setuptools.build_meta"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on your project architecture, &lt;code&gt;setup.cfg&lt;/code&gt; can be configured accordingly. I think &lt;a href="https://packaging.python.org/tutorials/packaging-projects/"&gt;this tutorial&lt;/a&gt; does a better job explaining the details, so you should definitely check it out. As for Silkie, this is how I set it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[metadata]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;silkie&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1.0.7&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Static site generator with the smoothness of silk&lt;/span&gt;
&lt;span class="py"&gt;long_description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;file: README.md&lt;/span&gt;
&lt;span class="py"&gt;long_description_content_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;text/markdown&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;https://github.com/oliver-pham/silkie&lt;/span&gt;
&lt;span class="py"&gt;project_urls&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="err"&gt;Bug&lt;/span&gt; &lt;span class="py"&gt;Tracker&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;https://github.com/oliver-pham/silkie/issues&lt;/span&gt;
&lt;span class="py"&gt;classifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="err"&gt;Programming&lt;/span&gt; &lt;span class="err"&gt;Language&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;Python&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt;
    &lt;span class="err"&gt;License&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;OSI&lt;/span&gt; &lt;span class="err"&gt;Approved&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;MIT&lt;/span&gt; &lt;span class="err"&gt;License&lt;/span&gt;
    &lt;span class="err"&gt;Operating&lt;/span&gt; &lt;span class="err"&gt;System&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;OS&lt;/span&gt; &lt;span class="err"&gt;Independent&lt;/span&gt;

&lt;span class="nn"&gt;[options]&lt;/span&gt;
&lt;span class="py"&gt;packages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;silkie&lt;/span&gt;
&lt;span class="py"&gt;python_requires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;gt;=3.9&lt;/span&gt;
&lt;span class="py"&gt;install_requires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="err"&gt;click&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="err"&gt;8.0.0&lt;/span&gt;
    &lt;span class="err"&gt;markdown&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="err"&gt;3.3.0&lt;/span&gt;
    &lt;span class="err"&gt;yattag&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="err"&gt;1.14.0&lt;/span&gt;
    &lt;span class="err"&gt;python-frontmatter&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="err"&gt;1.0.0&lt;/span&gt;

&lt;span class="nn"&gt;[options.entry_points]&lt;/span&gt;
&lt;span class="py"&gt;console_scripts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
    &lt;span class="s"&gt;silkie = silkie.cli:silkie&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some notes you may want to consider to avoid some misconfigurations (that I made):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;packages&lt;/code&gt;: I specify &lt;code&gt;silkie&lt;/code&gt; directory directly because my project only has a single package. If you have multiple packages nested under a single directory, you should use &lt;code&gt;find:&lt;/code&gt; and follow the instructions in &lt;a href="https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#using-find-or-find-packages"&gt;Setuptools documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;install_requires&lt;/code&gt;: specify all third-party dependencies that your project use. Simply put, anything that you &lt;code&gt;pip install&lt;/code&gt; and import in your code should be declared here&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Build
&lt;/h2&gt;

&lt;p&gt;Once you are done with configuration, let's build your Python package.&lt;/p&gt;

&lt;p&gt;Before we can build it, make sure you have the latest version of &lt;code&gt;build&lt;/code&gt; installed on your machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install --upgrade build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, just run build in the root directory of your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python -m build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should have a &lt;code&gt;.whl&lt;/code&gt; file and a &lt;code&gt;.tar.gz&lt;/code&gt; file inside your &lt;code&gt;dist/&lt;/code&gt; directory when the build process is completed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test
&lt;/h2&gt;

&lt;p&gt;Before publishing your package, make sure that the newly built distribution contains all the necessary packages and files. You can run this command to see what's inside your distribution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ tar tzf &amp;lt;your-package-name&amp;gt;.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Publish
&lt;/h2&gt;

&lt;p&gt;To publish your Python package to PyPI, you must have &lt;code&gt;twine&lt;/code&gt; installed on your machine. You can install its latest version with &lt;code&gt;pip&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install --upgrade twine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's installed, run this command to upload your distribution to TestPyPI for testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ twine upload --repository TestPyPI dist/*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompted, you can either enter your account credentials or use an API token like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enter your username: __token__
Enter your password: &amp;lt;your-API-token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When your package can be uploaded to TestPyPI and installed on your local machine, you're ready to upload it to PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ twine upload dist/*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like TestPyPI, your credentials or API token (&lt;strong&gt;for PyPI&lt;/strong&gt;) can be used to authenticate your upload. Congratulations! You can now ask others to install and test your package.&lt;/p&gt;

&lt;p&gt;As for Silkie, I've asked some of my friends to test the SSG. One of them found a &lt;a href="https://github.com/oliver-pham/silkie/issues/33"&gt;critical bug&lt;/a&gt; due to a lack of testing. Another one suggested a piece of ASCII Art to me for Silkie, which was really nice. Overall, we have only tested it on Mac and Linux. If you happen to encounter any bug or issue while using Silkie, feel free to &lt;a href="https://github.com/oliver-pham/silkie/issues/new/choose"&gt;open an issue&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;In general, I think publishing a Python package isn't really complex, but I was so nervous that I made a lot mistakes. It's fine if you mess up your release because you can always apply a new patch to it (unless your package is used by a majority of developers). Happy coding!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I Set Up GitHub Actions for a Python Project</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Sat, 20 Nov 2021 17:24:10 +0000</pubDate>
      <link>https://dev.to/oliverpham/how-i-set-up-github-actions-for-a-python-project-2b7o</link>
      <guid>https://dev.to/oliverpham/how-i-set-up-github-actions-for-a-python-project-2b7o</guid>
      <description>&lt;p&gt;Last week, I already set up some automation tests for &lt;a href="https://github.com/oliver-pham/silkie"&gt;Silkie&lt;/a&gt;, my static site generator (SSG). Instead of running tests manually on each Pull Request (PR), I made an attempt to configure GitHub Actions to automate this Continuous Integration (CI) workflow. Moreover, I also helped my friend, &lt;a href="https://github.com/mqnguyen5"&gt;Luke&lt;/a&gt;, add a test case to his SSG this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure GitHub Actions
&lt;/h2&gt;

&lt;p&gt;For the time being, I have several specifications for my CI pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test Silke only on Python 3.9 (hopefully I can support more versions in future releases)&lt;/li&gt;
&lt;li&gt;Install dependencies from &lt;code&gt;requirements.txt&lt;/code&gt; file (if it exists)&lt;/li&gt;
&lt;li&gt;Run Flake8 linter&lt;/li&gt;
&lt;li&gt;Run tests and generate code coverage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how it actually looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This is a basic workflow to help you get started with Actions&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="c1"&gt;# Controls when the workflow will run&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Triggers the workflow on push or pull request events but only for the main branch&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Allows you to run this workflow manually from the Actions tab&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="c1"&gt;# A workflow run is made up of one or more jobs that can run sequentially or in parallel&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# This workflow contains a single job called "build"&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# The type of runner that the job will run on&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Steps represent a sequence of tasks that will be executed as part of the job&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.python-version }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python -m pip install --upgrade pip&lt;/span&gt;
          &lt;span class="s"&gt;if [ -f requirements.txt ]; then pip install -r requirements.txt; fi&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run flake8 linter&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;pip install flake8&lt;/span&gt;
          &lt;span class="s"&gt;# stop the build if there are Python syntax errors or undefined names&lt;/span&gt;
          &lt;span class="s"&gt;flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics&lt;/span&gt;
          &lt;span class="s"&gt;# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide&lt;/span&gt;
          &lt;span class="s"&gt;flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test &amp;amp; Code coverage with pytest&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;pip install pytest&lt;/span&gt;
          &lt;span class="s"&gt;pip install pytest-cov&lt;/span&gt;
          &lt;span class="s"&gt;pytest --junitxml=junit/test-results.xml --cov=silkie --cov-report=xml --cov-report=html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Luke's SSG
&lt;/h2&gt;

&lt;p&gt;Luke used &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; as his testing framework. In my opinion, it wasn't really different from &lt;a href="https://github.com/pytest-dev/pytest"&gt;Pytest&lt;/a&gt;, so I had no problem with adding a new test case.&lt;/p&gt;

&lt;p&gt;Luke had also set up a simple but efficient CI workflow for his project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node&lt;/span&gt;
&lt;span class="c1"&gt;# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Node.js CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;14.x&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;16.x&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# See supported Node.js release schedule at https://nodejs.org/en/about/releases/&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use Node.js ${{ matrix.node-version }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luckily, my newly added test case didn't fail the CI workflow, so my &lt;a href="https://github.com/mqnguyen5/mini-ssg/pull/22"&gt;PR&lt;/a&gt; got merged eventually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;I think it's pretty easy and simple to get a CI pipeline up and running with GitHub Actions. However, its impact on our productivity and code quality is certainly significant as the project grows bigger.&lt;/p&gt;

</description>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Fixed an Issue in Firebase CLI</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Sat, 20 Nov 2021 04:00:31 +0000</pubDate>
      <link>https://dev.to/oliverpham/how-i-fixed-an-issue-in-firebase-cli-4i41</link>
      <guid>https://dev.to/oliverpham/how-i-fixed-an-issue-in-firebase-cli-4i41</guid>
      <description>&lt;p&gt;I found an issue on &lt;a href="https://github.com/firebase/firebase-tools"&gt;Firebase CLI repo&lt;/a&gt; that I could work on. The &lt;a href="https://github.com/firebase/firebase-tools/issues/3407"&gt;issue&lt;/a&gt; involved a hardcoded Node.js version in the generated &lt;code&gt;package.json&lt;/code&gt; file when the command &lt;code&gt;firebase init functions&lt;/code&gt; is executed. Moreover, there was no warning when a Node.js version in use is deprecated in Google Cloud Functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;To be honest, I had no idea how to approach this issue at first, so I decided to follow the contributing guide. It was very detailed and straightforward, so I had no problem setting up &lt;code&gt;firebase-tools&lt;/code&gt;. Unlike the previous open source projects, I had to sign Google's &lt;a href="https://cla.developers.google.com/"&gt;Contributor License Agreement&lt;/a&gt; (CLA) before I could contribute to Firebase CLI. Thanks to the intuitive folder structure of the project, I could identify which files to be modified.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamically set Node version
&lt;/h2&gt;

&lt;p&gt;Based on the folder structure, &lt;code&gt;firebase init functions&lt;/code&gt; should execute the code inside &lt;code&gt;firebase-tools/src/init/features/functions/&amp;lt;language&amp;gt;.js&lt;/code&gt; (&lt;code&gt;&amp;lt;language&amp;gt;&lt;/code&gt; is the programming language selected for your project). I made a &lt;a href="https://github.com/firebase/firebase-tools/issues/3407#issuecomment-958587839"&gt;comment&lt;/a&gt; on the issue to discuss my approach. Thankfully, I got some super helpful recommendations from &lt;a href="https://github.com/taeold"&gt;Daniel&lt;/a&gt;, the author of the issue.&lt;/p&gt;

&lt;p&gt;Basically, the &lt;code&gt;package.json&lt;/code&gt; file is generated from a pre-written template. To set the right Node.js version, I needed the logic to identify it and replace the hardcoded version with it dynamically.&lt;/p&gt;

&lt;p&gt;First, I replaced the hardcoded version with a template string inside &lt;code&gt;package.json&lt;/code&gt; template file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{NODE_VERSION}}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, a reliable function to detect Node.js version was implemented:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getNodeVersionString&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, before writing &lt;code&gt;package.json&lt;/code&gt; file to a dev's machine, I just need to replace the template string with the detected Node.js version like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodeEngineVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getNodeVersionString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;useLint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;PACKAGE_LINTING_TEMPLATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/{{NODE_VERSION}}/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nodeEngineVersion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;PACKAGE_NO_LINTING_TEMPLATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/{{NODE_VERSION}}/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nodeEngineVersion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;PACKAGE_LINTING_TEMPLATE&lt;/em&gt;: the content of &lt;code&gt;package.json&lt;/code&gt; file &lt;strong&gt;with&lt;/strong&gt; ESLint&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;PACKAGE_NO_LINTING_TEMPLATE&lt;/em&gt;: the content of &lt;code&gt;package.json&lt;/code&gt; file &lt;strong&gt;without&lt;/strong&gt; ESLint&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Handle deprecated Node versions
&lt;/h2&gt;

&lt;p&gt;The logic to warn developers of deprecated Node.js versions was still missing, so I discussed &lt;a href="https://github.com/firebase/firebase-tools/pull/3894#discussion_r748674087"&gt;my approach&lt;/a&gt; with Daniel again. I intended to use a function that I found in their codebase, &lt;code&gt;isDeprecatedRuntime()&lt;/code&gt;, to perform the check.&lt;/p&gt;

&lt;p&gt;Luckily, Daniel pointed out a small bug in my approach and suggested a better one, which was to use &lt;code&gt;isValidRuntime()&lt;/code&gt; instead. I finally got the last piece as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isValidRuntime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`nodejs&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nodeEngineVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Node &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nodeEngineVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is no longer supported in Google Cloud Functions.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;See https://firebase.google.com/docs/functions/manage-functions for more details&lt;/span&gt;&lt;span class="dl"&gt;"&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;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;I've never had to fix my code for a PR as much as this one. Thanks to their code review, I've learned a few good practices along with how I could avoid pitfalls by discussing my approach and getting feedback before actually coding it.&lt;/p&gt;




&lt;p&gt;Issue: &lt;a href="https://github.com/firebase/firebase-tools/issues/3407"&gt;https://github.com/firebase/firebase-tools/issues/3407&lt;/a&gt;&lt;br&gt;
PR: &lt;a href="https://github.com/firebase/firebase-tools/pull/3894"&gt;https://github.com/firebase/firebase-tools/pull/3894&lt;/a&gt;&lt;br&gt;
Firebase: &lt;a href="https://firebase.google.com"&gt;https://firebase.google.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>firebase</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How I Reviewed Code for Seneca-ICTOER</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Sat, 20 Nov 2021 04:00:18 +0000</pubDate>
      <link>https://dev.to/oliverpham/how-i-reviewed-code-for-seneca-ictoer-50lo</link>
      <guid>https://dev.to/oliverpham/how-i-reviewed-code-for-seneca-ictoer-50lo</guid>
      <description>&lt;p&gt;As I was working on the modernized version of &lt;a href="https://github.com/Seneca-ICTOER/IPC144/"&gt;IPC144&lt;/a&gt;, there were many pending pull requests (PR) made by other students. I decided to help them out with reviewing their pull requests. Most of them were well done, so I could only give a few recommendations to some of them. Here are two of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewed PR for &lt;code&gt;Informataion.md&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://github.com/Seneca-ICTOER/IPC144/pull/81"&gt;PR&lt;/a&gt;, I noticed the author accidentally committed two irrelevant files: &lt;code&gt;https-webhint-io.html&lt;/code&gt; and &lt;code&gt;yarn.lock&lt;/code&gt;. He was supposed to modify only a Markdown file, but he probably added his &lt;a href="https://webhint.io/"&gt;Web Hint&lt;/a&gt;'s report by mistake. I made a comment on it, which helped him eventually remove those irrelevant files from his PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewed PR for &lt;code&gt;More Input and Output.md&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;After reviewing the &lt;a href="https://github.com/Seneca-ICTOER/IPC144/pull/84"&gt;PR&lt;/a&gt;, I noticed a tiny whitespace character that could lead to misunderstanding. The original text was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;"%*c%c"&lt;/code&gt; discards/ignores &lt;strong&gt;one&lt;/strong&gt; character and accepts the next.&lt;br&gt;
&lt;code&gt;" %c"&lt;/code&gt; discards/ignores &lt;strong&gt;all&lt;/strong&gt; whitespace characters before the next non-whitespace character.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The whitespace in &lt;code&gt;" %c"&lt;/code&gt; was then removed by the author. However, this whitespace character was actually necessary for the expression to discard/ignore all whitespace characters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Code review surely requires attention to details, but it can be done more easily with collaboration. I really appreciate others' effort on reviewing my PR and suggesting interesting areas of improvement. I hope that I have a chance to review more challenging work next time.&lt;/p&gt;




&lt;p&gt;PR #1: &lt;a href="https://github.com/Seneca-ICTOER/IPC144/pull/81"&gt;https://github.com/Seneca-ICTOER/IPC144/pull/81&lt;/a&gt;&lt;br&gt;
PR #2: &lt;a href="https://github.com/Seneca-ICTOER/IPC144/pull/84"&gt;https://github.com/Seneca-ICTOER/IPC144/pull/84&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I Contributed to an OER Project</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Sat, 20 Nov 2021 04:00:06 +0000</pubDate>
      <link>https://dev.to/oliverpham/how-i-contributed-to-an-oer-project-pem</link>
      <guid>https://dev.to/oliverpham/how-i-contributed-to-an-oer-project-pem</guid>
      <description>&lt;p&gt;I finally had a chance to work on &lt;a href="https://github.com/Seneca-ICTOER/IPC144"&gt;IPC144&lt;/a&gt;, an Open Educational Resources (OER) project with a view to modernizing the old &lt;a href="https://ict.senecacollege.ca/~ipc144/"&gt;IPC144 course website&lt;/a&gt; with &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt;. As a first time contributor, I found a perfect issue to get familiar with the project: auditing and fixing the automatically translated Markdown files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;As instructed in the &lt;a href="https://github.com/Seneca-ICTOER/IPC144/issues/18"&gt;meta issue&lt;/a&gt;, I opened a new &lt;a href="https://github.com/Seneca-ICTOER/IPC144/issues/28"&gt;issue&lt;/a&gt; for the page on which I'd like to work. Once I'd cloned the repository, I could immediately start auditing the Markdown file without any additional configuration. I had to go over a long checklist posted in the meta issue, but I'll discuss what I've found interesting in this blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docusaurus Features
&lt;/h2&gt;

&lt;p&gt;Docusaurus has 2 distinct features that can significantly improve the old IPC144 website:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontmatter: the top section of a content file used for specifying metadata and Docusaurus's configuration variables&lt;/li&gt;
&lt;li&gt;Admonition: a highlighted section in a content file&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Frontmatter
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BjKhi7UP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1qfuec97yu37tjna7ja3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BjKhi7UP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1qfuec97yu37tjna7ja3.jpg" alt="Frontmatter example" width="880" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docusaurus supports a large number of &lt;a href="https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-docs#markdown-frontmatter"&gt;Frontmatter fields&lt;/a&gt;. I added 4 of them to the Markdown file for SEO optimization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
id: algorithms
...
title: "Algorithms"
slug: /refinements/algorithms
description: "An algorithm is the set of rules that define the sequence of operations required to complete the task."
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Admonition
&lt;/h3&gt;

&lt;p&gt;There were some noteworthy sections of the page. However, they barely stood out because of the use of Markdown blockquote like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hSA2-UPR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dvvf597a7lqa04njn20m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hSA2-UPR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dvvf597a7lqa04njn20m.jpg" alt="Blockquote" width="880" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily, Docusaurus' &lt;a href="https://docusaurus.io/docs/markdown-features/admonitions"&gt;Admonition&lt;/a&gt; is perfect for this role. It is really eye-catching and easy to use. For example, I can enclose the content with &lt;code&gt;:::note&lt;/code&gt; and &lt;code&gt;:::&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:::note

The value returned by `find()` (variable `i`) is validated to ensure that it is within the bounds of the key array (that is, we check that it is not -1 and not more than the number of items in the array). 

:::
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and it will be rendered like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pXHbV4v9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lo39l1hhnh2tv2oykec7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pXHbV4v9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lo39l1hhnh2tv2oykec7.jpg" alt="Admonition Example" width="880" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Although it might seem like a small issue, it's still very satisfying to know that &lt;a href="https://github.com/Seneca-ICTOER/IPC144/pull/72"&gt;my contribution&lt;/a&gt; can enhance the learning experience of other fellow students.&lt;/p&gt;




&lt;p&gt;Issue: &lt;a href="https://github.com/Seneca-ICTOER/IPC144/issues/28"&gt;https://github.com/Seneca-ICTOER/IPC144/issues/28&lt;/a&gt;&lt;br&gt;
PR: &lt;a href="https://github.com/Seneca-ICTOER/IPC144/pull/72"&gt;https://github.com/Seneca-ICTOER/IPC144/pull/72&lt;/a&gt;&lt;br&gt;
Docusaurus: &lt;a href="https://docusaurus.io/docs"&gt;https://docusaurus.io/docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Set Up Testing for My Python Project</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Fri, 12 Nov 2021 20:56:20 +0000</pubDate>
      <link>https://dev.to/oliverpham/how-i-set-up-testing-for-my-python-project-5c13</link>
      <guid>https://dev.to/oliverpham/how-i-set-up-testing-for-my-python-project-5c13</guid>
      <description>&lt;p&gt;After setting up static analysis tools &lt;a href="https://dev.to/oliverpham/2-static-analysis-tools-to-enhance-your-productivity-3d5b"&gt;last week&lt;/a&gt;, it's time to configure a testing framework for Continuous Integration (CI). There are several options for &lt;a href="https://github.com/oliver-pham/silkie"&gt;Silkie&lt;/a&gt;, my work-in-progress static site generator, but I decided to give &lt;a href="https://docs.pytest.org/en/6.2.x/index.html"&gt;Pytest&lt;/a&gt; a try. In this blog, I'll show you how I set up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
pytest - a Python testing framework&lt;/li&gt;
&lt;li&gt;
pytest-watch - a CLI tool for running tests automatically on changes&lt;/li&gt;
&lt;li&gt;
pytest-cov - a Pytest's plugin for producing code coverage reports&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Pytest
&lt;/h2&gt;

&lt;p&gt;I find Pytest easy to set up tests without too much boilerplate code and to add more functionalities with many extensions. You can add it to your project with a single installation command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install -U pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check whether it's already installed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pytest --version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can write your test as easy as adding a Python function. First, create a file whose name starts with &lt;code&gt;test_&lt;/code&gt; or ends with &lt;code&gt;_test&lt;/code&gt;. Pytest automatically discovers those tests if you name them according to &lt;a href="https://docs.pytest.org/en/6.2.x/goodpractices.html#test-discovery"&gt;their convention&lt;/a&gt;. In my case, I created &lt;code&gt;test_get_file_name.py&lt;/code&gt; for testing this function:&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;def&lt;/span&gt; &lt;span class="nf"&gt;get_filename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;"""Extract the name of the file from a file path and exclude any file extension"""&lt;/span&gt;
    &lt;span class="k"&gt;return&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;file_path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stem&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, simply add your test cases as Python functions. For instance, I tried to test whether my &lt;code&gt;get_filename()&lt;/code&gt; function works on a file path of both Windows and Unix. I also wanted to check if it can exclude multiple file extensions from the file name:&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;def&lt;/span&gt; &lt;span class="nf"&gt;test_windows_file_path&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;expected_file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt;
    &lt;span class="n"&gt;file_extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"txt"&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;fr&lt;/span&gt;&lt;span class="s"&gt;"C:\Documents\{expected_file_name}.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_extension&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_filename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected_file_name&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_unix_file_path&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;expected_file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt;
    &lt;span class="n"&gt;file_extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"txt"&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"/Users/anonymous/Documents/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_extension&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_filename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected_file_name&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_file_path_multiple_extensions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;expected_file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt;
    &lt;span class="n"&gt;file_extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"rc.txt"&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"/Users/anonymous/Documents/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_extension&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_filename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected_file_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also configured Pytest to only search for tests in my &lt;code&gt;tests&lt;/code&gt; folder by specifying it in &lt;code&gt;pytest.ini&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[pytest]&lt;/span&gt;
&lt;span class="py"&gt;minversion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;6.0&lt;/span&gt;
&lt;span class="py"&gt;testpaths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I can finally run my tests with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unexpectedly, I caught an error in my code. The test failed when a Windows file path is passed to the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;====================================================================================================== test session starts =======================================================================================================
platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /Users/ptpham/Projects/silkie
collected 3 items                                                                                                                                                                                                                

tests/unit/test_get_file_name.py F..                                                                                                                                                                                       [100%]

============================================================================================================ FAILURES ============================================================================================================
_____________________________________________________________________________________________________ test_windows_file_path _____________________________________________________________________________________________________

    def test_windows_file_path():
        expected_file_name = "test"
        file_extension = "txt"
        file_path = fr"C:\Documents\{expected_file_name}.{file_extension}"
        file_name = get_filename(file_path=file_path)

&amp;gt;       assert file_name == expected_file_name
E       AssertionError: assert 'C:\\Documents\\test' == 'test'
E         - test
E         + C:\Documents\test

tests/unit/test_get_file_name.py:10: AssertionError
==================================================================================================== short test summary info =====================================================================================================
FAILED tests/unit/test_get_file_name.py::test_windows_file_path - AssertionError: assert 'C:\\Documents\\test' == 'test'
================================================================================================== 1 failed, 2 passed in 0.19s ===================================================================================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I decided to apply a quick fix to the function:&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;def&lt;/span&gt; &lt;span class="nf"&gt;get_filename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;"""Extract the name of the file from a file path and exclude any file extension"""&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="c1"&gt;# Replace any backslash(es) in Windows file path with forwardslash(es)
&lt;/span&gt;    &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&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="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;return&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;file_path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stem&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It made the test passed 🥳! With &lt;code&gt;pytest&lt;/code&gt; working, let's set up &lt;code&gt;pytest-watch&lt;/code&gt; so my tests can run automatically whenever I make some changes to my source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pytest-watch
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/joeyespo/pytest-watch"&gt;pytest-watch&lt;/a&gt; is a zero-config CLI tool that runs pytest, and re-runs it when a file in your project changes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you don't want to run your tests manually every time you update your code, you can install this tool with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install pytest-watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you just need to run the tool in the root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ptw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pytest-cov
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/pytest-dev/pytest-cov"&gt;pytest-cov&lt;/a&gt; is a pytest's plugin that produces coverage reports. If you want to see how much your tests have covered your codebase, you can install this tool by running this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install pytest-cov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the package is installed, you can run &lt;code&gt;pytest-cov&lt;/code&gt; by adding &lt;code&gt;--cov=&amp;lt;your-root-folder&amp;gt;&lt;/code&gt; to your &lt;code&gt;pytest&lt;/code&gt; command. In my case, I'd also like to see which lines of code are not covered by running this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pytest --cov-report term-missing  --cov=silkie
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Setting up a testing framework isn't as complicated as I expected, but it's certainly beneficial to my project's quality. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>2 Static Analysis Tools to Enhance Your Productivity</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Sat, 06 Nov 2021 03:01:57 +0000</pubDate>
      <link>https://dev.to/oliverpham/2-static-analysis-tools-to-enhance-your-productivity-3d5b</link>
      <guid>https://dev.to/oliverpham/2-static-analysis-tools-to-enhance-your-productivity-3d5b</guid>
      <description>&lt;p&gt;If you are tired of maintaining your coding style, I have good news for you. Fortunately, there are developer tools that can automate and streamline mundane development tasks. In this blog, I'll show you how I integrated 2 &lt;a href="https://en.wikipedia.org/wiki/Static_program_analysis" rel="noopener noreferrer"&gt;static code analysis&lt;/a&gt; tools and a package manager for pre-commit hooks into &lt;a href="https://github.com/oliver-pham/silkie" rel="noopener noreferrer"&gt;Silke&lt;/a&gt;, my work-in-progress static site generator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Black: The uncompromising Python code formatter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/psf/black" rel="noopener noreferrer"&gt;Black&lt;/a&gt; can help you automatically format your Python code that follows (most of) the style conventions in &lt;a href="http://www.python.org/dev/peps/pep-0008/" rel="noopener noreferrer"&gt;PEP 8&lt;/a&gt;. You can install this tool with &lt;a href="https://pypi.org/project/pip/" rel="noopener noreferrer"&gt;Pip&lt;/a&gt; and &lt;strong&gt;Python 3.6.2+&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install black
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, Black should work without configuring anything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ black .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once Black is executed, it will tell you which files have been reformatted. When I ran it on Silkie, Black produced the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reformatted silkie/silkie.py
All done! ✨ 🍰 ✨
1 file reformatted.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After testing Silkie, I was pleasantly amazed that Black didn't break my code. If you are still worried that Black may break your program by alternating your code, you can include the &lt;code&gt;--diff&lt;/code&gt; flag when running it. That should allow Black to only suggest their changes. &lt;/p&gt;

&lt;h3&gt;
  
  
  VSCode Integration
&lt;/h3&gt;

&lt;p&gt;If you use Visual Studio Code as your code editor, you can easily integrate Black into it. First, you need to create a &lt;code&gt;settings.json&lt;/code&gt; file under &lt;code&gt;.vscode&lt;/code&gt; folder if you haven't had it. Then, add these 2 lines to enable Black to reformat your Python code when you save your files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"python.formatting.provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"black"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"editor.formatOnSave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Flake8: Python 3-in-one linter
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Lint_(software)" rel="noopener noreferrer"&gt;code linter&lt;/a&gt; should help developers identify potential errors and coding style violations in your code; and I think &lt;a href="https://github.com/pycqa/flake8" rel="noopener noreferrer"&gt;Flake8&lt;/a&gt; can accomplish that. Flake8 combines &lt;code&gt;PyFlakes&lt;/code&gt;, &lt;code&gt;pycodestyle&lt;/code&gt;, and &lt;code&gt;Ned Batchelder's McCabe script&lt;/code&gt; to enhance our Python code quality.&lt;/p&gt;

&lt;p&gt;Setting it up is as easy as a one-line command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install flake8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's installed, you can run it on your project right away. However, you can save your time later by specifying which files and/or directories Flake8 should exclude from its scan. In order to do that: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;tox.ini&lt;/code&gt; file in your root directory.&lt;/li&gt;
&lt;li&gt;Specify the excluded files/folders separated by commas. Take my project as an example:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[flake8]&lt;/span&gt;
&lt;span class="py"&gt;exclude&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c"&gt;# Version control directory
&lt;/span&gt;    &lt;span class="err"&gt;.git,&lt;/span&gt;
    &lt;span class="c"&gt;# GitHub configuration directory
&lt;/span&gt;    &lt;span class="err"&gt;.github&lt;/span&gt;
    &lt;span class="c"&gt;# Python compiled bytecode 
&lt;/span&gt;    &lt;span class="err"&gt;__pycache__,&lt;/span&gt;
    &lt;span class="c"&gt;# Python third-party libraries &amp;amp; packages
&lt;/span&gt;    &lt;span class="err"&gt;lib,&lt;/span&gt;
    &lt;span class="c"&gt;# Python scripts &amp;amp; binary files
&lt;/span&gt;    &lt;span class="err"&gt;bin,&lt;/span&gt;
    &lt;span class="c"&gt;# Build directory
&lt;/span&gt;    &lt;span class="err"&gt;dist,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I ran Flake8 on my project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ flake8 .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, Flake8's style rules may have some conflicts with Black's. For instance, although Black already formatted my code, Flake8 would still bug me for having too many characters per line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./silkie/silkie.py:37:80: E501 line too long (83 &amp;gt; 79 characters)
./silkie/silkie.py:43:80: E501 line too long (84 &amp;gt; 79 characters)
./silkie/silkie.py:52:80: E501 line too long (80 &amp;gt; 79 characters)
./silkie/silkie.py:76:80: E501 line too long (86 &amp;gt; 79 characters)
./silkie/silkie.py:80:80: E501 line too long (80 &amp;gt; 79 characters)
./silkie/silkie.py:104:80: E501 line too long (80 &amp;gt; 79 characters)
./silkie/silkie.py:149:80: E501 line too long (80 &amp;gt; 79 characters)
./silkie/silkie.py:157:80: E501 line too long (80 &amp;gt; 79 characters)
./silkie/silkie.py:159:80: E501 line too long (83 &amp;gt; 79 characters)
./silkie/silkie.py:199:80: E501 line too long (85 &amp;gt; 79 characters)
./silkie/silkie.py:210:80: E501 line too long (88 &amp;gt; 79 characters)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moreover, Flake8 found a &lt;a href="https://github.com/PyCQA/pycodestyle/issues/373" rel="noopener noreferrer"&gt;false positive error&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./silkie/silkie.py:159:56: E203 whitespace before ':'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flake8 actually complained about the whitespace before a list slice:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8vr0fpkumvohc746e8k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8vr0fpkumvohc746e8k.jpg" alt="Whitespace before list slice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To resolve those issues, I configured Flake8 in &lt;code&gt;tox.ini&lt;/code&gt; file, located in the root directory, to ignore those false positive stylistic errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[flake8]&lt;/span&gt;
&lt;span class="py"&gt;max-line-length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;88&lt;/span&gt;
&lt;span class="py"&gt;ignore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c"&gt;# False positive whitespace before ':' on list slice.
&lt;/span&gt;    &lt;span class="c"&gt;# See https://github.com/PyCQA/pycodestyle/issues/373 for details
&lt;/span&gt;    &lt;span class="err"&gt;E203&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  VSCode Integration
&lt;/h3&gt;

&lt;p&gt;Flake8 can integrated into Visual Studio Code by adding these lines to &lt;code&gt;settings.json&lt;/code&gt; file under &lt;code&gt;.vscode&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"python.linting.flake8Enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"python.linting.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"python.linting.flake8Args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"--max-line-length=88"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pre-commit: Multi-language pre-commit hooks manager
&lt;/h2&gt;

&lt;p&gt;If you don't want to manually run Black and Flake8 before committing your changes, you can automate it with &lt;a href="https://pre-commit.com/" rel="noopener noreferrer"&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To install &lt;code&gt;pre-commit&lt;/code&gt;, run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install pre-commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's installed, you can set up pre-commit hooks by specifying them in the &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; file. For example, this is how I added Black and Flake8 as pre-commit Git hook scripts to my project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/psf/black&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;21.10b0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;black&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/pycqa/flake8&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4.0.1&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flake8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run these tools before every &lt;code&gt;git commit&lt;/code&gt; commands, you need to install the scripts with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pre-commit install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, Black and Flake8 should be executed before any modification is committed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I initially thought integrating these tools into my project would take a lot of time, but it was actually really quick and straightforward. Thanks to the development teams behind Black &amp;amp; Flake8, my life as a developer has been much easier. I can't wait to try out other awesome tools!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>3 Ways to Get Unstuck while Fixing Bugs</title>
      <dc:creator>Oliver Pham</dc:creator>
      <pubDate>Sun, 31 Oct 2021 03:27:08 +0000</pubDate>
      <link>https://dev.to/oliverpham/3-ways-to-get-unstuck-while-fixing-bugs-4ec9</link>
      <guid>https://dev.to/oliverpham/3-ways-to-get-unstuck-while-fixing-bugs-4ec9</guid>
      <description>&lt;p&gt;When you contribute to open source projects, getting frustrated with arbitrary issues is inevitable. I was in the same situation while trying to resolve an &lt;a href="https://github.com/appwrite/appwrite/issues/1920"&gt;issue&lt;/a&gt; about writing a demo cloud function for one of &lt;a href="https://appwrite.io/"&gt;Appwrite&lt;/a&gt;'s features. There were 3 resources that helped me get through the problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Community Channels&lt;/li&gt;
&lt;li&gt;GitHub Issues&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;Let's take a look at the issue for an overall context:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your task is to implement &lt;code&gt;generateOpenStreetMap&lt;/code&gt; function. You can look at the existing &lt;a href="https://github.com/appwrite/demos-for-functions"&gt;Appwrite Functions demo&lt;/a&gt; in the coding language you prefer to see how it works.&lt;/p&gt;

&lt;p&gt;This function should take &lt;code&gt;latitude&lt;/code&gt; and &lt;code&gt;longitude&lt;/code&gt; as an input, generate map preview using &lt;a href="https://wiki.openstreetmap.org/wiki/API"&gt;OpenStreetMap API&lt;/a&gt; and save it into Appwrite Storage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There were 3 main challenges I had to tackle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating an Appwrite Cloud Function (I can somewhat understand the process)&lt;/li&gt;
&lt;li&gt;Write my code in Ruby (doesn't look really bad)&lt;/li&gt;
&lt;li&gt;Implementing OpenStreetMap API (absolutely clueless)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📜 Documentation
&lt;/h2&gt;

&lt;p&gt;While experimenting with a new technology, you should seek out its documentation first. You can find the answers to most of your questions there. If you try asking for help before reading the docs, you'll probably get this kind of response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HEoCrUoD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ck2dsj0vwj1xzjj927es.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HEoCrUoD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ck2dsj0vwj1xzjj927es.png" alt="RTFM" width="350" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, not really that rude, but they will tell you to read it first most of the time. In my case, thanks to their docs, I could easily create an Appwrite Function for Ruby. The process was pretty straightforward, so instead of discussing it with someone else, I just tested it myself.&lt;/p&gt;

&lt;p&gt;On the other hand, documentation can sometimes be outdated or too hard to navigate to the part that you need. In fact, OpenStreetMap Wiki took me quite a lot time to find out how I could retrieve a map image from their API. Because even the search box can't find any explicit example of such functionality, I struggled for a few days until I decided to reach out to the next source of support: community channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  💬 Community Channels
&lt;/h2&gt;

&lt;p&gt;Almost every open source projects that welcome contributors use a platform for communication (e.g. Discord, Slack, and Matrix). This should help you resolve most issues which can't be found in the docs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TWbiLijw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7uy9zvggyhw3wpdo5jxo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TWbiLijw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7uy9zvggyhw3wpdo5jxo.png" alt="Tech support" width="880" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just kidding. Most of the time, you'll get more helpful advice. For example, I realized my initial approach to get a map image from OpenStreetMap API was a dead end after discussing it with a project member on Appwrite's Discord server. Thankfully, I was introduced to OpenStreetMap tile server, where I can get a static map image from a tile number. Moreover, I even found the exact code snippet from the docs for calculating the tile number from a latitude, a longitude, and a zoom level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Source: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Ruby&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_tile_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat_deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lng_deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;lat_rad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lat_deg&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PI&lt;/span&gt;
  &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;zoom&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;lng_deg&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;180.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;360.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
  &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;tan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat_rad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat_rad&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:y&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎫 GitHub Issues
&lt;/h2&gt;

&lt;p&gt;Sometimes, when something doesn't work as expected, it's not your fault. It can be a bug that has yet to be detected. Even if it is not, you will get more attention to your issue when you file it on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y37JcYy_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lndb0479igfo0b1ilu2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y37JcYy_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lndb0479igfo0b1ilu2.png" alt="Bug" width="271" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While testing my cloud function on Appwrite Console, I got a bizzare error with my HTTP requests. After doing a lot of research online and asking for help on Discord, I still had no clue. When I was about to give up, I decided to file an &lt;a href="https://github.com/appwrite/appwrite/issues/2245"&gt;issue&lt;/a&gt; on Appwrite's repo saying how my function didn't work as expected. After a while, I got a &lt;a href="https://github.com/appwrite/appwrite/issues/2245#issuecomment-946304322"&gt;response&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SKIqz69o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9yad8whvpltu0r6b3jqw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SKIqz69o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9yad8whvpltu0r6b3jqw.jpg" alt="A response to my issue" width="880" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After following his suggestion, I managed to fix the bug and eventually made a &lt;a href="https://github.com/appwrite/demos-for-functions/pull/248"&gt;pull request&lt;/a&gt;. You can check out my recent &lt;a href="https://dev.to/oliverpham/how-to-generate-and-store-map-previews-in-ruby-with-appwrite-43ha"&gt;blog post&lt;/a&gt; to learn more details about how I implemented and deployed the cloud function.&lt;/p&gt;




&lt;p&gt;This is my last blog post for Hacktoberfest 2021. It was truly an amazing and eye-opening journey! I'm really looking forward to participating in Hacktoberfest again next year.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>hacktoberfest</category>
    </item>
  </channel>
</rss>
