<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Arturo Linares</title>
    <link>https://arturo.linar.es/</link>
    <description></description>
    <pubDate>Fri, 01 May 2026 01:08:13 +0000</pubDate>
    <item>
      <title>Sublime Text for modern PHP development</title>
      <link>https://arturo.linar.es/sublime-text-for-modern-php-development?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I decided to give a chance to the editor I spent many years before I switched to Neovim: Sublime Text. I found that the plugin ecosystem is not as big as VS Code, but it has enough packages to make modern PHP work very well. These are the features I was able to configure:&#xA;&#xA;Code diagnostics&#xA;  Coding style&#xA;  Semantic errors&#xA;  Static analysis errors&#xA;Debugging&#xA;Test runners&#xA;Refactoring tools&#xA;  Rename variables and functions&#xA;  Extract interfaces&#xA;  Move classes&#xA;Code navigation&#xA;  Find references&#xA;  Go to definition&#xA;  Find implementations&#xA;  etc&#xA;&#xA;!--more--&#xA;&#xA;PHP&#xA;&#xA;Using the LSP package, I configured phpactor to use it in my projects. For this, I downloaded the latest phar from GitHub, moved it to /usr/local/bin/phpactor and added this to the LSP settings in Sublime Text (using the command palette, select Preferences: LSP Settings):&#xA;&#xA;// Settings in here override those in &#34;LSP/LSP.sublime-settings&#34;,&#xA;{&#xA;  &#34;diagnosticsdelayms&#34;: 800,&#xA;  &#34;showdiagnosticspanelonsave&#34;: 0,&#xA;  &#34;documenthighlightstyle&#34;: &#34;background&#34;,&#xA;  &#34;showdiagnosticsinviewstatus&#34;: false,&#xA;  &#34;showdiagnosticscountinviewstatus&#34;: true,&#xA;  &#34;diagnosticsguttermarker&#34;: &#34;dot&#34;,&#xA;  &#34;logdebug&#34;: false,&#xA;  &#34;showcodeactions&#34;: &#34;bulb&#34;,&#xA;  &#34;showinlayhints&#34;: true,&#xA;  &#34;clients&#34;: {&#xA;    &#34;phpactor&#34;: {&#xA;      &#34;command&#34;: [&#34;/usr/local/bin/phpactor&#34;, &#34;language-server&#34;, &#34;-v&#34;],&#xA;      &#34;selector&#34;: &#34;source.php | embedding.php&#34;,&#xA;    },&#xA;    ...&#xA;  },&#xA;&#xA;}&#xA;&#xA;Then, in an existing project, I enabled it by default by selecting in the command palette LSP: Enable Language Server in Project, and then phpactor.&#xA;&#xA;Now, phpactor can use a file in the root of the project named .phpactor.json for configurations. I normally work on Drupal projects, so I followed the instructions in their documentation and additionally added support for PHP Stan, PHP Code Sniffer, PHP Code Beautifier. Also, phpactor is capable of detecting Symfony services, which is very useful when developing custom modules, and with a contrib module, it is possible to use this plugin with Drupal!&#xA;&#xA;I&#39;m using phpcs and phpstan locally, i.e. not running in a container because it is faster and easier to debug and update. So, after installing PHP locally, I installed the tools globally so they can be accessed in ~/.config/composer/vendor/bin&#xA;&#xA;Install phpcs, phpcbf and style rules&#xA;composer global req drupal/coder&#xA;Install PHP Stan&#xA;composer global req phpstan/phpstan&#xA;&#xA;For all Drupal projects I work on, I install the container dumper and the autoloader with the Drupal classes to help phpactor read the Drupal project:&#xA;&#xA;Genereates autoloader entries for Drupal classes&#xA;composer req --dev fenetikm/autoload-drupal&#xA;composer dump-autoload&#xA;Install the Drupal container dumper&#xA;composer req --dev en drupal/containerdumper&#xA;drush en containerdumper&#xA;The container is dumped by default to .cache/drupalcontainer.xml on cache rebuild.&#xA;drush cr &#xA;&#xA;.phpactor.json&#xA;&#xA;{&#xA;    &#34;$schema&#34;: &#34;/phpactor.schema.json&#34;,&#xA;    &#34;languageserverphpstan.enabled&#34;: true,&#xA;    &#34;languageserverphpstan.level&#34;: &#34;4&#34;,&#xA;    &#34;languageserverphpstan.bin&#34;: &#34;/home/USER/.config/composer/vendor/bin/phpstan&#34;,&#xA;    &#34;phpcodesniffer.enabled&#34;: true,&#xA;    &#34;phpcodesniffer.bin&#34;: &#34;/home/USER/.config/composer/vendor/bin/phpcs&#34;,&#xA;    &#34;phpcodesniffer.cwd&#34;: &#34;%projectroot%&#34;,&#xA;    &#34;phpcodesniffer.args&#34;: [&#xA;        &#34;--standard=Drupal,DrupalPractice&#34;&#xA;    ],&#xA;&#xA;    &#34;symfony.enabled&#34;: true,&#xA;    &#34;symfony.xmlpath&#34;: &#34;%projectroot%/.cache/drupalcontainer.xml&#34;,&#xA;&#xA;    &#34;behat.enabled&#34;: false,&#xA;    &#34;codetransform.indentation&#34;: &#34;  &#34;,&#xA;    &#34;indexer.includepatterns&#34;: [&#xA;        &#34;/*/.php&#34;,&#xA;        &#34;/*/.inc&#34;,&#xA;        &#34;/*/.module&#34;&#xA;    ],&#xA;    &#34;indexer.supportedextensions&#34;: [&#xA;        &#34;php&#34;,&#xA;        &#34;inc&#34;,&#xA;        &#34;module&#34;&#xA;    ],&#xA;    &#34;prophecy.enabled&#34;: false&#xA;}&#xA; &#xA;&#xA;Testing&#xA;&#xA;Using the package PHPUnitKit I can run PHP Unit files in docker, something I was unable to do in Neovim with neotest.&#xA;&#xA;It is possible to override settings per project, so for projects that have PHP unit tests in DDEV I normally have these settings:&#xA;&#xA;{&#xA;  ...&#xA;  &#34;settings&#34;: {&#xA;    ...&#xA;&#x9;&#x9;&#34;phpunit.docker&#34;: true,&#xA;&#x9;  &#34;phpunit.dockercommand&#34;: [ &#34;docker&#34;, &#34;exec&#34;, &#34;-t&#34;, &#34;ddev-YOUR PROJECT-web&#34;,],&#xA;&#x9;  &#34;phpunit.dockerpaths&#34;: {&#xA;&#x9;    // DDEV mount dir&#xA;&#x9;  &#x9;&#34;$folder&#34;: &#34;/var/www/html&#34;,&#xA;&#x9;  },&#xA;&#x9;  &#34;phpunit.debug&#34;: false,&#xA;&#x9;  &#34;phpunit.executable&#34;: &#34;vendor/bin/phpunit&#34;,&#xA;&#x9;  &#34;phpunit.options&#34;: {&#xA;&#x9;  &#x9;&#34;printer&#34;: &#34;\\Drupal\\Tests\\Listeners\\HtmlOutputPrinter&#34;,&#xA;&#x9;  &#x9;&#34;bootstrap&#34;: &#34;/var/www/html/docroot/core/tests/bootstrap.php&#34;,&#xA;&#x9;  &#x9;&#34;testdox&#34;: &#34;&#34;,&#xA;&#x9;  },&#xA;&#xA;Configured this way, phpactor can provide style diagnostics, semantic errors, static analysis warnings and refactoring tools. Additionally, I get service container autocomplete with types.&#xA;&#xA;Debugging&#xA;&#xA;Using the Debugger package, it is possible to use the same protocol VS Code uses, and so far, I haven&#39;t had any problem with it. Instead of having the debugger configurations in a .vscode/launch.json file, they live in the project settings. I&#39;m using DDEV, so I use this:&#xA;&#xA;{&#xA;  ...&#xA;&#xA;  &#34;debuggerconfigurations&#34;:&#xA;&#x9;[&#xA;&#x9;&#x9;{&#xA;      &#34;name&#34;: &#34;Listen for XDebug&#34;,&#xA;      &#34;type&#34;: &#34;php&#34;,&#xA;      &#34;request&#34;: &#34;launch&#34;,&#xA;      &#34;port&#34;: 9003,&#xA;      &#34;log&#34;: false,&#xA;      &#34;pathMappings&#34;: {&#xA;        &#34;/var/www/html&#34;: &#34;${folder}&#34;,&#xA;      },&#xA;    },&#xA;&#x9;],&#xA;}&#xA;&#xA;VIM&#xA;&#xA;Coming from Neovim, I wanted to use the same shortcuts, so I installed Neovintageous. I was surprised by how well this program can do things and how easily it can be set up, and by how well it is documented. I was able to copy my most used key-bindings from LunarVim to Sublime Text, and keep almost the same workflow.&#xA;&#xA;Below is my .neovintageousrc, the .vimrc equivalent.&#xA;&#xA;&#34; Type :help nv for help.&#xA;&#xA;let mapleader = Space&#xA;&#xA;&#34; Go to definition&#xA;nnoremap gd :LspSymbolDefinition sidebyside=false forcegroup=true fallback=trueCR&#xA;&#34; Find Implementation&#xA;nnoremap gI :LspSymbolImplementation sidebyside=false forcegroup=true fallback=trueCR&#xA;&#34; Find Type Definition&#xA;nnoremap gD :LspSymbolTypeDefinition sidebyside=false forcegroup=true fallback=trueCR&#xA;&#34; Find References&#xA;nnoremap gr :LspSymbolReferences sidebyside=false forcegroup=true fallback=false group=-1 includedeclaration=falseCR&#xA;&#xA;&#34; Close all terminus panes and open the workspace selector.&#xA;nnoremap leadero :TerminusCloseAllbar:PromptSelectWorkspaceCR&#xA;&#xA;nnoremap leadergm :ShowOverlay overlay=goto text=@CR&#xA;&#34; nnoremap leadergm :LspDocumentSymbolsCR&#xA;nnoremap leadergs :GsShowStatusCR&#xA;nnoremap leadergp :ToggleInlineDiffCR&#xA;noremap leadergr :RevertModificationCR&#xA;noremap leadergj :NextModificationCR&#xA;noremap leadergk :PrevModificationCR&#xA;&#xA;&#34; Yank and paste using system clipboard.&#xA;noremap leadery &#34;+y&#xA;noremap leaderY &#34;+Y&#xA;noremap leaderp &#34;+p&#xA;noremap leaderP &#34;+P&#xA;&#xA;&#34; Jump to next/previous.&#xA;noremap M-- :JumpBackCR&#xA;&#xA;&#34; Terminal&#xA;&#34; noremap leadertt :ToggleTerminusPanelCR&#xA;noremap leadertt :TerminusOpenCR&#xA;&#34; Debugger&#xA;&#34; nnoremap leaderld :ShowOverlay overlay=commandpalette text=debuggerCR&#xA;nnoremap leaderdt :Debugger action=togglebreakpointCR&#xA;nnoremap leaderdB :Debugger action=clearbreakpointsCR&#xA;nnoremap leaderdo :Debugger action=stepoverCR&#xA;nnoremap leaderdi :Debugger action=stepinCR&#xA;nnoremap leaderdc :Debugger action=continueCR&#xA;nnoremap leaderdq :Debugger action=quitCR&#xA;nnoremap leaderds :Debugger action=startCR&#xA;nnoremap leaderdU :Debugger action=openCR&#xA;&#xA;&#34; LSP&#xA;nnoremap leaderlS :LspWorkspaceSymbolsCR&#xA;nnoremap leaderls :LspDocumentSymbolsCR&#xA;nnoremap leaderlj :LspNextDiagnosticCR&#xA;nnoremap leaderlk :LspPrevDiagnosticCR&#xA;nnoremap leaderlH :LspToggleInlayHintsCR&#xA;nnoremap FileType json,js,rb,go,rs leaderlf :LspFormatDocumentCR&#xA;nnoremap FileType php leaderlf :PhpcbfCR&#xA;nnoremap leaderlh :LspHoverCR&#xA;nnoremap leaderla :LspCodeActionsCR&#xA;nnoremap leaderlA :ShowOverlay overlay=commandpalette text=phpactorCR&#xA;vnoremap leaderlr :LspCodeActions onlykinds=refactorCR&#xA;nnoremap leaderr :LspSymbolRenameCR&#xA;nnoremap leaderld :SublimeLinterPanelToggleCR&#xA;&#34; nnoremap leaderld :LspGotoDiagnostic uri=%CR&#xA;nnoremap leaderc :tabcCR&#xA;nnoremap Tab :NextViewCR&#xA;nnoremap S-Tab :PrevViewCR&#xA;&#xA;&#34; nnoremap C-j :SelectLines forward=falseCR&#xA;&#34; nnoremap C-k :SelectLines forward=trueCR&#xA;&#xA;nnoremap C-h :TravelToPane direction=leftCR&#xA;nnoremap C-l :TravelToPane direction=rightCR&#xA;nnoremap C-j :TravelToPane direction=downCR&#xA;nnoremap C-k :TravelToPane direction=upCR&#xA;&#xA;nnoremap gc :ToggleCommentCR&#xA;&#xA;nnoremap leadersr :SublimeLinterLineReportCR&#xA;nnoremap leaderst :ShowPanel panel=findinfilesCR&#xA;&#xA;&#34; Sidebar&#xA;nnoremap leadere :Neovintageous action=togglesidebarCR&#xA;&#xA;&#34; Bookmarks&#xA;nnoremap leadera :ToggleBookmarkCR&#xA;nnoremap C-e :ViewBookmarksCR&#xA;&#xA;&#34; GoTo file&#xA;nnoremap leader, :ShowOverlay overlay=goto showfiles=trueCR&#xA;&#xA;&#34; Use nv CTRL keys&#xA;nnoremap C-P :ShowOverlay overlay=commandpaletteCR&#xA;&#xA;&#34; Select lines&#xA;nnoremap C-down :SelectLines forward=trueCR&#xA;nnoremap C-up :SelectLines forward=falseCR&#xA;&#xA;&#34; Buffers&#xA;nnoremap leaderbf :TabFilterCR&#xA;nnoremap leadersr :OpenRecentlyClosedFileCR&#xA;nnoremap leaderbc :CloseOtherTabsCR&#xA;nnoremap leaderz :ZoomPane fraction=0.9CR&#xA;nnoremap leaderZ :UnzoomPaneCR&#xA;&#xA;&#34; GitOpen&#xA;&#34; nnoremap leadergo :GitOpenCR&#xA;&#34; nnoremap leadergc :GitOpen commit=trueCR&#xA;&#34; nnoremap leadergi :GitOpen issue=trueCR&#xA;&#xA;&#34; text manipulation&#xA;noremap A-j :SwapLineDownCR&#xA;noremap A-k :SwapLineUpCR&#xA;vnoremap A-j :SwapLineDownCR&#xA;vnoremap A-k :SwapLineUpCR&#xA;&#xA;&#34; Related files&#xA;nnoremap leaderR :RelatedFilesCR&#xA;&#xA;&#34; Search Ignore case&#xA;set ic&#xA;&#xA;Packages&#xA;&#xA;Many of the VIM maps use commands provided by third-party packages. Below some of my most used ones, but I also recommend a visit to the NeoVintageuos developer blog.&#xA;&#xA;Origami - To manage &#34;buffers&#34; as tabs, create splits, change layout, etc.&#xA;GitGutter - Creates dimmed text with the latest commit message&#xA;GitHubinator - Opens files and commits in remote servers&#xA;GitSavvy - Lots of great git tools&#xA;PHPNamespaces - To coy namespaces&#xA;FuzzyFileNav - To navigate directories using the command palette&#xA;Terminus - A real terminal in Sublime Text&#xA;Tab Filter - To search in open tabs, to mimic Neovim find open buffers&#xA;CloseOtherWindows - The package name says it all, but I needed this for a Neovim key binding that I used a lot&#xA;NeoVintageousFiles - Sidebar navigation with the keyboard&#xA;NeoVintageousHighlightLine - To remove the line highlight in insert mode]]&gt;</description>
      <content:encoded><![CDATA[<p>I decided to give a chance to the editor I spent many years before I switched to Neovim: Sublime Text. I found that the plugin ecosystem is not as big as VS Code, but it has enough packages to make modern PHP work very well. These are the features I was able to configure:</p>
<ul><li>Code diagnostics
<ul><li>Coding style</li>
<li>Semantic errors</li>
<li>Static analysis errors</li></ul></li>
<li>Debugging</li>
<li>Test runners</li>
<li>Refactoring tools
<ul><li>Rename variables and functions</li>
<li>Extract interfaces</li>
<li>Move classes</li></ul></li>
<li>Code navigation
<ul><li>Find references</li>
<li>Go to definition</li>
<li>Find implementations</li>
<li>etc</li></ul></li></ul>



<h2 id="php" id="php">PHP</h2>

<p>Using the <a href="https://packagecontrol.io/packages/LSP" rel="nofollow">LSP</a> package, I configured <a href="https://github.com/phpactor/phpactor/" rel="nofollow">phpactor</a> to use it in my projects. For this, I downloaded the <a href="https://github.com/phpactor/phpactor/releases" rel="nofollow">latest</a> <em>phar</em> from GitHub, moved it to <code>/usr/local/bin/phpactor</code> and added this to the LSP settings in Sublime Text (using the command palette, select <em>Preferences: LSP Settings</em>):</p>

<pre><code class="language-javascript">// Settings in here override those in &#34;LSP/LSP.sublime-settings&#34;,
{
  &#34;diagnostics_delay_ms&#34;: 800,
  &#34;show_diagnostics_panel_on_save&#34;: 0,
  &#34;document_highlight_style&#34;: &#34;background&#34;,
  &#34;show_diagnostics_in_view_status&#34;: false,
  &#34;show_diagnostics_count_in_view_status&#34;: true,
  &#34;diagnostics_gutter_marker&#34;: &#34;dot&#34;,
  &#34;log_debug&#34;: false,
  &#34;show_code_actions&#34;: &#34;bulb&#34;,
  &#34;show_inlay_hints&#34;: true,
  &#34;clients&#34;: {
    &#34;phpactor&#34;: {
      &#34;command&#34;: [&#34;/usr/local/bin/phpactor&#34;, &#34;language-server&#34;, &#34;-v&#34;],
      &#34;selector&#34;: &#34;source.php | embedding.php&#34;,
    },
    ...
  },

}
</code></pre>

<p>Then, in an existing <a href="https://www.sublimetext.com/docs/projects.html" rel="nofollow">project</a>, I enabled it by default by selecting in the command palette <em>LSP: Enable Language Server in Project,</em> and then <em>phpactor.</em></p>

<p>Now, phpactor can use a file in the root of the project named <code>.phpactor.json</code> for configurations. I normally work on Drupal projects, so I followed the instructions in their <a href="https://phpactor.readthedocs.io/en/master/integrations/drupal8.html" rel="nofollow">documentation</a> and additionally added support for PHP Stan, PHP Code Sniffer, PHP Code Beautifier. Also, phpactor is capable of detecting Symfony services, which is very useful when developing custom modules, and with a contrib module, it is possible to use this plugin with Drupal!</p>

<p>I&#39;m using phpcs and phpstan locally, i.e. not running in a container because it is faster and easier to debug and update. So, after installing PHP locally, I installed the tools globally so they can be accessed in <code>~/.config/composer/vendor/bin</code></p>

<pre><code class="language-javascript"># Install phpcs, phpcbf and style rules
composer global req drupal/coder
# Install PHP Stan
composer global req phpstan/phpstan
</code></pre>

<p>For all Drupal projects I work on, I install the container dumper and the autoloader with the Drupal classes to help phpactor read the Drupal project:</p>

<pre><code class="language-plain"># Genereates autoloader entries for Drupal classes
composer req --dev fenetikm/autoload-drupal
composer dump-autoload
# Install the Drupal container dumper
composer req --dev en drupal/container_dumper
drush en container_dumper
# The container is dumped by default to .cache/drupal_container.xml on cache rebuild.
drush cr 
</code></pre>

<p><em>.phpactor.json</em></p>

<pre><code class="language-javascript">{
    &#34;$schema&#34;: &#34;/phpactor.schema.json&#34;,
    &#34;language_server_phpstan.enabled&#34;: true,
    &#34;language_server_phpstan.level&#34;: &#34;4&#34;,
    &#34;language_server_phpstan.bin&#34;: &#34;/home/&lt;USER&gt;/.config/composer/vendor/bin/phpstan&#34;,
    &#34;php_code_sniffer.enabled&#34;: true,
    &#34;php_code_sniffer.bin&#34;: &#34;/home/&lt;USER&gt;/.config/composer/vendor/bin/phpcs&#34;,
    &#34;php_code_sniffer.cwd&#34;: &#34;%project_root%&#34;,
    &#34;php_code_sniffer.args&#34;: [
        &#34;--standard=Drupal,DrupalPractice&#34;
    ],

    &#34;symfony.enabled&#34;: true,
    &#34;symfony.xml_path&#34;: &#34;%project_root%/.cache/drupal_container.xml&#34;,

    &#34;behat.enabled&#34;: false,
    &#34;code_transform.indentation&#34;: &#34;  &#34;,
    &#34;indexer.include_patterns&#34;: [
        &#34;/**/*.php&#34;,
        &#34;/**/*.inc&#34;,
        &#34;/**/*.module&#34;
    ],
    &#34;indexer.supported_extensions&#34;: [
        &#34;php&#34;,
        &#34;inc&#34;,
        &#34;module&#34;
    ],
    &#34;prophecy.enabled&#34;: false
}
 
</code></pre>

<h3 id="testing" id="testing">Testing</h3>

<p>Using the package <a href="https://packagecontrol.io/packages/PHPUnitKit" rel="nofollow">PHPUnitKit</a> I can run PHP Unit files <em>in docker</em>, something I was <a href="https://github.com/nvim-neotest/neotest/issues/89" rel="nofollow">unable</a> to do in Neovim with <a href="https://github.com/nvim-neotest/neotest" rel="nofollow">neotest</a>.</p>

<p>It is possible to override settings per project, so for projects that have PHP unit tests in DDEV I normally have these settings:</p>

<pre><code class="language-javascript">{
  ...
  &#34;settings&#34;: {
    ...
		&#34;phpunit.docker&#34;: true,
	  &#34;phpunit.docker_command&#34;: [ &#34;docker&#34;, &#34;exec&#34;, &#34;-t&#34;, &#34;ddev-&lt;YOUR PROJECT&gt;-web&#34;,],
	  &#34;phpunit.docker_paths&#34;: {
	    // DDEV mount dir
	  	&#34;$folder&#34;: &#34;/var/www/html&#34;,
	  },
	  &#34;phpunit.debug&#34;: false,
	  &#34;phpunit.executable&#34;: &#34;vendor/bin/phpunit&#34;,
	  &#34;phpunit.options&#34;: {
	  	&#34;printer&#34;: &#34;\\Drupal\\Tests\\Listeners\\HtmlOutputPrinter&#34;,
	  	&#34;bootstrap&#34;: &#34;/var/www/html/docroot/core/tests/bootstrap.php&#34;,
	  	&#34;testdox&#34;: &#34;&#34;,
	  },
</code></pre>

<p>Configured this way, phpactor can provide style diagnostics, semantic errors, static analysis warnings and refactoring tools. Additionally, I get service container autocomplete with types.</p>

<p><img src="https://i.snap.as/cTXsov5R.png" alt=""/></p>

<h3 id="debugging" id="debugging">Debugging</h3>

<p>Using the <a href="https://github.com/daveleroy/SublimeDebugger" rel="nofollow">Debugger</a> package, it is possible to use the same protocol VS Code uses, and so far, I haven&#39;t had any problem with it. Instead of having the debugger configurations in a <code>.vscode/launch.json</code> file, they live in the project settings. I&#39;m using DDEV, so I use this:</p>

<pre><code class="language-javascript">{
  ...

  &#34;debugger_configurations&#34;:
	[
		{
      &#34;name&#34;: &#34;Listen for XDebug&#34;,
      &#34;type&#34;: &#34;php&#34;,
      &#34;request&#34;: &#34;launch&#34;,
      &#34;port&#34;: 9003,
      &#34;log&#34;: false,
      &#34;pathMappings&#34;: {
        &#34;/var/www/html&#34;: &#34;${folder}&#34;,
      },
    },
	],
}
</code></pre>

<h2 id="vim" id="vim">VIM</h2>

<p>Coming from Neovim, I wanted to use the same shortcuts, so I installed <a href="https://neovintageous.github.io" rel="nofollow">Neovintageous</a>. I was surprised by how well this program can do things and how easily it can be set up, and by how well it is documented. I was able to copy my most used key-bindings from <a href="https://www.lunarvim.org/" rel="nofollow">LunarVim</a> to Sublime Text, and keep almost the same workflow.</p>

<p>Below is my <code>.neovintageousrc</code>, the <code>.vimrc</code> equivalent.</p>

<pre><code>&#34; Type :help nv for help.

let mapleader = &lt;Space&gt;

&#34; Go to definition
nnoremap gd :LspSymbolDefinition side_by_side=false force_group=true fallback=true&lt;CR&gt;
&#34; Find Implementation
nnoremap gI :LspSymbolImplementation side_by_side=false force_group=true fallback=true&lt;CR&gt;
&#34; Find Type Definition
nnoremap gD :LspSymbolTypeDefinition side_by_side=false force_group=true fallback=true&lt;CR&gt;
&#34; Find References
nnoremap gr :LspSymbolReferences side_by_side=false force_group=true fallback=false group=-1 include_declaration=false&lt;CR&gt;

&#34; Close all terminus panes and open the workspace selector.
nnoremap &lt;leader&gt;o :TerminusCloseAll&lt;bar&gt;:PromptSelectWorkspace&lt;CR&gt;

nnoremap &lt;leader&gt;gm :ShowOverlay overlay=goto text=@&lt;CR&gt;
&#34; nnoremap &lt;leader&gt;gm :LspDocumentSymbols&lt;CR&gt;
nnoremap &lt;leader&gt;gs :GsShowStatus&lt;CR&gt;
nnoremap &lt;leader&gt;gp :ToggleInlineDiff&lt;CR&gt;
noremap &lt;leader&gt;gr :RevertModification&lt;CR&gt;
noremap &lt;leader&gt;gj :NextModification&lt;CR&gt;
noremap &lt;leader&gt;gk :PrevModification&lt;CR&gt;

&#34; Yank and paste using system clipboard.
noremap &lt;leader&gt;y &#34;+y
noremap &lt;leader&gt;Y &#34;+Y
noremap &lt;leader&gt;p &#34;+p
noremap &lt;leader&gt;P &#34;+P

&#34; Jump to next/previous.
noremap &lt;M--&gt; :JumpBack&lt;CR&gt;

&#34; Terminal
&#34; noremap &lt;leader&gt;tt :ToggleTerminusPanel&lt;CR&gt;
noremap &lt;leader&gt;tt :TerminusOpen&lt;CR&gt;
&#34; Debugger
&#34; nnoremap &lt;leader&gt;ld :ShowOverlay overlay=command_palette text=debugger&lt;CR&gt;
nnoremap &lt;leader&gt;dt :Debugger action=toggle_breakpoint&lt;CR&gt;
nnoremap &lt;leader&gt;dB :Debugger action=clear_breakpoints&lt;CR&gt;
nnoremap &lt;leader&gt;do :Debugger action=step_over&lt;CR&gt;
nnoremap &lt;leader&gt;di :Debugger action=step_in&lt;CR&gt;
nnoremap &lt;leader&gt;dc :Debugger action=continue&lt;CR&gt;
nnoremap &lt;leader&gt;dq :Debugger action=quit&lt;CR&gt;
nnoremap &lt;leader&gt;ds :Debugger action=start&lt;CR&gt;
nnoremap &lt;leader&gt;dU :Debugger action=open&lt;CR&gt;

&#34; LSP
nnoremap &lt;leader&gt;lS :LspWorkspaceSymbols&lt;CR&gt;
nnoremap &lt;leader&gt;ls :LspDocumentSymbols&lt;CR&gt;
nnoremap &lt;leader&gt;lj :LspNextDiagnostic&lt;CR&gt;
nnoremap &lt;leader&gt;lk :LspPrevDiagnostic&lt;CR&gt;
nnoremap &lt;leader&gt;lH :LspToggleInlayHints&lt;CR&gt;
nnoremap FileType json,js,rb,go,rs &lt;leader&gt;lf :LspFormatDocument&lt;CR&gt;
nnoremap FileType php &lt;leader&gt;lf :Phpcbf&lt;CR&gt;
nnoremap &lt;leader&gt;lh :LspHover&lt;CR&gt;
nnoremap &lt;leader&gt;la :LspCodeActions&lt;CR&gt;
nnoremap &lt;leader&gt;lA :ShowOverlay overlay=command_palette text=phpactor&lt;CR&gt;
vnoremap &lt;leader&gt;lr :LspCodeActions only_kinds=refactor&lt;CR&gt;
nnoremap &lt;leader&gt;r :LspSymbolRename&lt;CR&gt;
nnoremap &lt;leader&gt;ld :SublimeLinterPanelToggle&lt;CR&gt;
&#34; nnoremap &lt;leader&gt;ld :LspGotoDiagnostic uri=%&lt;CR&gt;
nnoremap &lt;leader&gt;c :tabc&lt;CR&gt;
nnoremap &lt;Tab&gt; :NextView&lt;CR&gt;
nnoremap &lt;S-Tab&gt; :PrevView&lt;CR&gt;

&#34; nnoremap &lt;C-j&gt; :SelectLines forward=false&lt;CR&gt;
&#34; nnoremap &lt;C-k&gt; :SelectLines forward=true&lt;CR&gt;

nnoremap &lt;C-h&gt; :TravelToPane direction=left&lt;CR&gt;
nnoremap &lt;C-l&gt; :TravelToPane direction=right&lt;CR&gt;
nnoremap &lt;C-j&gt; :TravelToPane direction=down&lt;CR&gt;
nnoremap &lt;C-k&gt; :TravelToPane direction=up&lt;CR&gt;

nnoremap gc :ToggleComment&lt;CR&gt;

nnoremap &lt;leader&gt;sr :SublimeLinterLineReport&lt;CR&gt;
nnoremap &lt;leader&gt;st :ShowPanel panel=find_in_files&lt;CR&gt;

&#34; Sidebar
nnoremap &lt;leader&gt;e :Neovintageous action=toggle_side_bar&lt;CR&gt;

&#34; Bookmarks
nnoremap &lt;leader&gt;a :ToggleBookmark&lt;CR&gt;
nnoremap &lt;C-e&gt; :ViewBookmarks&lt;CR&gt;

&#34; GoTo file
nnoremap &lt;leader&gt;, :ShowOverlay overlay=goto show_files=true&lt;CR&gt;

&#34; Use nv CTRL keys
nnoremap &lt;C-P&gt; :ShowOverlay overlay=command_palette&lt;CR&gt;

&#34; Select lines
nnoremap &lt;C-down&gt; :SelectLines forward=true&lt;CR&gt;
nnoremap &lt;C-up&gt; :SelectLines forward=false&lt;CR&gt;

&#34; Buffers
nnoremap &lt;leader&gt;bf :TabFilter&lt;CR&gt;
nnoremap &lt;leader&gt;sr :OpenRecentlyClosedFile&lt;CR&gt;
nnoremap &lt;leader&gt;bc :CloseOtherTabs&lt;CR&gt;
nnoremap &lt;leader&gt;z :ZoomPane fraction=0.9&lt;CR&gt;
nnoremap &lt;leader&gt;Z :UnzoomPane&lt;CR&gt;

&#34; GitOpen
&#34; nnoremap &lt;leader&gt;go :GitOpen&lt;CR&gt;
&#34; nnoremap &lt;leader&gt;gc :GitOpen commit=true&lt;CR&gt;
&#34; nnoremap &lt;leader&gt;gi :GitOpen issue=true&lt;CR&gt;

&#34; text manipulation
noremap &lt;A-j&gt; :SwapLineDown&lt;CR&gt;
noremap &lt;A-k&gt; :SwapLineUp&lt;CR&gt;
vnoremap &lt;A-j&gt; :SwapLineDown&lt;CR&gt;
vnoremap &lt;A-k&gt; :SwapLineUp&lt;CR&gt;

&#34; Related files
nnoremap &lt;leader&gt;R :RelatedFiles&lt;CR&gt;

&#34; Search Ignore case
set ic
</code></pre>

<h2 id="packages" id="packages">Packages</h2>

<p>Many of the VIM maps use commands provided by third-party packages. Below some of my most used ones, but I also recommend a visit to the NeoVintageuos <a href="https://blog.gerardroche.com" rel="nofollow">developer blog</a>.</p>
<ul><li><a href="https://github.com/SublimeText/Origami" rel="nofollow">Origami</a> – To manage “buffers” as tabs, create splits, change layout, etc.</li>
<li><a href="https://github.com/SublimeText/Origami" rel="nofollow">GitGutter</a> – Creates dimmed text with the latest commit message</li>
<li><a href="https://github.com/ehamiter/GitHubinator" rel="nofollow">GitHubinator</a> – Opens files and commits in remote servers</li>
<li><a href="https://github.com/timbrel/GitSavvy" rel="nofollow">GitSavvy</a> – Lots of great git tools</li>
<li><a href="https://lubriciousdevelopers.github.io/projects/sublime-php-namespace/" rel="nofollow">PHPNamespaces</a> – To coy namespaces</li>
<li><a href="https://facelessuser.github.io/FuzzyFileNav/" rel="nofollow">FuzzyFileNav</a> – To navigate directories using the command palette</li>
<li><a href="https://packagecontrol.io/packages/Terminus" rel="nofollow">Terminus</a> – A real terminal in Sublime Text</li>
<li><a href="https://github.com/robinmalburn/sublime-tabfilter" rel="nofollow">Tab Filter</a> – To search in open tabs, to mimic Neovim <em>find open buffers</em></li>
<li><a href="https://github.com/mavuio/CloseOtherWindows_SublimePlugin" rel="nofollow">CloseOtherWindows</a> – The package name says it all, but I needed this for a Neovim key binding that I used a lot</li>
<li><a href="https://github.com/gerardroche/NeoVintageousFiles" rel="nofollow">NeoVintageousFiles</a> – Sidebar navigation with the keyboard</li>
<li><a href="https://github.com/gerardroche/NeoVintageousFiles" rel="nofollow">NeoVintageousHighlightLine</a> – To remove the line highlight in insert mode</li></ul>
]]></content:encoded>
      <guid>https://arturo.linar.es/sublime-text-for-modern-php-development</guid>
      <pubDate>Sat, 30 Mar 2024 04:35:09 +0000</pubDate>
    </item>
    <item>
      <title>Adding links to a custom entity</title>
      <link>https://arturo.linar.es/adding-links-to-a-custom-entity?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Using the method ::toUrl() in custom entities is very handy when using pre-defined URLs, like canonical and add-form. But what if we want to create a new URL and use it in the same way?&#xA;&#xA;Let’s assume we have a custom entity named CustomPost, and that we wish to obtain a “preview” URL using this method, how can we do it?&#xA;&#xA;$post-  toUrl(&#39;preview&#39;);&#xA;&#xA;!--more--&#xA;&#xA;Let’s start by adding a link handler in the custom entity plugin definition:&#xA;&#xA;links = {&#xA;  &#34;collection&#34; = &#34;/admin/content/custompost&#34;,&#xA;  ...&#xA;  &#34;preview&#34; = &#34;/admin/content/custompost/preview/{custompost}&#34;,&#xA;}&#xA;&#xA;Now we have a URL, but we don’t have a route. Let’s create it in the custom.routing.yml file. Links defined in custom entities follow this route pattern: entity.entitytype.linkname&#xA;&#xA;Custom entity route for &#34;preview&#34; link&#xA;entity.custompost.preview:&#xA;  path: &#39;/admin/content/custompost/preview/{entity}&#39;&#xA;  defaults:&#xA;    title: &#39;Preview&#39;&#xA;    controller: &#39;\Drupal\mymodule\Controller\MyCustomController::preview&#39;&#xA;  requirements:&#xA;    permission: &#39;access any content&#39;&#xA;  options:&#xA;    parameters:&#xA;      entity:&#xA;        type: entity:custompost&#xA;&#xA;In this route definition, we’re specifying that we want to receive the entity parameter as CustomPost entity. However, this translation will not happen automatically when we have an instance of CustomPost, so we have to tell the custom entity how to do it. &#xA;&#xA;In the CustomPost class, override the method urlRouteParameters to catch the new “preview” link and inject the necessary parameters. Note that the array id must match the name of the route parameter name we want to replace.&#xA;&#xA;  /*&#xA;   {@inheritdoc}&#xA;   /&#xA;  public function urlRouteParameters($rel) {&#xA;    if ($rel === &#39;preview&#39;) {&#xA;      return [&#39;entity&#39; =  $this-  id()];&#xA;    }&#xA;    return parent::urlRouteParameters($rel);&#xA;  }&#xA;&#xA;Finally, we can define the controller action that receives a parameter of type CustomPost_&#xA;&#xA;class MyCustomController extends ControllerBase {&#xA;  public function preview(CustomPost $post) {&#xA;    $build = [];&#xA;    // @todo build the preview&#xA;    return $build;&#xA;  }&#xA;}&#xA;&#xA;Clear cache and the new link should be ready to be used.]]&gt;</description>
      <content:encoded><![CDATA[<p>Using the method <code>::toUrl()</code> in custom entities is very handy when using pre-defined URLs, like <code>canonical</code> and <code>add-form</code>. But what if we want to create a new URL and use it in the same way?</p>

<p>Let’s assume we have a custom entity named <em>CustomPost</em>, and that we wish to obtain a “preview” URL using this method, how can we do it?</p>

<pre><code class="language-php">$post-&gt;toUrl(&#39;preview&#39;);
</code></pre>



<p>Let’s start by adding a link handler in the custom entity plugin definition:</p>

<pre><code>links = {
  &#34;collection&#34; = &#34;/admin/content/custom_post&#34;,
  ...
  &#34;preview&#34; = &#34;/admin/content/custom_post/preview/{custom_post}&#34;,
}
</code></pre>

<p>Now we have a URL, but we don’t have a route. Let’s create it in the <em>.routing.yml</em> file. Links defined in custom entities follow this route pattern: <em>entity.&lt;entity</em>type&gt;._</p>

<pre><code class="language-yaml"># Custom entity route for &#34;preview&#34; link
entity.custom_post.preview:
  path: &#39;/admin/content/custom_post/preview/{entity}&#39;
  defaults:
    _title: &#39;Preview&#39;
    _controller: &#39;\Drupal\mymodule\Controller\MyCustomController::preview&#39;
  requirements:
    _permission: &#39;access any content&#39;
  options:
    parameters:
      entity:
        type: entity:custom_post
</code></pre>

<p>In this route definition, we’re specifying that we want to receive the <em>entity</em> parameter as <em>CustomPost</em> entity. However, this translation will not happen automatically when we have an instance of <em>CustomPost</em>, so we have to tell the custom entity how to do it.</p>

<p>In the <em>CustomPost</em> class, override the method <code>urlRouteParameters</code> to catch the new “preview” link and inject the necessary parameters. <strong>Note that the array id must match the name of the route parameter name we want to replace.</strong></p>

<pre><code class="language-php">  /**
   * {@inheritdoc}
   */
  public function urlRouteParameters($rel) {
    if ($rel === &#39;preview&#39;) {
      return [&#39;entity&#39; =&gt; $this-&gt;id()];
    }
    return parent::urlRouteParameters($rel);
  }
</code></pre>

<p>Finally, we can define the controller action that receives a parameter of type <em>CustomPost</em></p>

<pre><code class="language-php">class MyCustomController extends ControllerBase {
  public function preview(CustomPost $post) {
    $build = [];
    // @todo build the preview
    return $build;
  }
}
</code></pre>

<p>Clear cache and the new link should be ready to be used.</p>
]]></content:encoded>
      <guid>https://arturo.linar.es/adding-links-to-a-custom-entity</guid>
      <pubDate>Sat, 13 Jan 2024 16:33:54 +0000</pubDate>
    </item>
    <item>
      <title>GNOME Evolution Expression Functions Cheat Sheet</title>
      <link>https://arturo.linar.es/gnome-evolution-expression-functions-cheat-sheet?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[&#xA;&#xA;Time-Related Functions&#xA;&#xA;time-now: Returns the current time.&#xA;make-time: Creates a new time value.&#xA;time-add-day: Adds a day to a given time.&#xA;time-day-begin: Finds the beginning of a day for a given time.&#xA;time-day-end: Finds the end of a day for a given time.&#xA;&#xA;Component-Related Functions&#xA;&#xA;uid?: Checks if a component matches a specific UID.&#xA;occur-in-time-range?: Checks if an event occurs within a specified time range.&#xA;due-in-time-range?: Checks if a task is due within a specified time range.&#xA;contains?: Checks if a component contains a certain text.&#xA;has-start?: Checks if a component has a start time.&#xA;has-end?: Checks if a component has an end time.&#xA;has-due?: Checks if a task has a due date.&#xA;has-duration?: Checks if an event has a specified duration.&#xA;has-alarms?: Checks if a component has alarms.&#xA;has-alarms-in-range?: Checks if a component has alarms within a specified time range.&#xA;has-recurrences?: Checks if an event is recurring.&#xA;has-categories?: Checks if a component is assigned to certain categories.&#xA;is-completed?: Checks if a task is completed.&#xA;completed-before?: Checks if a task was completed before a specified time.&#xA;has-attachments?: Checks if a component has attachments.&#xA;percent-complete?: Checks the completion percentage of a task.&#xA;occurrences-count?: Counts the occurrences of an event within a range.&#xA;starts-before?: Checks if an event starts before a specified time.&#xA;&#xA;Example:&#xA;&#xA;A filter that shows tasks that don’t have a due date or a start date.&#xA;&#xA;(or (not has-due?) (not has-start?))&#xA;&#xA;Reference in the code can be found here.]]&gt;</description>
      <content:encoded><![CDATA[<h4 id="time-related-functions" id="time-related-functions">Time-Related Functions</h4>
<ul><li><strong><code>time-now</code></strong>: Returns the current time.</li>
<li><strong><code>make-time</code></strong>: Creates a new time value.</li>
<li><strong><code>time-add-day</code></strong>: Adds a day to a given time.</li>
<li><strong><code>time-day-begin</code></strong>: Finds the beginning of a day for a given time.</li>
<li><strong><code>time-day-end</code></strong>: Finds the end of a day for a given time.</li></ul>

<h4 id="component-related-functions" id="component-related-functions">Component-Related Functions</h4>
<ul><li><strong><code>uid?</code></strong>: Checks if a component matches a specific UID.</li>
<li><strong><code>occur-in-time-range?</code></strong>: Checks if an event occurs within a specified time range.</li>
<li><strong><code>due-in-time-range?</code></strong>: Checks if a task is due within a specified time range.</li>
<li><strong><code>contains?</code></strong>: Checks if a component contains a certain text.</li>
<li><strong><code>has-start?</code></strong>: Checks if a component has a start time.</li>
<li><strong><code>has-end?</code></strong>: Checks if a component has an end time.</li>
<li><strong><code>has-due?</code></strong>: Checks if a task has a due date.</li>
<li><strong><code>has-duration?</code></strong>: Checks if an event has a specified duration.</li>
<li><strong><code>has-alarms?</code></strong>: Checks if a component has alarms.</li>
<li><strong><code>has-alarms-in-range?</code></strong>: Checks if a component has alarms within a specified time range.</li>
<li><strong><code>has-recurrences?</code></strong>: Checks if an event is recurring.</li>
<li><strong><code>has-categories?</code></strong>: Checks if a component is assigned to certain categories.</li>
<li><strong><code>is-completed?</code></strong>: Checks if a task is completed.</li>
<li><strong><code>completed-before?</code></strong>: Checks if a task was completed before a specified time.</li>
<li><strong><code>has-attachments?</code></strong>: Checks if a component has attachments.</li>
<li><strong><code>percent-complete?</code></strong>: Checks the completion percentage of a task.</li>
<li><strong><code>occurrences-count?</code></strong>: Counts the occurrences of an event within a range.</li>
<li><strong><code>starts-before?</code></strong>: Checks if an event starts before a specified time.</li></ul>

<p><strong>Example:</strong></p>

<p>A filter that shows tasks that don’t have a due date or a start date.</p>

<pre><code>(or (not has-due?) (not has-start?))
</code></pre>

<p>Reference in the code can be found <a href="https://gitlab.gnome.org/GNOME/evolution-data-server/-/blob/219641c4b79a6cf23cedefdbd9864559c4d53537/src/calendar/libedata-cal/e-cal-backend-sexp.c#L1301" rel="nofollow">here</a>.</p>
]]></content:encoded>
      <guid>https://arturo.linar.es/gnome-evolution-expression-functions-cheat-sheet</guid>
      <pubDate>Mon, 20 Nov 2023 18:05:39 +0000</pubDate>
    </item>
    <item>
      <title>Fedora 33 breaks ssh with gitlab</title>
      <link>https://arturo.linar.es/fedora-33-breaks-ssh-with-gitlab?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[tldr; add PubkeyAcceptedKeyTypes +ssh-rsa to your gitlab entry on ~/.ssh/config.&#xA;!--more--&#xA;Fedora introduced new crypto policies with their latest version. These new policies will add better security to your environment. Hoever, this changes can break your ssh access to certain servers, which happened to me with gitlab.&#xA;&#xA;$ git pull&#xA;git@gitlab.com: Permission denied (publickey,keyboard-interactive).&#xA;fatal: Could not read from remote repository.&#xA;&#xA;You can update the remote server to the latest version, which hopefully support newer algorithms. The list of algorithms supported in Fedora 33 is shorter than the previous versions. You can verify that here:  /etc/crypto-policies/back-ends/openssh.config. But usually, that&#39;s something you can&#39;t do with third party services like github or gitlab.&#xA;&#xA;The easy solution&#xA;&#xA;If you don&#39;t have control over the server, then you can simply lower the security a little so you can continue working normally.&#xA;&#xA;~/.ssh/config&#xA;gitlab.com&#xA;    PubkeyAcceptedKeyTypes +ssh-rsa&#xA;&#xA;In 50 years when all the new policies are adopted globally, you can regenerate your keys and remove that line.&#xA;&#xA;The better solution&#xA;&#xA;Create new keys. Make sure you use the  ed25519  algorithm (or one supported in /etc/crypto-policies/back-ends/openssh.config). This way you will have a secure and future proof system... until the next policy upgrade, at least. Here is how:&#xA;&#xA;ssh-keygen -t ed25519 -a 64&#xA;&#xA;Then add your public key to the authorized_keys file in the server as you would normally do.&#xA;&#xA;---&#xA;&#xA;@see&#xA;&#xA;Dev.to article&#xA;The twitt that made me aware of the issue.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><strong>tldr;</strong> add <code>PubkeyAcceptedKeyTypes +ssh-rsa</code> to your gitlab entry on <code>~/.ssh/config</code>.

Fedora introduced new <a href="https://fedoraproject.org/wiki/Changes/StrongCryptoSettings2" rel="nofollow">crypto policies</a> with their latest version. These new policies will add better security to your environment. Hoever, this changes can break your ssh access to certain servers, which happened to me with gitlab.</p>

<pre><code class="language-bash">$ git pull
git@gitlab.com: Permission denied (publickey,keyboard-interactive).
fatal: Could not read from remote repository.
</code></pre>

<p>You can update the remote server to the latest version, which hopefully support newer algorithms. The list of algorithms supported in Fedora 33 is shorter than the previous versions. You can verify that here: <code>/etc/crypto-policies/back-ends/openssh.config</code>. But usually, that&#39;s something you can&#39;t do with third party services like github or gitlab.</p>

<h2 id="the-easy-solution" id="the-easy-solution">The easy solution</h2>

<p>If you don&#39;t have control over the server, then you can simply lower the security a little so you can continue working normally.</p>

<p><em>~/.ssh/config</em></p>

<pre><code>gitlab.com
    PubkeyAcceptedKeyTypes +ssh-rsa
</code></pre>

<p>In 50 years when all the new policies are adopted globally, you can regenerate your keys and remove that line.</p>

<h2 id="the-better-solution" id="the-better-solution">The better solution</h2>

<p>Create new keys. Make sure you use the  <code>ed25519</code>  algorithm (or one supported in <code>/etc/crypto-policies/back-ends/openssh.config</code>). This way you will have a secure and future proof system... until the next policy upgrade, at least. Here is how:</p>

<pre><code>ssh-keygen -t ed25519 -a 64
</code></pre>

<p>Then add your public key to the <code>authorized_keys</code> file in the server as you would normally do.</p>

<hr/>

<p><strong>@see</strong></p>
<ul><li><a href="https://dev.to/bowmanjd/upgrade-ssh-client-keys-and-remote-servers-after-fedora-33-s-new-crypto-policy-47ag" rel="nofollow">Dev.to article</a></li>
<li><a href="https://twitter.com/juanjeojeda/status/1330939095200182273?s=20" rel="nofollow">The twitt</a> that made me aware of the issue.</li></ul>
]]></content:encoded>
      <guid>https://arturo.linar.es/fedora-33-breaks-ssh-with-gitlab</guid>
      <pubDate>Mon, 23 Nov 2020 19:15:53 +0000</pubDate>
    </item>
    <item>
      <title>What would make WSL2 perfect for PHP development?</title>
      <link>https://arturo.linar.es/what-would-make-wsl2-perfect-for-php-development?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I&#39;ve been using WSL2 to develop Drupal sites for some months now, and it has been a surprise to me how well it works. I have native Linux speed and great tools (I use Sublime through X server and VS Code).&#xA;!--more--&#xA;However, there&#39;s a small issue that if solved, would make the development experience perfect: DNS resolution to dev environments. I&#39;ll describe how I work to illustrate what I mean with this.&#xA;&#xA;I use Docksal to manage all my development environments on a WSL2 distribution using docker. So, to create a new Laravel project I would normally do this:&#xA;&#xA;From there I can access the project using the generated local address from Linux. Docksal uses dnsmasq to resolve these addresses:&#xA;&#xA;$ curl http://helloworld.docksal&#xA;!DOCTYPE html&#xA;html lang=&#34;en&#34;&#xA;    head&#xA;        meta charset=&#34;utf-8&#34;&#xA;        meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;&#xA;...&#xA;&#xA;However, I cannot access that address from Windows. What I&#39;ve been doing until now is to add an entry in my Windows hosts file to redirect these new address to localhost:&#xA;&#xA;127.0.0.1     helloworld.docksal&#xA;&#xA;But once I added this entry, the site is still not accessible from Windows. I need to run a powershell script that I took from a github issue to create firewall rules, so I can actually access it.&#xA;&#xA;I haven&#39;t found a solution for this yet, but what I would love to see is:&#xA;&#xA;The local address created in Linux doesn&#39;t require me to add a hosts entry in Windows.&#xA;The firewall rules are automatically updated to allow access.&#xA;&#xA;Installing a custom DNS entry&#xA;&#xA;To solve the first issue I tried to use a custom DNS proxy (Acrylic) to redirect all .docksal addresses back to localhost, like I&#39;m doing with the hosts file. But when I try to execute the server it fails to start because UDP port is already in use:&#xA;&#xA;2020-02-12 10:29:00.233 [E] TDnsResolver.Execute: TDualUdpServerCommunicationChannel.Bind: Binding to IPv4 address failed with Windows Sockets error code 10048.&#xA;&#xA;What is using port 53 (the standard DNS port)? I used tcpview to find out. &#xA;&#xA;It looks it is svchost. Using the PID and the Process Explorer I found that it was related to a service called Shared Access.&#xA;&#xA;I had walked so far to stop digging now, so I opened the services and looked for ICS (maybe the initials for Internet Connection Sharing in English) and found it was configured to start manually.&#xA;&#xA;I was feeling brave and tried to stop it, but I couldn&#39;t do it. It refused to stop with an error dialog that said (in a loose translation): &#34;Windows couldn&#39;t stop the service in the local computer&#34;. Nice. I switched the service to Disabled* and decided to reboot. Upon reboot, it was enabled again, set to start in &#34;Manual&#34; mode. So no luck so far to enable a custom DNS Proxy.&#xA;&#xA;Enabling the firewall by default&#xA;&#xA;I&#39;m running the script I found in github manually when I can&#39;t access the sites in WSL2 (normally after a reboot), and since I don&#39;t often reboot I could keep doing it.&#xA;&#xA;I don&#39;t know if the script  creates security issues, but I if I get tired of running it manually I could configure it to run every time I boot or every hour even. &#xA;&#xA;Update 2021-04-28: It is possible to run the script from WSL2. Since I&#39;musing fish shell, I created the alias winfirewall to run it:&#xA;&#xA;alias --save winfirewall &#39;powershell.exe start-process PowerShell -verb runas &#34;C:\Users\alinares\firewall_rules.ps1&#34;&#39;&#xA;&#xA;#windows #wsl]]&gt;</description>
      <content:encoded><![CDATA[<p>I&#39;ve been using WSL2 to develop Drupal sites for some months now, and it has been a surprise to me how well it works. I have native Linux speed and great tools (I use Sublime through X server and VS Code).

However, there&#39;s a small issue that if solved, would make the development experience perfect: <strong>DNS resolution to dev environments</strong>. I&#39;ll describe how I work to illustrate what I mean with this.</p>

<p>I use <a href="https://docksal.io" rel="nofollow">Docksal</a> to manage all my development environments on a WSL2 distribution using docker. So, to create a new Laravel project I would normally do this:</p>

<p><img src="https://i.snap.as/9j7tIIc.image" alt=""/></p>

<p>From there I can access the project using the generated local address <strong>from Linux</strong>. <a href="https://docs.docksal.io/core/system-dns/" rel="nofollow">Docksal uses dnsmasq</a> to resolve these addresses:</p>

<pre><code>$ curl http://helloworld.docksal
&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
    &lt;head&gt;
        &lt;meta charset=&#34;utf-8&#34;&gt;
        &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;&gt;
...
</code></pre>

<p>However, I cannot access that address from Windows. What I&#39;ve been doing until now is to add an entry in my Windows <code>hosts</code> file to redirect these new address to localhost:</p>

<pre><code>127.0.0.1     helloworld.docksal
</code></pre>

<p>But once I added this entry, the site is still not accessible from Windows. I need to run a powershell script that I took from a <a href="https://github.com/microsoft/WSL/issues/4150#issuecomment-504209723" rel="nofollow">github issue</a> to create firewall rules, so I can actually access it.</p>

<p><img src="https://i.snap.as/LYWPUI0.png" alt=""/></p>

<p>I haven&#39;t found a solution for this yet, but what I would love to see is:</p>
<ol><li>The local address created in Linux doesn&#39;t require me to add a hosts entry in Windows.</li>
<li>The firewall rules are automatically updated to allow access.</li></ol>

<h2 id="installing-a-custom-dns-entry" id="installing-a-custom-dns-entry">Installing a custom DNS entry</h2>

<p>To solve the first issue I tried to use a custom DNS proxy (<a href="https://mayakron.altervista.org/support/acrylic/Home.htm" rel="nofollow">Acrylic</a>) to redirect all <code>*.docksal</code> addresses back to localhost, like I&#39;m doing with the hosts file. But when I try to execute the server it fails to start because UDP port is already in use:</p>

<pre><code>2020-02-12 10:29:00.233 [E] TDnsResolver.Execute: TDualUdpServerCommunicationChannel.Bind: Binding to IPv4 address failed with Windows Sockets error code 10048.
</code></pre>

<p>What is using port 53 (the standard DNS port)? I used <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/tcpview" rel="nofollow">tcpview</a> to find out.</p>

<p><img src="https://i.snap.as/j17rMRc.image" alt=""/></p>

<p>It looks it is <code>svchost</code>. Using the <strong>PID</strong> and the <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer" rel="nofollow">Process Explorer</a> I found that it was related to a service called <strong>Shared Access</strong>.</p>

<p><img src="https://i.snap.as/JGFsCGu.png" alt=""/></p>

<p>I had walked so far to stop digging now, so I opened the services and looked for <em>ICS</em> (maybe the initials for <em>Internet Connection Sharing</em> in English) and found it was configured to start manually.</p>

<p><img src="https://i.snap.as/ihhsMKM.png" alt=""/></p>

<p>I was feeling brave and tried to stop it, but I couldn&#39;t do it. It refused to stop with an error dialog that said (in a loose translation): “Windows couldn&#39;t stop the service in the local computer”. Nice. I switched the service to <em>Disabled</em> and decided to reboot. Upon reboot, it was enabled again, set to start in “Manual” mode. So no luck so far to enable a custom DNS Proxy.</p>

<h2 id="enabling-the-firewall-by-default" id="enabling-the-firewall-by-default">Enabling the firewall by default</h2>

<p>I&#39;m running the script I found <a href="https://github.com/microsoft/WSL/issues/4150#issuecomment-504209723" rel="nofollow">in github</a> manually when I can&#39;t access the sites in WSL2 (normally after a reboot), and since I don&#39;t often reboot I could keep doing it.</p>

<p>I don&#39;t know if the script  creates security issues, but I if I get tired of running it manually I could configure it to run every time I boot or every hour even.</p>

<p><strong>Update 2021-04-28</strong>: It is possible to run the script from WSL2. Since I&#39;musing fish shell, I created the alias <code>winfirewall</code> to run it:</p>

<pre><code>alias --save winfirewall &#39;powershell.exe start-process PowerShell -verb runas &#34;C:\Users\alinares\firewall_rules.ps1&#34;&#39;
</code></pre>

<p><a href="https://arturo.linar.es/tag:windows" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">windows</span></a> <a href="https://arturo.linar.es/tag:wsl" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">wsl</span></a></p>
]]></content:encoded>
      <guid>https://arturo.linar.es/what-would-make-wsl2-perfect-for-php-development</guid>
      <pubDate>Sat, 01 Aug 2020 19:36:55 +0000</pubDate>
    </item>
    <item>
      <title>Transcode a video using a context menu on Windows</title>
      <link>https://arturo.linar.es/transcode-a-video-using-a-context-menu-on-windows?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[#windows&#xA;&#xA;I&#39;m using the game bar to create quick screencasts. The only problem is that the videos are too big to show only a few clicks. So, to quickly optimize a video for sharing I need to transcode it as easy as possible. &#xA;&#xA;Updated with a new windows native tool.&#xA;!--more--&#xA;Creating a context menu in explorer would be ideal. This menu action will execute VLC to transcode the video.&#xA;&#xA;I&#39;m using this tool to easily create a context menu:&#xA;&#xA;FileActionsManager&#xA;&#xA;The paths need to be adjusted we&#39;re on Windows and VLC is a ported application. So I wrote a script to handle that (in PHP because I have it installed it and I don&#39;t know anything about PowerShell):&#xA;&#xA;if (!isset($argv[1])) {&#xA;  echo &#34;Usage: {$argv[0]} video&#34;;&#xA;  die;&#xA;}&#xA;$path = $argv[1];&#xA;$path = strreplace(&#39;\\&#39;, &#39;/&#39;, $path);&#xA;$path = pregreplace(&#39;/^C:/&#39;, &#39;&#39;, $path);&#xA;&#xA;$cmd = &lt;&lt;&lt;CMD&#xA;&#34;C:\\Program Files\\VideoLAN\\VLC\\vlc.exe&#34; &#34;-I dummy&#34; &#34;%s&#34; &#34;:sout=#transcode{vcodec=h264,scale=Automático,width=720,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}:std{access=file{no-overwrite},mux=mp4,dst=&#39;%s-small.mp4&#39;}&#34; &#34;vlc://quit&#34;&#xA;CMD;&#xA;$cmd = sprintf($cmd, $argv[1], $path);&#xA;system($cmd);&#xA;&#xA;To actually create the menu entry that runs the php script, run:&#xA;&#xA;FileActionsConsole.exe add mp4 compress_video &#34;Compress Video (720p)&#34; &#34;\&#34;C:\tools\php74\php.exe\&#34; \&#34;/Users/user/bin/compress-video.php\&#34; \&#34;%1\&#xA;&#34;&#34;&#xA;&#xA;---&#xA;&#xA;Update (2020-08-26): I had to wipe my windows partition and decided to write a native tool to implement this. You can find it here.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><code>#windows</code></p>

<p><img src="https://i.snap.as/2vjJZvZ.png" alt=""/></p>

<p>I&#39;m using the game bar to create quick screencasts. The only problem is that the videos are too big to show only a few clicks. So, to quickly optimize a video for sharing I need to transcode it as easy as possible.</p>

<p><strong>Updated with a new windows native tool.</strong>

Creating a context menu in explorer would be ideal. This menu action will execute VLC to transcode the video.</p>

<p>I&#39;m using this tool to easily create a context menu:</p>

<p><a href="https://github.com/ORelio/FileActionsManager" rel="nofollow">FileActionsManager</a></p>

<p>The paths need to be adjusted we&#39;re on Windows and VLC is a ported application. So I wrote a script to handle that (in PHP because I have it installed it and I don&#39;t know anything about PowerShell):</p>

<pre><code class="language-php">if (!isset($argv[1])) {
  echo &#34;Usage: {$argv[0]} &lt;video&gt;&#34;;
  die;
}
$path = $argv[1];
$path = str_replace(&#39;\\&#39;, &#39;/&#39;, $path);
$path = preg_replace(&#39;/^C:/&#39;, &#39;&#39;, $path);

$cmd = &lt;&lt;&lt;CMD
&#34;C:\\Program Files\\VideoLAN\\VLC\\vlc.exe&#34; &#34;-I dummy&#34; &#34;%s&#34; &#34;:sout=#transcode{vcodec=h264,scale=Automático,width=720,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}:std{access=file{no-overwrite},mux=mp4,dst=&#39;%s-small.mp4&#39;}&#34; &#34;vlc://quit&#34;
CMD;
$cmd = sprintf($cmd, $argv[1], $path);
system($cmd);
</code></pre>

<p>To actually create the menu entry that runs the php script, run:</p>

<pre><code>FileActionsConsole.exe add mp4 compress_video &#34;Compress Video (720p)&#34; &#34;\&#34;C:\tools\php74\php.exe\&#34; \&#34;/Users/user/bin/compress-video.php\&#34; \&#34;%1\
&#34;&#34;
</code></pre>

<hr/>

<p><strong>Update (2020-08-26)</strong>: I had to wipe my windows partition and decided to write a native tool to implement this. You can find it <a href="https://github.com/arturolinares/VideoCompressMenu" rel="nofollow">here</a>.</p>
]]></content:encoded>
      <guid>https://arturo.linar.es/transcode-a-video-using-a-context-menu-on-windows</guid>
      <pubDate>Wed, 29 Jul 2020 04:02:51 +0000</pubDate>
    </item>
    <item>
      <title>Dokku: A Heroku alternative</title>
      <link>https://arturo.linar.es/dokku-a-heroku-alternative?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[&#xA;&#xA;TL;DR: Needed a way to easily deploy and manage legacy and new sites without spending a lot of money. Dokku gives me the awesome Heroku developer experience on a cheap VPS box.&#xA;!--more--&#xA;Since I started working on the web, I&#39;ve been publishing small sites. Usually I use these pet projects to try out new technologies, so most of them don&#39;t share the same technology. Some are almost 10 years old. Of course, I don&#39;t have the time to maintain them and their owners are not interested in spend more resources to bring them up-to-date. So I&#39;m stuck maintaining the hosting for very different projects.&#xA;&#xA;To save on expenses and time I used to host them all in one cheap VPS, but things were becoming complicated as language versions become deprecated. Keeping them in a shared environment is a challenge because different applications use different versions of PHP, databases (some use mysql, others Postgres). I even have .NET core sites running.&#xA;&#xA;This year I decided to find a better solution, where I can keep all together and still make it easy to deploy or create resources on the server. So I tried Azure, AWS, Heroku and several others.&#xA;&#xA;The best ones, of course, were not cheap. I think Heroku is still one of the best one in terms of developer experience. So looking around I came to know Dokku, which is an open source platform similar to Heroku (and it even uses their Buildpacks).&#xA;&#xA;What&#39;s in a Buildpack&#xA;&#xA;Heroku works using Buildpacks. You can picture them as the instructions on how to build and deploy an application. Dokku use these Buildpacks too to create container images and run them.&#xA;&#xA;But, what about the ones with specific requirements? I have some sites that require wkhtmltopdf. Well, it turns out that it is possible to customize the image that Dokku builds by using deploy scripts that run at different stages in the deployment process.&#xA;&#xA;OK, How does it look like?&#xA;&#xA;To deploy a new PHP application that uses a MySQL database, this would be enough (assuming you installed and configured the Dokku bash client):&#xA;&#xA;Create a database&#xA;dokku mysql:create myphpdb&#xA;Create a new application&#xA;dokku apps:create myphpapp&#xA;Allow the application access the database&#xA;dokku mysql:link myphpdb myphpapp&#xA;Deply the application&#xA;git push dokku master&#xA;&#xA;Dokku identifies it is a PHP application if it finds a composer.json file. In it, you can specify the PHP version to use and if you need special extensions, like bcmath. &#xA;&#xA;For example:&#xA;&#xA;&#34;require&#34;: {&#xA;  &#34;php&#34;: &#34;  =7.1&#34;,&#xA;  &#34;ext-bcmath&#34;: &#34;&#34;&#xA;}&#xA;&#xA;You can then configure your PHP application to read the database connection details using environment variables. Once a database is linked to your application you will have in the DATABASEURL environment variable a full DSN to connect to it. If you need to, you can declare more environment variables like this, not necessarily related to the database:&#xA;&#xA;dokku config:set MYSQLDBNAME=myphpapp MYSQL_USERNAME=mysql ...&#xA;&#xA;Advantages&#xA;&#xA;The developer experience. It allows me to manage sites and its resources without leaving the terminal. The experience is very similar to Heroku.&#xA;&#xA;It can be fully automated. For example, pulling database dumps to a development environment, schedule nightly backups and SSL certificate renewals. &#xA;&#xA;Zero downtime deployments.&#xA;&#xA;Kubernetes&#xA;&#xA;But... Dokku works using docker... that&#39;s so 2014. Dokku is already working on Kubernetes support (and should be functional at this point), although I don&#39;t mind to use and old* technology if it works. However, I wish it could use podman to avoid the security risk of having a docker daemon running. &#xA;&#xA;devops&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://i.snap.as/WCQT4If.png" alt=""/></p>

<p><em><strong>TL;DR</strong>: Needed a way to easily deploy and manage legacy and new sites without spending a lot of money. Dokku gives me the awesome Heroku developer experience on a cheap VPS box.</em>

Since I started working on the web, I&#39;ve been publishing small sites. Usually I use these pet projects to try out new technologies, so most of them don&#39;t share the same technology. Some are almost 10 years old. Of course, I don&#39;t have the time to maintain them and their owners are not interested in spend more resources to bring them up-to-date. <strong>So I&#39;m stuck maintaining the hosting for very different projects</strong>.</p>

<p>To save on expenses and time I used to host them all in one cheap VPS, but things were becoming complicated as language versions become deprecated. Keeping them in a shared environment is a challenge because different applications use different versions of PHP, databases (some use mysql, others Postgres). I even have .NET core sites running.</p>

<p>This year I decided to find a better solution, where I can keep all together and still make it easy to deploy or create resources on the server. So I tried Azure, AWS, Heroku and several others.</p>

<p>The best ones, of course, were not cheap. <strong>I think Heroku is still one of the best one in terms of developer experience. So looking around I came to know <a href="http://dokku.viewdocs.io/dokku/" rel="nofollow">Dokku</a></strong>, which is an open source platform similar to Heroku (and it even uses their Buildpacks).</p>

<h2 id="what-s-in-a-buildpack" id="what-s-in-a-buildpack">What&#39;s in a Buildpack</h2>

<p>Heroku works using Buildpacks. You can picture them as the instructions on how to build and deploy an application. Dokku use these Buildpacks too to create container images and run them.</p>

<p>But, what about the ones with specific requirements? I have some sites that require <code>wkhtmltopdf</code>. Well, <strong>it turns out that it is possible to customize the image that Dokku builds by using deploy scripts that run at different stages in the deployment process</strong>.</p>

<h2 id="ok-how-does-it-look-like" id="ok-how-does-it-look-like">OK, How does it look like?</h2>

<p>To deploy a new PHP application that uses a MySQL database, this would be enough (assuming you installed and configured the Dokku <a href="http://dokku.viewdocs.io/dokku/community/clients/" rel="nofollow">bash client</a>):</p>

<pre><code class="language-bash"># Create a database
dokku mysql:create myphp_db
# Create a new application
dokku apps:create myphp_app
# Allow the application access the database
dokku mysql:link myphp_db myphp_app
# Deply the application
git push dokku master
</code></pre>

<p>Dokku identifies it is a PHP application if it finds a <code>composer.json</code> file. In it, you can specify the PHP version to use and if you need special extensions, like <code>bcmath</code>.</p>

<p>For example:</p>

<pre><code class="language-json">&#34;require&#34;: {
  &#34;php&#34;: &#34;&gt;=7.1&#34;,
  &#34;ext-bcmath&#34;: &#34;*&#34;
}
</code></pre>

<p>You can then configure your PHP application to read the database connection details using environment variables. Once a database is linked to your application you will have in the <code>DATABASE_URL</code> environment variable a full DSN to connect to it. If you need to, you can declare more environment variables like this, not necessarily related to the database:</p>

<pre><code class="language-bash">dokku config:set MYSQL_DBNAME=myphpapp MYSQL_USERNAME=mysql ...
</code></pre>

<h2 id="advantages" id="advantages">Advantages</h2>
<ol><li><p><strong>The developer experience</strong>. It allows me to manage sites and its resources without leaving the terminal. The experience is very similar to Heroku.</p></li>

<li><p><strong>It can be fully automated</strong>. For example, pulling database dumps to a development environment, schedule nightly backups and SSL certificate renewals.</p></li>

<li><p><strong>Zero downtime deployments</strong>.</p></li></ol>

<h2 id="kubernetes" id="kubernetes">Kubernetes</h2>

<p>But... Dokku works using docker... that&#39;s so 2014. Dokku is already working on Kubernetes support (and should be functional at this point), although I don&#39;t mind to use and <em>old</em> technology if it works. However, I wish it could use <code>podman</code> to avoid the security risk of having a docker daemon running.</p>

<p><a href="https://arturo.linar.es/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a></p>
]]></content:encoded>
      <guid>https://arturo.linar.es/dokku-a-heroku-alternative</guid>
      <pubDate>Thu, 16 Jul 2020 02:21:04 +0000</pubDate>
    </item>
    <item>
      <title>Easy VRT testing</title>
      <link>https://arturo.linar.es/easy-vrt-testing?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[&#xA;&#xA;I normally work on different and very active projects in several testing environments. I needed a simple and fast way to generate visual regression tests between them, or even to just to compare my local environment to an integration server. However, it was cumbersome to edit backstop configuration files frequently just to change servers, thus I created a tool that simplifies the task a lot:&#xA;&#xA;https://github.com/arturolinares/easy-vrt&#xA;&#xA;!--more--&#xA;&#xA;  It is a Symfony console application that wraps Docker backstop containers to run the tests. The application reads a list of URLs from a CSV file to generate the JSON configuration files for BackstopJS.&#xA;&#xA;Usage&#xA;&#xA;Once the project is installed you will want to compare two sites. Follow these steps:&#xA;&#xA;Generate the backstop scenarios using a CSV file containing the routes to visit.&#xA;Run the tests.&#xA;Browse the results to visualize the differences.&#xA;&#xA;To generate the scenarios use the command vrt:gen. It will use a CSV file with the routes to generate them:&#xA;&#xA;bin/console vrt:gen path/to/routes.csv --ref-domain=http://prod.com --url=http://my-qa.com&#xA;&#xA;You can find a sample of the CSV file in the sample directory. The file has two fields: the route and an optional label. For example:&#xA;&#xA;/,&#34;The Homepage&#34;&#xA;/category/tag,&#xA;&#34;/about-us&#34;, &#34;About US&#34;&#xA;&#xA;You can set the reference domain using the flag --ref-domain. This is normally production. Use the flag --url to specify the domain you want to compare.&#xA;&#xA;Sometimes you need to create several scenarios for the same route but different prefix. For example, when using multilingual sites. To specify the prefixes you can use the --prefix (-p) option:&#xA;&#xA;bin/console vrt:gen path/to/routes.csv -u http://my-qa.com -r prod.com -p es -p en -p fr&#xA;&#xA;The above will generate scenarios for these urls:&#xA;&#xA;/es/&#xA;/en/&#xA;/fr/&#xA;/es/category/tag&#xA;/en/category/tag&#xA;/fr/category/tag&#xA;/es/about-us&#xA;/en/about-us&#xA;/fr/about-us&#xA;&#xA;Until now, you only have configured backstop. To actually run the tests use vrt:run. This command will run backstop using a Docker container. Also, if you want to see what the script is doing, you can change the verbosity with -v, -vv and -vvv.&#xA;&#xA;Finally, to see the report, use vrt:server to browse the diffs.&#xA;&#xA;#devops #testing]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://i.snap.as/bCR0UZe.png" alt=""/></p>

<p>I normally work on different and very active projects in several testing environments. I needed a simple and fast way to generate visual regression tests between them, or even to just to compare my local environment to an integration server. However, it was cumbersome to edit backstop configuration files frequently just to change servers, thus I created a tool that simplifies the task a lot:</p>

<p><a href="https://github.com/arturolinares/easy-vrt" rel="nofollow">https://github.com/arturolinares/easy-vrt</a></p>



<blockquote><p>It is a Symfony console application that wraps Docker backstop containers to run the tests. The application reads a list of URLs from a CSV file to generate the JSON configuration files for BackstopJS.</p></blockquote>

<h2 id="usage" id="usage">Usage</h2>

<p>Once the project is installed you will want to compare two sites. Follow these steps:</p>
<ol><li>Generate the backstop scenarios using a CSV file containing the routes to visit.</li>
<li>Run the tests.</li>
<li>Browse the results to visualize the differences.</li></ol>

<p>To <strong>generate the scenarios</strong> use the command <code>vrt:gen</code>. It will use a CSV file with the routes to generate them:</p>

<pre><code>bin/console vrt:gen path/to/routes.csv --ref-domain=http://prod.com --url=http://my-qa.com

</code></pre>

<p>You can find a sample of the CSV file in the <code>sample</code> directory. The file has two fields: the route and an optional label. For example:</p>

<pre><code>/,&#34;The Homepage&#34;
/category/tag,
&#34;/about-us&#34;, &#34;About US&#34;

</code></pre>

<p>You can set the reference domain using the flag <code>--ref-domain</code>. This is normally production. Use the flag <code>--url</code> to specify the domain you want to compare.</p>

<p><strong>Sometimes you need to create several scenarios for the same route but different prefix</strong>. For example, when using multilingual sites. To specify the prefixes you can use the <code>--prefix</code> (<code>-p</code>) option:</p>

<pre><code>bin/console vrt:gen path/to/routes.csv -u [http://my-qa.com](http://my-qa.com) -r prod.com -p es -p en -p fr

</code></pre>

<p>The above will generate scenarios for these urls:</p>

<pre><code>/es/
/en/
/fr/
/es/category/tag
/en/category/tag
/fr/category/tag
/es/about-us
/en/about-us
/fr/about-us

</code></pre>

<p>Until now, you only have configured backstop. <strong>To actually run the tests use</strong> <code>vrt:run</code>. This command will run backstop using a Docker container. Also, if you want to see what the script is doing, you can change the verbosity with <code>-v</code>, <code>-vv</code> and <code>-vvv</code>.</p>

<p>Finally, <strong>to see the report</strong>, use <code>vrt:server</code> to browse the diffs.</p>

<p><a href="https://arturo.linar.es/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a> <a href="https://arturo.linar.es/tag:testing" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">testing</span></a></p>
]]></content:encoded>
      <guid>https://arturo.linar.es/easy-vrt-testing</guid>
      <pubDate>Sun, 12 Apr 2020 05:09:21 +0000</pubDate>
    </item>
    <item>
      <title>Git log filtering and how to show a better history tree</title>
      <link>https://arturo.linar.es/git-log-filtering-and-how-to-show-a-better-history-tree?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[git&#xA;&#xA;Some useful commands to find commits using Git:&#xA;!--more--&#xA;Filter by content&#xA;&#xA;Filter by the contents of a commit. Think of it as if you do a git log -p (shows the diff of each commit) and then you search the output.&#xA;&#xA;git log -S var_dump&#xA;&#xA;Filter by commit message&#xA;&#xA;git log --grep &#34;FRB-666&#34;&#xA;&#xA;Filter by author&#xA;&#xA;git log --author &#34;Arturo&#34;&#xA;&#xA;You can use emails and several values:&#xA;&#xA;git log --author &#34;outlook.com|hotmail.com&#34;&#xA;&#xA;Filter by date&#xA;&#xA;Conveniently accepts --before and --afterbrbrgit log --before &#34;2017-06-11&#34;&#xA;&#xA;Git log format&#xA;&#xA;I&#39;ve found that git log is powerful, but sometimes difficult to read. Normally I use this git alias to show a nice tree in the console:&#xA;&#xA;$ git config --global alias.lg&#xA;log --color --graph --pretty=format:&#39;%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)%an%Creset&#39; --abbrev-commit&#xA;&#xA;This will show a tree like this when you type git lgbr&#xA;br&#xA;Terminal with git log tree]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://arturo.linar.es/tag:git" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">git</span></a></p>

<p>Some useful commands to find commits using Git:

<strong>Filter by content</strong></p>

<p>Filter by the contents of a commit. Think of it as if you do a <code>git log -p</code> (shows the diff of each commit) and then you search the output.</p>

<p><code>git log -S var_dump</code></p>

<p><strong>Filter by commit message</strong></p>

<p><code>git log --grep &#34;FRB-666&#34;</code></p>

<p><strong>Filter by author</strong></p>

<p><code>git log --author &#34;Arturo&#34;</code></p>

<p>You can use emails and several values:</p>

<p><code>git log --author &#34;outlook.com|hotmail.com&#34;</code></p>

<p><strong>Filter by date</strong></p>

<p>Conveniently accepts <code>--before</code> and <code>--after</code><br><br><code>git log --before &#34;2017-06-11&#34;</code></p>

<h2 id="git-log-format" id="git-log-format">Git log format</h2>

<p>I&#39;ve found that <code>git log</code> is powerful, but sometimes difficult to read. Normally I use this git alias to show a nice tree in the console:</p>

<pre><code class="language-bash">$ git config --global alias.lg
log --color --graph --pretty=format:&#39;%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)&lt;%an&gt;%Creset&#39; --abbrev-commit

</code></pre>

<p>This will show a tree like this when you type <code>git lg</code><br>
<br>
<img src="https://i.snap.as/nusqCYO.png" alt="Terminal with git log tree"/></p>
]]></content:encoded>
      <guid>https://arturo.linar.es/git-log-filtering-and-how-to-show-a-better-history-tree</guid>
      <pubDate>Mon, 04 Nov 2019 06:05:13 +0000</pubDate>
    </item>
    <item>
      <title>Windows running on a Linux kernel?</title>
      <link>https://arturo.linar.es/windows-running-linux-kernel?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[windows &#xA;&#xA;Surfae Duo&#xA;&#xA;This week Microsoft unveiled their Surface Phone, called Surface Duo. It looks great and I think it puts the company back in the innovators list.&#xA;!--more--&#xA;But the fact that the Duo runs on Android is not easy to digest. Some people think that Microsoft soon will make a Windows version that runs on a Linux kernel. Although it might be nothing more than crazy theories, they make sense when Nadella says that the operative system is not that important for Microsoft any more:&#xA;&#xA;  The operating system is no longer the most important layer for us. What is most important for us is the app model and the experience.&#xA;&#xA;To add more fuel to the conspiracy theory, Intel has been working on a highly optimized Linux distro (for Intel architecture of course) called Clear Linux.&#xA;&#xA;  Remember when I&#39;ve said that my money is on Microsoft moving Windows to a Linux kernel? Well, Android is based on Linux so this might be testing the waters with a mobile device first. Remember, Microsoft wants unified architecture&#xA;  🤔&#xA;  -- @Barnacules&#xA;&#xA;Intel has been focusing mainly in performance, and while you still run Gnome or other Desktop Environment on it, they have stated that their priority is performance and security.&#xA;&#xA;What if... they are waiting for a Windows that runs on a Linux kernel to finally integrate it in Clear Linux?&#xA;&#xA;Personally, a Windows that runs on a Linux kernel doesn&#39;t make sense to me. The main reasons why these rumors started was because Microsoft started opening source code, bought GitHub and, specially, WSL. But with the last case (WSL2), which allowed a Linux kernel to run on Windows, is a Windows technology, also used in Windows Sandbox. Why would they invest in this awesome technology that runs on Windows if you&#39;re about to jump off the boat.&#xA;&#xA;Another reason is that it would break backwards compatibility of many applications, something Windows has respected for many years. I know open source technologies like Wine have been making possible to run windows applications on Linux, but they are not perfect, and they certainly would have a limit. The same limit that Microsoft hit when trying to write their reversed Wine: WSL v1. Some issues like performance, networking and file system access are simply not easy to emulate.&#xA;&#xA;I guess only time will say, meanwhile it is worth to keep an eye on Clear Linux and, of course, the new foldable devices that are about to hit the market 😁]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://arturo.linar.es/tag:windows" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">windows</span></a></p>

<p><img src="https://i.snap.as/f9erLiZ.png" alt="Surfae Duo"/></p>

<p>This week Microsoft unveiled their Surface Phone, called Surface Duo. It looks great and I think it puts the company back in the innovators list.

But the fact that the Duo runs on Android is not easy to digest. Some people think that Microsoft soon will make a Windows version that runs on a Linux kernel. Although it might be nothing more than crazy theories, they make sense when Nadella says that the operative system <a href="https://www.wired.com/story/microsoft-surface-duo-neo-phone/" rel="nofollow">is not that important for Microsoft any more</a>:</p>

<blockquote><p>The operating system is no longer the most important layer for us. What is most important for us is the app model and the experience.</p></blockquote>

<p>To add more fuel to the conspiracy theory, Intel has been working on a highly optimized Linux distro (for Intel architecture of course) called <a href="https://clearlinux.org/" rel="nofollow">Clear Linux</a>.</p>

<blockquote><p>Remember when I&#39;ve said that my money is on Microsoft moving Windows to a Linux kernel? Well, Android is based on Linux so this might be testing the waters with a mobile device first. Remember, Microsoft wants unified architecture
🤔
— <a href="https://twitter.com/Barnacules/status/1179549169704304640?s=20" rel="nofollow">@Barnacules</a></p></blockquote>

<p>Intel has been focusing mainly in performance, and while you still run Gnome or other Desktop Environment on it, they have stated that their priority is performance and security.</p>

<p>What if... they are waiting for a Windows that runs on a Linux kernel to finally integrate it in Clear Linux?</p>

<p>Personally, a Windows that runs on a Linux kernel doesn&#39;t make sense to me. The main reasons why these rumors started was because Microsoft started opening source code, bought GitHub and, specially, WSL. But with the last case (WSL2), which allowed a Linux kernel to run on Windows, is a Windows technology, also used in Windows Sandbox. Why would they invest in this awesome technology that runs on Windows if you&#39;re about to jump off the boat.</p>

<p>Another reason is that it would break backwards compatibility of many applications, something Windows has respected for many years. I know open source technologies like Wine have been making possible to run windows applications on Linux, but they are not perfect, and they certainly would have a limit. The same limit that Microsoft hit when trying to write their reversed Wine: WSL v1. Some issues like performance, networking and file system access are simply not easy to emulate.</p>

<p>I guess only time will say, meanwhile it is worth to keep an eye on Clear Linux and, of course, the new foldable devices that are about to hit the market 😁</p>
]]></content:encoded>
      <guid>https://arturo.linar.es/windows-running-linux-kernel</guid>
      <pubDate>Mon, 07 Oct 2019 05:01:14 +0000</pubDate>
    </item>
  </channel>
</rss>