<?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: Saad Shakil</title>
    <description>The latest articles on DEV Community by Saad Shakil (@codedir).</description>
    <link>https://dev.to/codedir</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%2F1983622%2F7ac40839-c0c6-4d7f-b4c4-effe3698a506.jpg</url>
      <title>DEV Community: Saad Shakil</title>
      <link>https://dev.to/codedir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codedir"/>
    <language>en</language>
    <item>
      <title>Understanding Vite’s File Structure: Why index.html Belongs at the Root</title>
      <dc:creator>Saad Shakil</dc:creator>
      <pubDate>Tue, 21 Jan 2025 02:45:26 +0000</pubDate>
      <link>https://dev.to/codedir/understanding-vites-file-structure-why-indexhtml-belongs-at-the-root-5a0h</link>
      <guid>https://dev.to/codedir/understanding-vites-file-structure-why-indexhtml-belongs-at-the-root-5a0h</guid>
      <description>&lt;p&gt;When working with Vite, a common gotcha is the location of the &lt;code&gt;index.html&lt;/code&gt; file. Unlike traditional build tools like Webpack, Vite requires your &lt;code&gt;index.html&lt;/code&gt; to reside in the project root directory, not in a &lt;code&gt;public&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;What happens when &lt;code&gt;index.html&lt;/code&gt; is in &lt;code&gt;public&lt;/code&gt;? You get HTTP ERROR 404, indicative of a running server but the resource not found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This localhost page can’t be found
No webpage was found for the web address: http://localhost:5173/
HTTP ERROR 404
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Does Vite Require This?
&lt;/h2&gt;

&lt;p&gt;Vite uses &lt;code&gt;index.html&lt;/code&gt; as an entry point to optimize and bundle your project. Placing it at the root allows Vite to:&lt;br&gt;
• Detect and handle linked assets (e.g., JS, CSS) efficiently.&lt;br&gt;
• Inline scripts and styles directly during development.&lt;br&gt;
• Provide accurate paths for module resolution.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Correct Structure
&lt;/h2&gt;

&lt;p&gt;Here’s the expected structure for a basic Vite project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-project/
├── index.html      // Root-level entry point
├── src/            // Source files (components, styles, etc.)
│   └── main.js
├── public/         // Static assets (not processed by Vite)
│   └── favicon.ico
└── vite.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>vite</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>URL Matching Nuance with mux Router in Go</title>
      <dc:creator>Saad Shakil</dc:creator>
      <pubDate>Thu, 16 Jan 2025 21:12:58 +0000</pubDate>
      <link>https://dev.to/codedir/url-matching-nuance-with-mux-router-in-go-74b</link>
      <guid>https://dev.to/codedir/url-matching-nuance-with-mux-router-in-go-74b</guid>
      <description>&lt;p&gt;When building web applications in Go with the &lt;code&gt;mux&lt;/code&gt; router, it’s essential to understand how route matching works, especially when dealing with URLs that have query parameters. One common issue developers face is with trailing slashes in the URL paths, which can break route matching unexpectedly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Problem: Trailing Slash on Route Matching&lt;/li&gt;
&lt;li&gt;Solution&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problem: Trailing Slash on Route Matching
&lt;/h2&gt;

&lt;p&gt;Consider the following two endpoints in a Go application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Broken&lt;/span&gt;

&lt;span class="c"&gt;// http://localhost:8080/org/send&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/org/send/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Methods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// http://localhost:8080/org/retrieve/?param=val&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/org/{id}/retrieve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Methods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trailing Slash Issues with Static and Dynamic Endpoints
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;mux&lt;/code&gt;, &lt;strong&gt;static paths&lt;/strong&gt; like &lt;code&gt;/org/send&lt;/code&gt; &lt;strong&gt;do not match&lt;/strong&gt; if a trailing slash is added (e.g., &lt;code&gt;/org/send/&lt;/code&gt;), resulting in a &lt;code&gt;404 page not found&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;strong&gt;dynamic paths&lt;/strong&gt; like &lt;code&gt;/org/{id}/retrieve/?param=val&lt;/code&gt; &lt;strong&gt;require&lt;/strong&gt; a trailing slash to match correctly (e.g., &lt;code&gt;/org/{id}/retrieve/&lt;/code&gt;); without it, a &lt;code&gt;404&lt;/code&gt; error will occur.&lt;/p&gt;

&lt;p&gt;This happens because &lt;code&gt;mux&lt;/code&gt; treats paths strictly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;To fix this, register route paths consistently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static paths&lt;/strong&gt; (e.g., &lt;code&gt;/org/send&lt;/code&gt;) should not include a trailing slash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic paths&lt;/strong&gt; (e.g., &lt;code&gt;/org/{id}/retrieve/&lt;/code&gt;) should include a trailing slash in the matcher.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Fixed&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/org/send"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Methods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// No trailing slash&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/org/{id}/retrieve/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Methods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// Trailing slash needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you can add additional matchers to handle static routes with and without slashes and error-correcting middleware to match dynamic routes with missing slashes.&lt;/p&gt;

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

&lt;p&gt;Understanding how &lt;code&gt;mux&lt;/code&gt; handles trailing slashes and how it matches paths is key to ensuring that your routes work as expected. Be mindful of static vs. dynamic path matching, and ensure consistency in how you define your routes.&lt;/p&gt;

&lt;p&gt;Back to Top&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understanding Git Rebase Merge: Chronological vs Logical Order and Commit History</title>
      <dc:creator>Saad Shakil</dc:creator>
      <pubDate>Sun, 24 Nov 2024 10:55:24 +0000</pubDate>
      <link>https://dev.to/codedir/understanding-git-rebase-merge-chronological-vs-logical-order-and-commit-histories-36ae</link>
      <guid>https://dev.to/codedir/understanding-git-rebase-merge-chronological-vs-logical-order-and-commit-histories-36ae</guid>
      <description>&lt;h1&gt;
  
  
  Logical Reverse Chronological Order: Interpreting Git's Rebase's &lt;code&gt;git log&lt;/code&gt; History
&lt;/h1&gt;

&lt;p&gt;This article explores how chronological order and logical order can be used to understand default ordering of &lt;code&gt;git log&lt;/code&gt;'s history upon a singular &lt;code&gt;git rebase&lt;/code&gt; operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Chronological Order&lt;/li&gt;
&lt;li&gt;Logical Order&lt;/li&gt;
&lt;li&gt;Rebase&lt;/li&gt;
&lt;li&gt;Interpreting Git Log&lt;/li&gt;
&lt;li&gt;Git Rebase Merge Commands&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Chronological Order
&lt;/h2&gt;

&lt;p&gt;Chronological order refers to the order of commits based on their timestamps. &lt;/p&gt;

&lt;p&gt;This reflects when the commits are made, regardless of their logical relationships.&lt;/p&gt;

&lt;p&gt;For example, say we have the following chronologically ascending commits from top to bottom, earliest/oldest to latest/newest/recent :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A: Jan 1, 1pm
B: Jan 2, 1pm
C: Jan 3, 1pm
X: Jan 3, 2pm
Y: Jan 4, 2pm
D: Jan 4, 3pm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Corresponding chronological graph, left to right:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A - B - C - X - Y - D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to Top&lt;/p&gt;

&lt;h2&gt;
  
  
  Logical Order
&lt;/h2&gt;

&lt;p&gt;Logical order refers to the parent-child relationships between commits in &lt;code&gt;git&lt;/code&gt;, regardless of chronological order.&lt;/p&gt;

&lt;p&gt;It represents the commit graph, where each commit logically follows its parent, regardless of the timestamp.&lt;/p&gt;

&lt;p&gt;In the example, say &lt;code&gt;feature&lt;/code&gt; branch was created after commit &lt;code&gt;B&lt;/code&gt; and has logical commits &lt;code&gt;C&lt;/code&gt; and &lt;code&gt;D&lt;/code&gt; (after &lt;code&gt;B&lt;/code&gt;), while &lt;code&gt;X&lt;/code&gt; and &lt;code&gt;Y&lt;/code&gt; follow logically after &lt;code&gt;B&lt;/code&gt; on &lt;code&gt;main&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;A - B - X - Y
      \
       - C - D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The common, or base, ancestor is &lt;code&gt;B&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebase
&lt;/h2&gt;

&lt;p&gt;Upon a &lt;code&gt;git rebase&lt;/code&gt;, which here includes the typical merging in a single operation, and not a separate rebase then merge commit (say &lt;code&gt;M&lt;/code&gt;), we get this logical order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A - B - X - Y - C' - D'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The apostrophes for &lt;code&gt;C'&lt;/code&gt;, &lt;code&gt;D'&lt;/code&gt; indicate distinct rebased versions of the commits, with different hashes created during the rebase because their parent commit changed to &lt;code&gt;Y&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Commits &lt;code&gt;C'&lt;/code&gt; and &lt;code&gt;D'&lt;/code&gt;, together in that order, are appended at &lt;code&gt;main&lt;/code&gt;'s tip, after &lt;code&gt;Y&lt;/code&gt; - which is now the new base, regardless of the timestamps that determine the chronological order.&lt;/p&gt;

&lt;p&gt;The tip is the "end" of the history, currently with &lt;code&gt;D&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Back to Top&lt;/p&gt;

&lt;h2&gt;
  
  
  Interpreting Git Log
&lt;/h2&gt;

&lt;p&gt;By default, &lt;code&gt;git log&lt;/code&gt; shows logical order in reverse chronological order, or logical reverse chronological order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D' - C' - Y - X - B - A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit &lt;code&gt;D&lt;/code&gt; appears at the top.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commits are displayed starting from the chronologically most recent commit from the logical graph

&lt;ul&gt;
&lt;li&gt;This refers to the tip being the starting point, &lt;code&gt;D&lt;/code&gt;, which happens to be so since it is chronologically the last/most recent commit&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Timestamps do not determine position (not inconsistent with being chronological, as that's covered above)

&lt;ul&gt;
&lt;li&gt;Once the logical graph is constructed, the order of commits in the log is based on parent-child relationships, not the timestamps. This means commits may appear in a sequence (logical order) that doesn’t align with their actual time of creation.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This default behavior reflects how &lt;code&gt;git&lt;/code&gt; manages the commit graph internally.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;A side note, I'd say that the &lt;code&gt;git&lt;/code&gt; &lt;a href="https://git-scm.com/docs/git-log" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; simplifies this too much when it refers to this as "reverse chronological order". Maybe this can lead to some confusion as to how that's consistent with being chronological when it's not timestamps per-se that determine the chronological-ness of the order. 🤔 &lt;/p&gt;

&lt;p&gt;This would be reverse chronological order in the strict sense, which is not what we see from &lt;code&gt;git log&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;D' - Y - X - C' - B - A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to Top&lt;/p&gt;

&lt;h2&gt;
  
  
  Git Rebase Merge Commands
&lt;/h2&gt;

&lt;p&gt;This is the typical sequence for the 'singular' step rebase and merge that doesn't create a separate merge commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Ensure you're on the feature branch
git checkout feature

# make commits on feature (and someone else does so on main)

# Rebase feature branch onto main
# Probably need to pull latest for main
git checkout main
git pull
git checkout feature
git rebase main

# Switch to main branch
git checkout main

# Fast-forward main to include the rebased feature branch
git merge --ff-only feature

# Push the updated main branch to the remote
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is the &lt;code&gt;git merge --ff-only&lt;/code&gt; command, as compared to the &lt;code&gt;--no-ff&lt;/code&gt; option for the separate rebase and merge commit.&lt;/p&gt;

&lt;p&gt;Back to Top&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Points to Remember
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git log&lt;/code&gt; shows commits in logical reverse chronological order by default.&lt;/li&gt;
&lt;li&gt;Rebase rewrites commit history: Commits from the source branch are applied first, followed by the rebased commits, regardless of their timestamps.&lt;/li&gt;
&lt;li&gt;Timestamps do not influence the logical structure of the commit graph.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Understanding the distinction between logical and chronological order in Git can be useful for interpreting &lt;code&gt;git log&lt;/code&gt;, especially after &lt;code&gt;git rebase&lt;/code&gt; operations.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;git log&lt;/code&gt; commit history primarily reflects the logical structure of the commit graph. After a rebase, commits from the source (e.g., &lt;code&gt;feature&lt;/code&gt;) are incorporated at the tip of the destination branch (e.g., &lt;code&gt;main&lt;/code&gt;), regardless of their original timestamps.&lt;/p&gt;

&lt;p&gt;Timestamps play a secondary role and may not align with the logical order of the commits, particularly after a rebase, where the logical graph takes precedence over chronological order.&lt;/p&gt;

&lt;p&gt;By keeping these concepts in mind, you can more effectively navigate and analyze your Git history during development.&lt;br&gt;
Back to Top&lt;/p&gt;

</description>
      <category>git</category>
      <category>rebase</category>
      <category>commit</category>
      <category>gitlog</category>
    </item>
    <item>
      <title>Unable to Transfer Files from Android to MacOS</title>
      <dc:creator>Saad Shakil</dc:creator>
      <pubDate>Thu, 07 Nov 2024 02:41:15 +0000</pubDate>
      <link>https://dev.to/codedir/unable-to-transfer-files-from-android-to-macos-eo6</link>
      <guid>https://dev.to/codedir/unable-to-transfer-files-from-android-to-macos-eo6</guid>
      <description>&lt;h3&gt;Issue&lt;/h3&gt;

&lt;p&gt;
When attempting to connect to Android devices on MacOS via OpenMTP or MacDroid, I was receiving errors and the devices could not connect.
&lt;/p&gt;

&lt;h3&gt;Cause&lt;/h3&gt;

&lt;p&gt;
It turns out that the PTP Webcam service binds the camera access upon connecting devices, preventing access by OpenMTP and MacDroid.
&lt;/p&gt;

&lt;h3&gt;Solutions&lt;/h3&gt;

&lt;p&gt;
Disabling the service didn't work, as newer versions of MacOS prevent system service changes 
(&lt;a href="https://discussions.apple.com/thread/254703577?sortBy=rank" rel="noopener noreferrer"&gt;Permanently Disable ptpcamerad&lt;/a&gt;).
&lt;/p&gt;

&lt;h3&gt;Workaround&lt;/h3&gt;

&lt;p&gt;
Use Activity Monitor to manually stop ptpcamerad and quickly refresh the connection on OpenMTP.
&lt;/p&gt;

&lt;p&gt;
There's also running a service that periodically stops the ptpcamerad service quickly enough to run OpenMTP or other ways to access Android devices on MacOS. I may update this with an example in the future.&lt;/p&gt;

&lt;h3&gt;Resources&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/ganeshrvel/openmtp/releases" rel="noopener noreferrer"&gt;https://github.com/ganeshrvel/openmtp/releases&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.macdroid.app/" rel="noopener noreferrer"&gt;https://www.macdroid.app/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>osx</category>
      <category>openmtp</category>
      <category>android</category>
      <category>androiddev</category>
    </item>
    <item>
      <title>Full Page Screenshots in Chrome</title>
      <dc:creator>Saad Shakil</dc:creator>
      <pubDate>Fri, 18 Oct 2024 05:20:30 +0000</pubDate>
      <link>https://dev.to/codedir/full-page-screenshots-in-chrome-65g</link>
      <guid>https://dev.to/codedir/full-page-screenshots-in-chrome-65g</guid>
      <description>&lt;h1&gt;
  
  
  How to Take a Full-Page Screenshot in Google Chrome
&lt;/h1&gt;

&lt;p&gt;Capturing a full-page screenshot is a useful feature when you need to capture an entire webpage, including the parts that are off-screen. In Google Chrome, this can be done without the need for any third-party extensions. Chrome’s built-in Developer Tools offer a straightforward way to capture full-page screenshots.&lt;/p&gt;

&lt;p&gt;As of 2024, follow these steps to take a full-page screenshot in Google Chrome:&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;1. Open Developer Tools&lt;/li&gt;
&lt;li&gt;2. Capture the Full-Page Screenshot&lt;/li&gt;
&lt;li&gt;3. Locate and Save Your Screenshot&lt;/li&gt;
&lt;li&gt;4. Conclusion&lt;/li&gt;
&lt;li&gt;5. Key Shortcuts Recap&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Open Developer Tools
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open Google Chrome and navigate to the webpage you want to capture.&lt;/li&gt;
&lt;li&gt;Right-click anywhere on the page and select &lt;strong&gt;Inspect&lt;/strong&gt;, or use the keyboard shortcut:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows/Linux&lt;/strong&gt;: &lt;code&gt;Ctrl + Shift + I&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mac&lt;/strong&gt;: &lt;code&gt;Cmd + Option + I&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This will open the &lt;strong&gt;Developer Tools&lt;/strong&gt; panel.&lt;/p&gt;

&lt;p&gt;Back to Top&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Capture the Full-Page Screenshot
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;With the Developer Tools panel open, click the three-dot menu (kebab menu) in the top-right corner of the Developer Tools panel.&lt;/li&gt;
&lt;li&gt;From the dropdown menu, select &lt;strong&gt;Run command&lt;/strong&gt; or press:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows/Linux&lt;/strong&gt;: &lt;code&gt;Ctrl + Shift + P&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mac&lt;/strong&gt;: &lt;code&gt;Cmd + Shift + P&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In the command menu that appears, type &lt;strong&gt;screenshot&lt;/strong&gt; to filter the available commands.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Capture full size screenshot&lt;/strong&gt; from the list.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Chrome will now capture a full-page screenshot and save it to your default downloads location.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Locate and Save Your Screenshot
&lt;/h2&gt;

&lt;p&gt;Once the screenshot is captured, you can save it as a PNG file. &lt;/p&gt;

&lt;p&gt;Back to Top&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Conclusion
&lt;/h2&gt;

&lt;p&gt;Taking a full-page screenshot in Chrome is straightforward using Developer Tools. This built-in feature allows you to capture everything on a webpage, including off-screen content, without needing any additional extensions or tools.&lt;/p&gt;

&lt;p&gt;Next time you need to capture a full webpage for your needs, be it for a report, presentation, or documentation, follow these quick steps to get the job done!&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Key Shortcuts Recap:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Open Developer Tools:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows/Linux&lt;/strong&gt;: &lt;code&gt;Ctrl + Shift + I&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mac&lt;/strong&gt;: &lt;code&gt;Cmd + Option + I&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Run Command:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows/Linux&lt;/strong&gt;: &lt;code&gt;Ctrl + Shift + P&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mac&lt;/strong&gt;: &lt;code&gt;Cmd + Shift + P&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Back to Top&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>webdev</category>
      <category>googlechrome</category>
      <category>shortcuts</category>
    </item>
    <item>
      <title>Importing and Using Existing GPG Keys to Sign Git Commits</title>
      <dc:creator>Saad Shakil</dc:creator>
      <pubDate>Thu, 29 Aug 2024 18:11:47 +0000</pubDate>
      <link>https://dev.to/codedir/using-existing-gpg-key-to-sign-git-commits-5181</link>
      <guid>https://dev.to/codedir/using-existing-gpg-key-to-sign-git-commits-5181</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Main Steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Check GPG (GNU Privacy Guard) is Installed&lt;/li&gt;
&lt;li&gt;Import Keys&lt;/li&gt;
&lt;li&gt;List GPG Keys&lt;/li&gt;
&lt;li&gt;Configure Git to Use Your GPG Key&lt;/li&gt;
&lt;li&gt;Enable Commit Signing by Default (Optional)&lt;/li&gt;
&lt;li&gt;Sign a Commit&lt;/li&gt;
&lt;li&gt;Verify the Signed Commit&lt;/li&gt;
&lt;li&gt;Push Your Signed Commits&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Additional Steps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GitHub/GitLab Setup Resources&lt;/li&gt;
&lt;li&gt;Prevent Repeated Passphrase Entry with GPG Key Caching&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Main Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install and Check GPG (GNU Privacy Guard)
&lt;/h3&gt;

&lt;p&gt;Install GPG through your package manager, like &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;brew&lt;/code&gt;, &lt;code&gt;dnf&lt;/code&gt;, &lt;code&gt;pacman&lt;/code&gt;, &lt;code&gt;yum&lt;/code&gt;, &lt;code&gt;zypper&lt;/code&gt;, etc. You may need to prepend &lt;code&gt;sudo&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;brew install gnupg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





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

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Import Keys
&lt;/h3&gt;

&lt;p&gt;Instead of manually placing keys into specific folders, you should import your existing keys using GPG commands. This way, GPG will handle storing the keys properly within its internal keyring.&lt;/p&gt;

&lt;p&gt;To import private and public keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpg --import ~/my-backup-keys/private-key.asc  # Import private key
gpg --import ~/my-backup-keys/public-key.asc   # Import public key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. List GPG Keys
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; gpg --list-secret-keys --keyid-format LONG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; /home/user/.gnupg/secring.gpg
 ------------------------------------
 sec   4096R/ABCDEF1234567890 2023-01-01 [expires: 2025-01-01]
 uid                          Your Name &amp;lt;your.email@example.com&amp;gt;
 ssb   4096R/1234567890ABCDEF 2023-01-01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ABCDEF1234567890&lt;/code&gt; part is the key ID.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Configure Git to Use Your GPG Key
&lt;/h3&gt;

&lt;p&gt;Set the GPG key for the specific repository (or globally for all repositories).&lt;/p&gt;

&lt;p&gt;Specific repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; git config user.signingkey ABCDEF1234567890
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; git config --global user.signingkey ABCDEF1234567890
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;ABCDEF1234567890&lt;/code&gt; with your actual GPG key ID.&lt;/p&gt;

&lt;p&gt;Back to Top&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Enable Commit Signing by Default (Optional)
&lt;/h3&gt;

&lt;p&gt;You can configure Git to sign all commits by default.&lt;/p&gt;

&lt;p&gt;Specific repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; git config commit.gpgSign true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; git config --global commit.gpgSign true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Sign a Commit
&lt;/h3&gt;

&lt;p&gt;Manually&lt;br&gt;
If you don’t enable signing by default, you can sign a commit manually by using the &lt;code&gt;-S&lt;/code&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; git commit -S -m "Your commit message"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatic&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git commit -m "Your commit message"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passphrase Prompt&lt;br&gt;
If your GPG key is passphrase-protected, which is &lt;b&gt;highly&lt;/b&gt; recommended, you’ll be prompted to enter the passphrase whenever you sign a commit. See &lt;strong&gt;"Prevent Repeated Passphrase Entry with GPG Key Caching"&lt;/strong&gt; to cache the key and prevent repeated passphrase entry for a timespan.&lt;/p&gt;
&lt;h3&gt;
  
  
  7. Verify the Signed Commit
&lt;/h3&gt;

&lt;p&gt;You can verify that your commit was signed by using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; git log --show-signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should show something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; commit abcdef1234567890abcdef1234567890abcdef12 (HEAD -&amp;gt; main)
 gpg: Signature made Mon 01 Jan 2023 12:00:00 PM UTC using RSA key ID ABCDEF1234567890
 gpg: Good signature from "Your Name &amp;lt;your.email@example.com&amp;gt;" 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to Top&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Push Your Signed Commits
&lt;/h3&gt;

&lt;p&gt;Before being able to push, you'll need to ensure your GPG key is added to your remote repo account. See &lt;strong&gt;"GitHub/GitLab Setup Resources"&lt;/strong&gt;.&lt;br&gt;
Now, when you push your commits, they will be signed with your GPG key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Additional Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub/GitLab Setup Resources
&lt;/h3&gt;

&lt;p&gt;If you’re using GitHub or GitLab, make sure your GPG key is added to your account.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For GitHub: &lt;a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account" rel="noopener noreferrer"&gt;Adding a GPG Key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;For GitLab: &lt;a href="https://docs.gitlab.com/ee/user/project/repository/gpg_signed_commits/" rel="noopener noreferrer"&gt;Adding a GPG Key&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back to Top&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevent Repeated Passphrase Entry with GPG Key Caching
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;gpg-agent&lt;/code&gt; tool can be used to prevent repeated passphrase entry for multiple commits during a timespan.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Locate or Create gpg-agent Configuration File
&lt;/h4&gt;

&lt;p&gt;It is usually named gpg-agent.conf and is located in your &lt;code&gt;.gnupg&lt;/code&gt; directory, which is typically &lt;code&gt;~/.gnupg/gpg-agent.conf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If it doesn’t exist, you can create it: &lt;code&gt;touch ~/.gnupg/gpg-agent.conf&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Edit the gpg-agent Configuration
&lt;/h4&gt;

&lt;p&gt;Open the &lt;code&gt;gpg-agent.conf&lt;/code&gt; file with a text editor, and you can set the following options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;default-cache-ttl&lt;/code&gt;: This sets the time in seconds that the passphrase is cached. The default is usually 600 seconds (10 minutes).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max-cache-ttl&lt;/code&gt;: This sets the maximum time in seconds that the passphrase is cached after the first time it is used. The default is usually 7200 seconds (2 hours).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, to cache the passphrase for 1 hour and allow it to be cached for a maximum of 4 hours after first use, add these lines and save the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default-cache-ttl 3600
max-cache-ttl 14400
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Scenario
&lt;/h5&gt;

&lt;p&gt;Continuing with the example Time To Live (TTL) values, say you add your key to the agent at 00:00 during your 1st commit of the session and enter the passphrase. &lt;br&gt;
Subsequent commits at 00:45, 1:30, and 2:15 will each extend the cache expiry by an hour, until 4 hours since the initial passphrase entry at 00:00, as long as the key is used contiguously within 1-hour intervals of each other:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;User Key Activity&lt;/th&gt;
&lt;th&gt;Passphrase?&lt;/th&gt;
&lt;th&gt;Key Cache Expiry&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;00:00&lt;/td&gt;
&lt;td&gt;1st commit&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;01:00 (due to &lt;code&gt;default-cache-ttl&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;00:45&lt;/td&gt;
&lt;td&gt;Commit&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;01:45 (expiry reset after commit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01:30&lt;/td&gt;
&lt;td&gt;Commit&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;02:30 (expiry reset after commit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02:15&lt;/td&gt;
&lt;td&gt;Commit&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;03:15 (expiry reset after commit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04:00&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;max-cache-ttl&lt;/code&gt; of 4 hours expired since 00:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04:20&lt;/td&gt;
&lt;td&gt;Commit&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;05:20 (new expiry after entering passphrase)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h4&gt;
  
  
  3. Restart gpg-agent
&lt;/h4&gt;

&lt;p&gt;Restart the agent for changes to take effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Verify Configuration
&lt;/h4&gt;

&lt;p&gt;Ensure your configuration changes are recognized by checking the active configuration with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpgconf --list-options gpg-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to Top&lt;/p&gt;

</description>
      <category>gpg</category>
      <category>signing</category>
      <category>git</category>
    </item>
    <item>
      <title>nio4r "Failed to build gem native extension."</title>
      <dc:creator>Saad Shakil</dc:creator>
      <pubDate>Wed, 28 Aug 2024 15:25:58 +0000</pubDate>
      <link>https://dev.to/codedir/nio4r-failed-to-build-gem-native-extension-3f3a</link>
      <guid>https://dev.to/codedir/nio4r-failed-to-build-gem-native-extension-3f3a</guid>
      <description>&lt;p&gt;In an RoR project, &lt;code&gt;bundle install&lt;/code&gt; was giving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Installing nio4r 2.5.8 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following commands fix this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle config build.nio4r --with-cflags="-Wno-incompatible-pointer-types"
gem install nio4r -v 2.5.8 -- --with-cflags="-Wno-incompatible-pointer-types"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rails</category>
      <category>gem</category>
      <category>nio4r</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Python and Ruby Development Tools: A Quick Reference</title>
      <dc:creator>Saad Shakil</dc:creator>
      <pubDate>Mon, 26 Aug 2024 23:32:51 +0000</pubDate>
      <link>https://dev.to/codedir/python-and-ruby-development-tools-a-quick-reference-2aa7</link>
      <guid>https://dev.to/codedir/python-and-ruby-development-tools-a-quick-reference-2aa7</guid>
      <description>&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Python Tool&lt;/th&gt;
&lt;th&gt;Ruby Tool&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Python Installation Method&lt;/th&gt;
&lt;th&gt;Ruby Installation Method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Package Manager&lt;/td&gt;
&lt;td&gt;pip&lt;/td&gt;
&lt;td&gt;gem&lt;/td&gt;
&lt;td&gt;Installing packages&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install &amp;lt;package&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gem install &amp;lt;gem_name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency Management&lt;/td&gt;
&lt;td&gt;pipenv, poetry&lt;/td&gt;
&lt;td&gt;bundler&lt;/td&gt;
&lt;td&gt;Managing dependencies&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pip install pipenv&lt;/code&gt; / &lt;code&gt;pip install poetry&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bundle install&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;venv, Conda&lt;/td&gt;
&lt;td&gt;rbenv, RVM, chruby&lt;/td&gt;
&lt;td&gt;Isolating project environments&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;python -m venv &amp;lt;env_name&amp;gt;&lt;/code&gt; / &lt;code&gt;conda create -n &amp;lt;env_name&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;rbenv install &amp;lt;version&amp;gt;&lt;/code&gt; / &lt;code&gt;rvm install &amp;lt;version&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version Management&lt;/td&gt;
&lt;td&gt;pyenv, Conda&lt;/td&gt;
&lt;td&gt;rbenv, RVM, chruby&lt;/td&gt;
&lt;td&gt;Managing Python/Ruby versions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pyenv install &amp;lt;version&amp;gt;&lt;/code&gt; / &lt;code&gt;conda install python=&amp;lt;version&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;rbenv install &amp;lt;version&amp;gt;&lt;/code&gt; / &lt;code&gt;rvm install &amp;lt;version&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combined (Version + Environment)&lt;/td&gt;
&lt;td&gt;pyenv-virtualenv&lt;/td&gt;
&lt;td&gt;RVM&lt;/td&gt;
&lt;td&gt;Version + env management&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pyenv virtualenv &amp;lt;version&amp;gt; &amp;lt;env_name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rvm use &amp;lt;version&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;Sphinx, MkDocs&lt;/td&gt;
&lt;td&gt;yard&lt;/td&gt;
&lt;td&gt;Generating project documentation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pip install sphinx&lt;/code&gt; / &lt;code&gt;pip install mkdocs&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gem install yard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing Framework&lt;/td&gt;
&lt;td&gt;pytest, unittest&lt;/td&gt;
&lt;td&gt;RSpec, minitest&lt;/td&gt;
&lt;td&gt;Running unit tests&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pip install pytest&lt;/code&gt; / included in Python&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gem install rspec&lt;/code&gt; / included in Ruby&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task Management&lt;/td&gt;
&lt;td&gt;invoke, doit&lt;/td&gt;
&lt;td&gt;rake&lt;/td&gt;
&lt;td&gt;Task automation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pip install invoke&lt;/code&gt; / &lt;code&gt;pip install doit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gem install rake&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project Management&lt;/td&gt;
&lt;td&gt;tox&lt;/td&gt;
&lt;td&gt;rake&lt;/td&gt;
&lt;td&gt;Automating testing/commands&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install tox&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gem install rake&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>python</category>
      <category>ruby</category>
      <category>development</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
