<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Mikhail Grachev&apos;s blog</title>
    <description>Developer and DevOps Engineer Blog</description>
    <link>https://www.mgrachev.com/</link>
    <atom:link href="https://www.mgrachev.com/feed.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>Update-informer v0.5.0</title>
        <description>&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2022-08-29-update-informer-v050/update-informer-v050-hf83hjdknfdubw2gh6s.png&quot; alt=&quot;update-informer&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/mgrachev/update-informer&quot; target=&quot;_blank&quot;&gt;Update-informer&lt;/a&gt; is a crate for CLI applications that written in Rust — such as &lt;a href=&quot;https://github.com/orhun/git-cliff&quot; target=&quot;_blank&quot;&gt;git-cliff&lt;/a&gt; and &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter&quot; target=&quot;_blank&quot;&gt;dotenv-linter&lt;/a&gt;.
It checks for a new version on &lt;a href=&quot;https://github.com/mgrachev/update-informer#cratesio&quot; target=&quot;_blank&quot;&gt;Crates.io&lt;/a&gt; and &lt;a href=&quot;https://github.com/mgrachev/update-informer#github&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, it has the minimum number of dependencies — only &lt;a href=&quot;https://github.com/dirs-dev/directories-rs&quot; target=&quot;_blank&quot;&gt;directories&lt;/a&gt;, &lt;a href=&quot;https://github.com/algesten/ureq&quot; target=&quot;_blank&quot;&gt;ureq&lt;/a&gt;, &lt;a href=&quot;https://github.com/dtolnay/semver&quot; target=&quot;_blank&quot;&gt;semver&lt;/a&gt; and &lt;a href=&quot;https://github.com/serde-rs/serde&quot; target=&quot;_blank&quot;&gt;serde&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The idea is actually not new.
This feature has long been present in the &lt;a href=&quot;https://github.com/cli/cli/blob/trunk/internal/update/update.go&quot; target=&quot;_blank&quot;&gt;GitHub CLI application&lt;/a&gt; and &lt;a href=&quot;https://github.com/npm/cli/blob/latest/lib/utils/update-notifier.js&quot; target=&quot;_blank&quot;&gt;npm&lt;/a&gt;.
There is also a popular &lt;a href=&quot;https://github.com/yeoman/update-notifier&quot; target=&quot;_blank&quot;&gt;JavaScript library&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;what-is-new-in-v050&quot;&gt;What is new in v0.5.0?&lt;/h2&gt;

&lt;h3 id=&quot;pypi-support&quot;&gt;PyPi support&lt;/h3&gt;

&lt;p&gt;Now &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update-informer&lt;/code&gt; can check for a new version on &lt;a href=&quot;https://pypi.org&quot; target=&quot;_blank&quot;&gt;PyPi&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;update_informer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Check&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;informer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;update_informer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PyPI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;package_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0.1.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;informer&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.check_version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;configurable-check-frequency-and-request-timeout&quot;&gt;Configurable check frequency and request timeout&lt;/h3&gt;

&lt;p&gt;By default, the first check will start only after the interval has expired (= 24 hours), but you can change it:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;update_informer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Check&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EVERY_HOUR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_secs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;informer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;update_informer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Crates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;crate_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0.1.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.interval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EVERY_HOUR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;informer&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.check_version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// The check will start only after an hour&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is also possible to change the request timeout (default 5 seconds).&lt;/p&gt;

&lt;h3 id=&quot;disabling-caching&quot;&gt;Disabling caching&lt;/h3&gt;

&lt;p&gt;To avoid spam requests to the registry API, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update-informer&lt;/code&gt; creates a file in the cache directory.
In order not to cache requests, use a zero interval:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;update_informer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Check&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;informer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;update_informer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Crates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;crate_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0.1.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.interval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ZERO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;informer&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.check_version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;implementing-your-own-registry&quot;&gt;Implementing your own registry&lt;/h3&gt;

&lt;p&gt;You can implement your own registry to check updates. For example:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;update_informer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Check&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Registry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;YourOwnRegistry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Registry&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;YourOwnRegistry&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;your_own_registry&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_latest_version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pkg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;format!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://your_own_registry.com/{}/latest-version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pkg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ureq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.into_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;informer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;update_informer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;YourOwnRegistry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;package_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0.1.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;informer&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.check_version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;Please support the project 🙏&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Star on &lt;a href=&quot;https://github.com/mgrachev/update-informer&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt; ⭐️&lt;/li&gt;
  &lt;li&gt;Become a sponsor on &lt;a href=&quot;https://opencollective.com/update-informer&quot; target=&quot;_blank&quot;&gt;OpenCollective&lt;/a&gt; ❤️&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 29 Aug 2022 08:00:00 +0000</pubDate>
        <link>https://www.mgrachev.com/2022/08/29/update-informer-v050/</link>
        <guid isPermaLink="true">https://www.mgrachev.com/2022/08/29/update-informer-v050/</guid>
      </item>
    
      <item>
        <title>Dotenv-linter v3.1.0</title>
        <description>&lt;p&gt;We are happy to announce the release of a new version of &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter&quot; target=&quot;_blank&quot;&gt;dotenv-linter&lt;/a&gt; - &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/releases/tag/v3.1.0&quot; target=&quot;_blank&quot;&gt;v3.1.0&lt;/a&gt; 🎉&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2021-06-10-dotenv-linter-v310/dotenv-linter-v310-m8d3o8asgd7.png&quot; alt=&quot;dotenv-linter&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;what-is-dotenv-linter&quot;&gt;What is dotenv-linter?&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter&quot; target=&quot;_blank&quot;&gt;Dotenv-linter&lt;/a&gt; is a lightning-fast linter for checking, fixing and comparing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; files.&lt;br /&gt;
And it’s written in &lt;a href=&quot;https://www.rust-lang.org&quot; target=&quot;_blank&quot;&gt;Rust&lt;/a&gt; 🦀&lt;/p&gt;

&lt;h3 id=&quot;what-is-a-env-file&quot;&gt;What is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file?&lt;/h3&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv&lt;/code&gt; file is a simple text file containing all the environment variables of a project.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file has a simple key-value format, for example: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FOO=BAR&lt;/code&gt;.
Storing &lt;a href=&quot;https://12factor.net/config&quot; target=&quot;_blank&quot;&gt;configuration in the environment variables&lt;/a&gt; is one of the tenets of the &lt;a href=&quot;https://12factor.net&quot; target=&quot;_blank&quot;&gt;Manifesto of Twelve-Factor App&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;what-is-new-in-v310&quot;&gt;What is new in v3.1.0?&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://dotenv-linter.github.io/#/whats_new/v310?id=_1-suggestion-of-changes-%f0%9f%94%a5&quot; target=&quot;_blank&quot;&gt;Suggestion of changes&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://dotenv-linter.github.io/#/whats_new/v310?id=_2-variable-substitution-%f0%9f%86%95&quot; target=&quot;_blank&quot;&gt;Variable substitution&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://dotenv-linter.github.io/#/whats_new/v310?id=_3-improving-checks-%f0%9f%91%8d&quot; target=&quot;_blank&quot;&gt;Improving checks&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://dotenv-linter.github.io/#/whats_new/v310?id=_4-checking-backups-%f0%9f%91%8c&quot; target=&quot;_blank&quot;&gt;Checking backups&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://dotenv-linter.github.io/#/whats_new/v310?id=_5-displaying-messages-%f0%9f%91%80&quot; target=&quot;_blank&quot;&gt;Displaying messages&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More information you can find here:
&lt;a href=&quot;https://dotenv-linter.github.io/#/whats_new/v310&quot; target=&quot;_blank&quot;&gt;https://dotenv-linter.github.io/#/whats_new/v310&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;How you can support the project 😉&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Star on &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt; ⭐️&lt;/li&gt;
  &lt;li&gt;Become a sponsor on &lt;a href=&quot;https://github.com/sponsors/dotenv-linter&quot; target=&quot;_blank&quot;&gt;GitHub Sponsors&lt;/a&gt; or &lt;a href=&quot;https://opencollective.com/dotenv-linter&quot; target=&quot;_blank&quot;&gt;OpenCollective&lt;/a&gt; ❤️&lt;/li&gt;
  &lt;li&gt;Contribute to the &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/blob/master/CONTRIBUTING.md&quot; target=&quot;_blank&quot;&gt;dotenv-linter&lt;/a&gt; ⚙️&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 10 Jun 2021 08:00:00 +0000</pubDate>
        <link>https://www.mgrachev.com/2021/06/10/dotenv-linter-v310/</link>
        <guid isPermaLink="true">https://www.mgrachev.com/2021/06/10/dotenv-linter-v310/</guid>
      </item>
    
      <item>
        <title>GitHub Actions to guard your workflow</title>
        <description>&lt;p&gt;Modern development is so complex that it is simply impossible to keep everything in mind, especially various practices for writing code. This is where linters come to the rescue. They help maintain certain standards in the project and keep the code base in order.&lt;/p&gt;

&lt;p&gt;I develop projects in a variety of programming languages such as: Rust, Go, Ruby and I connect different linters to each project. To make sure my code meets all quality standards, I run linters using CI services for every commit submitted to GitHub.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2021-02-10-github-actions/github-actions-h843askf87f.png&quot; alt=&quot;github-actions&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;reviewdog&quot;&gt;Reviewdog&lt;/h3&gt;

&lt;p&gt;It is very important to me that the result of linters’ work is always visible on GitHub, for example, in the form of comments to pull requests. To do this, I use &lt;a href=&quot;https://github.com/reviewdog/reviewdog&quot; target=&quot;_blank&quot;&gt;reviewdog&lt;/a&gt;, which automates code review and provides seamless integration of any linter with GitHub. Here’s why reviewdog is so good:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It is written in Go and can be compiled into a binary file and connected to any project, regardless of the programming language;&lt;/li&gt;
  &lt;li&gt;It can work with any linters, you just need to redirect the linter result to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reviewdog&lt;/code&gt; input and define the linter output format, for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ dotenv-linter | reviewdog -efm=&quot;%f:%l %m&quot;&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;It supports a large number of linters out of the box, such as &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter&quot; target=&quot;_blank&quot;&gt;dotenv-linter&lt;/a&gt;, &lt;a href=&quot;https://github.com/rubocop-hq/rubocop&quot; target=&quot;_blank&quot;&gt;rubocop&lt;/a&gt; and others.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;github-actions&quot;&gt;GitHub Actions&lt;/h3&gt;

&lt;p&gt;As cool as reviewdog is, I still had to spend time setting up CI services to run linters for each project. But that all changed when GitHub announced &lt;a href=&quot;https://github.com/features/actions&quot; target=&quot;_blank&quot;&gt;GitHub Actions&lt;/a&gt;, a new tool for automating workflows. Simply put, this is a full-fledged CI/CD service with great capabilities that allows you to create your own actions and share them with the community.&lt;/p&gt;

&lt;p&gt;Having switched to GitHub Actions, I decided to write my own actions to run popular linters. By doing this, I could simplify the process of connecting linters to any project. This is what I ended up with:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/reviewdog/action-rubocop&quot; target=&quot;_blank&quot;&gt;action-rubocop&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/reviewdog/action-brakeman&quot; target=&quot;_blank&quot;&gt;action-brakeman&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/reviewdog/action-reek&quot; target=&quot;_blank&quot;&gt;action-reek&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/reviewdog/action-hadolint&quot; target=&quot;_blank&quot;&gt;action-hadolint&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dotenv-linter/action-dotenv-linter&quot; target=&quot;_blank&quot;&gt;action-dotenv-linter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of my actions can publish linter comments in two modes.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;As an annotation in the code (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-pr-check&lt;/code&gt;):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2021-02-10-github-actions/example-github-pr-check-83gsjdoj2.png&quot; alt=&quot;example-github-pr-check&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;And in the form of comments to pull requests (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-pr-review&lt;/code&gt;):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2021-02-10-github-actions/example-github-pr-review-cm72hdkis7.png&quot; alt=&quot;example-github-pr-review&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;ruby-actions&quot;&gt;Ruby Actions&lt;/h3&gt;

&lt;p&gt;The first 3 actions (&lt;a href=&quot;https://github.com/reviewdog/action-rubocop&quot; target=&quot;_blank&quot;&gt;action-rubocop&lt;/a&gt;, &lt;a href=&quot;https://github.com/reviewdog/action-brakeman&quot; target=&quot;_blank&quot;&gt;action-brakeman&lt;/a&gt; and &lt;a href=&quot;https://github.com/reviewdog/action-reek&quot; target=&quot;_blank&quot;&gt;action-reek&lt;/a&gt;) allow you to run popular linters from the Ruby community: &lt;a href=&quot;https://github.com/rubocop-hq/rubocop&quot; target=&quot;_blank&quot;&gt;rubocop&lt;/a&gt;, &lt;a href=&quot;https://github.com/presidentbeef/brakeman&quot; target=&quot;_blank&quot;&gt;brakeman&lt;/a&gt; and &lt;a href=&quot;https://github.com/troessner/reek&quot; target=&quot;_blank&quot;&gt;reek&lt;/a&gt;. To connect these actions to your project, you just need to create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows/linters.yml&lt;/code&gt; file with the following content:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# .github/workflows/linters.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;linters&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;linters&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;runner / linters&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Check out code&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rubocop&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;reviewdog/action-rubocop@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;rubocop_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gemfile&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;rubocop_extensions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rubocop-rails:gemfile rubocop-rspec:gemfile&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;brakeman&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;reviewdog/action-brakeman@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;brakeman_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gemfile&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;reek&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;reviewdog/action-reek@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;reek_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gemfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For all these action it is possible to specify the linter version. There are 3 options available:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;An empty value or no version - the latest version will be installed;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemfile&lt;/code&gt; - the version from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; will be installed;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.0.0&lt;/code&gt; - the specified version will be installed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Action-rubocop also provides the ability to install additional extensions. The following extensions are installed by default: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rubocop-rails&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rubocop-performance&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rubocop-rspec&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rubocop-i18n&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rubocop-rake&lt;/code&gt;. But this can be overridden using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rubocop_extensions&lt;/code&gt; attribute.&lt;/p&gt;

&lt;h3 id=&quot;dockerfile-action&quot;&gt;Dockerfile Action&lt;/h3&gt;

&lt;p&gt;The next action, &lt;a href=&quot;https://github.com/reviewdog/action-hadolint&quot; target=&quot;_blank&quot;&gt;action-hadolint&lt;/a&gt;, looks for all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; in the project and checks them using the &lt;a href=&quot;https://github.com/hadolint/hadolint&quot; target=&quot;_blank&quot;&gt;hadolint&lt;/a&gt; linter. Usage example:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# .github/workflows/hadolint.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;hadolint&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hadolint&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;runner / hadolint&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Check out code&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;hadolint&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;reviewdog/action-hadolint@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;hadolint_ignore&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DL3008&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;dotenv-linter-action&quot;&gt;Dotenv-linter Action&lt;/h3&gt;

&lt;p&gt;And last but not least, is &lt;a href=&quot;https://github.com/dotenv-linter/action-dotenv-linter&quot; target=&quot;_blank&quot;&gt;action-dotenv-linter&lt;/a&gt;. It allows you to easily and simply check all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; files on the project. Usage example:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# .github/workflows/dotenv_linter.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dotenv-linter&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;dotenv-linter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;runner / dotenv-linter&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Check out code&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dotenv-linter&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dotenv-linter/action-dotenv-linter@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;dotenv_linter_flags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--skip UnorderedKey&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Find more GitHub Actions on the &lt;a href=&quot;https://github.com/marketplace?type=actions&amp;amp;query=reviewdog&quot; target=&quot;_blank&quot;&gt;GitHub Marketplace&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;GitHub Actions is a cool workflow automation tool that allows you to completely replace all existing CI/CD services on a project. I wrote several GitHub Actions to launch popular linters, which allowed me to significantly simplify the process of connecting and configuring linters on each project.&lt;/p&gt;
</description>
        <pubDate>Wed, 10 Feb 2021 08:00:00 +0000</pubDate>
        <link>https://www.mgrachev.com/2021/02/10/github-actions/</link>
        <guid isPermaLink="true">https://www.mgrachev.com/2021/02/10/github-actions/</guid>
      </item>
    
      <item>
        <title>Dotenv-linter v3.0.0</title>
        <description>&lt;p&gt;Прошло 2 с половиной месяца с момента прошлого релиза, а мы уже готовы вам представить новую версию &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter&quot; target=&quot;_blank&quot;&gt;dotenv-linter&lt;/a&gt; - v3.0.0 🎉&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2021-01-28-dotenv-linter-v300/dotenv-linter-v300-cover-sn29fh5ma.png&quot; alt=&quot;dotenv-linter&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;что-такое-env-файлы&quot;&gt;Что такое &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлы?&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv&lt;/code&gt; файлы — это простые текстовые файлы, содержащие в себе переменные окружения проекта.
Файлы имеют формат «ключ-значение», например: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FOO=BAR&lt;/code&gt;.
Хранение &lt;a href=&quot;https://12factor.net/config&quot; target=&quot;_blank&quot;&gt;конфигурации в переменных окружения&lt;/a&gt; является одним из принципов манифеста &lt;a href=&quot;https://12factor.net&quot; target=&quot;_blank&quot;&gt;Twelve-Factor App&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Вот обзор ключевых изменений, которые попали в этот релиз.&lt;/p&gt;

&lt;h3 id=&quot;1-улучшение-api-&quot;&gt;1. Улучшение API 👍&lt;/h3&gt;

&lt;p&gt;В предыдущей версии, у &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv-linter&lt;/code&gt; было несколько флагов, которые по сути являлись отдельными командами - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fix&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--show-checks&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FLAGS:
    -f, --fix            Automatically fixes warnings
    -h, --help           Prints help information
        --no-backup      Prevents .env files from being backed up when modified by -f/--fix
    -q, --quiet          Doesn&apos;t display additional information
    -r, --recursive      Recursively search and check .env files
        --show-checks    Shows list of available checks
    -v, --version        Prints version information
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Это приводило к небольшой путанице, т.к. эти флаги конфликтовали и не работали друг с другом:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ dotenv-linter --fix --show-checks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В новой версии мы исправили это и вынесли флаги &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fix&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--show-checks&lt;/code&gt; в отдельные команды &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fix&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FLAGS:
    -h, --help         Prints help information
    -q, --quiet        Doesn&apos;t display additional information
    -r, --recursive    Recursively searches and checks .env files
    -v, --version      Prints version information

SUBCOMMANDS:
    fix        Automatically fixes warnings [aliases: f]
    list       Shows list of available checks [aliases: l]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;PR: &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/pull/342&quot; target=&quot;_blank&quot;&gt;#342&lt;/a&gt; (&lt;a href=&quot;https://github.com/mgrachev&quot; target=&quot;_blank&quot;&gt;@mgrachev&lt;/a&gt;)&lt;/p&gt;

&lt;h3 id=&quot;2-сравнение-env-файлов-&quot;&gt;2. Сравнение &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлов 🤲&lt;/h3&gt;

&lt;p&gt;Также, в новой версии мы добавили новую команду &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compare&lt;/code&gt;, которая позволяет сравнивать ключи в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлах:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ dotenv-linter compare .env .env.example
Comparing .env
Comparing .env.example
.env is missing keys: BAR
.env.example is missing keys: FOO
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;PR: &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/pull/282&quot; target=&quot;_blank&quot;&gt;#282&lt;/a&gt; (&lt;a href=&quot;https://github.com/mstruebing&quot; target=&quot;_blank&quot;&gt;@mstruebing&lt;/a&gt;)&lt;/p&gt;

&lt;h3 id=&quot;3-отображение-проверяемых-файлов-&quot;&gt;3. Отображение проверяемых файлов 👀&lt;/h3&gt;

&lt;p&gt;При наличии нескольких &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлов, не всегда было понятно, какие из них проверил &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv-linter&lt;/code&gt;, а какие нет.&lt;/p&gt;

&lt;p&gt;Это могло быть связано с нестандартным именем файла, либо с отсутствием каких-либо проблем в этих файлах.&lt;/p&gt;

&lt;p&gt;Поэтому в новой версии мы добавили отображение проверяемых файлов:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ dotenv-linter
Checking .env
.env:1 LeadingCharacter: Invalid leading character detected

Checking .env.example
Checking .env.test

Found 1 problem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Отключить такое поведение можно с помощью флага &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--quiet/-q&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ dotenv-linter --quiet
.env:1 LeadingCharacter: Invalid leading character detected
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;PR: &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/pull/311&quot; target=&quot;_blank&quot;&gt;#311&lt;/a&gt; (&lt;a href=&quot;https://github.com/anthuang&quot; target=&quot;_blank&quot;&gt;@Anthuang&lt;/a&gt;), &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/pull/336&quot; target=&quot;_blank&quot;&gt;#336&lt;/a&gt; (&lt;a href=&quot;https://github.com/DDtKey&quot; target=&quot;_blank&quot;&gt;@DDtKey&lt;/a&gt;)&lt;/p&gt;

&lt;h3 id=&quot;4-цветной-вывод-&quot;&gt;4. Цветной вывод 🌈&lt;/h3&gt;

&lt;p&gt;Для улучшения удобства использования, мы добавили цветной вывод предупреждений:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2021-01-28-dotenv-linter-v300/dotenv-linter-v300-colored_output-hhd82h4kd9.png&quot; alt=&quot;colored-output&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Отключить цветной вывод можно с помощью флага &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--no-color&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;PR: &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/pull/307&quot; target=&quot;_blank&quot;&gt;#307&lt;/a&gt; (&lt;a href=&quot;https://github.com/Nikhil0487&quot; target=&quot;_blank&quot;&gt;@Nikhil0487&lt;/a&gt;)&lt;/p&gt;

&lt;h3 id=&quot;5-поддержка-многострочных-значений-&quot;&gt;5. Поддержка многострочных значений 💪&lt;/h3&gt;

&lt;p&gt;В &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлах можно хранить многострочные значения. Один из вариантов выглядит следующим образом:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# .env
MULTILINE=&quot;new\nline&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Но при проверке таких значений, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv-linter&lt;/code&gt; всегда выводил предупреждение:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ dotenv-linter
.env:1 QuoteCharacter: The value has quote characters (&apos;, &quot;)

Found 1 problem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Мы исправили это и теперь &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv-linter&lt;/code&gt; не выводит предупреждения для многострочных значений, обернутых кавычками.&lt;/p&gt;

&lt;p&gt;PR: &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/pull/341&quot; target=&quot;_blank&quot;&gt;#341&lt;/a&gt; (&lt;a href=&quot;http://github.com/artem-russkikh&quot; target=&quot;_blank&quot;&gt;@artem-russkikh&lt;/a&gt;)&lt;/p&gt;

&lt;h3 id=&quot;6-поддержка-export-префикса-&quot;&gt;6. Поддержка &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export&lt;/code&gt; префикса 🔥&lt;/h3&gt;

&lt;p&gt;Некоторые библиотеки для работы с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлами, такие как: &lt;a href=&quot;https://github.com/bkeepers/dotenv&quot; target=&quot;_blank&quot;&gt;dotenv&lt;/a&gt;, &lt;a href=&quot;https://github.com/joho/godotenv&quot; target=&quot;_blank&quot;&gt;godotenv&lt;/a&gt; и &lt;a href=&quot;https://github.com/theskumar/python-dotenv&quot; target=&quot;_blank&quot;&gt;python-dotenv&lt;/a&gt; поддерживают &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export&lt;/code&gt; префикс:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# .env
export S3_BUCKET=YOURS3BUCKET
export SECRET_KEY=YOURSECRETKEYGOESHERE
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export&lt;/code&gt; префикс дает возможность экспортировать переменные окружения из файла с помощью команды &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;source&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ source .env
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;При проверке таких файлов, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv-linter&lt;/code&gt; всегда выводил предупреждения:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ dotenv-linter
.env:1 IncorrectDelimiter: The export S3_BUCKET key has incorrect delimiter
.env:1 LowercaseKey: The export S3_BUCKET key should be in uppercase
.env:2 IncorrectDelimiter: The export SECRET_KEY key has incorrect delimiter
.env:2 LowercaseKey: The export SECRET_KEY key should be in uppercase

Found 4 problems
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В новой версии мы добавили поддержку &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export&lt;/code&gt; префикса и теперь &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv-linter&lt;/code&gt; не выводит никаких предупреждений.&lt;/p&gt;

&lt;p&gt;PR: &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/pull/340&quot; target=&quot;_blank&quot;&gt;#340&lt;/a&gt; (&lt;a href=&quot;https://github.com/skonik&quot; target=&quot;_blank&quot;&gt;@skonik&lt;/a&gt;)&lt;/p&gt;

&lt;h3 id=&quot;7-поддержка-пробелов-&quot;&gt;7. Поддержка пробелов 🙌&lt;/h3&gt;

&lt;p&gt;Ещё одним из улучшений стала поддержка пробелов в значениях, обернутых кавычками:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# .env
WHITESPACES=&quot;a b c&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Теперь, для таких значения &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv-linter&lt;/code&gt; не будет выводить предупреждения.&lt;/p&gt;

&lt;p&gt;PR: &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/pull/349&quot; target=&quot;_blank&quot;&gt;#349&lt;/a&gt; (&lt;a href=&quot;https://github.com/sebastiantoh&quot; target=&quot;_blank&quot;&gt;@sebastiantoh&lt;/a&gt;)&lt;/p&gt;

&lt;h3 id=&quot;8-улучшенная-проверка-&quot;&gt;8. Улучшенная проверка 👌&lt;/h3&gt;

&lt;p&gt;Также, мы исправили проблему, при которой &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv-linter&lt;/code&gt; мог выводить не все предупреждения за один раз:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ dotenv-linter fix .env
Fixing .env
Original file was backed up to: &quot;.env_1606422805&quot;

.env:1 KeyWithoutValue: The test key should be with a value or have an equal sign

All warnings are fixed. Total: 1

$ dotenv-linter .env
Checking .env
.env:1 LowercaseKey: The test key should be in uppercase

Found 1 problem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;PR: &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/pull/348&quot; target=&quot;_blank&quot;&gt;#348&lt;/a&gt; (&lt;a href=&quot;https://github.com/vbrandl&quot; target=&quot;_blank&quot;&gt;@vbrandl&lt;/a&gt;)&lt;/p&gt;

&lt;h3 id=&quot;9-улучшение-производительности-&quot;&gt;9. Улучшение производительности 🚀&lt;/h3&gt;

&lt;p&gt;И последнее, но не по значению - мы улучшили производительность и без того быстрого &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv-linter&lt;/code&gt;, что теперь максимальное время запуска уменьшилось с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.3&lt;/code&gt; мс до &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4.3&lt;/code&gt; мс (более чем на &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;50%&lt;/code&gt;) 🙀&lt;/p&gt;

&lt;p&gt;Для подтверждения этого, мы сделали бенчмарк с помощью утилиты &lt;a href=&quot;https://github.com/sharkdp/hyperfine&quot; target=&quot;_blank&quot;&gt;hyperfine&lt;/a&gt;:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Command&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Mean [ms]&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Min [ms]&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Max [ms]&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Relative&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv-linter/dotenv-linter .env&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2.7 ± 0.4&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2.0&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4.3&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.00&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wemake-services/dotenv-linter .env&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;162.6 ± 12.1&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;153.0&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;201.3&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;60.83 ± 10.20&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;PR: &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/pull/350&quot; target=&quot;_blank&quot;&gt;#350&lt;/a&gt; (&lt;a href=&quot;https://github.com/vbrandl&quot; target=&quot;_blank&quot;&gt;@vbrandl&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Это все ключевые изменения, которые вошли в новый релиз &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter/releases/tag/v3.0.0&quot; target=&quot;_blank&quot;&gt;v3.0.0&lt;/a&gt;.
Спасибо всем кто участвовал в подготовке этого релиза 🙏&lt;/p&gt;

&lt;p&gt;Вы можете поддержать проект 😉&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Поставить звездочку на &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt; ⭐️&lt;/li&gt;
  &lt;li&gt;Стать спонсором на &lt;a href=&quot;https://github.com/sponsors/dotenv-linter&quot; target=&quot;_blank&quot;&gt;GitHub Sponsors&lt;/a&gt; или &lt;a href=&quot;https://opencollective.com/dotenv-linter&quot; target=&quot;_blank&quot;&gt;OpenCollective&lt;/a&gt; ❤️&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 28 Jan 2021 09:00:00 +0000</pubDate>
        <link>https://www.mgrachev.com/2021/01/28/dotenv-linter-v300/</link>
        <guid isPermaLink="true">https://www.mgrachev.com/2021/01/28/dotenv-linter-v300/</guid>
      </item>
    
      <item>
        <title>Dotenv-linter: линтер .env файлов</title>
        <description>&lt;p&gt;При разработке приложений я всегда стараюсь придерживаться манифеста &lt;a href=&quot;https://12factor.net&quot; target=&quot;_blank&quot;&gt;двенадцати факторов&lt;/a&gt;. Такой подход позволяет избежать большого количества проблем, связанных с развертыванием приложений и их дальнейшей поддержкой.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://12factor.net/config&quot; target=&quot;_blank&quot;&gt;Один из принципов&lt;/a&gt; этого манифеста гласит, что все настройки должны хранится в переменных окружения. Это позволяет менять их под разные окружения (Staging, QA, Production) без изменения кода.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2020-04-20-dotenv-linter/dotenv-linter-cover-zgs7ns5ah2m5.png&quot; alt=&quot;dotenv-linter&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Однако, во время разработки могут возникать сложности при работе с такими переменными. С ростом проекта, обычно также увеличивается количество переменных окружения и с ними становится не так удобно работать. Нужно каждый раз указывать их все при запуске приложения:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ RAILS_ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;development &lt;span class=&quot;nv&quot;&gt;REDIS_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;redis://redis:6379 &lt;span class=&quot;nv&quot;&gt;DATABASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;postgres://postgres:postgres@db:5432 bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rails server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

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

&lt;p&gt;И тут на помощь приходят &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлы. Они предоставляют удобный способ хранения всех переменных окружения в одном месте:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# .env&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;development
&lt;span class=&quot;nv&quot;&gt;REDIS_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;redis://redis:6379
&lt;span class=&quot;nv&quot;&gt;DATABASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;postgres://postgres:postgres@db:5432
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Для работы с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлами есть довольно много библиотек на разных языках программирования. Всё что они делают — это загружают переменные из &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файла при запуске приложения:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Rust: &lt;a href=&quot;https://github.com/dotenv-rs/dotenv&quot; target=&quot;_blank&quot;&gt;https://github.com/dotenv-rs/dotenv&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;Golang: &lt;a href=&quot;https://github.com/joho/godotenv&quot; target=&quot;_blank&quot;&gt;https://github.com/joho/godotenv&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;Ruby: &lt;a href=&quot;https://github.com/bkeepers/dotenv&quot; target=&quot;_blank&quot;&gt;https://github.com/bkeepers/dotenv&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;PHP: &lt;a href=&quot;https://github.com/vlucas/phpdotenv&quot; target=&quot;_blank&quot;&gt;https://github.com/vlucas/phpdotenv&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;Elixir: &lt;a href=&quot;https://github.com/avdi/dotenv_elixir&quot; target=&quot;_blank&quot;&gt;https://github.com/avdi/dotenv_elixir&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;JavaScript: &lt;a href=&quot;https://github.com/motdotla/dotenv&quot; target=&quot;_blank&quot;&gt;https://github.com/motdotla/dotenv&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;Haskell: &lt;a href=&quot;https://github.com/stackbuilders/dotenv-hs&quot; target=&quot;_blank&quot;&gt;https://github.com/stackbuilders/dotenv-hs&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;Python: &lt;a href=&quot;https://github.com/theskumar/python-dotenv&quot; target=&quot;_blank&quot;&gt;https://github.com/theskumar/python-dotenv&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;При использовании Docker и Docker Compose переменные окружения можно указать с помощью ключа &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;environment&lt;/code&gt; в файле &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2.4&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;development&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;REDIS_URL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;redis://redis:6379&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;DATABASE_URL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres://postgres:postgres@db:5432&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Но, как я уже писал ранее, с ростом проекта также увеличивается количество переменных, а вместе с ними и количество сервисов, которые необходимы вашему приложению, например Redis, RabbitMQ и т.д. И со временем, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt; увеличивается в размерах и становится просто нечитаемым. В такой ситуации опять же могут помочь &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлы, в которые можно вынести все переменные окружения и подключить их к &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt; с помощью ключа &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env_file&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;2.4&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;env_file&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

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

&lt;p&gt;В процессе работы с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлами, часто могут возникать проблемы, которые можно не заметить с первого взгляда и даже пропустить при проверке кода, но которые могут стать причиной неправильной работы приложения:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Дублирование ключей&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Такую проблему можно встретить, если в проекте есть &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файл, который содержит большое количество строк. В этой ситуации, легко не заметить появление новых переменных окружения с уже существующими ключами. В таком случае, новые переменные перезапишут старые значения:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# .env&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;BAR
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FOO
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Неправильный разделитель&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Для удобства ключи переменных окружения можно разделять с помощью символа &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt;. Однако в процессе разработки можно легко ошибиться и использовать другой символ, например &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# .env&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Неправильно&lt;/span&gt;
FOO-BAR&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FOOBAR

&lt;span class=&quot;c&quot;&gt;# Правильно&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FOO_BAR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FOOBAR
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Переменные без значения&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Иногда бывают ситуации, когда нужно добавить новую переменную в приложение, но пока неизвестно, какое значение у неё будет. В таком случае обычно добавляют пустую переменную окружения. Но и здесь можно допустить ошибку, забыв указать символ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;=&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# .env&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Неправильно&lt;/span&gt;
FOO

&lt;span class=&quot;c&quot;&gt;# Правильно&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4. Некорректный первый символ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Не все знают, но есть ограничения при наименование переменных окружения. Они не могут начинаться с цифр и других символов, за исключением символа &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# .env&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Неправильно&lt;/span&gt;
 &lt;span class=&quot;nv&quot;&gt;FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;BAR
.FOO&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;BAR
&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;BAR
&lt;span class=&quot;nv&quot;&gt;1FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;BAR

&lt;span class=&quot;c&quot;&gt;# Правильно&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;_FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;BAR
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5. Ключи в нижнем регистре&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Продолжая тему ограничений, нельзя не упомянуть, что все ключи переменных окружения должны быть в верхнем регистре:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# .env&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Неправильно&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;bar

&lt;span class=&quot;c&quot;&gt;# Правильно&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;bar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;6. Пробелы&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ещё одной проблемой могут стать пробелы. Особенно, если они присутствуют между ключом и значением переменной окружения:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# .env&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Неправильно&lt;/span&gt;
FOO &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; BAR

&lt;span class=&quot;c&quot;&gt;# Правильно&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;BAR
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;7. Сортировка по алфавиту&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Сортировка, конечно, не является проблемой, но куда приятнее работать с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлами, где все ключи отсортированы по алфавиту:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# .env&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Неправильно&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;BAR
&lt;span class=&quot;nv&quot;&gt;BAR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FOO

&lt;span class=&quot;c&quot;&gt;# Правильно&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;BAR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FOO
&lt;span class=&quot;nv&quot;&gt;FOO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;BAR
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

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

&lt;p&gt;Все проблемы, описанные выше, взяты из реальных проектов. На протяжение всей своей работы я постоянно сталкивался с ними, что доставляло различные неудобства в процессе разработки, а в некоторых случаях даже приводило к неправильной работе приложений.&lt;/p&gt;

&lt;p&gt;Поэтому в один прекрасный день я решил создать инструмент, который позволил бы мне избежать подобных проблем в будущем, раз и навсегда.&lt;/p&gt;

&lt;p&gt;И вот так появился &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter&quot; target=&quot;_blank&quot;&gt;dotenv-linter&lt;/a&gt; — линтер для &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлов. Он позволяет проверить &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлы на наличие всех проблем, описанных выше — от дублирования ключей и до сортировки.&lt;/p&gt;

&lt;p&gt;Его ключевые особенности:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Молниеносно быстрый и всё благодаря тому, что он написан на &lt;a href=&quot;https://www.rust-lang.org&quot; target=&quot;_blank&quot;&gt;Rust&lt;/a&gt; 🦀&lt;/li&gt;
  &lt;li&gt;Можно использовать на любом проекте, вне зависимости от языка программирования 🔥&lt;/li&gt;
  &lt;li&gt;Возможна интеграция с &lt;a href=&quot;https://github.com/reviewdog/reviewdog&quot; target=&quot;_blank&quot;&gt;reviewdog&lt;/a&gt; и CI сервисами (включая &lt;a href=&quot;https://github.com/dotenv-linter/action-dotenv-linter&quot; target=&quot;_blank&quot;&gt;GitHub Actions&lt;/a&gt;) 🚀&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;установка&quot;&gt;Установка&lt;/h3&gt;

&lt;p&gt;Есть несколько вариантов установки dotenv-linter. Самый простой вариант — скачать бинарный файл с помощью &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wget&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Linux / macOS / Windows (MINGW и т.д.). По умолчанию устанавливается в директорию ./bin&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-sSfL&lt;/span&gt; https://raw.githubusercontent.com/dotenv-linter/dotenv-linter/master/install.sh | sh &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Можно указать директорию установки и версию&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-sSfL&lt;/span&gt; https://raw.githubusercontent.com/dotenv-linter/dotenv-linter/master/install.sh | sh &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt; usr/local/bin v2.0.0

&lt;span class=&quot;c&quot;&gt;# Alpine Linux (wget)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-O&lt;/span&gt; - https://raw.githubusercontent.com/dotenv-linter/dotenv-linter/master/install.sh | sh &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Другой вариант — установить через &lt;a href=&quot;https://brew.sh&quot; target=&quot;_blank&quot;&gt;Homebrew&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;dotenv-linter
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Так же есть возможность запуска dotenv-linter с помощью Docker:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;:/app &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /app dotenvlinter/dotenv-linter
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Другие варианты установки можно найти на сайте проекта &lt;a href=&quot;https://dotenv-linter.github.io/#/installation&quot; target=&quot;_blank&quot;&gt;dotenv-linter.github.io&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;использование&quot;&gt;Использование&lt;/h3&gt;

&lt;p&gt;По умолчанию dotenv-linter проверяет все &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; файлы в текущей директории:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;dotenv-linter
.env:2 DuplicatedKey: The FOO key is duplicated
.env:3 UnorderedKey: The BAR key should go before the FOO key
.env.test:1 LeadingCharacter: Invalid leading character detected
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Чтобы проверить другую директорию, достаточно передать её путь в качестве аргумента. Этот же подход работает, если нужно проверить какие-нибудь файлы по отдельности:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;dotenv-linter dir1 dir2/.my-env-file
dir1/.env:1 LeadingCharacter: Invalid leading character detected
dir1/.env:3 IncorrectDelimiter: The FOO-BAR key has incorrect delimiter
dir2/.my-env-file:1 LowercaseKey: The bar key should be &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;uppercase
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Исключить какой-нибудь файл из проверки можно с помощью аргумента &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--exclude&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;dotenv-linter &lt;span class=&quot;nt&quot;&gt;--exclude&lt;/span&gt; .env.test
.env:2 DuplicatedKey: The FOO key is duplicated
.env:3 UnorderedKey: The BAR key should go before the FOO key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;интеграция&quot;&gt;Интеграция&lt;/h3&gt;

&lt;p&gt;Отдельно стоит упомянуть про возможность интеграции dotenv-linter с &lt;a href=&quot;https://github.com/reviewdog/reviewdog&quot; target=&quot;_blank&quot;&gt;reviewdog&lt;/a&gt;, что позволяет получать предупреждения от линтера в виде комментариев на GitHub или GitLab.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;dotenv-linter | reviewdog &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;dotenv-linter &lt;span class=&quot;nt&quot;&gt;-diff&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git diff master&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;При использование &lt;a href=&quot;https://github.com/features/actions&quot; target=&quot;_blank&quot;&gt;GitHub Actions&lt;/a&gt;, достаточно будет подключить к проекту &lt;a href=&quot;https://github.com/dotenv-linter/action-dotenv-linter&quot; target=&quot;_blank&quot;&gt;dotenv-linter/action-dotenv-linter&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# .github/workflows/dotenv_linter.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dotenv-linter&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;dotenv-linter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;runner / dotenv-linter&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Check out code&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v1&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dotenv-linter&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dotenv-linter/action-dotenv-linter@v1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Больше информации вы можете найти на сайте проекта &lt;a href=&quot;https://dotenv-linter.github.io&quot; target=&quot;_blank&quot;&gt;dotenv-linter.github.io&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;С самого начала dotenv-linter разрабатывался силами Open Source сообщества.&lt;br /&gt;
Поэтому, если у вас есть желание поизучать &lt;a href=&quot;https://www.rust-lang.org&quot; target=&quot;_blank&quot;&gt;Rust&lt;/a&gt; и заодно поучаствовать в разработке замечательного Open Source проекта, то &lt;a href=&quot;https://github.com/dotenv-linter/dotenv-linter&quot; target=&quot;_blank&quot;&gt;dotenv-linter&lt;/a&gt; — это то, что вам нужно!&lt;br /&gt;
Мы будем рады любой помощи!&lt;/p&gt;
</description>
        <pubDate>Mon, 20 Apr 2020 17:00:00 +0000</pubDate>
        <link>https://www.mgrachev.com/2020/04/20/dotenv-linter/</link>
        <guid isPermaLink="true">https://www.mgrachev.com/2020/04/20/dotenv-linter/</guid>
      </item>
    
      <item>
        <title>Автоматическая проверка кода на Go</title>
        <description>&lt;p&gt;В прошлый раз, я уже писал небольшую инструкцию про то, как &lt;a href=&quot;http://www.mgrachev.com/2017/03/20/automatic-code-review-with-vexor&quot; target=&quot;_blank&quot;&gt;настроить автоматическую проверку кода&lt;/a&gt; с помощью &lt;a href=&quot;https://github.com/prontolabs/pronto&quot; target=&quot;_blank&quot;&gt;pronto&lt;/a&gt;. Это отличный инструмент, который позволяет проверить качество кода при помощи всевозможных линтеров и получить результат в виде комментариев на GitHub или GitLab. Но, он написан на Ruby, следовательно, подключить его к проектам на других языках программирования, не представляется возможным. Поэтому, я начал искать альтернативу и нашел - &lt;a href=&quot;https://github.com/haya14busa/reviewdog&quot; target=&quot;_blank&quot;&gt;reviewdog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2018-11-26-automatic-code-review-in-go/reviewdog-dh9ga6g0.png&quot; alt=&quot;reviewdog&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;В отличие от pronto, reviewdog написан на Go. Его можно скомпилировать и подключить к любому проекту, вне зависимости от языка программирования.&lt;/p&gt;

&lt;p&gt;Также, он может работать с любыми линтерами, достаточно лишь перенаправить результат работы линтера на вход reviewdog и определить формат сообщения. Для этого, reviewdog нужно запустить с флагом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-efm&lt;/code&gt; и передать в качестве параметра строку вида: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;%f:%l:%c: %m&quot;&lt;/code&gt;. Для примера, возьмем &lt;a href=&quot;https://github.com/golang/lint&quot; target=&quot;_blank&quot;&gt;golint&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;golint ./... | reviewdog &lt;span class=&quot;nt&quot;&gt;-efm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%f:%l:%c: %m&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-diff&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git diff master&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Флаг &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-diff&lt;/code&gt; обязателен лишь для локального запуска, он позволяет отфильтровать результат и вывести сообщения только для новых изменений.&lt;/p&gt;

&lt;p&gt;Чтобы постоянно не определять формат для каждого линтера, reviewdog поставляется с уже предопределенными форматами. Список можно получить, запустив его с флагом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-list&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;reviewdog &lt;span class=&quot;nt&quot;&gt;-list&lt;/span&gt;
eslint		&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;eslint &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; stylish]&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; A fully pluggable tool &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;identifying and reporting on patterns &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;JavaScript	- https://github.com/eslint/eslint
eslint-compact	&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;eslint &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; compact&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; A fully pluggable tool &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;identifying and reporting on patterns &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;JavaScript	- https://github.com/eslint/eslint
golint		linter &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;Go &lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;code										- https://github.com/golang/lint
govet		Vet examines Go &lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;code and reports suspicious problems						- https://golang.org/cmd/vet/
pep8		Python style guide checker										- https://pypi.python.org/pypi/pep8
phpstan		&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;phpstan &lt;span class=&quot;nt&quot;&gt;--errorFormat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;raw&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; PHP Static Analysis Tool - discover bugs &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;your code without running it!	- https://github.com/phpstan/phpstan
rubocop		A Ruby static code analyzer, based on the community Ruby style guide					- https://github.com/bbatsov/rubocop
sbt		the interactive build tool										- http://www.scala-sbt.org/
sbt-scalastyle	Scalastyle - SBT plugin											- http://www.scalastyle.org/sbt.html
scalac		Scala compiler												- http://www.scala-lang.org/
scalastyle	Scalastyle - Command line										- http://www.scalastyle.org/command-line.html
stylelint	A mighty modern CSS linter										- https://github.com/stylelint/stylelint
tsc		TypeScript compiler											- https://www.typescriptlang.org/
tslint		An extensible linter &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;the TypeScript language							- https://github.com/palantir/tslint
checkstyle	checkstyle XML format											- http://checkstyle.sourceforge.net/&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Список небольшой, но он включает все популярные линтеры для разных языков программирования. Теперь, вместо определения формата, можно передать его название с помощью флага &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-f&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;golint ./... | reviewdog &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;golint &lt;span class=&quot;nt&quot;&gt;-diff&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git diff master&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Этот же формат можно использовать и для других линтеров, например для &lt;a href=&quot;https://github.com/golangci/golangci-lint&quot; target=&quot;_blank&quot;&gt;golangci-lint&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;golangci-lint run | reviewdog &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;golint &lt;span class=&quot;nt&quot;&gt;-diff&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git diff master&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Отдельно стоит упомянуть поддержку XML-формата &lt;a href=&quot;http://checkstyle.sourceforge.net&quot; target=&quot;_blank&quot;&gt;checkstyle&lt;/a&gt;. С его помощью можно работать с такими линтерами, как например &lt;a href=&quot;https://github.com/alecthomas/gometalinter&quot; target=&quot;_blank&quot;&gt;gometalinter&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gometalinter &lt;span class=&quot;nt&quot;&gt;--checkstyle&lt;/span&gt; | reviewdog &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;checkstyle &lt;span class=&quot;nt&quot;&gt;-diff&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git diff master&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Для более удобного запуска нескольких линтеров, reviewdog поддерживает конфигурационные файлы следующего формата:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .reviewdog.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;runner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;&amp;lt;tool-name&amp;gt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;command&amp;gt;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# (required)&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;errorformat&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# (optional if there is supporeted format for &amp;lt;tool-name&amp;gt;. see reviewdog -list)&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;list of errorformat&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;tool-name&amp;gt;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# (optional. you can overwrite &amp;lt;tool-name&amp;gt; defined by runner key)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# examples&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;golint&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;golint ./...&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;errorformat&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%f:%l:%c:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%m&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;govet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;go tool vet -all -shadowstrict .&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Теперь достаточно просто запустить reviewdog и он сам все сделает:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;reviewdog &lt;span class=&quot;nt&quot;&gt;-diff&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git diff master&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;По умолчанию reviewdog выводит весь результат работы в консоль. Для того, чтобы опубликовать результат на GitHub, нужно объявить переменную окружения &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REVIEWDOG_GITHUB_API_TOKEN&lt;/code&gt; и запустить reviewdog с флагом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-reporter=github-pr-review&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;REVIEWDOG_GITHUB_API_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;token&amp;gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;reviewdog &lt;span class=&quot;nt&quot;&gt;-reporter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;github-pr-review&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;В таком случае флаг &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-diff&lt;/code&gt; уже не нужен, reviewdog получит необходимые данные из переменных окружения:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CI_PULL_REQUEST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;7
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CI_REPO_OWNER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;mgrachev
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CI_REPO_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;gastly
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CI_COMMIT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;git rev-parse HEAD&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;На этом плюсы reviewdog не заканчиваются, т.к. он из коробки поддерживает несколько популярных CI, таких как: Travis CI, Circle CI и GitLab CI. Если вы используете один из этих сервисов, то вам не нужно объявлять эти переменные окружения.&lt;/p&gt;

&lt;p&gt;Это лишь малая часть тех возможностей, на что способен reviewdog. Более подробную информацию можно получить на странице проекта &lt;a href=&quot;https://github.com/haya14busa/reviewdog&quot; target=&quot;_blank&quot;&gt;haya14busa/reviewdog&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 26 Nov 2018 08:00:00 +0000</pubDate>
        <link>https://www.mgrachev.com/2018/11/26/automatic-code-review-in-go/</link>
        <guid isPermaLink="true">https://www.mgrachev.com/2018/11/26/automatic-code-review-in-go/</guid>
      </item>
    
      <item>
        <title>Docker for Mac и Kubernetes</title>
        <description>&lt;p&gt;Начиная с версии 17.12 CE Edge, Docker for Mac поставляется вместе с &lt;a href=&quot;https://docs.docker.com/docker-for-mac/kubernetes&quot; target=&quot;_blank&quot;&gt;Kubernetes&lt;/a&gt;. Теперь для создания локального Kubernetes кластера нет необходимости в использовании дополнительных инструментов, таких как &lt;a href=&quot;https://github.com/kubernetes/minikube&quot; target=&quot;_blank&quot;&gt;Minikube&lt;/a&gt;. В качестве эксперимента, попробуем развернуть на кластере небольшой сервис и посмотрим, что из этого получится.&lt;/p&gt;

&lt;p&gt;На момент написания статьи, у меня установлена самая последняя версия Docker for Mac - 18.02.0.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2018-02-26-docker-for-mac-and-kubernetes/docker-ja2lcwo8.png&quot; alt=&quot;Docker For Mac&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Для эксперимента, я написал простой генератор коротких ссылок на Go - &lt;a href=&quot;https://github.com/mgrachev/brevity&quot; target=&quot;_blank&quot;&gt;brevity&lt;/a&gt;. Сервис разделен на 2 части: API для генерация коротких ссылок и их обработчик. Для хранения данных используется PostgreSQL.&lt;/p&gt;

&lt;p&gt;Чтобы развернуть его и все необходимые зависимости, нам потребуется &lt;a href=&quot;https://helm.sh&quot; target=&quot;_blank&quot;&gt;Helm&lt;/a&gt; - пакетный менеджер для Kubernetes, который заметно упрощает процесс развертывания и дальнейшей поддержки различных сервисов. Для его установки мы воспользуемся &lt;a href=&quot;https://brew.sh&quot; target=&quot;_blank&quot;&gt;Homebrew&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;kubernetes-helm&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;После установки, нам нужно будет инициализировать Helm:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm init&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Если у вас настроен доступ к нескольких Kubernetes кластерам, то вам необходимо будет переключиться на локальный:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl config use-context docker-for-desktop&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Подготовка завершена, теперь приступаем к установке необходимых сервисов. Первым делом устанавливаем PostgreSQL:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; pg &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt; db &lt;span class=&quot;nt&quot;&gt;--set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;postgresPassword&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;postgres,persistence.size&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1Gi stable/postgresql&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Helm позволяет переопределять значения по-умолчанию и тем самым настроить сервис под собственные нужды. Список сервисов, которые можно установить через Helm можно найти здесь - &lt;a href=&quot;https://hub.kubeapps.com&quot; target=&quot;_blank&quot;&gt;hub.kubeapps.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Теперь нам нужно удостовериться, что сервис установлен и работает корректно:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get pods &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; db
NAME                            READY     STATUS                       RESTARTS   AGE
pg-postgresql-bbd4bbb5c-njkzs   0/1       CreateContainerConfigError   0          &amp;lt;invalid&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Сервис не запущен из-за ошибки. Чтобы узнать подробности, достаточно выполнить следующую команду:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl describe pods &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; db pg-postgresql-bbd4bbb5c-njkzs | &lt;span class=&quot;nb&quot;&gt;tail&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; 1
Warning  Failed                 &amp;lt;invalid&amp;gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;x4 over 19s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  kubelet, docker-for-desktop  Error: lstat /Users/mgrachev/.docker/Volumes/pg-postgresql/pvc-588afa8b-0e90-11e8-8d22-025000000001: no such file or directory&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Kubernetes не видит директорию, хотя она на самом деле есть. Оказалось, что Docker for Mac не  понимает опцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subPath&lt;/code&gt; в конфигурации &lt;a href=&quot;https://github.com/kubernetes/charts/blob/master/stable/postgresql/templates/deployment.yaml#L77&quot; target=&quot;_blank&quot;&gt;PostgreSQL&lt;/a&gt; для Kubernetes. В Minikube тоже была такая проблема, но её уже &lt;a href=&quot;https://github.com/kubernetes/minikube/issues/2256&quot; target=&quot;_blank&quot;&gt;исправили&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Для решения этой проблемы, нам понадобится отредактировать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deployment&lt;/code&gt; для PostgreSQL:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl edit deployment &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; db pg-postgresql&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;И удалить следующую строчку:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;subPath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql-db&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Теперь, если мы еще раз посмотрим на список запущенных подов, то увидим, что ошибок нет и сервис запущен:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get pods &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; db
NAME                             READY     STATUS    RESTARTS   AGE
pg-postgresql-5c954c46c6-6bt2t   1/1       Running   0          26s&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Далее, нам нужно создать БД для brevity. Сначала зайдем в контейнер:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; db pg-postgresql-5c954c46c6-6bt2t bash&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Внутри контейнера запускаем &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;psql&lt;/code&gt; от имени пользователя &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;postgres&lt;/code&gt; и создаем БД:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;root@pg-postgresql-5c954c46c6-6bt2t:/# gosu postgres psql
&lt;span class=&quot;nv&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# CREATE DATABASE brevity_production;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Чтобы наше приложение было доступно из кластера, нам понадобится &lt;a href=&quot;https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers&quot; target=&quot;_blank&quot;&gt;Ingress Controller&lt;/a&gt;. Для этих целей отлично подойдет &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; nginx stable/nginx-ingress&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Переходим к установке brevity. Склонируем проект и запустим команду &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helm install&lt;/code&gt;, только в качестве аргумента передадим путь к заранее подготовленному &lt;a href=&quot;https://github.com/mgrachev/brevity/tree/master/helm&quot; target=&quot;_blank&quot;&gt;чарту&lt;/a&gt; (пакеты в Helm называются чартами):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git clone https://github.com/mgrachev/brevity
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;brevity
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;helm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; brevity ./helm&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Проверяем, что все сервисы запущены:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get pods
NAME                                                   READY     STATUS    RESTARTS   AGE
brevity-brevity-778799dd4-zlb6g                        1/1       Running   0          1h
nginx-nginx-ingress-controller-c8cf56768-h4txk         1/1       Running   0          1h
nginx-nginx-ingress-default-backend-864c9484bf-rfdq4   1/1       Running   0          1h&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Далее попробуем сгенерировать короткую ссылку, отправив POST запрос к API brevity:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;POST&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http://localhost/api/v1/shortlink&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--data-urlencode&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;url=https://github.com/mgrachev&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;shortlink&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;http://localhost/MyAyEy&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Переходим по ней, чтобы удостовериться, что все работает. Так же мы можем посмотреть логи:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl logs brevity-brevity-778799dd4-zlb6g
&lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2018-02-19T09:31:03Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info &lt;span class=&quot;nv&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;HTTP Server started at port: 80&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2018-02-19T09:32:35Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info &lt;span class=&quot;nv&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Successful returns a short link, short: http://localhost/MyAyEy, original: https://github.com/mgrachev&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2018-02-19T09:32:43Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info &lt;span class=&quot;nv&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Redirect to https://github.com/mgrachev, using token: MyAyEy&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Как видно из логов, сервис успешно обработал короткую ссылку и перенаправил на страницу &lt;a href=&quot;https://github.com/mgrachev&quot; target=&quot;_blank&quot;&gt;github.com/mgrachev&lt;/a&gt;. На этом можно закончить наш эксперимент.&lt;/p&gt;

&lt;p&gt;В заключение скажу, что Docker for Mac с поддержкой Kubernetes меня порадовал. Достаточно в настройках поставить всего одну галочку и в твоем распоряжение будет полностью готовый к работе кластер с Kubernetes на борту. Из минусов отмечу, что это пока нестабильная версия и могут встречаться разного рода ошибки. Но даже сейчас, он вполне пригоден для локальной разработки и тестирования.&lt;/p&gt;
</description>
        <pubDate>Mon, 26 Feb 2018 18:00:00 +0000</pubDate>
        <link>https://www.mgrachev.com/2018/02/26/docker-for-mac-and-kubernetes/</link>
        <guid isPermaLink="true">https://www.mgrachev.com/2018/02/26/docker-for-mac-and-kubernetes/</guid>
      </item>
    
      <item>
        <title>Автоматическая проверка кода с помощью Vexor</title>
        <description>&lt;p&gt;Я думаю многим известен сервис &lt;a href=&quot;https://houndci.com/&quot; target=&quot;_blank&quot;&gt;Hound&lt;/a&gt;, который занимается автоматической проверкой кода. Когда вы создаете новый Pull Request на GitHub, Hound запускает проверку вашего кода на основе таких решений, как &lt;a href=&quot;https://github.com/bbatsov/rubocop&quot; target=&quot;_blank&quot;&gt;rubocop&lt;/a&gt;, &lt;a href=&quot;https://github.com/brigade/haml-lint&quot; target=&quot;_blank&quot;&gt;haml-lint&lt;/a&gt; или &lt;a href=&quot;https://github.com/brigade/scss-lint&quot; target=&quot;_blank&quot;&gt;scss-lint&lt;/a&gt;. Если в процессе проверки всплывают какие-то проблемы, Hound сообщит об этом, добавив соответствующий комментарий в ваш Pull Request. Это отличный инструмент для работы, но как говорится, за всё хорошее, приходится платить.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2017-03-20-automatic-code-review-with-vexor/hound_price_65d0c3b2.png&quot; alt=&quot;Hound Price&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Не каждая компания готова платить такие деньги, поэтому я решил пойти другим путём. За основу взял библиотеку &lt;a href=&quot;https://github.com/mmozuras/pronto&quot; target=&quot;_blank&quot;&gt;pronto&lt;/a&gt;, которая из коробки умеет всё тоже самое, что и Hound. Осталось дело за малым - интегрировать pronto с &lt;a href=&quot;https://vexor.io/&quot; target=&quot;_blank&quot;&gt;Vexor&lt;/a&gt;. Далее идет пошаговая инструкция, что для этого понадобится сделать.&lt;/p&gt;

&lt;p&gt;Первым делом заходим на &lt;a href=&quot;https://github.com/settings/tokens/new&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt; и генерируем персональный токен.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2017-03-20-automatic-code-review-with-vexor/access_token_fa6c4cc3.png&quot; alt=&quot;Access Token&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Затем, добавляем 2 переменные окружения в настройках вашего проекта на Vexor.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2017-03-20-automatic-code-review-with-vexor/env_97b84eb4.png&quot; alt=&quot;Env Variables&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Далее, добавляем pronto и несколько его плагинов в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt; вашего проекта. Список доступных плагинов можно найти по этой &lt;a href=&quot;https://github.com/mmozuras/pronto#runners&quot; target=&quot;_blank&quot;&gt;ссылке&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto-brakeman&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto-coffeelint&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto-fasterer&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto-flay&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto-haml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto-jshint&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto-rails_best_practices&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto-reek&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto-rubocop&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto-scss&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Для запуска pronto нам потребуется создать файл &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/pronto&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env ruby&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;bundler/setup&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pronto/cli&apos;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Pronto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CLI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Octokit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;NotFound&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Pull Request Not Found&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;И дать ему права на выполнение:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;chmod&lt;/span&gt; +x bin/pronto&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Теперь нам остаётся только добавить несколько команд в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vexor.yml&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;ss&quot;&gt;language: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt;

&lt;span class=&quot;ss&quot;&gt;script:
  &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;remote&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;branches&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;origin&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;*&apos;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fetch&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;checkout&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$CI_BRANCH&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pronto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;github_pr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$DEFAULT_BRANCH&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bundle&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rake&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;И всё, можно проверять.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2017-03-20-automatic-code-review-with-vexor/pr_1530758d.png&quot; alt=&quot;Pull Request&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Кто-то может возразить, зачем всё это нужно, если есть &lt;a href=&quot;https://github.com/brigade/overcommit&quot; target=&quot;_blank&quot;&gt;overcommit&lt;/a&gt;? Новые разработчики на проекте часто забывают настроить overcommit и это обнаруживается только спустя несколько дней, когда другой разработчик не может отправить свои изменения на сервер. Чтобы исключить подобные ситуации, я рекомендую использовать эти инструменты вместе.&lt;/p&gt;

&lt;p&gt;P.S. Хочу поблагодарить &lt;a href=&quot;https://github.com/saratovsource&quot; target=&quot;_blank&quot;&gt;Сашу Кириллова&lt;/a&gt;, за помощь с интеграцией pronto и Vexor.&lt;/p&gt;
</description>
        <pubDate>Mon, 20 Mar 2017 12:00:00 +0000</pubDate>
        <link>https://www.mgrachev.com/2017/03/20/automatic-code-review-with-vexor/</link>
        <guid isPermaLink="true">https://www.mgrachev.com/2017/03/20/automatic-code-review-with-vexor/</guid>
      </item>
    
      <item>
        <title>Управление зависимостями через Homebrew</title>
        <description>&lt;p&gt;&lt;img src=&quot;https://www.mgrachev.com/assets/images/2016-11-17-manage-dependecies-through-homebrew/2960edcf98e4b.png&quot; alt=&quot;Homebrew&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Для управления зависимостями в проектах на Ruby мы используем &lt;a href=&quot;http://bundler.io&quot; target=&quot;_blank&quot;&gt;Bundler&lt;/a&gt;. Он позволяет с легкостью устанавливать в проект необходимые нам библиотеки, вместе со всеми зависимостями.&lt;/p&gt;

&lt;p&gt;Но что делать с внешними зависимостями проекта, такими как Redis или Node.js?
Ситуация усугубляется, когда тебя подключают к новому проекту с большим багажом таких зависимостей. И вместо того, чтобы разбираться в проекте, ты тратишь целый день на установку всего этого зоопарка.&lt;/p&gt;

&lt;h3 id=&quot;homebrew-bundle&quot;&gt;Homebrew Bundle&lt;/h3&gt;

&lt;p&gt;Для решения этой проблемы можно воспользоваться &lt;a href=&quot;https://github.com/Homebrew/homebrew-bundle&quot; target=&quot;_blank&quot;&gt;Homebrew Bundle&lt;/a&gt;. Он позволяет управлять внешними зависимостями проекта так же, как и Bundler внутренними, с помощью специального файла - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Brewfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Итак, приступим. Устанавливаем Homebrew Bundle:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew tap Homebrew/bundle
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Tapping homebrew/bundle
Cloning into &lt;span class=&quot;s1&quot;&gt;&apos;/usr/local/Homebrew/Library/Taps/homebrew/homebrew-bundle&apos;&lt;/span&gt;...
remote: Counting objects: 57, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
remote: Compressing objects: 100% &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;53/53&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
remote: Total 57 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;delta 5&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, reused 16 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;delta 2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, pack-reused 0
Unpacking objects: 100% &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;57/57&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
Checking connectivity... &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
Tapped 0 formulae &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;126 files, 154.5K&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Теперь необходимо создать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Brewfile&lt;/code&gt; в корне проекта и добавить туда все необходимые зависимости. Это можно сделать вручную или запустить команду &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew bundle dump&lt;/code&gt;, которая создаст файл со всеми установленными пакетами из Homebrew, например:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;tap prosody/prosody

brew &lt;span class=&quot;s1&quot;&gt;&apos;node&apos;&lt;/span&gt;
brew &lt;span class=&quot;s1&quot;&gt;&apos;redis&apos;&lt;/span&gt;
brew &lt;span class=&quot;s1&quot;&gt;&apos;prosody&apos;&lt;/span&gt;
brew &lt;span class=&quot;s1&quot;&gt;&apos;rabbitmq&apos;&lt;/span&gt;
brew &lt;span class=&quot;s1&quot;&gt;&apos;phantomjs&apos;&lt;/span&gt;
brew &lt;span class=&quot;s1&quot;&gt;&apos;postgresql&apos;&lt;/span&gt;
brew &lt;span class=&quot;s1&quot;&gt;&apos;imagemagick&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Запускаем команду &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew bundle&lt;/code&gt;, чтобы установить или обновить все зависимости.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew bundle
Succeeded &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;installing node
Succeeded &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;installing redis
Succeeded &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;installing prosody
Succeeded &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;installing rabbitmq
Succeeded &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;installing phantomjs
Succeeded &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;installing postgresql
Succeeded &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;installing imagemagick

Success: 7 Fail: 0&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;На этом можно было закончить, но что делать, если в команде есть разработчики на Linux? У них ведь нет Homebrew.&lt;/p&gt;

&lt;h3 id=&quot;linuxbrew&quot;&gt;Linuxbrew&lt;/h3&gt;

&lt;p&gt;Homebrew - нет, но есть аналог - &lt;a href=&quot;http://linuxbrew.sh&quot; target=&quot;_blank&quot;&gt;Linuxbrew&lt;/a&gt;. Он абсолютно так же работает, как и Homebrew на macOS.&lt;/p&gt;

&lt;p&gt;Для его использования потребуется установить несколько дополнительных пакетов в свой дистрибутив:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;build-essential curl git python-setuptools ruby&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Далее устанавливаем Linuxbrew:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Linuxbrew/install/master/install&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; This script will &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;:
/home/ubuntu/.linuxbrew/bin/brew
/home/ubuntu/.linuxbrew/Library/...
/home/ubuntu/.linuxbrew/share/doc/homebrew
/home/ubuntu/.linuxbrew/share/man/man1/brew.1
/home/ubuntu/.linuxbrew/share/zsh/site-functions/_brew
/home/ubuntu/.linuxbrew/etc/bash_completion.d/brew
/home/ubuntu/.cache/Homebrew/

Press RETURN to &lt;span class=&quot;k&quot;&gt;continue &lt;/span&gt;or any other key to abort
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Downloading and installing Linuxbrew...
remote: Counting objects: 1040, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
remote: Compressing objects: 100% &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;930/930&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
remote: Total 1040 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;delta 106&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, reused 481 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;delta 75&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, pack-reused 0
Receiving objects: 100% &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1040/1040&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, 1.01 MiB | 325.00 KiB/s, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
Resolving deltas: 100% &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;106/106&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
From https://github.com/Linuxbrew/brew
 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;new branch]      master     -&amp;gt; origin/master
HEAD is now at b75ca7b CONTRIBUTING.md: Update Contributing to Linuxbrew
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Homebrew has enabled anonymous aggregate user behaviour analytics
Read the analytics documentation &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;and how to opt-out&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; here:
  https://git.io/brew-analytics
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Tapping homebrew/core
Cloning into &lt;span class=&quot;s1&quot;&gt;&apos;/home/ubuntu/.linuxbrew/Library/Taps/homebrew/homebrew-core&apos;&lt;/span&gt;...
remote: Counting objects: 3773, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
remote: Compressing objects: 100% &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;3663/3663&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
remote: Total 3773 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;delta 12&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, reused 335 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;delta 1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, pack-reused 0
Receiving objects: 100% &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;3773/3773&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, 3.05 MiB | 577.00 KiB/s, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
Resolving deltas: 100% &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;12/12&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
Checking connectivity... &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
Tapped 3651 formulae &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;3,800 files, 9.5M&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Warning: /home/ubuntu/.linuxbrew/bin is not &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;your PATH.
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Installation successful!
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Next steps
Install the Linuxbrew dependencies:

Debian, Ubuntu, etc.:
  &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;build-essential&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;

Fedora, Red Hat, CentOS, etc.:
  &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum groupinstall &lt;span class=&quot;s1&quot;&gt;&apos;Development Tools&apos;&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;

See http://linuxbrew.sh/#dependencies &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;more information.

Add to your ~/.zshrc by running
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;export PATH=&quot;/home/ubuntu/.linuxbrew/bin:$PATH&quot;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;~/.zshrc
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;export MANPATH=&quot;/home/ubuntu/.linuxbrew/share/man:$MANPATH&quot;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;~/.zshrc
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;export INFOPATH=&quot;/home/ubuntu/.linuxbrew/share/info:$INFOPATH&quot;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;~/.zshrc

We recommend you &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;GCC by running &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;gcc&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
Run &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;brew &lt;span class=&quot;nb&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; to get started
Further documentation: https://git.io/brew-docs
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Homebrew has enabled anonymous aggregate user behaviour analytics
Read the analytics documentation &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;and how to opt-out&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; here:
  https://git.io/brew-analytics&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Затем добавляем пути в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;export PATH=&quot;/home/ubuntu/.linuxbrew/bin:$PATH&quot;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;export MANPATH=&quot;/home/ubuntu/.linuxbrew/share/man:$MANPATH&quot;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;export INFOPATH=&quot;/home/ubuntu/.linuxbrew/share/info:$INFOPATH&quot;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Перезапускаем zsh:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; ~/.zshrc&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;И всё, теперь можно перейти в проект и выполнить команду &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew bundle&lt;/code&gt;. Все необходимые зависимости для вашего проекта будут установлены.&lt;/p&gt;
</description>
        <pubDate>Thu, 17 Nov 2016 11:00:00 +0000</pubDate>
        <link>https://www.mgrachev.com/2016/11/17/manage-dependecies-through-homebrew/</link>
        <guid isPermaLink="true">https://www.mgrachev.com/2016/11/17/manage-dependecies-through-homebrew/</guid>
      </item>
    
      <item>
        <title>Класс Set и уникальные коллекции объектов</title>
        <description>&lt;p&gt;На одном проекте мне понадобилось получать список форматов для баннеров из одной рекламной платформы.
Задача с виду простая, но возникла одна проблема - список форматов приходит не уникальный.
Для решения этой проблемы я решил использовать встроенный в Ruby класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Set&lt;/code&gt; и немного &lt;a href=&quot;https://ru.wikipedia.org/wiki/Проблемно-ориентированное_проектирование&quot; target=&quot;_blank&quot;&gt;DDD&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Итак, по порядку.&lt;/p&gt;

&lt;p&gt;Список форматов мы будем получать по API, для этого создадим сервис:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# app/services/my_target_service.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyTargetService&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;API_URL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;https://example.com&apos;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;banner_formats&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RestClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;API_URL&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/api/banner_formats.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;symbolize_names: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;В ответ нам приходит массив примерно следующего вида:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;active&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Format 1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;...&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;240&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;active&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Format 1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;...&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;240&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;inactive&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Format 2&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;...&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1080&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;607&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Как видно из примера, форматы в списке повторяются.
Воспользуемся классом &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Set&lt;/code&gt;, чтобы сделать этот список уникальным:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# app/services/my_target_service.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;set&apos;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyTargetService&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;API_URL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;https://example.com&apos;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;banner_formats&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RestClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;API_URL&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/api/banner_formats.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;formats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;symbolize_names: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;banner_formats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;banner_formats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;banner_formats&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Для удобства работы, обернем каждый формат в отдельную сущность.
Для этого создадим класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BannerFormatEntity&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# app/entities/banner_format_entity.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BannerFormatEntity&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:format&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@format&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;description&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;active?&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;active&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Теперь обновим наш сервис, чтобы использовать класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BannerFormatEntity&lt;/code&gt;.
Так же добавим условие, чтобы в списке были только активные форматы:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# app/services/my_target_service.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;set&apos;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyTargetService&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;API_URL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;https://example.com&apos;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;banner_formats&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RestClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;API_URL&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/api/banner_formats.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;formats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;symbolize_names: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;banner_formats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;banner_format&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BannerFormatEntity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;banner_formats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;banner_format&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;banner_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;active?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;banner_formats&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Остается последний штрих.
Для того чтобы Set мог определять уникальность наших объектов, необходимо определить в классе &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BannerFormatEntity&lt;/code&gt; 2 метода: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object#eql?&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object#hash&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# app/entities/banner_format_entity.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BannerFormatEntity&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:format&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@format&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;description&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;active?&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;active&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;eql?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Вот таким простым и элегантным способом, мы решили нашу задачу с получением уникального списка форматов для баннеров.
Сервис &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyTargetService&lt;/code&gt; отправляет запрос к API рекламной площадки и возвращает уникальную коллекцию объектов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BannerFormatEntity&lt;/code&gt;.&lt;/p&gt;
</description>
        <pubDate>Thu, 31 Mar 2016 18:00:00 +0000</pubDate>
        <link>https://www.mgrachev.com/2016/03/31/class-set-and-unique-collection-of-objects/</link>
        <guid isPermaLink="true">https://www.mgrachev.com/2016/03/31/class-set-and-unique-collection-of-objects/</guid>
      </item>
    
  </channel>
</rss>
