Pierre-Adrien Buisson : Le Blog !
https://blog.pabuisson.com/
2023-12-03T17:49:00+00:00
Pierre-Adrien Buisson
Filter rails routes easily with FZF
https://blog.pabuisson.com/2023/12/filters-rails-routes-easily-with-fzf/
2023-12-03T17:49:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>In my last two jobs, I've been working on pretty big Ruby on Rails applications. Over time and despite our best efforts, interacting with the application through the built-in commands becomes slower. This is especially sensible for commands such as <code>rails routes</code> that I use frequently. But searching the <code>rails routes</code> output does not have to be painful: I have a tip for this that I shared with many colleagues over the years, and I thought it was worth sharing here. It involves a neat command-line utility called <code>fzf</code>.</p>
<h2 id="rails-routes">Rails routes?</h2>
<p><code>rails routes</code> allows one to see all routes defined in a <a href="https://rubyonrails.org/">Ruby on Rails</a> application. The output contains all the URLs, the controller and action on which they're mapped and the name of <a href="https://guides.rubyonrails.org/routing.html#path-and-url-helpers">the corresponding route helper</a>. A growing or mature Rails project easily reaches hundreds or thousands of line in the output of this command.</p>
<p>When you need to dig into an existing feature or to investigate a bug, starting from the queries you see in your browser network inspector, then finding the matching controller is a mandatory first step and you may need to launch <code>rails routes</code> for this.</p>
<p><code>rails routes</code> itself does provide a few options to filter its output:</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre>Usage:
rails routes [options]
Options:
-c, [--controller=CONTROLLER] # Filter by a specific controller, e.g. PostsController or Admin::PostsController.
-g, [--grep=GREP] # Grep routes by a specific pattern.
-E, [--expanded], [--no-expanded] # Print routes expanded vertically with parts explained.
</pre></td></tr></tbody></table></code></pre></div>
<p>But to be honest, I never really managed to use them (especially on applications with namespaces and nested routes, never sure what values to pass). I generally turned to grep, using <code>rails routes | grep pattern</code> to only show the relevant routes. But as discussed, if <code>rails routes</code> is slow and your initial search fails, you need to relaunch another <code>rails routes</code>, and another one, and it becomes frustrating pretty quickly.</p>
<p>Therefore, in the same spirit of using grep on the output, I turned to another tool for this: <code>fzf</code>.</p>
<h2 id="what-is-fzf">What is FZF?</h2>
<p><a href="https://github.com/junegunn/fzf">FZF</a> is a command-line fuzzy finder that, given some text, offers you a prompt allowing you to filter the content on the fly. Allowing you to type something, see what matches, delete or reword your search, type something else, complete it, etc. This is the tool powering my file-search in vim via the <a href="https://github.com/junegunn/fzf.vim">fzf.vim</a> plugin, it is both very reliable and extremely fast, and it works wonder for <code>rails routes</code>, as you can see:</p>
<figure>
<img src="/public/images/2023-12-03-rails-routes-fzf-animated.gif" alt="A recording of my terminal where I filter a dummy project rails routes with FZF" />
<figcaption>Filtering a dummy project rails routes with FZF</figcaption>
</figure>
<p>Make sure to look into the project readme and wiki for more ideas: if you spend some time in your terminal, I'm sure you'll find many more use-cases for this great tool.</p>
<h2 id="fzf-a-few-tips-for-more-precise-search">FZF: a few tips for more precise search</h2>
<p>By default, you'll be in <em>fuzzy search mode</em>: you'll see results matching all letters from your search in the same order, no matter if they're adjacent or not. That's a good start but sometimes you'll want to be more precise than that.</p>
<ul>
<li>You want to find an <strong>exact pattern</strong>? Prefix your search with a single quote, and <code>'pattern</code> will only show results containing exactly "pattern".</li>
<li>In the contrary, do you need to <strong>exclude some words</strong>? Prefix your search with an exclamation point: <code>!pattern</code> will return all lines that don't contain "pattern".</li>
</ul>
<p>There are a few other search syntaxes but these 2 are clearly the most useful ones. And you can combine them, meaning you can search for <code>'account !admin</code> to find, for instance, all routes containing exactly <code>account</code> but not the ones containing <code>admin</code>. That should be enough to cover most searches you'll be doing, and definitely enough for searching through your rails routes!</p>
<p>Happy searching 🕵️♂️</p>
Show background jobs number in your ZSH prompt
https://blog.pabuisson.com/2023/11/show-background-jobs-in-zsh-prompt/
2023-11-26T17:14:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>My daily workflow is based around vim (neovim, actually) in an iTerm 2 terminal. As a consequence, I spend most my day navigating iTerm 2 tabs and switching between editing/viewing code in vim and doing stuff in my terminal: running tests, interacting with my file system or with git for instance. Which makes me use <code>Ctrl-Z</code> pretty often.</p>
<h2 id="ctrl-z-and-background-jobs"><code>Ctrl-Z</code> and background jobs</h2>
<p>What is <code>Ctrl-Z</code> you may ask? It allows you to suspend your current process, and move it to the background. You can list background jobs by using the <code>jobs</code> command in your shell:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span><span class="nb">jobs</span>
<span class="o">[</span>1] - suspended nvim
<span class="o">[</span>2] + suspended bundle
</pre></td></tr></tbody></table></code></pre></div>
<p>And you can then make these processes active again by using the <code>fg</code> command. Running <code>fg</code> without any argument would make the last suspended process active again (here, it would be <code>bundle</code>). Or you can specify which process to bring back to the foreground, by using <code>fg $1</code> for instance (which would bring <code>nvim</code> back to the foreground here).</p>
<h2 id="prefix-my-zsh-prompt-with-the-number-of-background-jobs">Prefix my ZSH prompt with the number of background jobs</h2>
<p>But I had one problem with this: I often forgot I had some process running, and ended up re-opening a brand new editor, or simply closing my terminal with these process still running, realizing afterwards that I lost my vim buffers and tabs and all that. Not a super big deal but annoying 😖</p>
<p>Having in my prompt an indicator of background jobs would do the job (ah!) for me, so I started looking into this. Like in Bash, you'll want to look into how to customize your <code>PS1</code> environment variable.</p>
<h3 id="prompt-sequences-to-the-rescue">Prompt sequences to the rescue!</h3>
<p>Lucky for me, ZSH provides <em>prompt sequences</em>. They are sets of characters, prefixed with a <code>%</code>, that are interpreted by ZSH when present in your PS1 environment variable, and are replaced with some specific information. For instance, the following prompt:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="c"># .zshrc</span>
<span class="nv">PS1</span><span class="o">=</span><span class="s1">'%n @ %m ▶ '</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>would show the current username (<code>%n</code>) and the current host (<code>%m</code>), and then a "triangle" character that I like to use for my prompt. Among all the available prompt sequences, one of them is <code>%j</code> and is "the number of jobs". Exactly what I'm looking for.</p>
<p>So let's try something simple like this:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="c"># .zshrc</span>
<span class="nv">PS1</span><span class="o">=</span><span class="s1">'[%j] %n @ %m ▶ '</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>And re-source my zsh configuration file or open a new shell. Here's what I'm getting:</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>[0] myself @ myhost ▶
</pre></td></tr></tbody></table></code></pre></div>
<p>That's a good start already! 💪</p>
<h3 id="show-the-number-of-background-jobs-conditionally">Show the number of background jobs conditionally</h3>
<p>But most of the time, I have no process in the background and I don't care about this information. What I'd love is to display this information <strong>only when I actually have background processes</strong>. Once again, ZSH has this built-in. It's called <em>"conditional substrings in prompts"</em> and uses this format:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>%<span class="o">(</span>expression.true-text.false-text<span class="o">)</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Simply put, it's a ternary expression. The trick is in the <code>expression</code> part: it accepts a few values, and I'll be honest, most of them seem of little use for me. BUT one of them is, again, exactly what I'm looking for:</p>
<blockquote>
<ul>
<li>…</li>
<li><code>j</code> returns true if the number of jobs is at least n.</li>
<li>…</li>
</ul>
</blockquote>
<p>So, writing something like this:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="c"># .zshrc</span>
<span class="nv">PS1</span><span class="o">=</span><span class="s1">'%(1j.[%j] .)%n @ %m ▶ '</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>would check if the number of background jobs is at least 1. If so, it will display the number of jobs in square brackets, and otherwise, will display nothing.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>myself @ myhost ▶ <span class="nb">sleep </span>5
^Z
<span class="o">[</span>1] myself @ myhost ▶
</pre></td></tr></tbody></table></code></pre></div>
<p>It works! The first line shows my prompt with no background process, then I start a long running process that I <code>Ctrl-Z</code>, and then my prompts shows this <code>[1]</code> prefix, indicating that I have one process in background!</p>
<h3 id="adding-colors">Adding colors…</h3>
<p>Now, I "only" need to customize the color for my prompt to make this a bit more appealing. I won't dive into the details as this topic could justify a whole other blog post, but here's what I finally got:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="c"># .zshrc</span>
<span class="c"># %(x.true-text.false-text)</span>
<span class="c"># x = 1j <=> evaluates to true if the number of jobs is at least 1, false otherwise</span>
<span class="c"># %j = number of background jobs</span>
<span class="nv">BACKGROUND_JOBS</span><span class="o">=</span><span class="s1">'%(1j.%F{red}[%j] %f.)'</span>
<span class="c"># %F{color} starts the color, %f stops it</span>
<span class="c"># %B starts bold, %b stops it</span>
<span class="nv">PS1</span><span class="o">=</span><span class="s1">'%B$BACKGROUND_JOBS%F{cyan}%n%f@%F{blue}%m%f%b %F{yellow}▶%f '</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Hard to decipher, but let's break this down into smaller chunks:</p>
<ul>
<li><code>%B</code>: starts bold text,</li>
<li><code>$BACKGROUND_JOBS</code>: insert my <code>$BACKGROUND_JOBS</code> variable that we broke down before,</li>
<li><code>%F{cyan}</code>: starts writing in cyan,</li>
<li><code>%n</code>: insert the username,</li>
<li><code>%f</code>: stops the current color (cyan),</li>
<li><code>@</code>: just insert an arobase character,</li>
<li><code>%F{blue}</code>: starts writing in blue,</li>
<li><code>%m</code>: insert my hostname,</li>
<li><code>%f</code>: stops writing in blue,</li>
<li><code>%b</code>: stops writing in bold,</li>
<li><code>%F{yellow}▶ %f</code>: my prompt character, in yellow.</li>
</ul>
<p>Which eventually looks like this, exactly what I was looking for:</p>
<figure>
<img src="/public/images/2023-11-26-background-jobs-in-zsh-prompt.png" alt="Successive ZSH prompts with no background jobs, one background job, two background jobs" />
<figcaption>Exactly what I was looking for!</figcaption>
</figure>
<p>Hope this will also help you to tune your ZSH prompt!</p>
<hr />
<h2 id="references">References</h2>
<ul>
<li><a href="https://zsh.sourceforge.io/Doc/Release/Prompt-Expansion.html">ZSH documentation: prompt expansion</a></li>
<li><a href="https://stackoverflow.com/questions/10194094/zsh-prompt-checking-if-there-are-any-background-jobs/10194174">Stackoverflow: ZSH prompt - checking if there are any background jobs</a></li>
</ul>
Neovim modern features: treesitter and LSP
https://blog.pabuisson.com/2022/08/neovim-modern-features-treesitter-and-lsp/
2022-08-15T07:14:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>Since its 0.5 release, Neovim received huge updates allowing advanced features that we usually find in full-fledged IDEs. The main ones are <strong>treesitter</strong>, which gives Neovim more precise language parsing and syntax highlighting, and <strong>native LSP</strong>, allowing Neovim to benefit from autocompletion, go-to-definition, inline documentation, refactoring actions, and such awesome features, tailored to each language we're editing.</p>
<p>But there's one thing: all these improvements nearly happened at the same time, and I never really took the time to understand and configure this correctly. It's not so complex, but one still needs to know what exists, which features brings which capability, and how to actually use it. That's the purpose of this blog post.</p>
<p>Also, on top of what I previously wrote, many plugins now take advantage of these features (particularly the LSP integration) and it's a good way to supercharge these plugins and, maybe, get rid of some of them.</p>
<h2 id="disclaimer-about-vimscript-and-lua-configuration">📢 disclaimer: about vimscript and Lua configuration</h2>
<p>I've not migrated my configuration files to LUA (and don't intend to, for now) but as most Neovim plugins configuration examples are in LUA, that's what I'll be using here. If you're still using vimscript, you can wrap these lua configuration sections inside lua markers like this, and you'll be good to go:</p>
<div class="highlight"><pre class="highlight viml"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">lua</span> <span class="p"><<</span>EOF
<span class="p">--</span> Some configuration written <span class="k">in</span> LUA
<span class="p">--</span> <span class="p">...</span>
EOF
</pre></td></tr></tbody></table></code></pre></div>
<h2 id="treesitter">🌳 Treesitter</h2>
<h3 id="what-is-treesitter">What is Treesitter?</h3>
<p><img class="inline" src="/public/images/nvim-treesitter-logo.png" alt="" style="float: right; max-width: 200px;" /> <a href="https://github.com/nvim-treesitter/nvim-treesitter">nvim-treesitter</a> gives Neovim better language parsing capabilities. The most noticeable improvement is a <strong>better and more precise syntax highlighting</strong>, but it also brings other improvements to folding and indentation. It does that by building a syntax tree of our code rather than relying on regexps and patterns.</p>
<p>To function correctly, it requires sets of rules on how to understand the language we're editing, aka "parsers". These parsers can be installed manually with the <code>:TSInstall</code> command, but if we're always using the same languages, we can define a list of our favorite ones and use the <code>ensure_installed</code> option in the configuration to make sure these parsers are always installed (super useful when, say, we sync our vim configuration across multiple devices).</p>
<h3 id="installing-treesitter">Installing Treesitter</h3>
<p>For me, using vim-plug:</p>
<div class="highlight"><pre class="highlight viml"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>Plug <span class="s1">'nvim-treesitter/nvim-treesitter'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'do'</span><span class="p">:</span> <span class="s1">':TSUpdate'</span><span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>The <code>:TSUpdate</code> command runs on each install or update of the plugin, and ensures that parsers are up-to-date. Treesitter parsers are indeed specific to one version of the plugin and must be updated along with the plugin itself.</p>
<blockquote>
<p>This plugin is only guaranteed to work with specific versions of language parsers (…). When upgrading the plugin, you must make sure that all installed parsers are updated to the latest version via <code>:TSUpdate</code></p>
</blockquote>
<h3 id="configuring-treesitter">Configuring Treesitter</h3>
<p>There's not much configuration required here: the main thing we want to make sure of is that the parsers we need most of the time are installed by default.</p>
<div class="highlight"><pre class="highlight lua"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="nb">require</span><span class="p">(</span><span class="s1">'nvim-treesitter.configs'</span><span class="p">).</span><span class="n">setup</span> <span class="p">{</span>
<span class="c1">-- one of "all", "maintained" (parsers with maintainers),</span>
<span class="c1">-- or a list of languages</span>
<span class="n">ensure_installed</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"javascript"</span><span class="p">,</span> <span class="s2">"ruby"</span><span class="p">,</span> <span class="s2">"elixir"</span><span class="p">,</span> <span class="s2">"comment"</span> <span class="p">},</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div>
<p class="notice question">What is the <a href="https://github.com/stsewd/tree-sitter-comment" target="_blank" rel="noopener" title="Link to tree-sitter-comment github project">comment</a> parser, you may ask? well, it's a specific parser that allows <code>TODO:</code>, <code>FIXME:</code>, <code>NOTE:</code> and such comments to be correctly highlighted. They wouldn't be without this parser.</p>
<h3 id="making-use-of-treesitter">Making use of Treesitter</h3>
<p>Now, to take full advantage of this, <strong>we need to make sure that the colorscheme we're using takes treesitter highlighting groups into account</strong>. Most recently updated color schemes do, and it's generally either mentioned in their readme or we'll be able to find commits referencing "Treesitter". We can also check out this <a href="https://github.com/nvim-treesitter/nvim-treesitter/wiki/Colorschemes">list of compatible colorschemes on nvim-treesitter wiki</a>, or even search for highlight groups named <code>TSSomething</code> in our favorite color scheme code.</p>
<p>I'll surely make a follow-up to my <a href="/2018/06/favorite-color-schemes-modern-vim-neovim/">8 favorite color schemes for modern vim</a> article, but in the meantime, I can already advise you the excellent <a href="https://github.com/sainnhe/everforest/">sainnhe/everforest</a> or <a href="https://github.com/navarasu/onedark.nvim">navarasu/onedark</a> themes, for instance 💙</p>
<h2 id="lsp-or-language-server-protocol">🤖 LSP or Language Server Protocol</h2>
<h3 id="what-is-lsp">What is LSP?</h3>
<p>LSP means <a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol</a>. Fine, but again, what does it actually mean? To support features such as autocompletion, linting, go-to-definition, each editor needs to "understand" the edited language. Before LSP, each editor had to implement these features, for each language. As you can imagine, this is a lot of work for something that's common to all text editors: no matter the editor, the way a language works remains the same.</p>
<p>That's why Microsoft built LSP: to define a standard protocol for this, so that editors can hook up to "language servers" and benefit from these features without reinventing the wheel.</p>
<p>The Vim ecosystem has multiple LSP clients that can attach to a LSP server:</p>
<ul>
<li><a href="https://github.com/prabirshrestha/vim-lsp">vim lsp</a>,</li>
<li><a href="https://github.com/dense-analysis/ale">ale</a>, a well known linting/fixing tool that also implements a LSP client,</li>
<li><a href="https://github.com/neoclide/coc.nvim">coc</a>,</li>
<li>and built-in <a href="https://neovim.io/doc/user/lsp.html">Neovim lsp</a>, introduced in Neovim 0.5.</li>
</ul>
<h3 id="native-lsp-in-neovim">Native LSP in Neovim</h3>
<p>As I like to avoid external dependencies, I chose to use Neovim LSP. I believe that relying on the built-in LSP client is the best way to have a common solution and eventually make the ecosystem stronger.</p>
<h4 id="plugins-will-make-our-life-easier">2 plugins will make our life easier</h4>
<p>As we previously said, LSP relies on Language Servers, each Language Server defining the rule for its own language. Therefore, for our LSP setup to be of any use, we'll need to setup language servers and make sure our LSP client knows how to communicate with them.</p>
<p>These language servers are third party dependencies: they can be ruby gems, node packages, etc. Meaning that we need to manage them outside vim, which can be pretty painful. Say we're Ruby developers, we may want to use <a href="https://solargraph.org/">solargraph</a>. For this, we need to install this dependency on our system with <code>$ gem install solargraph</code>, but if we're upgrading to a different Ruby version or switching to another project using a different Ruby version, we'll need to reinstall <code>solargraph</code> for this Ruby version too (been there, done that, and trust me, it's annoying).</p>
<p>That's why I'll be using two plugins to help with that. These two plugins work hand-in-hand:</p>
<ul>
<li><a href="https://github.com/neovim/nvim-lspconfig">nvim-lspconfig</a> : as LSP is built-in Neovim, we <em>can</em> configure it manually, but it's pretty complex. <strong>nvim-lspconfig makes LSP configuration easier and much more concise</strong>. Among other things, it will handle launching the proper Language Server when a filetype is detected, send it the correct options and automatically attach buffers.</li>
<li><a href="https://github.com/williamboman/mason.nvim">mason</a> and <a href="https://github.com/williamboman/mason-lspconfig.nvim">mason-lspconfig</a> make Language Servers management easier. <strong>Among other things, they makes sure the servers configured with nvim-lspconfig are actually available and will install them if they're not</strong>. They actually do more than that (among other things, mason provides a GUI to view and manage all installed Language Servers, but also allows to handle other third-party tools such as linters and formatters, I've not dived into this yet), but I'm mainly using it to avoid manually handling language servers. At the time of writing, they requires neovim >= 0.7.0.</li>
</ul>
<h4 id="installing-mason-mason-lspconfig-and-nvim-lspconfig">Installing mason, mason-lspconfig and nvim-lspconfig</h4>
<p>First, let's install these three plugins. You know the drill! For me, using vim-plug:</p>
<div class="highlight"><pre class="highlight viml"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>Plug <span class="s1">'williamboman/mason.nvim'</span>
Plug <span class="s1">'williamboman/mason-lspconfig.nvim'</span>
Plug <span class="s1">'neovim/nvim-lspconfig'</span>
</pre></td></tr></tbody></table></code></pre></div>
<h4 id="configuring-mason-mason-lspconfig-and-nvim-lspconfig">Configuring mason, mason-lspconfig and nvim-lspconfig</h4>
<p>Then, let's move on to the configuration itself:</p>
<div class="highlight"><pre class="highlight lua"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="c1">-- makes sure the language servers configured later with lspconfig are</span>
<span class="c1">-- actually available, and install them automatically if they're not</span>
<span class="c1">-- !! THIS MUST BE CALLED BEFORE ANY LANGUAGE SERVER CONFIGURATION</span>
<span class="nb">require</span><span class="p">(</span><span class="s2">"mason"</span><span class="p">).</span><span class="n">setup</span><span class="p">()</span>
<span class="nb">require</span><span class="p">(</span><span class="s2">"mason-lspconfig"</span><span class="p">).</span><span class="n">setup</span> <span class="p">{</span>
<span class="c1">-- automatically install language servers setup below for lspconfig</span>
<span class="n">automatic_installation</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="c1">-- Actually setup the language servers so that they're available for our</span>
<span class="c1">-- LSP client. I'm mainly working with Ruby and JS, so I'm configuring</span>
<span class="c1">-- language servers for these 2 languages</span>
<span class="kd">local</span> <span class="n">nvim_lsp</span> <span class="o">=</span> <span class="nb">require</span><span class="p">(</span><span class="s1">'lspconfig'</span><span class="p">)</span>
<span class="n">nvim_lsp</span><span class="p">.</span><span class="n">solargraph</span><span class="p">.</span><span class="n">setup</span><span class="p">{}</span>
<span class="n">nvim_lsp</span><span class="p">.</span><span class="n">tsserver</span><span class="p">.</span><span class="n">setup</span><span class="p">{}</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>There are many language servers, each with their own configuration options, make sure to check out <a href="https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md">nvim-lspconfig page on this</a> to make the best of our language servers.</p>
<p>To make sure our setup is actually working, let's nwo head over to one file of the languages we have configured, and run the <code>:LspInfo</code> command. If it's working, we should be seeing something like this:</p>
<figure>
<img src="/public/images/2022-08-15-succesful-lsp-info-tsserver.png" alt="Succesful :LspInfo output for tsserver" />
<figcaption>Yay, `:LspInfo` is happy, so are we!</figcaption>
</figure>
<h3 id="making-use-of-lsp">Making use of LSP</h3>
<p>It's all good and well, we have LSP configured, now what? We now have access to many LSP functions, and can bind them to whatever keybinding we want. By default, nvim-lspconfig does not set keybinding or autocompletion by default, we need to configure that.</p>
<p>Here's the <a href="https://github.com/neovim/nvim-lspconfig#suggested-configuration">suggested configuration in the nvim-lspconfig plugin documentation</a>. I urge you to take a look at each of these functions, check their documentation using <code>:help</code>, and keep the ones you actually use. For me, the most useful ones have been go to "definition/declaration" and "find references" ones, that allowed me to completely get rid of my former clunky ctags setup.</p>
<div class="highlight"><pre class="highlight lua"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
</pre></td><td class="rouge-code"><pre><span class="c1">-- Mappings.</span>
<span class="c1">-- See `:help vim.diagnostic.*` for documentation on any of the below functions</span>
<span class="kd">local</span> <span class="n">opts</span> <span class="o">=</span> <span class="p">{</span> <span class="n">noremap</span><span class="o">=</span><span class="kc">true</span><span class="p">,</span> <span class="n">silent</span><span class="o">=</span><span class="kc">true</span> <span class="p">}</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'<space>e'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">diagnostic</span><span class="p">.</span><span class="n">open_float</span><span class="p">,</span> <span class="n">opts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'[d'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">diagnostic</span><span class="p">.</span><span class="n">goto_prev</span><span class="p">,</span> <span class="n">opts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">']d'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">diagnostic</span><span class="p">.</span><span class="n">goto_next</span><span class="p">,</span> <span class="n">opts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'<space>q'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">diagnostic</span><span class="p">.</span><span class="n">setloclist</span><span class="p">,</span> <span class="n">opts</span><span class="p">)</span>
<span class="c1">-- Use an on_attach function to only map the following keys</span>
<span class="c1">-- after the language server attaches to the current buffer</span>
<span class="kd">local</span> <span class="n">on_attach</span> <span class="o">=</span> <span class="k">function</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">bufnr</span><span class="p">)</span>
<span class="c1">-- Enable completion triggered by <c-x><c-o></span>
<span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_buf_set_option</span><span class="p">(</span><span class="n">bufnr</span><span class="p">,</span> <span class="s1">'omnifunc'</span><span class="p">,</span> <span class="s1">'v:lua.vim.lsp.omnifunc'</span><span class="p">)</span>
<span class="c1">-- Mappings.</span>
<span class="c1">-- See `:help vim.lsp.*` for documentation on any of the below functions</span>
<span class="kd">local</span> <span class="n">bufopts</span> <span class="o">=</span> <span class="p">{</span> <span class="n">noremap</span><span class="o">=</span><span class="kc">true</span><span class="p">,</span> <span class="n">silent</span><span class="o">=</span><span class="kc">true</span><span class="p">,</span> <span class="n">buffer</span><span class="o">=</span><span class="n">bufnr</span> <span class="p">}</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'gD'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">declaration</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'gd'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">definition</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'K'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">hover</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'gi'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">implementation</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'<C-k>'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">signature_help</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'<space>wa'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">add_workspace_folder</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'<space>wr'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">remove_workspace_folder</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'<space>wl'</span><span class="p">,</span> <span class="k">function</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">vim</span><span class="p">.</span><span class="n">inspect</span><span class="p">(</span><span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">list_workspace_folders</span><span class="p">()))</span>
<span class="k">end</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'<space>D'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">type_definition</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'<space>rn'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">rename</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'<space>ca'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">code_action</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'gr'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">references</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="n">vim</span><span class="p">.</span><span class="n">keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'n'</span><span class="p">,</span> <span class="s1">'<space>f'</span><span class="p">,</span> <span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">buf</span><span class="p">.</span><span class="n">formatting</span><span class="p">,</span> <span class="n">bufopts</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1">-- Make sure to use this new on_attach function when you setup</span>
<span class="c1">-- your language servers</span>
<span class="nb">require</span><span class="p">(</span><span class="s1">'lspconfig'</span><span class="p">)[</span><span class="s1">'solargraph'</span><span class="p">].</span><span class="n">setup</span><span class="p">{</span>
<span class="n">on_attach</span> <span class="o">=</span> <span class="n">on_attach</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>And since we have now configured LSP, we can take advantage of it for other things. For instance, if you're using an autocompletion plugin, you should take a look at how you can take advantage of this to get better completion suggestions.</p>
<h2 id="conclusion">Conclusion</h2>
<p>There may be other blog posts coming on how to make even more use of the LSP features and such, but I hope this one will make things a bit clearer as to what Treesitter and LSP are, what each of them brings to the table, and how we can setup and configure each of them. Let me know on <a href="https://twitter.com/pabuisson">Twitter</a> if that was useful to you and if I missed anything at all.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://github.com/nvim-treesitter/nvim-treesitter">nvim-treesitter</a></li>
<li><a href="https://microsoft.github.io/language-server-protocol/">Microsoft Langue Server Protocol page</a></li>
<li><a href="https://wikipedia.org/wiki/Language_Server_Protocol">Language Server Protocol wikipedia page</a></li>
<li><a href="https://github.com/neovim/nvim-lspconfig">nvim-lspconfig</a></li>
<li><a href="https://github.com/williamboman/mason.nvim">mason</a></li>
<li><a href="https://github.com/williamboman/mason-lspconfig.nvim">mason-lspconfig</a></li>
</ul>
My 8 favorite color schemes for modern vim
https://blog.pabuisson.com/2018/06/favorite-color-schemes-modern-vim-neovim/
2018-06-16T08:01:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>I've been a vim user for a long time now, and a color schemes nerd for quite a while as well. The color scheme landscape has changed a lot lately. Since the introduction of true colors terminals, you're not limited to a 256 colors palette anymore and therefore, lots of new fancy color schemes appeared over the course of the past years. In the same time, several "modern" text editors appeared and encounter success those last years such as Sublime Text, Atom, VS Code, and they all came with new pretty solid default color schemes, when previously, most editors came with… let's say basic color schemes. Eventually, many of the color schemes that were once popular now feel outdated, compared to more recent options.</p>
<figure>
<img class="badge" src="/public/images/neovim-logo-shadow.png" alt="Neovim logo" />
</figure>
<p>So here's a selection of the color schemes <em>I</em>'ve used the most over the past few years. This post merely reflects my own tastes and the themes listed here are the ones that I prefer and have worked with the most. I mainly work with ruby, javascript and markdown on a daily basis, with <em>Neovim</em> in a regular <em>iTerm2</em> terminal (all those themes should render pretty well in modern vims as well, as long as you have true colors configured). I am very much of a dark-theme guy, so most of my recommendations here will be dark themes. I'll still cover two light color schemes that I really appreciate at the end of this post. Let's dive in!</p>
<h2 id="dark-themes">6 dark themes</h2>
<p>It seems dark themes are prominent in the vim color schemes community, and you have lots of different options in this category. Lately, lots of color schemes have been inspired by the original One Dark color scheme of the Atom Editor and by the Google Material color palette, and as a consequence, all those themes can sometimes feel all pretty similar. This selection should allow you to explore other options.</p>
<h3 id="palenight">palenight</h3>
<p><a href="https://github.com/drewtempelmeyer/palenight.vim">palenight by drewtempelmeyer</a></p>
<blockquote>
<p>A dark color scheme for Vim/Neovim based off the Material Pale Night color scheme. Much of the work is based on the lovely onedark.vim color scheme.</p>
</blockquote>
<p>This one is my absolute favorite, and without a doubt, the less well-known option of all themes I'm going to cover in this post. I came across it months ago, and it's been my main theme since then. As usual, I've kept on testing other color schemes, but always quickly came back to this one. It's one of those themes based on <code>onedark</code>, but to me, it's one of the best out there. Even if the repository does not show much activity, this must not prevent you from testing this gorgeous theme. It's a <code>onedark</code>-ish theme, with a yellow-orange and purple flavour, making it more lively and less boring than most others out there, in my opinion. But still not too bling-bling. A sweet spot!</p>
<ul class="gallery palenight">
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-palenight-js.png" title="Palenight theme editing Javascript in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-palenight-js-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-palenight-scss.png" title="Palenight theme editing SCSS in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-palenight-scss-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-palenight-ruby.png" title="Palenight theme editing Ruby in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-palenight-ruby-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-palenight-markdown.png" title="Palenight theme editing Markdown in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-palenight-markdown-thumb.png" />
</a>
</li>
</ul>
<h3 id="ayu">ayu</h3>
<p><a href="https://github.com/ayu-theme/ayu-vim">ayu-vim</a></p>
<blockquote>
<p>Simple, Bright and Elegant theme for modern Vims.</p>
</blockquote>
<p>Ayu comes in three different flavors : dark, mirage (dark, but less dark), and light. The three of them are really nice, I've used them extensively for a while before I found out palenight. Among the two dark flavours of this theme, I personally prefer the mirage one that I find more balanced, the dark version being too contrasted to my liking (although very good as well).</p>
<ul class="gallery ayumirage">
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-ayu-js.png" title="Ayu Mirage theme editing Javascript in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-ayu-js-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-ayu-scss.png" title="Ayu Mirage theme editing SCSS in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-ayu-scss-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-ayu-ruby.png" title="Ayu Mirage theme editing Ruby in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-ayu-ruby-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-ayu-markdown.png" title="Ayu Mirage theme editing Markdown in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-ayu-markdown-thumb.png" />
</a>
</li>
</ul>
<h3 id="gruvbox">gruvbox</h3>
<p><a href="https://github.com/morhetz/gruvbox">gruvbox by morhetz</a></p>
<blockquote>
<p>Retro groove color scheme for Vim</p>
</blockquote>
<p>Gruvbox is definitely one of the most recognizable themes around there. It has a very distinct yellow-ish color palette, which makes it stand out from the crowd. According to its Github page, it's inspired by classical and well-known vim themes such as badwolf, jellybeans, and solarized. It comes in two flavours, dark and light, and each one can be tweaked to a specific contrast mode: hard contrast, medium, or low. It's a great choice and has lots of configuration options and supported third-party plugins.</p>
<ul class="gallery gruvbox">
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-gruvbox-js.png" title="Gruvbox theme editing Javascript in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-gruvbox-js-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-gruvbox-scss.png" title="Gruvbox theme editing SCSS in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-gruvbox-scss-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-gruvbox-ruby.png" title="Gruvbox theme editing Ruby in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-gruvbox-ruby-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-gruvbox-markdown.png" title="Gruvbox theme editing Markdown in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-gruvbox-markdown-thumb.png" />
</a>
</li>
</ul>
<h3 id="nord-vim">nord-vim</h3>
<p><a href="https://github.com/arcticicestudio/nord-vim">nord-vim</a></p>
<blockquote>
<p>An arctic, north-bluish clean and elegant Vim theme.</p>
</blockquote>
<p>I came across Nord quite recently, and even though I've rarely used it as my main theme so far, it really deserves a mention in this article. Nord is a more general color palette project, based on bluish/artic colors, really easy on the eye. It lacks a bit of "pop" to my liking to be honest, but it still feels really nicely done, and the palette has been declined over many other editors and terminals, which can make it a really good choice if you like to get your console and editor themes aligned. It supports a lot of options and third-party plugins as well.</p>
<ul class="gallery nord">
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-nord-js.png" title="Nord theme editing Javascript in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-nord-js-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-nord-scss.png" title="Nord theme editing SCSS in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-nord-scss-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-nord-ruby.png" title="Nord theme editing Ruby in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-nord-ruby-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-nord-markdown.png" title="Nord theme editing Markdown in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-nord-markdown-thumb.png" />
</a>
</li>
</ul>
<h3 id="vim-one">vim-one</h3>
<p><a href="https://github.com/rakr/vim-one">vim-one by rakr</a></p>
<blockquote>
<p>One of the best Atom colorscheme, now for Vim and Neovim.</p>
</blockquote>
<p>There are a lot of colorschemes inspired by Atom's default one colorscheme out there, for all editors. This has really been a game-changing color scheme that inspired many theme designers. This one feels like one of the most polished for vim, I've used it quite a long time ago and don't use it anymore, but it's a solid choice, you can't really go wrong using it. If you're looking for a clone of the Atom One theme, this might be the <em>one</em> (🙃) for you.</p>
<ul class="gallery one">
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-one-js.png" title="vim-one theme editing Javascript in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-one-js-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-one-scss.png" title="vim-one theme editing SCSS in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-one-scss-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-one-ruby.png" title="vim-one theme editing Ruby in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-one-ruby-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-one-markdown.png" title="vim-one theme editing markdown in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-one-markdown-thumb.png" />
</a>
</li>
</ul>
<h3 id="oceanic-next">oceanic-next</h3>
<p><a href="https://github.com/mhartington/oceanic-next">oceanic-next by mhartington</a></p>
<blockquote>
<p>Oceanic-Next.vim is a neovim theme inspired by Oceanic Next for Sublime. It is not a direct port but uses some colors from the sublime theme, that are fitted to work with neovim and vim8.</p>
</blockquote>
<p>This one feels more "classical", with a dark-bluish feel. I don't use it that often, but still, I keep it around and use to it from time to time, as it feels quite soothing to me.</p>
<ul class="gallery oceanicnext">
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-oceanicnext-js.png" title="OceanicNext theme editing Javascript in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-oceanicnext-js-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-oceanicnext-scss.png" title="OceanicNext theme editing SCSS in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-oceanicnext-scss-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-oceanicnext-ruby.png" title="OceanicNext theme editing Ruby in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-oceanicnext-ruby-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-oceanicnext-markdown.png" title="OceanicNext theme editing Markdown in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-oceanicnext-markdown-thumb.png" />
</a>
</li>
</ul>
<hr />
<h2 id="light-themes">2 light themes</h2>
<p>As I previously said, I'm more of a dark theme guy myself and rarely use light color-schemes. From time to time, I try one of them but never keep it for long. But I know there are developers out there that favor light color schemes, and even though there are not so many good ones around there, some of them are worth being listed.</p>
<h3 id="ayu-light">ayu-light</h3>
<p><a href="https://github.com/ayu-theme/ayu-vim">ayu-vim</a></p>
<blockquote>
<p>Simple, Bright and Elegant theme for modern Vims.</p>
</blockquote>
<p>We already covered the ayu color scheme in the "dark themes" part of this article, but the "light" version of ayu really deserves to be listed. I like light color schemes with bright and distinct colors, and ayu does a really good job at this. If I had to use a light color scheme, it would certainly be this one.</p>
<ul class="gallery ayulight">
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-ayulight-js.png" title="Ayu light theme editing Javascript in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-ayulight-js-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-ayulight-scss.png" title="Ayu light theme editing SCSS in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-ayulight-scss-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-ayulight-ruby.png" title="Ayu light theme editing Ruby in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-ayulight-ruby-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-ayulight-markdown.png" title="Ayu light theme editing Markdown in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-ayulight-markdown-thumb.png" />
</a>
</li>
</ul>
<h3 id="solarized-light">solarized light</h3>
<p>We can't really write a round up of colorschemes without talking about solarized, can we? Solarized was one of the first well-thought color schemes those last few years, it came out with a nice palette and felt quite nice. I never really enjoyed the dark version of the theme (too much blue, not enough contrast with the main foreground color), but I do really appreciate the light version, a pale yellow/beige with colourful keywords. The original solarized by ethanschoonover has unfortunately not been worked on in a long time (last commit 7 years ago !) but you'll find many forks out there. I'll particularly mention those two projects :</p>
<p><a href="https://github.com/lifepillar/vim-solarized8">vim-solarized8 by lifepillar</a></p>
<blockquote>
<p>Optimized Solarized colorschemes. Best served with true-color terminals!</p>
</blockquote>
<p><a href="https://github.com/romainl/flattened">flattened by romainl</a></p>
<blockquote>
<p>Solarized, without the bullshit.</p>
</blockquote>
<p>I have no personal opinion about which one is the best, they feel a bit different (especially flattened which might present more tweaks and changes over the original repository). vim-solarized8 has the drawback of adding a lot of different colorschemes which I don't like, but I guess it's not that important. I'll leave that out to your own judgment, here are some screenshots for flattened:</p>
<ul class="gallery flattenedlight">
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-flattenedlight-js.png" title="Flattened light theme editing Javascript in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-flattenedlight-js-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-flattenedlight-scss.png" title="Flattened light theme editing SCSS in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-flattenedlight-scss-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-flattenedlight-ruby.png" title="Flattened light theme editing Ruby in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-flattenedlight-ruby-thumb.png" />
</a>
</li>
<li class="gallery__item">
<a class="gallery__itemLink" href="/public/images/2018-06-17-neovim-flattenedlight-markdown.png" title="Flattened light theme editing Markdown in Neovim 0.3.0" data-rel="aiLightbox">
<img class="gallery__itemThumb" src="/public/images/2018-06-17-neovim-flattenedlight-markdown-thumb.png" />
</a>
</li>
</ul>
<hr />
<p>Of course, there are a lot of other well-designed vim colorschemes out there. I could have talked about <em>Tomorrow</em>, <em>PaperColor</em>, <em>Yowish</em>, <em>Dracula</em>, <em>Tender</em>, <em>Iceberg</em>, <em>Hybrid</em>, etc. but I never really got hooked by any of those and I couldn't cover every single vim theme in this post. They might be worth a look though. If you know of other interesting options I didn't mention, feel free to ping me over Twitter <a href="https://twitter.com/pabuisson" alt="Pierre-Adrien Buisson on Twitter">@pabuisson</a>, I'm always looking for new themes to try out 🙃</p>
<h2 id="references">References</h2>
<ul>
<li>Find vim plugins and themes on <a href="https://vimawesome.com">vim awesome</a>,</li>
<li>Check out hundreds of vim color schemes on <a href="http://vimcolors.com/">vimcolors</a></li>
</ul>
À la découverte de Sinatra #3 - Utiliser SASS/SCSS dans un projet Sinatra
https://blog.pabuisson.com/2018/05/a-la-decouverte-de-sinatra-3-utiliser-sass-avec-sinatra/
2018-05-20T22:00:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>Comme nous l'avons évoqué dans un article précédent (voir par ailleurs : <a href="/2014/03/3-raisons-d-aimer-scss/">3 raisons d'aimer SCSS</a>), <a href="http://www.sass-lang.com/">SASS/SCSS</a> est un pré-processeur de CSS qui apporte à vos feuilles de style un lot de fonctionnalités qui vont vous faciliter la vie et alléger votre code. Il devient très vite impossible de s'en passer ! Nous allons reprendre le projet Sinatra débuté dans les articles <a href="/2014/05/a-la-decouverte-de-sinatra-1-hello-world/">À la découverte de Sinatra #1 - Hello World</a> et <a href="/2014/07/a-la-decouverte-de-sinatra-2-routes-et-templates/">À la découverte de Sinatra #2 - Routes et templates</a>, et ajouter SASS dans le cadre de ce projet pour alléger nos feuilles de style et profiter de la souplesse apportée par SASS.</p>
<p>Tout le code correspondant à ce troisième article sera disponible sur <a href="https://github.com/pabuisson/blog-decouverte-sinatra">mon dépôt github pabuisson/blog-decouverte-sinatra</a> déjà utilisé dans les billets précédents, avec le tag <code>partie-3</code>. Pour le récupérer et lancer le projet :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>$ git clone git@github.com:pabuisson/blog-decouverte-sinatra.git
$ git checkout -f partie-3
$ cd blog-decouverte-sinatra && bundle && rackup
</pre></td></tr></tbody></table></code></pre></div>
<h2 id="sinatra-et-rack">Sinatra et Rack</h2>
<p><a href="http://www.sinatrarb.com/">Sinatra</a> est un framework web très léger (mais néanmoins puissant) en Ruby, que j'affectionne particulièrement et que j'utilise régulièrement pour du prototypage ou le développement de projets personnels. Comme beaucoup de framework web Ruby, il s'appuie sur le <a href="https://rubygems.org/gems/rack">gem rack</a> qui, selon les mots même de son créateur, fournit <em>"une interface minimale pour connecter les serveurs web qui supportent Ruby (Webrick, mongrel, Thin, etc.) avec les frameworks Ruby (comme Rails, Sinatra, Merb, etc.)"</em>.</p>
<p><em>Note: Vous pouvez d'ailleurs tout à fait développer un site web directement avec Rack, sans avoir recours à un framework tel que Sinatra ou Padrino, auquel cas la méthode exposée ici devrait également fonctionner. À vous de me signaler si ce n'est pas le cas :)</em></p>
<h2 id="mise-en-place">Mise en place</h2>
<h3 id="ajout-de-sass-au-gemfile">Ajout de SASS au Gemfile</h3>
<p>Notre projet Sinatra est d'ores et déjà fonctionnel. Première étape avant d'utiliser Sass, nous allons tout d'abord ajouter la dépendance <code>sass</code> à notre Gemfile. Rien de plus simple, il suffit d'ajouter la ligne suivante à notre fichier <code>Gemfile</code>, à la racine du projet :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="c1"># Gemfile</span>
<span class="n">gem</span> <span class="s1">'sass'</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Puis lancer la commande <code>bundle</code> pour installer le gem, et ensuite hop, <em>ready to rock</em> !</p>
<h3 id="fichier-rackup">Fichier Rackup</h3>
<p>Rack fournit différents outils, et notamment <code>rackup</code> qui permet de lancer des applications Rack. Pour modifier le comportement de nos applications Rack, on utilise des fichiers de configuration dont l'extension habituelle est le <code>.ru</code> (<code>ru</code> comme <strong>R</strong>ack<strong>U</strong>p, car oui, on est des déglingos !). Par défaut, Rackup recherche le fichier de configuration <code>config.ru</code> : nous allons donc créer ce fichier à la racine du projet. Dans ce fichier, nous allons ensuite indiquer à Rack qu'il doit utiliser Sass en ajoutant les deux lignes suivantes :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="c1"># ./config.ru</span>
<span class="c1">#</span>
<span class="c1"># On importe sass/plugin/rack depuis le gem sass</span>
<span class="c1"># Et on indique à Rack d'utiliser le middleware fourni par ce gem</span>
<span class="c1">#</span>
<span class="nb">require</span> <span class="s1">'sass/plugin/rack'</span>
<span class="n">use</span> <span class="no">Sass</span><span class="o">::</span><span class="no">Plugin</span><span class="o">::</span><span class="no">Rack</span>
<span class="c1">#</span>
<span class="c1"># Le reste de votre fichier config.ru, qui indique comment lancer</span>
<span class="c1"># l'application. Par exemple, pour un projet Sinatra basique:</span>
<span class="c1">#</span>
<span class="nb">require</span> <span class="s1">'./app.rb'</span>
<span class="n">run</span> <span class="no">Sinatra</span><span class="o">::</span><span class="no">Application</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Avec les deux premières lignes, on indique simplement à Rack qu'il doit utiliser le MiddleWare Sass <em>(un Middleware dans le monde Rack représentant un composant utilisé lors du processing de la requête HTTP)</em>. Et c'est tout, nous sommes prêts à utiliser Sass dans notre projet Sinatra !</p>
<p>⚠️ <strong>Attention, cela implique que désormais, pour lancer notre projet sinatra, nous ne devons plus utiliser la commande <code>$ ruby app.rb</code> qui ne prendrait pas en compte le fichier <code>config.ru</code> que l'on vient de créer, mais la commande <code>$ rackup</code>. À noter que <code>$ ruby app.rb</code> lançait Sinatra sur le port 4567 par défaut tandis que <code>$ rackup</code> utilisera par défaut le port 9292.</strong></p>
<h2 id="utilisation-de-sass">Utilisation de SASS</h2>
<p>C'était plutôt simple, n'est-ce pas ? À partir de maintenant, lorsque vous lancerez votre serveur au moyen de la commande <code>rackup</code>, le middleware Sass sera chargé et examinera à chaque nouvelle requête vers le serveur les fichiers SASS/SCSS localisés dans le répertoire <code>public/stylesheets/sass</code>. En cas de modification, les fichiers modifiés seront alors (re)transpilés au format CSS dans le répertoire <code>public/stylesheets</code>.</p>
<p>Le middleware SASS accepte bien évidemment <a href="http://sass-lang.com/documentation/file.SASS_REFERENCE.html#options">de nombreuses options</a> vous permettant de l'adapter à vos besoins :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="c1"># Pour compresser les fichiers CSS générés</span>
<span class="no">Sass</span><span class="o">::</span><span class="no">Plugin</span><span class="p">.</span><span class="nf">options</span><span class="p">[</span><span class="ss">:style</span><span class="p">]</span> <span class="o">=</span> <span class="ss">:compressed</span>
<span class="c1"># Emplacement des fichiers SASS/SCSS</span>
<span class="c1"># Défaut: 'public/stylesheets/sass/'</span>
<span class="no">Sass</span><span class="o">::</span><span class="no">Plugin</span><span class="p">.</span><span class="nf">options</span><span class="p">[</span><span class="ss">:template_location</span><span class="p">]</span> <span class="o">=</span> <span class="o">...</span>
<span class="c1"># Emplacement où sont générés les CSS transpilés</span>
<span class="c1"># Défaut: 'public/stylesheets/'</span>
<span class="no">Sass</span><span class="o">::</span><span class="no">Plugin</span><span class="p">.</span><span class="nf">options</span><span class="p">[</span><span class="ss">:css_location</span><span class="p">]</span> <span class="o">=</span> <span class="o">...</span>
</pre></td></tr></tbody></table></code></pre></div>
<h2 id="traduisons-nos-css-en-scss">Traduisons nos CSS en SCSS</h2>
<p>Jusqu'à présent, notre projet Sinatra contenait une unique feuille de styles <code>./public/stylesheets/app.css</code>. Première étape, étant donné ce que nous venons de voir : créons un répertoire <code>/public/stylesheets/sass/</code>, renommons notre fichier <code>app.css</code> en <code>app.scss</code> et déplaçons-le dans notre nouveau répertoire :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>$ mkdir public/stylesheets/sass
$ mv public/stylesheets/app.css public/stylesheets/sass/app.scss
</pre></td></tr></tbody></table></code></pre></div>
<p>Le fichier <code>app.scss</code> sera donc transpilés en CSS dans le répertoire <code>public/stylesheets/app.css</code>. Comme c'est là que se trouvait notre ancien fichier CSS, nous n'avons même pas besoin de mettre à jour nos layouts qui pointent déjà vers le bon emplacement. Et étant donné que la syntaxe SCSS est rétro-compatible avec la syntaxe CSS, nous devons d'ores et déjà pouvoir afficher une page sans erreur.</p>
<p>Lançons notre serveur :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>$ rackup
[2018-05-21 18:00:40] INFO WEBrick 1.3.1
[2018-05-21 18:00:40] INFO ruby 2.3.3 (2016-11-21) [x86_64-darwin15]
[2018-05-21 18:00:40] INFO WEBrick::HTTPServer#start: pid=88205 port=9292
</pre></td></tr></tbody></table></code></pre></div>
<p>Et rendons-nous sur <code>localhost:9292</code> pour nous assurer que tout se passe bien :</p>
<figure>
<img src="/public/images/2018-05-21-sinatra-scss-reloaded.png" alt="Sinatra reloaded with SCSS" />
<figcaption>Twingo, ça fonctionne!</figcaption>
</figure>
<h2 id="conclusion">Conclusion</h2>
<p>Maintenant, ne reste plus qu'à réellement nous servir de la syntaxe SCSS : à nous les joies du nesting, des variables, des mixins, des modules ! Je vous laisse voir mon précédent article <a href="/2014/03/3-raisons-d-aimer-scss/">3 raisons d'aimer SCSS</a> dans lequel je décris les principaux avantages que je trouve à ce super outil. Je vous laisse vous rendre sur le github du projet pour voir le fichier SCSS final, après qu'il a été restructuré.</p>
<p>Vous pouvez récupérer le code source de cet exemple que j'ai mis à disposition sur <a href="https://github.com/pabuisson/blog-decouverte-sinatra">mon dépôt github pabuisson/blog-decouverte-sinatra</a> :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>$ git clone git@github.com:pabuisson/blog-decouverte-sinatra.git
$ git checkout -f partie-3
$ cd blog-decouverte-sinatra && bundle && rackup
</pre></td></tr></tbody></table></code></pre></div>
<h2 id="rfrences">Références</h2>
<ul>
<li>⏪ <strong>Article précédent</strong> : <a href="/2014/07/a-la-decouverte-de-sinatra-2-routes-et-templates/">À la découverte de Sinatra #2 : routes et templates</a></li>
<li><a href="http://rubylearning.com/blog/a-quick-introduction-to-rack/">A quick introduction to Rack</a></li>
<li><a href="http://sass-lang.com/documentation/file.SASS_REFERENCE.html#rackrailsmerb_plugin">SASS : Using SASS Rack plugin</a></li>
<li><a href="http://sass-lang.com/documentation/file.SASS_REFERENCE.html#options">SASS : Rack plugin options</a></li>
</ul>
Sinatra : authentification avec Warden
https://blog.pabuisson.com/2018/01/sinatra-authentification-avec-warden/
2018-01-31T20:07:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>Récemment, pour plusieurs sites et prototypes développés avec Sinatra, j'ai eu à me poser la question de comment faire un système d'authentification digne de ce nom. En Rails, la solution de-facto s'appelle <a href="https://github.com/heartcombo/devise">Devise</a>, mais étant très rails-centric, cette solution est compliquée à utiliser dans un projet Sinatra. Pour ce genre de fonctionnalités, on va alors plutôt avoir recours à la gem warden.</p>
<h2 id="warden-quest-ce-que-cest-">Warden, qu'est-ce que c'est ?</h2>
<p>D'après la description même du projet, Warden est un <em>"framework rack d'authentification"</em>. Plus précisément, il s'agit d'un middleware qui a pour vocation de fournir les mécanismes d'authentification à des applications Ruby. C'est d'ailleurs sur Warden que s'appuie <a href="https://github.com/heartcombo/devise">Devise</a>, qui est la solution la plus courante d'authentification pour les projets Rails. Autant dire que si on utilise finalement assez peu Warden directement, ça n'en reste pas moins une brique logicielle éprouvée et sur laquelle on peut s'appuyer sans craintes.</p>
<p>Voyons comme procéder pour utiliser Warden dans un projet Sinatra.</p>
<h2 id="que-va-t-on-dvelopper">Que va-t-on développer</h2>
<p>On va développer un projet assez simple :</p>
<ul>
<li>Un site Sinatra,</li>
<li>Qui se connectera à une base de données sqlite avec activerecord,</li>
<li>Qui présentera une page de login,</li>
<li>Ainsi qu'une barre de navigation avec les actions habituelles (login, logout, affichage du nom de l'utilisateur connecté),</li>
<li>Et qui aura une page protégée, accessible uniquement aux utilisateurs connectés.</li>
</ul>
<p>On a déjà de quoi faire ! Je passerai vite sur la mise en place du projet et ne détaillerai pas la mise en place des vues et du code HTML, si vous êtes intéressés, vous pouvez aller jeter un oeil vers mes articles <a href="/2014/05/a-la-decouverte-de-sinatra-1-hello-world/">À la découverte de Sinatra 1</a> et son petit frère <a href="/2014/07/a-la-decouverte-de-sinatra-2-routes-et-templates/">À la découverte de Sinatra 2 - Routes et templates</a>.</p>
<h2 id="mettons-sinatra-en-place">Mettons Sinatra en place</h2>
<h3 id="dpendances">Dépendances</h3>
<p>Commençons par initier un nouveau projet Sinatra, et par mettre en place tout le nécessaire. Créons, un gemfile, ajoutons les gems nécessaires puis lançons un <code>bundle install</code> pour installer tout ça :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>$ bundle init
</pre></td></tr></tbody></table></code></pre></div>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c1"># -- Gemfile --</span>
<span class="n">gem</span> <span class="s1">'puma'</span>
<span class="n">gem</span> <span class="s1">'rake'</span>
<span class="n">gem</span> <span class="s1">'sinatra'</span>
<span class="n">gem</span> <span class="s1">'warden'</span>
<span class="n">gem</span> <span class="s1">'sinatra-activerecord'</span>
<span class="n">gem</span> <span class="s1">'sqlite3'</span>
<span class="n">gem</span> <span class="s1">'bcrypt'</span>
<span class="n">gem</span> <span class="s1">'rack-flash3'</span>
</pre></td></tr></tbody></table></code></pre></div>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>$ bundle
</pre></td></tr></tbody></table></code></pre></div>
<p>On va également créer un <code>Rakefile</code> pour disposer des tâches rake de <code>sinatra/activerecord</code>, ça nous facilitera grandement la tâche au moment de générer la base et les migrations :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="c1"># -- Rakefile --</span>
<span class="nb">require</span> <span class="s2">"sinatra/activerecord/rake"</span>
<span class="n">namespace</span> <span class="ss">:db</span> <span class="k">do</span>
<span class="n">task</span> <span class="ss">:load_config</span> <span class="k">do</span>
<span class="nb">require</span> <span class="s2">"./app"</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<h3 id="fichier-sinatra-principal">Fichier sinatra principal</h3>
<p>Créons maintenant notre fichier <code>app.rb</code> qui contiendra la majorité de notre petite application Sinatra. Sur ce blog, vous m'avez souvent vu utiliser la version modulaire de Sinatra. Pour changer, je vais utiliser la version classique, plus simple à mettre en oeuvre et parfaite pour un petit PoC comme celui-ci. Pour plus de détails sur les différences entre application sinatra classique et modulaire, je vous laisse lire le <a href="https://github.com/sinatra/sinatra#modular-vs-classic-style">readme de Sinatra</a> qui explique très bien cela.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="c1"># -- app.rb --</span>
<span class="nb">require</span> <span class="s1">'sinatra'</span>
<span class="nb">require</span> <span class="s1">'sinatra/activerecord'</span>
<span class="nb">require</span> <span class="s1">'rack-flash'</span>
<span class="nb">require</span> <span class="s1">'./model.rb'</span>
<span class="c1"># sinatra/activerecord : configuration de la connexion à la base</span>
<span class="n">set</span> <span class="ss">:database</span><span class="p">,</span> <span class="p">{</span> <span class="ss">adapter: </span><span class="s2">"sqlite3"</span><span class="p">,</span> <span class="ss">database: </span><span class="s2">"warden.sqlite3"</span> <span class="p">}</span>
<span class="n">use</span> <span class="no">Rack</span><span class="o">::</span><span class="no">Flash</span>
<span class="c1">#</span>
<span class="c1"># À venir : configuration de Warden</span>
<span class="c1">#</span>
</pre></td></tr></tbody></table></code></pre></div>
<h3 id="modle-activerecord-et-migration">Modèle ActiveRecord et migration</h3>
<p>Dans le fichier <code>app.rb</code>, vous avez pu remarquer la ligne <code>require './model.rb'</code> : c'est dans ce fichier que je vais définir mon modèle ActiveRecord. Créons d'ores et déjà ce fichier, avec le modèle <code>User</code>. On le complètera ultérieurement :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="c1"># -- model.rb --</span>
<span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Notre modèle est prêt, l'accès à la base est configuré dans <code>app.rb</code>, mais nous n'avons encore créé aucune base de données. Allons-y, créons une migration :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>$ bundle exec rake db:create_migration NAME=create_users
</pre></td></tr></tbody></table></code></pre></div>
<p>Puis éditons cette migration pour créer une table <code>users</code> basique :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="c1"># /db/migrate/20180126153804_create_users.rb</span>
<span class="k">class</span> <span class="nc">CreateUsers</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="p">[</span><span class="mf">5.1</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">change</span>
<span class="n">create_table</span> <span class="ss">:users</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
<span class="n">t</span><span class="p">.</span><span class="nf">string</span> <span class="ss">:username</span>
<span class="n">t</span><span class="p">.</span><span class="nf">string</span> <span class="ss">:encrypted_password</span>
<span class="n">t</span><span class="p">.</span><span class="nf">timestamps</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Maintenant, on peut lancer la création de la base et la migration que l'on vient d'écrire :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>$ bundle exec rake db:create && bundle exec rake db:migrate
</pre></td></tr></tbody></table></code></pre></div>
<p>OK, not so bad. On est prêts à s'attaquer à la logique d'authentification.</p>
<h2 id="lauthentification-avec-warden">L'authentification avec Warden</h2>
<h3 id="configuration">Configuration</h3>
<p>Première étape, il faut configurer Warden et lui expliquer comment il est censé identifier nos utilisateurs. On pense tout d'abord bien à ajouter Warden aux requires de notre fichier <code>app.rb</code> :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">require</span> <span class="s1">'warden'</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Ajoutons maintenant un bloc de configuration de Warden dans notre fichier <code>app.rb</code> :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
</pre></td><td class="rouge-code"><pre><span class="c1"># Ne faites pas ça en prod ! Utilisez l'option `secret` + variable d'environnement</span>
<span class="c1"># Pour plus de détails : https://martinfowler.com/articles/session-secret.html</span>
<span class="n">use</span> <span class="no">Rack</span><span class="o">::</span><span class="no">Session</span><span class="o">::</span><span class="no">Cookie</span>
<span class="n">use</span> <span class="no">Warden</span><span class="o">::</span><span class="no">Manager</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
<span class="c1"># Comment on sauve l'information représentation l'utilisateur dans une</span>
<span class="c1"># session. On va stocker l'ID de l'utilisateur.</span>
<span class="n">config</span><span class="p">.</span><span class="nf">serialize_into_session</span><span class="p">{</span> <span class="o">|</span><span class="n">user</span><span class="o">|</span> <span class="n">user</span><span class="p">.</span><span class="nf">id</span> <span class="p">}</span>
<span class="c1"># Comment on retrouve l'utilisateur à partir de l'information</span>
<span class="c1"># récupérée depuis la session (l'id de l'utilisateur, donc)</span>
<span class="n">config</span><span class="p">.</span><span class="nf">serialize_from_session</span><span class="p">{</span> <span class="o">|</span><span class="nb">id</span><span class="o">|</span> <span class="no">User</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span> <span class="p">}</span>
<span class="c1"># Les "stratégies" définissent comment Warden détermine si une tentative</span>
<span class="c1"># d'authentification réussit ou échoue. On définira notre stratégie à la</span>
<span class="c1"># prochaine étape.</span>
<span class="c1"># "action" représente une route `POST` où l'on redirige l'utilisateur</span>
<span class="c1"># quand `warden.authenticate!` renvoie une réponse fausse</span>
<span class="n">config</span><span class="p">.</span><span class="nf">scope_defaults</span> <span class="ss">:default</span><span class="p">,</span> <span class="ss">strategies: </span><span class="p">[],</span> <span class="ss">action: </span><span class="s1">'/unauthenticated'</span>
<span class="c1"># Quand l'utilisateur essaie de se logger et n'y arrive pas, on doit</span>
<span class="c1"># spécifier vers quelle application on le redirige.</span>
<span class="c1"># NOTE: si on utilisait une application Sinatra modulaire, il faudrait</span>
<span class="c1"># indiquer ici le nom de la classe de l'application ou self</span>
<span class="n">config</span><span class="p">.</span><span class="nf">failure_app</span> <span class="o">=</span> <span class="no">Sinatra</span><span class="o">::</span><span class="no">Application</span>
<span class="k">end</span>
<span class="c1"># Ce callback va transformer toutes les requêtes d'échec en POST. Sans ça,</span>
<span class="c1"># une authentification échouée dans une route GET redirigerait vers GET /unauthenticated,</span>
<span class="c1"># une authentification échouée dans une route POST redirigerait vers POST /unauthenticated,</span>
<span class="c1"># et il faudrait gérer tous ces cas dans des routes spécicifiques</span>
<span class="no">Warden</span><span class="o">::</span><span class="no">Manager</span><span class="p">.</span><span class="nf">before_failure</span> <span class="k">do</span> <span class="o">|</span><span class="n">env</span><span class="p">,</span><span class="n">opts</span><span class="o">|</span>
<span class="n">env</span><span class="p">[</span><span class="s1">'REQUEST_METHOD'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'POST'</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<h3 id="stratgie">Stratégie</h3>
<p>OK, comme vous avez pu le voir dans mes commentaires ci-dessus, Warden se base sur un concept de "stratégies". Une stratégie contient la logique d'authentification qui permet à la gem de déterminer si une tentative de login réussit ou échoue.</p>
<p>Il est possible d'utiliser plusieurs stratégies d'authentification dans une application (si l'une échoue, Warden essaiera ensuite la suivante jusqu'à les avoir toutes épuisées), mais pour notre exemple, nous allons nous contenter d'une seule stratégie "mot de passe". Créons là maintenant :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre><span class="no">Warden</span><span class="o">::</span><span class="no">Strategies</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="ss">:password</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># Valide que les paramètres fournis permettent de tenter l'authentification.</span>
<span class="c1"># En l'occurrence, on s'assure de la présence d'un login et d'un mot de passe</span>
<span class="k">def</span> <span class="nf">valid?</span>
<span class="n">params</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span> <span class="o">&&</span> <span class="n">params</span><span class="p">[</span><span class="s1">'password'</span><span class="p">]</span>
<span class="k">end</span>
<span class="c1"># C'est là que la logique d'authentification se place</span>
<span class="k">def</span> <span class="nf">authenticate!</span>
<span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">find_by</span><span class="p">(</span><span class="ss">username: </span><span class="n">params</span><span class="p">[</span><span class="s1">'username'</span><span class="p">])</span>
<span class="k">if</span> <span class="n">user</span> <span class="o">&&</span> <span class="n">user</span><span class="p">.</span><span class="nf">authenticate</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="s1">'password'</span><span class="p">])</span>
<span class="n">success!</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="k">else</span>
<span class="nb">fail</span><span class="o">!</span><span class="p">(</span><span class="s2">"Could not log in"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Sans oublier d'ajouter notre stratégie flambant neuve au bloc de configuration de Warden :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="n">config</span><span class="p">.</span><span class="nf">scope_defaults</span> <span class="ss">:default</span><span class="p">,</span> <span class="ss">strategies: </span><span class="p">[</span> <span class="ss">:password</span> <span class="p">],</span> <span class="ss">action: </span><span class="s1">'/unauthenticated'</span>
</pre></td></tr></tbody></table></code></pre></div>
<h3 id="modle--logique-dauthentification-et-gestion-de-mot-de-passe">Modèle : logique d'authentification et gestion de mot de passe</h3>
<p>Vous avez vu précédemment dans la méthode <code>authenticate!</code> de notre nouvelle stratégie que l'on attend que le modèle utilisateur possède une méthode <code>authenticate</code> : occupons-nous maintenant de ça, et nous serons bientôt arrivés :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre><span class="c1"># -- ./models.rb --</span>
<span class="nb">require</span> <span class="s1">'sinatra/activerecord'</span>
<span class="nb">require</span> <span class="s1">'bcrypt'</span>
<span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="kp">include</span> <span class="no">BCrypt</span>
<span class="k">def</span> <span class="nf">password</span>
<span class="vi">@password</span> <span class="o">||=</span> <span class="no">Password</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">self</span><span class="p">.</span><span class="nf">encrypted_password</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">password</span><span class="o">=</span><span class="p">(</span><span class="n">new_password</span><span class="p">)</span>
<span class="vi">@password</span> <span class="o">=</span> <span class="no">Password</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="n">new_password</span><span class="p">)</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">encrypted_password</span> <span class="o">=</span> <span class="vi">@password</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">authenticate</span><span class="p">(</span><span class="n">attempted_password</span><span class="p">)</span>
<span class="n">password</span> <span class="o">==</span> <span class="n">attempted_password</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Rien de bien original, vous pourrez trouver ça <a href="https://github.com/codahale/bcrypt-ruby">sur le wiki de bcrypt</a> :)</p>
<h3 id="contrleurs-et-routes">Contrôleurs et routes</h3>
<p>On a déjà parcouru du chemin depuis le début de cet article. Nous avons déjà :</p>
<ul>
<li>Ajouté warden à notre application,</li>
<li>Configuré comment il sauve un utilisateur en session</li>
<li>Configuré comment il récupère l'utilisateur a partir de l'information stockée en session,</li>
<li>Défini la logique à utiliser pour authentifier un utilisateur.</li>
</ul>
<p>Occupons-nous maintenant de définir les contrôleurs et les routes (je vous laisserai gérer vous-mêmes layous et formulaires, si vous voulez voir un exemple complet, vous pouvez aller voir le <a href="https://github.com/pabuisson/blog-sinatra-warden">repository</a>) :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
</pre></td><td class="rouge-code"><pre><span class="c1"># Affiche le formulaire de login qui enverra une requête POST à /login</span>
<span class="n">get</span> <span class="s1">'/login'</span> <span class="k">do</span>
<span class="n">erb</span> <span class="ss">:login</span>
<span class="k">end</span>
<span class="c1"># Requête de login, qui va utiliser la logique définie dans nos stratégies.</span>
<span class="c1"># On accède à warden à partir de l'environnement rack, via `env['warden']`</span>
<span class="n">post</span> <span class="s1">'/login'</span> <span class="k">do</span>
<span class="n">env</span><span class="p">[</span><span class="s1">'warden'</span><span class="p">].</span><span class="nf">authenticate!</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:success</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'Logged in!'</span>
<span class="n">redirect</span><span class="p">(</span> <span class="n">session</span><span class="p">[</span><span class="ss">:return_to</span><span class="p">]</span> <span class="o">||</span> <span class="s1">'/protected'</span> <span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Quand l'utilisateur veut se déconnecter...</span>
<span class="n">get</span> <span class="s1">'/logout'</span> <span class="k">do</span>
<span class="n">env</span><span class="p">[</span><span class="s1">'warden'</span><span class="p">].</span><span class="nf">logout</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:success</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'Successfully logged out'</span>
<span class="n">redirect</span> <span class="s1">'/'</span>
<span class="k">end</span>
<span class="n">post</span> <span class="s1">'/unauthenticated'</span> <span class="k">do</span>
<span class="n">session</span><span class="p">[</span><span class="ss">:return_to</span><span class="p">]</span> <span class="o">=</span> <span class="n">env</span><span class="p">[</span><span class="s1">'warden.options'</span><span class="p">][</span><span class="ss">:attempted_path</span><span class="p">]</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:error</span><span class="p">]</span> <span class="o">=</span> <span class="n">env</span><span class="p">[</span><span class="s1">'warden'</span><span class="p">].</span><span class="nf">message</span> <span class="o">||</span> <span class="s1">'You must log in'</span>
<span class="n">redirect</span> <span class="s1">'/login'</span>
<span class="k">end</span>
<span class="c1"># La page à accès restreint : seuls les utilisateurs authentifiés</span>
<span class="c1"># ont la permission d'y accéder</span>
<span class="n">get</span> <span class="s1">'/protected'</span> <span class="k">do</span>
<span class="n">env</span><span class="p">[</span><span class="s1">'warden'</span><span class="p">].</span><span class="nf">authenticate!</span>
<span class="n">erb</span> <span class="ss">:protected</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<h2 id="testons-tout-cela">Testons tout cela</h2>
<h3 id="crons-un-utilisateur">Créons un utilisateur</h3>
<p>Toutes nos routes sont maintenant définies, il ne nous reste plus qu'à tester tout cela. Si vous avez cloné mon dépôt git, alors vous pouvez simplement lancer le seed avec la tâche <code>$ rake db:seed</code>. Sinon, ouvrons une console ruby pour créer cet utilisateur :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre>$ irb
irb(main):001:0> require './app.rb'
irb(main):002:0> u = User.create!(username: 'test')
irb(main):003:0> u.password = 'test'
irb(main):004:0> u.save!
</pre></td></tr></tbody></table></code></pre></div>
<h3 id="et-lanons-notre-application-">Et lançons notre application !</h3>
<p>Maintenant, nous voilà donc fin prêts à lancer notre application Sinatra, naviguer jusqu'à notre page de login, et essayer de nous connecter :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre>$ ruby app.rb
== Sinatra (v3.0.6) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
* Puma version: 6.2.2 (ruby 3.1.2-p20) ("Speaking of Now")
* Min threads: 0
* Max threads: 5
* Environment: development
* PID: 60403
* Listening on http://127.0.0.1:4567
* Listening on http://[::1]:4567
Use Ctrl-C to stop
</pre></td></tr></tbody></table></code></pre></div>
<figure>
<img src="/public/images/2018-01-28-warden-sinatra-demo.gif" alt="GIF - Démo Warden Sinatra" />
</figure>
<p>Pour plus de détails sur l'implémentation, vous pouvez bien évidemment aller voir le code lui-même, lancer l'application, la modifier et l'enrichir. J'espère en tout cas que ce billet vous aura convaincu que l'authentification avec Sinatra est finalement simple à mettre en place, une fois qu'on sait comment elle s'articule ! Encore une bonne raison de s'amuser avec Sinatra !</p>
<p>Bon Ruby !</p>
<hr />
<h2 id="rfrences">Références</h2>
<ul>
<li>Tout le code présenté dans ce billet est disponible dans ce dépot github : <a href="https://github.com/pabuisson/blog-sinatra-warden">pabuisson/blog-sinatra-warden</a></li>
<li><a href="https://github.com/hassox/warden">hassox/warden</a> : le github de Warden</li>
<li><a href="https://github.com/codahale/bcrypt-ruby">codahale/bcrypt-ruby</a> : le github de Bcrypt</li>
<li><a href="https://ericplayground.com/2016/02/26/authentication-with-warden-without-devise/">EricPlayground - Authentication with Warden, without Devise</a> : une des rares ressources à jour et complètes sur le sujet.</li>
<li>[<a href="https://sklise.com/2013/03/08/sinatra-warden-auth/">Authentication with Sinatra and Warden</a> par Steve Klise (utilise datamapper plutôt qu'activerecord)</li>
</ul>
Grape : custom validator conditionnel
https://blog.pabuisson.com/2017/07/grape-custom-validator-conditionnel/
2017-07-29T22:00:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>Depuis le début de l'année, j'ai passé une partie importante de mon temps à travailler sur la conception et le développement d'une API publique. C'est un vaste chantier, et, une fois passé le temps de la recherche, de la structuration et de la définition des grandes lignes, il était temps de passer à l'implémentation. Pour cela, j'ai choisi d'utiliser le gem <a href="https://github.com/ruby-grape/grape">Grape</a>, qui fournit un DSL facilitant le développement d'API dans une application Ruby.</p>
<h2 id="mon-besoin--appliquer-un-custom-validator-dans-certains-cas-seulement">Mon besoin : appliquer un custom validator dans certains cas seulement</h2>
<p>Il y a quelques semaines, j'ai eu un besoin un peu particulier : valider l'unicité d'un critère fourni en entrée par le client, et stocké dans un hstore.</p>
<p>Pour ce faire, je me suis appuyé sur une fonctionnalité de Grape qui permet de développer ses propres validateurs pour les données fournies dans la requête. Ce validateur est exécuté lorsque le point d'entrée est appelé, il valide (ou pas) les paramètres donnés et passe (ou pas, donc) au traitement du point d'entrée. Je l'ai déjà fait plusieurs fois sans aucun problème. Seulement dans ce cas, j'avais une contrainte supplémentaire que je n'avais pas encore rencontrée.</p>
<p>Mes paramètres sont en effet définis dans un helper et partagés entre différents points d'entrée. Néanmoins, cette validation ne devait s'appliquer que lors d'une requête POST. À partir de là, comment faire pour accéder dans mon validateur à la requête elle-même, et faire une "validation conditionnelle" ? Pas d'objet <code>request</code> disponible dans ce contexte malheureusement…</p>
<p>Et en creusant un peu, j'ai trouvé sur le github de Grape une issue <a href="https://github.com/ruby-grape/grape/issues/986">"Access request in custom validator"</a> qui m'a orienté vers la bonne piste. Mais comme la solution est assez peu détaillée, j'ai dû fouiller le code de Grape pour trouver la façon de faire que voici :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
</pre></td><td class="rouge-code"><pre><span class="k">module</span> <span class="nn">PublicApi</span>
<span class="k">module</span> <span class="nn">Validators</span>
<span class="c1"># Check that an `outer_id` does not yet exist</span>
<span class="c1"># on current records. Only performed on `POST` requests</span>
<span class="c1">#</span>
<span class="c1"># Usage:</span>
<span class="c1"># requires :parameter, outer_id: true</span>
<span class="c1">#</span>
<span class="k">class</span> <span class="nc">OuterId</span> <span class="o"><</span> <span class="no">Grape</span><span class="o">::</span><span class="no">Validations</span><span class="o">::</span><span class="no">Base</span>
<span class="c1"># Only execute the validation for POST endpoints</span>
<span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
<span class="n">validate!</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="nf">params</span><span class="p">)</span> <span class="k">if</span> <span class="n">request</span><span class="p">.</span><span class="nf">post?</span>
<span class="k">end</span>
<span class="c1"># Validation logic</span>
<span class="k">def</span> <span class="nf">validate_param!</span><span class="p">(</span><span class="n">attr_name</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span>
<span class="n">attr_value</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span> <span class="n">attr_name</span> <span class="p">]</span>
<span class="n">already_exists</span> <span class="o">=</span> <span class="no">MyModel</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"keys -> 'outer_id'=?"</span><span class="p">,</span> <span class="n">attr_value</span><span class="p">)</span>
<span class="p">.</span><span class="nf">exists?</span>
<span class="k">if</span> <span class="n">already_exists</span>
<span class="nb">fail</span> <span class="no">Grape</span><span class="o">::</span><span class="no">Exceptions</span><span class="o">::</span><span class="no">Validation</span><span class="p">,</span>
<span class="ss">params: </span><span class="p">[</span> <span class="vi">@scope</span><span class="p">.</span><span class="nf">full_name</span><span class="p">(</span><span class="n">attr_name</span><span class="p">)</span> <span class="p">],</span>
<span class="ss">message: </span><span class="s2">"A record with 'outer_id'=</span><span class="si">#{</span> <span class="n">attr_value</span> <span class="si">}</span><span class="s2"> already exists."</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>La méthode <code>validate(request)</code> prend un paramètre <code>request</code> en entrée et permet de faire n'importe quelle vérification sur cet objet avant de lancer la validation des paramètres elle-même, qui est réalisée par la méthode <code>validate!(params)</code>, qui elle-même appelle la méthode <code>validate_param!(attr_name, params)</code> que vous overridez dans votre validateur.</p>
<p>Ce n'est pas forcément un cas d'utilisation très courant, et je ne suis pas sûr qu'il soit particulièrement conseillé de multiplier les validateurs de ce type, mais dans certaines situations, ça peut sans doute s'avérer très utile !</p>
Recherchez dans votre code avec Ack
https://blog.pabuisson.com/2017/02/recherchez-dans-votre-code-avec-ack/
2017-02-01T20:05:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>Si comme moi vous passez la majeure partie de votre temps à éditer du texte ou des documents, vous générez beaucoup de contenu, et vous avez régulièrement besoin de rechercher dans ce contenu. Et il s'avère que rechercher dans un projet de développement présente des spécificités pour lesquelles des outils génériques comme Spotlight montrent vite leurs limites. Dans un environnement unix, vous possédez déjà de plusieurs outils très puissants, parmi lesquels :</p>
<ul>
<li><code>grep</code> : très connu et polyvalent, supporte pas mal d'options (notamment des regexp). Mais personnellement, je n'ai jamais réussi à mémoriser la syntaxe, les options, l'ordre des paramètres, et la façon de s'en servir.</li>
<li><code>find</code> : puissant mais complexe, bien pour trouver et automatiser des actions sur les fichiers recherchés, mais pas simple à utiliser (il faut combiner un bon nombre d'options).</li>
</ul>
<h2 id="besoins">Besoins</h2>
<ul>
<li><strong>Recherche rapide</strong> : j'ai de nombreux projets de taille modeste, mais également certains qui comptent plusieurs dizaines de milliers de fichiers. Il fallait donc que l'outil que j'allais trouver soit très rapide. Idéalement, aussi rapide que les recherches lancées à partir d'éditeurs comme Sublime Text ou Atom.</li>
<li><strong>Ignorer les fichiers "inutiles"</strong> : fichiers liés à mon gestionnaire de versioning, fichiers binaires, etc.</li>
<li><strong>Facile à comprendre et mémoriser</strong> : <code>find</code> et <code>grep</code> sont puissants mais je suis incapable de mémoriser leurs syntaxes, donc je recherchais un outil plus simple à manipuler au quotidien</li>
<li><strong>Utilisation des regexps</strong> : je ne vous ai pas dit ? J'adore les regexps :)</li>
<li><strong>Compatible avec vim</strong> : vim, what else ?</li>
</ul>
<h2 id="ack--beyond-grep">Ack : "beyond grep"</h2>
<p>Sur le <a href="https://beyondgrep.com/">site officiel de ack</a>, le ton est donné très vite : <em>"ack is a tool like grep, optimized for programmers"</em>. Ça s'annonce plutôt bien ! On trouve également en première page les 5 points forts de cet outil :</p>
<blockquote>
<ul>
<li>Blazing fast : It's fast because it only searches the stuff it makes sense to search.</li>
<li>Better search : Searches entire trees by default while ignoring Subversion, Git and other VCS directories and other files that aren't your source code.</li>
<li>Designed for code search : Where grep is a general text search tool, ack is especially for the programmer searching source code. Common tasks take fewer keystrokes.</li>
<li>Highly portable : ack is pure Perl, so it easily runs on a Windows installation Perl (like Strawberry Perl) without modifications.</li>
<li>Free and open : Ack costs nothing. It's 100% free and open source under Artistic License v2.0.</li>
</ul>
</blockquote>
<p>Autant dire que ça semble répondre parfaitement à mes critères. Maintenant, un petit coup de <code>brew</code>, <code>apt-get</code>, <code>pacman</code> et consorts pour <a href="https://beyondgrep.com/install/">installer ack</a>, et on va voir un peu comment on se sert de cette petite merveille.</p>
<h2 id="utilisation">Utilisation</h2>
<p>Une commande de recherche ack se structure ainsi :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>$ ack [OPTIONS] MOTIF [FICHIERS] [TYPES-DE-FICHIERS]
</pre></td></tr></tbody></table></code></pre></div>
<ul>
<li><code>MOTIF</code> : le motif que vous recherchez. Il peut s'agir d'une chaîne toute simple (<code>ack find_by</code>) ou d'une regexp (<code>ack input.*autofocus</code>)</li>
<li><code>OPTIONS</code> : elles sont assez peu nombreuses et bien pensées, ce qui les rend très faciles à mémoriser (si vous utilisez grep, plusieurs options sont communes aux 2 outils, vous ne serez pas dépaysé). Je vous laisse vous référer <a href="https://beyondgrep.com/documentation/">à la documentation en ligne</a> pour une liste complète. Pour ma part, j'utilise souvent <code>-i / --ignore-case</code> pour ne pas prendre la casse en compte, mais il y a quelques autres options notables comme <code>-v</code> pour renvoyer les lignes <em>qui ne matchent pas</em>, ou <code>-w</code> pour rechercher uniquement des correspondances sur des mots entiers. Pour ce dernier cas, j'aurais plutôt tendance à utiliser le délimiteur <code>\b</code> des regexp, question d'habitude.
<ul>
<li>En parlant de casse, ack a une gestion de la casse intelligente, avec l'option… <code>--smart-case</code>. Si le motif recherché est entièrement en minuscule, alors il ne prend pas la casse en compte : <code>ack --smart-case post</code> recherchera <code>post / POST / pOsT</code>, etc. En revanche, si le motif recherché comporte au moin une majuscule, alors la recherche sera "case-sensitive". C'est très pratique à l'usage !</li>
</ul>
</li>
<li><code>FICHIERS</code> : fichiers ou répertoires auxquels on limite la recherche, je ne vous fais pas un dessin.</li>
<li><code>TYPES-DE-FICHIERS</code> : il est possible de filtrer les types de fichiers sur lesquels va porter une recherche. Ainsi, <code>--ruby</code> recherche dans tous les fichiers Ruby, et on peut également exclure certains types de fichiers avec <code>--noruby</code>, qui va rechercher dans tous les fichiers sauf les fichiers ruby. Et c'est tout simplement ultra-pratique ! Bon, en lisant ça vous vous dites sûrement : <em>"mouais… pourquoi pas simplement un pattern de recherche en regexp ?"</em>. Justement : là où ça s'avère intéressant, c'est que ces types des fichiers sont intelligement pensés : un type de fichier correspond ainsi à un ensemble de patterns. Ainsi, pour reprendre le cas de ruby, le type de fichier <code>--ruby</code> matche tout ça : <code>.rb .rhtml .rjs .rxml .erb .rake .spec; Rakefile; first line matches /^#!.*\bruby/</code>. Wow ! Si on voulait faire ça à la main à chaque recherche, ce serait un peu lourd n'est-ce pas ? Bref, vous l'avez compris, j'adore vraiment cette feature de <code>ack</code>. Et évidemment on peut combiner plusieurs types de fichiers différents. Et ça n'est pas tout…</li>
</ul>
<h2 id="cration-de-types-de-fichiers-custom">Création de types de fichiers custom</h2>
<p>Ack fournit de nombreux types de fichiers préconfigurés (80 chez moi à l'heure où j'écris ces lignes, avec ack 2.14) : vous pouvez voir le détail des différents types de fichiers avec la commande <code>ack --help-types</code>. Mais vous avez peut-être des besoins particuliers, des types de fichiers custom propres à un projet ou à votre environnement. Et c'est là que ça devient encore plus intéressant : ack vous laisse la main là-dessus et vous permet, si vous le souhaitez, de créer de nouveaux types de fichiers, ou de modifier des types existants.</p>
<p>Par exemple, j'ai défini les types de fichiers suivants dans mon fichier <code>~/.ackrc</code> avec l'option <code>--type-set</code> :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre># ~/.ackrc
--type-set=haml=.haml
--type-set=slim=.slim
--type-set=sass=.sass
--type-set=scss=.scss
--type-set=coffee=.coffee
</pre></td></tr></tbody></table></code></pre></div>
<p>Peut-être aussi que vous voudrez customiser les types de fichiers existants : pour cela, vous pouvez utiliser l'option <code>--type-add=filetype=pattern</code> qui permet d'ajouter le motif <code>pattern</code> au type de fichier <code>filetype</code>. Voici un autre exemple tiré de mon fichier de config :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre># ~/.ackrc
--type-add=js=.js.erb,.coffee,.coffee.erb
</pre></td></tr></tbody></table></code></pre></div>
<p>Lorsque je fais une recherche avec le filetype <code>--js</code>, cette ligne me permet donc de rechercher dans mes fichiers <code>.js</code>, mais aussi dans mes fichiers <code>.coffee</code> ainsi que dans les fichiers de scripts <code>.erb</code> dont on a parfois besoin en Rails.</p>
<p>Comme ça, plus de risque d'oublier des fichiers avec une regexp custom, vous définissez vos types de fichiers une fois pour toutes et vous êtes sûr que vos recherches gèrent bien tous les cas de figure !</p>
<figure>
<img src="http://i.giphy.com/k39w535jFPYrK.gif" alt="Thumbs up !" />
</figure>
<h2 id="ackvim">Ack.vim</h2>
<p>Je pense que vous commencez à comprendre pourquoi je trouve cet outil si pratique. Vous savez peut-être que je suis également un grand fan de vim (plus exactement <a href="https://neovim.io/">neovim</a> depuis de nombreux mois). Et justement, parmi les features qui font défaut à vim, il y a la recherche dans un projet entier. Encore une fois, <code>ack</code> vient à notre rescousse ! Il fut un temps où la meilleure solution était de créer des fonctions personnalisées pour lancer des recherches <code>ack</code> dans le shell, mais aujourd'hui, le plus simple est d'utiliser le <a href="https://github.com/mileszs/ack.vim">plugin quivabieng ©</a> : <code>ack.vim</code>. Je vous laisse vous référer à la <a href="https://github.com/mileszs/ack.vim#installation">documentation pour l'installation</a>, rien de bien compliqué.</p>
<h3 id="utilisation-1">Utilisation</h3>
<p>Côté utilisation, c'est bien simple : si vous savez utiliser <code>ack</code> en ligne de commande, alors vous savez utiliser <code>Ack.vim</code>. Tout fonctionne exactement de la même façon, mêmes options, mêmes comportements. Seule différence que j'ai repérée, je suis obligé d'utiliser des quotes autour de mon motif s'il s'agit d'une regexp. Mis à part ça, tout est identique, et vous pouvez totalement utiliser des commandes comme celles-ci :</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>:Ack! find_by --ruby spec/
:Ack! select -i -w --nolog
:Ack! "input.*type=('|\")(checkbox|radio)" --html
</pre></td></tr></tbody></table></code></pre></div>
<p>Les résultats sont ensuite affichés dans une fenêtre de type "quickfix", et vous pouvez facilement les parcourir avec les commandes habituelles de déplacement, et ouvrir chaque fichier avec la touche entrée.</p>
<h3 id="configuration">Configuration</h3>
<p>Voici la configuration que j'utilise. Le plugin ack.vim a 2 commandes :</p>
<ul>
<li><code>:Ack</code> : la liste des résultats s'affiche dans une fenêtre quifix, et le 1er résultat s'ouvre directement dans un nouveau buffer.</li>
<li><code>:Ack!</code> : la liste des résultats s'affiche dans une fenêtre quickfix, mais aucun résultat ne s'ouvre tant que ne le demandez pas (et c'est selon moi beaucoup plus logique comme façon de fonctionner).</li>
</ul>
<p>J'ai donc défini les 2 abbréviations suivantes en mode ligne de commande, pour que toutes mes saisies de la commande <code>Ack</code> soit remplacées par <code>Ack!</code>. Et comme c'était pénible de taper le <code>A</code> en majuscule, j'ai également mis une abbréviation pour que <code>ack</code> soit transformé en <code>Ack!</code>. Et hop :)</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>" Map Ack command to Ack! automatically so that
" it doesn't open the first item by default
ca Ack Ack!
ca ack Ack!
</pre></td></tr></tbody></table></code></pre></div>
<hr />
<p>Bref, ack est un outil dont je me sers depuis plusieurs années et des dizaines de fois chaque jour, et je pourrais difficilement envisager de m'en passer aujourd'hui. J'espère que cet article vous aura donné envie d'essayer, et peut-être améliorera un peu votre quotidien, comme ç'a été le cas pour moi !</p>
<h2 id="rfrences">Références</h2>
<ul>
<li><a href="https://beyondgrep.com/">beyondgrep.com</a> : le site officiel du projet ack</li>
<li><a href="https://bitbucket.org/snippets/pabuisson/gpR8y">mon ackrc</a></li>
<li><a href="https://github.com/mileszs/ack.vim">ack.vim</a></li>
<li><a href="https://github.com/ggreer/the_silver_searcher">Ag / the silver searcher</a> : un fork de ack qui a fait son chemin depuis. Encore plus performant mais moins bien documenté, et je ne suis pas sûr qu'il permette de faire tout ce que fait Ack (notamment personnaliser les types de fichier). Un outil intéressant en tout cas.</li>
</ul>
Firefox : filtrer les résultats de la barre d'adresse
https://blog.pabuisson.com/2016/12/firefox-filtres-awesome-bar/
2016-12-13T05:50:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>Voilà déjà un moment que je voulais vous présenter cette astuce de Firefox qui, à mon avis, n'est pas très connue mais très pratique. Pourquoi maintenant ? Parce que cette astuce est en lien avec la barre d'adresse de Firefox (la bien-nommée “Awesome Bar”) qui a récemment une refonte importante de son design. Si vous êtes comme moi et que vous utilisez Firefox depuis plusieurs années au quotidien, parfois plusieurs heures par jour (je pousse même le vice à utiliser plusieurs versions différentes de Firefox : Beta pour mon surf personnel, et <a href="https://www.mozilla.org/fr/firefox/developer/">Developer Edition</a> pour le surf "boulot" et le debug)… si vous êtes dans ce cas, toute astuce pour améliorer votre expérience utilisateur est bonne à prendre !</p>
<p>Pour ma part, et après que les services de stockage en ligne de favoris aient tous plus ou moins
périclités, je suis revenu aux favoris classiques de Firefox, synchronisés entre mes navigateurs et
machines avec Firefox Sync. J'ai donc beaucoup de favoris, un historique important, si on ajoute à cela les suggestions des moteurs de recherche (<a href="/2014/06/duckduckgo-moteur-recherche-vous-espionne-pas/">DuckDuckGo</a> par exemple), alors on obtient un vaste bazar dès qu'on commence à saisir quelque chose dans la barre d'adresse. De la même façon, lorsque vous souhaitez retrouver une page particulière de votre historique ou de vos favoris, vous en êtes bien souvent réduits à aller voir les interfaces dédiées. Mais heureusement, Firefox est malin, et vous offre une possibilité cacher de filtrer tout cela.</p>
<figure>
<img src="/public/images/2016-12-13-firefox-awesome-bar-filter.png" alt="Firefox Awesome Bar % filter" />
</figure>
<p>Il vous suffit pour ce faire d'ajouter le caractère suivant dans la requête que vous saisissez dans
la barre d'adresse :</p>
<ul>
<li><code>^</code> permet de n'afficher que des résultats de l'historique,</li>
<li><code>*</code> permet de n'afficher que des résultats des favoris,</li>
<li><code>%</code> permet de rechercher dans les onglets actuellement ouverts (très pratique quand vous en avez
beaucoup 👼)</li>
<li><code>+</code> permet de n'afficher que les résultats taggés,</li>
<li><code>#</code> permet de ne chercher que dans les titres de pages,</li>
<li><code>@</code> permet de ne rechercher que dans les URLs.</li>
</ul>
<p>Pour ma part, j'utilise principalement <code>*</code> et <code>%</code>.</p>
<p>Et autre chose, j'allais oublier: vous pouvez bien évidemment combiner certains de ces filtres, pour n'afficher par exemple que les résultats de votre historique, en filtrant uniquement par l'URL ! Si avec ça vous n'arrivez pas à trouver la page à laquelle vous souhaitez accéder, je ne peux plus rien pour vous 🙂</p>
<hr />
<h2 id="rfrences">Références</h2>
<ul>
<li><a href="https://support.mozilla.org/en-US/kb/awesome-bar-search-firefox-bookmarks-history-tabs">Mozilla Support : awesome bar search</a></li>
</ul>
Javascript : un debug plus facile avec le blackboxing
https://blog.pabuisson.com/2016/06/javascript-debug-facile-avec-blackboxing/
2016-06-25T22:00:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>Comme beaucoup de développeurs web avant le boom des frameworks front-end, mon utilisation de Javascript se limitait à envoyer des requêtes en Ajax et injecter leur résultat dynamiquement dans des pages, à modifier le DOM et à utiliser quelques plugins ou librairies. Même si j'avais entendu parler des SPA et des nouveaux frameworks Javascript, je n'avais jamais encore eu l'occasion de vraiment les utiliser ou d'écrire une application entière avec ces technos. Les outils de debug du monde JS m'étaient donc quasiment inconnus il y a encore 3 ans. Rapidement après avoir intégré l'équipe <a href="https://altagem.com">Altagem</a>, et pour pouvoir travailler efficacement au quotidien sur une application AngularJS conséquente (environ 20.000 LOC sans compter les specs), je m'y suis mis avec bonheur : j'ai tout d'abord découvert <code>debugger</code>, et depuis, finis le debug "artisanal" avec <code>console.log</code>, et à moi les joies de <em>step in</em>, <em>step over</em> et autres !</p>
<p>Mais (car il y a un mais), lorsque je devais procéder à un debug pas-à-pas, je me suis souvent retrouvé confronté au problème posé par les librairies tierces : effectivement, lorsqu'on lance un debug pas-à-pas, le debugger passe dans tous les fichiers et toutes les portions de code de la pile d'appel, y compris ceux qui appartiennent à des librairies tierces (au choix: jQuery, AngularJS, emberJS, lodash, momentjs..). Or, la majorité du temps, il nous est totalement inutile de débugger ces fichiers là, et passer dans ces fichiers ne fait que ralentir notre process de debug. Heureusement, les navigateurs majeurs ont apporté une solution à ce problème avec le "blackboxing de scripts".</p>
<p>En quoi ça consiste ? Tout simplement, vous indiquez à votre debugger certains scripts ou répertoires qu'il doit considérer comme des boîtes noires. Lors d'une session de debug, il ignorera totalement ces scripts et votre pas-à-pas passera outre les portions de code contenues dans ces fichiers.</p>
<h2 id="comment-a-se-configure-">Comment ça se configure ?</h2>
<p>D'une façon générale, vous indiquez à votre navigateur quels fichiers ignorer, au cas par cas. Selon les navigateurs, il est aussi possible de définir des pattern de fichiers qui seront toujours ignorés, pour tous les projets sur lesquels vous travaillez.</p>
<h3 id="dans-firefox">Dans Firefox</h3>
<p>Firefox a été le premier a implémenter cette fonctionnalité, et fournit deux options différentes de définir les scripts blackboxés:</p>
<h4 id="mthode-1--script-par-script">Méthode 1 : script par script</h4>
<p>Il suffit de se rendre dans l'onget "Débogueur", de sélectionner un des scripts de la page, et de cliquer sur l'icône en forme d'oeil en bas: à partir de là, ce script est blackboxé. Si vous essayez d'afficher son contenu, vous avez un message vous indiquant qu'il est blackboxé, et son nom est légèrement grisé dans la liste des sources.</p>
<figure>
<img src="/public/images/2016-06-26-firefox-blackboxing.png" alt="Firefox - Blackbox a script" />
</figure>
<p>À noter l'option "Mettre automatiquement dans une boîte noire les scripts compactés", qui peut être utile (selon votre configuration, mais globalement on débugge en local, et les scripts minifiés localement sont souvent des ressources tierces).</p>
<figure>
<img src="/public/images/2016-06-26-firefox-automatic-blackbox-minified.png" alt="Firefox - Automatically blackbox minified scripts" />
</figure>
<h4 id="mthode-2--en-ligne-de-commande-avec-la-developer-toolbar">Méthode 2 : en ligne de commande avec la "developer toolbar"</h4>
<p>Firefox fournit une autre façon de configurer cela, via la "developer toolbar" (dont je viens de
découvrir l'existence en rédigeant cet article). Il suffit pour cela d'afficher cette ligne de
commande via "Shift+F2" ou en sélectionnant "Barre de développement" dans le menu "Outils >
développement web". La barre s'affiche et vous pouvez alors y utiliser la commande <code>dbg blacbkox</code> pour saisir des patterns à blackboxer (on est d'accord, c'est la version geek 🙃 )</p>
<figure>
<img src="/public/images/2016-06-26-firefox-blackbox-developer-toolbar.png" alt="Firefox - Developer toolbar dbg blackbox" />
</figure>
<h3 id="dans-chrome-opera">Dans Chrome, Opera</h3>
<p>Dans Chrome et les autres navigateurs utilisant Webkit / Blink, la configuration se fait de façon centralisée, en définissant des patterns blackboxés pour tous les sites que vous debuggerez. Pour cela, il faut donc simplement vous rendre dans la partie "Settings > Blackboxing", et ajouter une liste de patterns que vous souhaitez blackboxer. Et voilà !</p>
<figure>
<img src="/public/images/2016-06-26-chrome-blacbkox-patterns.png" alt="Chrome - Blackbox settings" />
</figure>
<h2 id="que-se-passe-t-il-quand-une-source-est-blackboxe-">Que se passe-t-il quand une source est blackboxée ?</h2>
<p>Tout ça c'est bien beau, vous avez blackboxé des scripts mais… so what ? Hé bien désormais,
lorsque vous serez dans une session de debug:</p>
<ul>
<li>Les scripts blackboxés sont cachés dans la pile d'appels, ce qui vous permet d'identifier plus
rapidement les éléments de votre code qui entrent en jeu.</li>
<li>Si vous debuggez en mode pas à pas, vous ne passerez plus dans les scripts blackboxés… Finis
donc les pas-à-pas interminables qui vous emmènent dans les tréfonds du code de AngularJS ou
jQuery !</li>
<li>Les éventuels points d'arrêt présents dans ces fichiers seront ignorés.</li>
<li>Si vous utilisez l'option <em>"Pause on exceptions"</em>, alors le debugger ne s'arrêtera pas si
l'exception est lancée par un script blacbkboxé, et attendra que l'exception remonte
(éventuellement) dans un script non blackboxé.</li>
</ul>
<p>J'espère que cette astuce vous permettra des sessions de debug moins pénibles et plus efficaces ! À noter que je n'ai pas trouvé d'option pour blackboxer des scripts dans Safari.. si jamais vous
savez si/comment on peut faire ça, ça m'intéresse, n'hésitez pas à laisser un commentaire.</p>
<hr />
<h2 id="sources">Sources</h2>
<ul>
<li><a href="https://hacks.mozilla.org/2013/08/new-features-of-firefox-developer-tools-episode-25/">New features of Firefox Developer Tools - Episode 25 - Mozilla Hacks</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Black_box_a_source">Blackbox a source – Mozilla Developer Network</a></li>
<li><a href="https://developers.google.com/web/tools/chrome-devtools/debug/breakpoints/step-code#blackbox-third-party-code">How to step through the code – Google Developers</a></li>
</ul>
Déployer Middleman avec Mina
https://blog.pabuisson.com/2015/07/deployer-middleman-avec-mina/
2015-07-11T17:15:00+00:00
2023-12-03T19:59:57+00:00
Pierre-Adrien Buisson
<p>Si vous êtes des lecteurs réguliers de ce blog, vous savez probablement qu'il est propulsé par le générateur de sites statiques Middleman : j'ai déja rédigé <a href="/2014/03/un-blog-statique-avec-middleman/">différents</a> <a href="/2014/03/middleman-design-avec-slim-scss/">articles</a> <a href="/2014/03/une-sitemap-pour-middleman-avec-builder/">sur ce sujet</a>), n'hésitez pas à aller les parcourir. Néanmoins, je n'ai encore jamais évoqué un autre outil que j'utilise dans mon workflow pour déployer ce blog, ainsi que la quasi-totalité de mes projets : <a href="http://nadarei.co/mina/">Mina</a>.</p>
<hr />
<h2 id="mina-un-capistrano-light">Mina, un Capistrano light</h2>
<p>À l'époque où je cherchais des outils pour déployer facilement mes différents projets (un peu comme vous pouvez le faire avec les outils de heroku), je me suis bien entendu penché sur l'inévitable <a href="https://github.com/capistrano/capistrano">Capistrano</a>. Comme on peut le voir sur Ruby-Toolbox, ce gem est de loin le plus utilisé dans l'univers ruby pour automatiser le déploiement de web applications.</p>
<figure>
<img src="/public/images/2015-07-11-ruby-toolbox-deployment-automation.png" alt="Ruby Toolbox - Deployment automation" />
</figure>
<p>Néanmoins, lors de mes différents essais, je n'avais pas eu beaucoup de succès avec Capistrano malgré de nombreuses tentatives : le gem venait tout juste de passer en version 3, la majorité des documentations et tutoriels que je trouvais n'était pas à jour, et, il faut l'avouer, ma connaissance limitée de ce sujet n'aidait pas. Je me suis donc mis à la recherche d'outils comparables, mais plus simples à configurer et utiliser. Et c'est ainsi que j'ai fait la découverte de Mina, un excellent projet initié par le studio Nadarei.co.</p>
<h2 id="construire-et-dployer-un-site-middleman-avec-mina">Construire et déployer un site Middleman avec Mina</h2>
<p>Si vous ne connaissez pas du tout ce gem, je vous laisse aller jeter un oeil au <a href="https://github.com/mina-deploy/mina">README disponible sur le dépôt du projet</a>, il explique en détails les étapes à suivre pour utiliser Mina dans votre projet. Une fois votre serveur configuré et près à recevoir vos déploiements, toute la configuration de Mina est décrite dans le fichier <code>config/deploy.rb</code>. C'est ce fichier que je me propose de partager avec vous, afin de vous montrer à quel point Mina est simple d'accès :</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
</pre></td><td class="rouge-code"><pre><span class="nb">require</span> <span class="s1">'mina/bundler'</span>
<span class="nb">require</span> <span class="s1">'mina/git'</span>
<span class="nb">require</span> <span class="s1">'mina/rvm'</span>
<span class="n">set</span> <span class="ss">:domain</span><span class="p">,</span> <span class="s2">"my.server.com"</span>
<span class="n">set</span> <span class="ss">:user</span><span class="p">,</span> <span class="s2">"deploy-user"</span>
<span class="n">set</span> <span class="ss">:deploy_to</span><span class="p">,</span> <span class="s2">"/var/www/my-blog"</span>
<span class="n">set</span> <span class="ss">:repository</span><span class="p">,</span> <span class="s2">"git@my.git.repository.git"</span>
<span class="n">set</span> <span class="ss">:branch</span><span class="p">,</span> <span class="s2">"master"</span>
<span class="c1"># RVM: Environment loaded for most commands, such as `mina deploy` or `mina rake`.</span>
<span class="n">task</span> <span class="ss">:environment</span> <span class="k">do</span>
<span class="n">set</span> <span class="ss">:rvm_path</span><span class="p">,</span> <span class="s1">'/usr/local/rvm/scripts/rvm'</span>
<span class="n">invoke</span> <span class="ss">:'rvm:use[ruby-2.0.0-p247]'</span>
<span class="k">end</span>
<span class="n">desc</span> <span class="s2">"Deploys the current version to the server."</span>
<span class="n">task</span> <span class="ss">:deploy</span> <span class="o">=></span> <span class="ss">:environment</span> <span class="k">do</span>
<span class="n">deploy</span> <span class="k">do</span>
<span class="n">invoke</span> <span class="ss">:'git:clone'</span>
<span class="n">invoke</span> <span class="ss">:'bundle:install'</span>
<span class="n">queue</span> <span class="s2">"bundle exec middleman build"</span>
<span class="c1"># Only keep the last 4 releases</span>
<span class="n">set</span> <span class="ss">:keep_releases</span><span class="p">,</span> <span class="mi">4</span>
<span class="n">invoke</span> <span class="ss">:'deploy:cleanup'</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Comme vous le voyez, le fichier de configuration de Mina est particulièrement simple à comprendre : je déclare tout d'abord le domaine et l'utilisateur avec lesquels je vais effectuer ma connexion SSH au serveur, le répertoire où je vais déployer mon projet, le dépôt et la branche que je vais récupérer lors du déploiement. Mina prépare son environnement (en l'occurrence, il s'assure de bien utiliser la bonne version de Ruby) puis procède au déploiement en tant que tel:</p>
<ol>
<li>Clone du projet git,</li>
<li><code>bundle install</code>,</li>
<li><code>middleman build</code> pour générer le site statique à partir du source,</li>
<li>Nettoyage des anciennes versions déployées.</li>
</ol>
<p>Avec cet outil, déployer un site middleman se fait donc tout simplement avec un simple <code>mina deploy</code> : le site est généré sur votre serveur, votre historique git est propre (pas besoin de committer votre répertoire <code>build</code>)…c'est un peu magique !</p>
<p>Bien entendu, cela suppose que vous disposiez sur la machine distante d'un SSH et d'un serveur web correctement configuré (au hasard: l'excellent <a href="https://www.nginx.com">nginx</a>) et prêt à servir les fichiers HTML qui seront générés par la commande <code>middleman build</code>… mais ça dépasse le cadre de cet article.</p>
<hr />
<h2 id="rfrences">Références</h2>
<ul>
<li><a href="http://nadarei.co/mina/">Mina: really fast deployer and server automation tool</a></li>
<li><a href="https://github.com/mina-deploy/mina/blob/master/Readme.md">Mina README: user guide</a></li>
<li><a href="http://alii.pro/blog/2014/07/29/deploy-jekyll-with-mina/">Deploy Jekyll with Mina</a> par Ali Ismayilov</li>
</ul>