<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: arathunku</title>
    <description>The latest articles on DEV Community by arathunku (@arathunku).</description>
    <link>https://dev.to/arathunku</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F32556%2F9c8e648d-51ed-4a6f-ba0c-f94b5323c583.jpeg</url>
      <title>DEV Community: arathunku</title>
      <link>https://dev.to/arathunku</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arathunku"/>
    <language>en</language>
    <item>
      <title>FIT parsing and code generation in Elixir</title>
      <dc:creator>arathunku</dc:creator>
      <pubDate>Mon, 19 Feb 2024 13:00:00 +0000</pubDate>
      <link>https://dev.to/arathunku/fit-parsing-and-code-generation-in-elixir-1kb1</link>
      <guid>https://dev.to/arathunku/fit-parsing-and-code-generation-in-elixir-1kb1</guid>
      <description>&lt;p&gt;Flexible and Interoperable Data Transfer (FIT) developed by Garmin is a format used to store activity/workout files. Garmin provides SDKs in many languages, and there are even more various libraries that are based on its specification, some open source, some not. The specification consists of &lt;a href="https://developer.garmin.com/fit/protocol/"&gt;this FIT Protocol page&lt;/a&gt; and a huge spreadsheet in downloadable SDK describing types and messages the FIT file may contain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PY3w5Gjf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://arathunku.com/b/2024/fit-parsing-and-code-generation-in-elixir/Pasted_image_20240219141732.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PY3w5Gjf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://arathunku.com/b/2024/fit-parsing-and-code-generation-in-elixir/Pasted_image_20240219141732.png" alt="Pasted_image_20240219141732.png" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the end of last year, I’ve started implementing a FIT parser in pure Elixir. Excellent Erlang binary pattern matching made it very pleasant to do and at this moment, the decoder can parse many FIT files. Some encoders don’t fully follow the specification to the dot. I’ve scavenged various FIT files from issue reports, examples in SDKs and my own years of running. I wanted to fully support anything the file may contain - components, dev fields, subfields and it does! Mostly matching what official &lt;a href="https://developer.garmin.com/fit/fitcsvtool/"&gt;&lt;code&gt;fit2csv&lt;/code&gt;&lt;/a&gt; tool is outputting.&lt;/p&gt;

&lt;p&gt;This post is about a specific aspect of being able to even start parsing a FIT file, “Profile”. “Profile” is based on the spreadsheet in SDK. It describes all the various types of fields, records, their type, scales, offsets, bit offsets and more. This spreadsheet is also used to generate all encoders and decoders in official SDK for C, C++, Java, Python, JavaScript and more. Such profile in official SDK in JavaScript is whooping 25k (yes, k) lines of code, similar amount in Python.&lt;/p&gt;

&lt;p&gt;I also explored &lt;a href="https://github.com/stadelmanma/fitparse-rs/blob/master/fitparser/src/profile/field_types.rs"&gt;fitparser in Rust&lt;/a&gt; (almost 30k lines), and &lt;a href="https://github.com/tormoder/fit/blob/master/profile.go"&gt;fit in Go&lt;/a&gt;(only 1.5k lines!). In my extremely naive attempt in Elixir so far, my profile is around 45k lines, that’s a lot of lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking shortcuts with &lt;code&gt;inspect/2&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The very basic and simplified input for codegen looks somewhat like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;types = [
 ["activity", "enum", [["manual", 0], ["auto_multi_sport", 1]]],
 [
 "activity_class",
 "enum",
 [
 ["level_max", 100],
 ["level", 127],
 ["athlete", 128, "0 to 100"]
 ]
 ],
 ["workout_hr", "uint32", [["bpm_offset", 100]]],
 ["weight", "uint16", [["calculating", 65534]]]
 # ... 2000 more types
]

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

&lt;/div&gt;



&lt;p&gt;In reality, it’s nowhere like this and it’s coming from &lt;code&gt;xlsx&lt;/code&gt; file, but it should be enough to imagine what we’re working with.&lt;/p&gt;

&lt;p&gt;Each element of the list is a field with its type and values. Example, for the first element, &lt;code&gt;activity&lt;/code&gt; field is always &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt; and each values corresponds to specific label after parsing - &lt;code&gt;manaul&lt;/code&gt; or &lt;code&gt;auto_multi_sport&lt;/code&gt; in this case. In FIT file there actually won’t be any &lt;code&gt;activity&lt;/code&gt; string but something like &lt;code&gt;51&lt;/code&gt; which is described by another field… It’s fun and efficient binary format! &lt;a href="https://developer.garmin.com/fit/protocol/"&gt;FIT Protocol doc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my naive attempt to get the job done, I’ve used &lt;a href="https://hexdocs.pm/elixir/main/Inspect.html"&gt;Elixir’s Inspect protocol&lt;/a&gt; that’s implemented for all the data types. It can be used to dump data structs into strings and read them back later, matching the original data structure. And as you’ll see below, it &lt;em&gt;mostly&lt;/em&gt; works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex&amp;gt; field = %{type: "uint32", name: "distance"}
%{name: "distance", type: "uint32"}
iex&amp;gt; inspect(field) |&amp;gt; Code.eval_string()
{%{name: "distance", type: "uint32"}, []}
iex&amp;gt; IO.puts(inspect(field))
%{name: "distance", type: "uint32"}

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

&lt;/div&gt;



&lt;p&gt;If you try to do that for a very large data structure (like the one in FIT’s spreadsheet), inspect will truncate some data, this is easily fixed by adding &lt;code&gt;limit: :infinity&lt;/code&gt; to &lt;code&gt;inspect/1&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;We also need to be on a lookout for array of integers which may look awfully a lot like a charlist, or maybe… a charlist looks like an array of integers… all the same. Let’s ensure it stays consistent, with the &lt;code&gt;charlists&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex&amp;gt; inspect([?h, ?e, ?l, ?l, ?o, 112])
"~c\"hellop\""
iex&amp;gt; inspect([?h, ?e, ?l, ?l, ?o, 255])
"[104, 101, 108, 108, 111, 255]"
iex&amp;gt; inspect([?h, ?e, ?l, ?l, ?o], charlists: :as_lists)
"[104, 101, 108, 108, 111]"

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

&lt;/div&gt;



&lt;p&gt;It’s of course, all the same.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex&amp;gt; 'hello' == [?h, ?e, ?l, ?l, ?o]
true

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

&lt;/div&gt;



&lt;p&gt;This approach with dumping data structs into &lt;code&gt;profile.ex&lt;/code&gt; worked! My parser uses successfully the module with all the types and messages from the spreadsheet, but…&lt;/p&gt;

&lt;p&gt;It’s really annoying to depend on &lt;code&gt;inspect/2&lt;/code&gt;. I cannot, without additional options, define custom inspect for many structs, which makes debugging harder when everything is extremely verbose and structs contain many fields.&lt;/p&gt;

&lt;p&gt;Another big disadvantage is that while it may work for data structs, I cannot easily dump references to functions or call other modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex&amp;gt; f = &amp;amp; &amp;amp;1
#Function&amp;lt;42.3316493/1 in :erl_eval.expr/6&amp;gt;
iex&amp;gt; inspect(f)
"#Function&amp;lt;42.3316493/1 in :erl_eval.expr/6&amp;gt;"
iex&amp;gt; f.(1)
1
iex&amp;gt; inspect(f) |&amp;gt; Code.eval_string()
{nil, []}

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

&lt;/div&gt;



&lt;p&gt;I needed a better approach, and I didn’t want to step back to dumping elixir-like strings into profile.ex while deeply traversing the data structure. I wanted to use the power of Elixir, macros! After all, they can be used to generate other Elixir code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switch to macros
&lt;/h2&gt;

&lt;p&gt;There’s a lot to Elixir macros, I still don’t fully grasp all the intricate details about them, but maybe it’s for the best. They’re very powerful. &lt;a href="https://hexdocs.pm/elixir/macros.html"&gt;Official docs&lt;/a&gt;, and more importantly, &lt;a href="https://www.theerlangelist.com/article/macros_1"&gt;Understanding Elixir Macros series&lt;/a&gt; by Saša Jurić, were extremely helpful in making my prototype work. They can be used to capture AST for any Elixir code and then dump it into a file.&lt;/p&gt;

&lt;p&gt;How do we switch from &lt;code&gt;inspect&lt;/code&gt; to macros? Easy!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#### instead of this
iex&amp;gt; field = %{type: "uint32", name: "distance"}
#### do
iex&amp;gt; field = quote(do: %{type: "uint32", name: "distance"})
{:%{}, [], [type: "uint32", name: "distance"]}
iex&amp;gt; field |&amp;gt; Macro.to_string()
"%{type: \"uint32\", name: \"distance\"}"
#### or
iex&amp;gt; field = %{type: "uint32", name: "distance"}
iex&amp;gt; Macro.escape(field) |&amp;gt; Macro.to_string()
"%{name: \"distance\", type: \"uint32\"}"

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

&lt;/div&gt;



&lt;p&gt;This works for creating modules too! Elixir’s macros is still a proper AST. It works with syntax highlighting and autocomplete and any other Elixir code, except for comments, they’re not part of AST.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;field = quote(do: %{type: "uint32", name: "distance"})
quote do
 defmodule Exgen.Profile.ExampleField do
 @field unquote(field)

 def field, do: @field
 end
end
|&amp;gt; Macro.to_string()
|&amp;gt; IO.iodata_to_binary()
|&amp;gt; IO.puts()

# results in

defmodule Exgen.Profile.ExampleField do
 @field %{name: "distance", type: "uint32"}
 def field do
 @field
 end
end

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

&lt;/div&gt;



&lt;p&gt;This brings us closer to proper code generation for &lt;code&gt;profile&lt;/code&gt;. Macros also allow us to properly generate method calls!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex&amp;gt; field = %{type: quote(do: Exgen.Types.by_name("uint32")), name: "distance"}
iex&amp;gt; Macro.escape(field) |&amp;gt; Macro.to_string() |&amp;gt; IO.puts()
%{
 name: "distance",
 type: {
 {:., [], [
 {: __aliases__ , [alias: false], [:Exgen, :Types]},
 :by_name
 ]}, [], ["uint32"]
 }
}

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

&lt;/div&gt;



&lt;p&gt;…or not? That’s not right. Our &lt;code&gt;type&lt;/code&gt; quote with the function call gets escaped twice. To fix this, in this specific case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex&amp;gt; Macro.escape(%{field | type: {:unquote, [], [field.type]}}, unquote: true)
iex&amp;gt; v |&amp;gt; Macro.to_string() |&amp;gt; IO.puts()
%{name: "distance", type: Exgen.Types.by_name("uint32")}

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

&lt;/div&gt;



&lt;p&gt;That’s much better! In the case of &lt;code&gt;profile&lt;/code&gt; generation, it’s pretty straightforward to know what needs to be marked with &lt;code&gt;:unquote&lt;/code&gt; for proper code generation. For a more general approach, it should be possible to deeply walk the data structure and mark all &lt;code&gt;quote&lt;/code&gt; with &lt;code&gt;:unquote&lt;/code&gt; if needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formatting
&lt;/h2&gt;

&lt;p&gt;To tighten it all up, the whole code generation, there’s one last thing to make it pretty - formatting. We don’t want to have one massive unreadable line. Fear not, Elixir has a built-in formatter, &lt;a href="https://hexdocs.pm/mix/main/Mix.Tasks.Format.html"&gt;mix format&lt;/a&gt; , and it’s not only available in CLI but also as a method call - &lt;a href="https://hexdocs.pm/elixir/main/Code.html#format_string!/2"&gt;Code.format_string/2&lt;/a&gt;. Before the final dump into a file, it can be used to nicely format all the generated code in any way you want.&lt;/p&gt;

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

&lt;p&gt;The approach with macros is much more readable, testable and less error-prone. If something is wrong with syntax, it’s more obvious in macro than in a combination of string with inspect, &lt;code&gt;defmodule Profile do #{inspect(types)} end&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One last thing I’m still missing is how to attach comments. Elixir macros don’t have comments in AST, formatter handles them with &lt;a href="https://hexdocs.pm/elixir/Code.html#string_to_quoted_with_comments/2"&gt;Code.string_to_quoted_with_comments/2&lt;/a&gt;. I don’t see any easy way to attach them to AST from macro, but in the end, it may not be needed. I want to experiment with a switch to methods with &lt;code&gt;@doc&lt;/code&gt; annotations and creating proper named structs for each type, instead of generic one with &lt;code&gt;name:&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;The generated profile is now a lot of smaller and it’s still in progress due to above fiddle with &lt;code&gt;@doc&lt;/code&gt;. At this moment rest of the parser is still in a rough shape, missing some niceties like CRC calculation or speed (1st make it work!). When this is resolved, I plan to release it as open source library and maybe get some help with adding &lt;strong&gt;encoder&lt;/strong&gt; for it. It would be awesome to be able to decode, edit and encode back FIT files.&lt;/p&gt;




&lt;p&gt;My favorite Elixir macro is &lt;a href="https://hexdocs.pm/elixir/main/Kernel.html#dbg/2"&gt;dbg/2&lt;/a&gt;, or maybe &lt;a href="https://github.com/elixir-lang/elixir/blob/514615d0347cb9bb513faa44ae1e36406979e516/lib/elixir/lib/kernel.ex#L5132"&gt;def/2&lt;/a&gt;… It’s all macros! 😱&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Shell scripting with Elixir</title>
      <dc:creator>arathunku</dc:creator>
      <pubDate>Sun, 11 Feb 2024 20:00:00 +0000</pubDate>
      <link>https://dev.to/arathunku/shell-scripting-with-elixir-3kd7</link>
      <guid>https://dev.to/arathunku/shell-scripting-with-elixir-3kd7</guid>
      <description>&lt;p&gt;When simple bash scripts start to become unwieldy, you may consider reaching out for something “higher” level, like Perl, Ruby or Python. I’m reaching out for Elixir. Maybe the startup times are not perfect for every use case, but Elixir is extremely versatile. It’s easy to add dependencies, debug, iterate and even write tests, all in a single file! I believe, due to &lt;a href="//livebook.dev"&gt;LiveBook&lt;/a&gt;, it really hits its stride in recent years; the ecosystem leans heavily into ergonomic developer experience and great out-of-the-box defaults (&lt;a href="https://github.com/wojtekmach/req"&gt;Req&lt;/a&gt; is amazing!). In few lines of code, you can connect to Postgres, send HTTP requests or start HTTP server.&lt;/p&gt;

&lt;p&gt;For my own use cases I’ve written scripts for displaying weather(focus only on rain, v. important for dog walks when your dog hates rain) in &lt;a href="https://github.com/greshake/i3status-rust"&gt;&lt;code&gt;i3status-rust&lt;/code&gt;&lt;/a&gt;, transforming various CSV files from bank exports into &lt;a href="https://beancount.github.io/"&gt;&lt;code&gt;beancount&lt;/code&gt;&lt;/a&gt; format, or creating &lt;a href="https://github.com/visciang/telegram"&gt;Telegram bots&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A simple script may be just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env elixir&lt;/span&gt;
&lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, if it were that simple, I’d just do &lt;code&gt;echo "Hello world"&lt;/code&gt; and skip Elixir, but then there’d be no point in writing this blog post, right? So, I have another, a bit more involved and what may seem like over-complicated template. It’s a starting point for more complex scripts, and I can either remove some of the parts I don’t need or start extending it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The template
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env -S ERL_FLAGS=+B elixir&lt;/span&gt;
&lt;span class="no"&gt;Mix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DEPS_ONLY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;halt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:infinity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Hello&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="nv"&gt;@moduledoc&lt;/span&gt; &lt;span class="sd"&gt;"""
 &amp;lt;!-- TODO --&amp;gt;

 ## Usage

 $ bin/hello --help

 """&lt;/span&gt;

 &lt;span class="nv"&gt;@args&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;help:&lt;/span&gt; &lt;span class="ss"&gt;:boolean&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OptionParser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;strict:&lt;/span&gt; &lt;span class="nv"&gt;@args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;

 &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;help:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@moduledoc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_parsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@moduledoc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Hello&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First of all, the shebang is not the usual thing you’d expect. It configures an additional flag for &lt;a href="https://www.erlang.org/doc/man/erl.html"&gt;&lt;code&gt;erl&lt;/code&gt;&lt;/a&gt;. This flag, per &lt;a href="https://erlang.org/documentation/doc-4.9.1/erts-4.9.1/doc/html/erl.html"&gt;docs&lt;/a&gt;, “De-activates the break handler for (SIGINT) ^C and ^\ “. I’ll expand on it in Signals section.&lt;/p&gt;

&lt;p&gt;Now, we’re executing, and there’s a ready-to-go &lt;a href="https://hexdocs.pm/mix/Mix.html#install/2"&gt;&lt;code&gt;Mix.install/2&lt;/code&gt;&lt;/a&gt; statement where we can add additional dependencies. To make HTTP requests, we can add battries-included HTTP client like &lt;code&gt;{:req, "~&amp;gt; 0.4"}&lt;/code&gt;. Processing JSON? &lt;code&gt;{:jason, "~&amp;gt; 1.4"}&lt;/code&gt;. We can search for packages with &lt;code&gt;mix hex.search [package-name]&lt;/code&gt; or lookup the latest version &lt;code&gt;mix hex.info [package-name]&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mix hex.info req
Req is a batteries-included HTTP client &lt;span class="k"&gt;for &lt;/span&gt;Elixir.

Config: &lt;span class="o"&gt;{&lt;/span&gt;:req, &lt;span class="s2"&gt;"~&amp;gt; 0.4.8"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
Releases: 0.4.8, 0.4.7, 0.4.6, 0.4.5, 0.4.4, 0.4.3, 0.4.2, 0.4.1, ...

Licenses: Apache-2.0
Links:
 Changelog: https://hexdocs.pm/req/changelog.html
 GitHub: https://github.com/wojtekmach/req
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another unusual block is &lt;code&gt;if System.get_env("DEPS_ONLY") do ... end&lt;/code&gt;. If the script doesn’t have any dependencies, I’d just delete it or let it be. It’s useful in cases when the script has dependencies. We can use this block to cache dependencies and compilation of the script, skipping the execution of the rest of the script. This is handy for CI setups or when building container images. For CI, I’d also define a directory where the dependencies should be cached. for GitLab CI, a job may looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;print-hello&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="c1"&gt;# https://hub.docker.com/r/hexpm/elixir/tags?page=1&lt;/span&gt;
 &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hexpm/elixir:1.16.1-erlang-26.2.2-debian-bookworm-20240130-slim&lt;/span&gt;
 &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;MIX_INSTALL_DIR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$CI_PROJECT_DIR/.cache/mix"&lt;/span&gt;
 &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elixir-cache&lt;/span&gt;
 &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.cache&lt;/span&gt;
 &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DEPS_ONLY=true bin/hello&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bin/hello&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, there’s a module where we’ll be documenting what the script is about, defining options for parsing arguments and failing gracefully when invalid options are passed, ensuring proper error exit code. That’s also where we’d extend the script, before last &lt;code&gt;cmd/2&lt;/code&gt;. CLI argument parsing is done with built-in &lt;a href="https://hexdocs.pm/elixir/OptionParser.html"&gt;OptionParser&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This structure may seem a bit verbose and may look like a lot of boilerplate, but again, if it were simple, we’d have just stayed with the bash in the first place. Here, this structure can easily grow with the script.&lt;/p&gt;

&lt;p&gt;I used to define inline functions like &lt;code&gt;print_help = fn -&amp;gt; ... end&lt;/code&gt; or &lt;code&gt;process_args = fn (args) -&amp;gt; .. end&lt;/code&gt; but in the end, working within a module is cleaner, and no need to look if given function is anonymous function (&lt;code&gt;.()&lt;/code&gt; call) or module’s function.&lt;/p&gt;

&lt;p&gt;With the template in place, we’re ready to add some logic to it. Elixir can do quite a bit just with the standard library, but there are also some gotchas. Let’s go through some common needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Output&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://hexdocs.pm/elixir/IO.html"&gt;&lt;code&gt;IO&lt;/code&gt;&lt;/a&gt; will probably be the most often used module. It can be used to write stdout with functions like &lt;code&gt;IO.write/1&lt;/code&gt;, &lt;code&gt;IO.puts/1&lt;/code&gt;, or to &lt;code&gt;stderr&lt;/code&gt; with their equivalent 2-argument calls like &lt;code&gt;IO.puts(:stderr, "Error")&lt;/code&gt;. We can also read inputs with &lt;code&gt;IO.read/2&lt;/code&gt;. Any writing or reading can be also handled as a stream with &lt;a href="https://hexdocs.pm/elixir/IO.html#stream/2"&gt;&lt;code&gt;IO.stream/2&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Invalid argument"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# that's the default&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trim_trailing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Colors&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;There’s no need to define color codes manually. With &lt;a href="https://hexdocs.pm/elixir/IO.ANSI.html"&gt;IO.ANSI&lt;/a&gt;, we can add text and background colors easily.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ANSI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue_background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\e&lt;/span&gt;&lt;span class="s2"&gt;[44m"&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Example"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ANSI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\e&lt;/span&gt;&lt;span class="s2"&gt;[44mExample&lt;/span&gt;&lt;span class="se"&gt;\e&lt;/span&gt;&lt;span class="s2"&gt;[0m"&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="no"&gt;Example&lt;/span&gt;
&lt;span class="ss"&gt;:ok&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d32-QtPn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://arathunku.com/b/2024/shell-scripting-with-elixir/Pasted_image_20240211121425.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d32-QtPn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://arathunku.com/b/2024/shell-scripting-with-elixir/Pasted_image_20240211121425.png" alt="Pasted_image_20240211121425.png" width="800" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Exit code&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Another quick one, there’s &lt;a href="https://hexdocs.pm/elixir/1.12/System.html#stop/1"&gt;&lt;code&gt;System.stop(exit_code)&lt;/code&gt;&lt;/a&gt; to gently shutdown VM, what may not be obvious is that it’s async process. Make sure to call &lt;code&gt;Process.sleep(:infinity)&lt;/code&gt; after it to block the execution. This ensures that all the applications are taken down gently. There’s alternative of &lt;a href="https://hexdocs.pm/elixir/1.12/System.html#halt/1"&gt;&lt;code&gt;System.halt/1&lt;/code&gt;&lt;/a&gt; and it forces immediate shutdown of Erlang runtime system.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Subprocesses&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For one-off commands, &lt;a href="https://hexdocs.pm/elixir/main/System.html#cmd/3"&gt;&lt;code&gt;System.cmd/3&lt;/code&gt;&lt;/a&gt; is enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"rev-parse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--show-toplevel"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"/home/arathunku/code/github.com/arathunku/elixir-cli-template-example&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With pattern matching we ensure immediate exit if there’s any other exit code than the successful one, and we can process the output.&lt;/p&gt;

&lt;p&gt;It gets more tricky if you want to create another BEAM process while continuing with the rest of the execution. If something goes wrong or Erlang system crashes, OS process might get left behind. In these cases, instead of reinveting a wheel of managing OS processes, it’s good occasion to make a use of this &lt;code&gt;Mix.install/2&lt;/code&gt; at the beginning and add &lt;a href="https://github.com/fhunleth/muontrap"&gt;MuonTrap&lt;/a&gt;. It will ensure the processes are, as described in README, well-behaved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;Mix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;&lt;span class="ss"&gt;:muontrap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.0"&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Hello&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;_pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spawn_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="no"&gt;MuonTrap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ping"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;into:&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:stdio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:line&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:infinity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Signals, some are not like the others
&lt;/h3&gt;

&lt;p&gt;It’s tricky and it will probably not behave as you’d expect based on your experience in other languages. Some signals are handled by default by Erlang system, for more details check &lt;a href="https://github.com/erlang/otp/pull/1315"&gt;nice documentation in PR&lt;/a&gt;. For scripting… usually signals don’t matter that much, at least in my case. If you spawn a GenServer, it’ll receive &lt;a href="https://hexdocs.pm/elixir/GenServer.html#c:terminate/2"&gt;&lt;code&gt;terminate/2&lt;/code&gt;&lt;/a&gt; for cleanup, assuming gentle shutdown.&lt;/p&gt;

&lt;p&gt;We can still skip &lt;code&gt;BREAK&lt;/code&gt; menu(^C) and exit immediately if we start with &lt;code&gt;ERL_FLAGS=+B elixir&lt;/code&gt;. This is why it’s in the template at the beginning. Some other signals can be cough by swapping default &lt;code&gt;erl_signal_server&lt;/code&gt;, but not all of them*. In the example above we’ll do just that, handle what we’re interested it and defer rest to the default handler.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;*At this moment, &lt;code&gt;INT&lt;/code&gt; cannot be trapped, &lt;a href="https://github.com/erlang/otp/issues/4583"&gt;see this issue&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Signals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="nv"&gt;@behaviour&lt;/span&gt; &lt;span class="ss"&gt;:gen_event&lt;/span&gt;
 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sigusr1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"USR1, continue..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:sigterm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Ok, ok, let me take a moment and exit..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="ss"&gt;:erl_signal_handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Goodbye! &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="ss"&gt;:gen_event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;swap_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="ss"&gt;:erl_signal_server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:erl_signal_handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="bp"&gt;__MODULE__&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"I'll wait for signals!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:infinity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
 &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
   &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Something went wrong. err=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Testing&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In Rust, we can write tests next to the code with &lt;code&gt;#[cfg(test)]&lt;/code&gt;, and these will run when &lt;code&gt;cargo test&lt;/code&gt; is executed. Did you know you can do kind of a similar thing in Elixir scripts? There’s no magic here, we need to create a test module and trigger &lt;a href="https://hexdocs.pm/ex_unit/ExUnit.html"&gt;&lt;code&gt;ExUnit&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"MIX_ENV"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="no"&gt;ExUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

 &lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;HelloTest&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ExUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;async:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
 &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;ExUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CaptureIO&lt;/span&gt;

 &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"prints a message when no arguments are passed"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;capture_io&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Tests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Hello World&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;

 &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"prints help for unknown arguments"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;capture_io&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Tests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"--help"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="s2"&gt;"Example of adding ExUnit"&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
 &lt;span class="no"&gt;Hello&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Going beyond scripts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hexdocs.pm/elixir/OptionParser.html"&gt;&lt;code&gt;OptionParser&lt;/code&gt;&lt;/a&gt; is good. Maybe it doesn’t do all the stuff that something like &lt;a href="https://docs.rs/clap/latest/clap/"&gt;Rust clap&lt;/a&gt; does but it’s absolutely getting the job done.&lt;/p&gt;

&lt;p&gt;You can go beyond simple CLI scripts and build full TUI apps. Progress bars? Charts? Text editor? Here, &lt;code&gt;Ratatouille&lt;/code&gt; comes to the rescue. &lt;a href="https://github.com/arathunku/elixir-cli-template-example/blob/main/bin/tui-counter"&gt;Simple TUI counter example&lt;/a&gt; or more advanced ones - &lt;a href="https://github.com/ndreynolds/ratatouille/tree/cc7b6a37e0b1757cd89cc5084343814a79dd86dc?tab=readme-ov-file#example-applications"&gt;more advanced examples&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’d like to see even more examples with &lt;a href="https://hexdocs.pm/mix/Mix.html#install/2"&gt;&lt;code&gt;Mix.install/2&lt;/code&gt;&lt;/a&gt;, make sure to check &lt;a href="https://github.com/wojtekmach/mix_install_examples"&gt;mix_install_examples&lt;/a&gt;! There’re examples of HTTP servers, CSV parsing, web scraping, machine learning or &lt;a href="https://github.com/wojtekmach/mix_install_examples/blob/main/phoenix_live_view_upload_image.exs"&gt;full Phoenix LiveView file uploader&lt;/a&gt;(!!!), but at this point you may consider just using &lt;code&gt;mix new ...&lt;/code&gt; and setting up a proper Mix project. After that, you can use &lt;a href="https://github.com/burrito-elixir/burrito"&gt;burrito&lt;/a&gt; to ship a single Elixir CLI binary for end-users.&lt;/p&gt;

&lt;p&gt;Is Elixir more complicated than Ruby od Python as a bash replacement? It depends, of course it depends. If you already know Python or Ruby well, you’ll probably prefer them but Elixir can absolutely be used too! It’s a bit on the slow side to start up, may not be a best choice to implement PS1, but if the startup speed doesn’t matter that much and you want go have very ergonomic language for scripting at your hand - it’s great.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing note
&lt;/h2&gt;

&lt;p&gt;Writing this article was kind of a strange experience. You may think it’s a bit light on examples and details, and it is done on purpose. There’s strong focus on documentation within the community and I didn’t want to repeat what’s already out there in official docs and libraries. Just look at this beautiful &lt;a href="https://hexdocs.pm/elixir/main/System.html#cmd/3"&gt;&lt;code&gt;System.cmd/3&lt;/code&gt;&lt;/a&gt; documentation or &lt;a href="https://hexdocs.pm/elixir/main/IO.ANSI.html"&gt;&lt;code&gt;IO.ANSI&lt;/code&gt;&lt;/a&gt; docs, and it’s all available in &lt;a href="https://hexdocs.pm/iex/IEx.html"&gt;&lt;code&gt;iex&lt;/code&gt;&lt;/a&gt; with &lt;a href="https://hexdocs.pm/iex/IEx.Helpers.html#h/1"&gt;&lt;code&gt;h/1&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When writing the article, I’ve dumped all my scripts and tests into this repository. Thanks for reading!&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Seamless navigation between SPA and LiveView</title>
      <dc:creator>arathunku</dc:creator>
      <pubDate>Mon, 05 Feb 2024 04:40:00 +0000</pubDate>
      <link>https://dev.to/arathunku/seamless-navigation-between-spa-and-liveview-2j88</link>
      <guid>https://dev.to/arathunku/seamless-navigation-between-spa-and-liveview-2j88</guid>
      <description>&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;Navigating between SPA and LiveView mostly works!&lt;/p&gt;









&lt;p&gt;Recently, many websites have become single-page applications (SPAs), written in React/Vue/Svelte/ w/e else is trendy now, sometimes needlessly, but what’s done is done. I was wondering if it would be possible to break out from this path, where everything is rendered by SPA, and extend it with Phoenix LiveView, without radically affecting the user experience or requiring a months-long rewrite.&lt;/p&gt;

&lt;p&gt;I believe the approach and problems presented in the post should work for different tools working similarly to LiveView, like &lt;a href="https://laravel-livewire.com/"&gt;LiveWire&lt;/a&gt;, or &lt;a href="https://turbo.hotwired.dev/"&gt;Turbo&lt;/a&gt;, but I’ve not tested that. Similarly, here in this blog post, I’ll use React, but I’ll refer to the frontend app as SPA because it’s mostly independent of the library.&lt;/p&gt;

&lt;p&gt;The goal of the exercise is simple, can we replace a page with LiveView?&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;At the very beginning, the out-of-the-box setup for many SPAs configs is that they have their own dev server, with its own index.html that’s rendered on &lt;code&gt;/*&lt;/code&gt;. Later on deployment, if there’s no server-side rendering, everything is served as static files.&lt;/p&gt;

&lt;p&gt;Our server is nowhere to be found on the request’s path. That’s a bummer. We need to move to the good old way, where the app’s server is doing the routing and rendering HTML on the server. Starting with &lt;code&gt;index.html&lt;/code&gt;. Otherwise, there’s no place for our Phoenix app.&lt;/p&gt;

&lt;p&gt;This will be different for every SPA setup (there are dozens, or maybe hundreds, ?!). For brevity and for the purposes of the exercise, I created two apps. The first one is the most typical and boring Phoenix app. The second is the Vite React template for SPA. They’re living in their own worlds, without knowing about one another, at least at this stage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mix phx.new sample
$ mix phx.server
[info] Migrations already up
[info] Running SampleWeb.Endpoint with cowboy 2.10.0 at 127.0.0.1:4000 (http)
[info] Access SampleWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...

Rebuilding...
Done in 550ms.


$ npm create vite@latest sample -- --template react
$ cd sample
$ npm install
$ npm run dev

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extending SPA with LiveView
&lt;/h2&gt;

&lt;p&gt;Vite docs include a &lt;a href="https://vitejs.dev/guide/backend-integration"&gt;guide for integration with backend&lt;/a&gt; and we’ll follow it. It’s mostly correct; it’s just a matter of putting the things in the right place with additional LiveView specific glue.&lt;/p&gt;

&lt;p&gt;We start with defining LiveView page where we can dump all requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; scope "/", SampleWeb do
 pipe_through :browser

+ live "/app/*path", SpaLive
 end

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SpaLive&lt;/code&gt; is going to have a single responsibility - show SPA. This means the SPA in DOM will be rendered &lt;strong&gt;inside&lt;/strong&gt; LiveView.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule SampleWeb.SpaLive do
 use SampleWeb, :live_view

 def mount(params, session, socket) do
 {:ok, socket, layout: {SampleWeb.Layouts, :spa}}
 end

 # When URLs changes, do nothing, SPA is handling the routing
 def handle_params(uri, _, socket) do
 {:noreply, socket}
 end

 def render(assigns) do
 # It's critical that div where the SPA is going to be rendered
 # is ignored with phx-update
 ~H"""
 &amp;lt;div id="root" phx-update="ignore" phx-hook="LoadSPA"&amp;gt;&amp;lt;/div&amp;gt;
 """
 end
end

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

&lt;/div&gt;



&lt;p&gt;Starting from the top, we have &lt;code&gt;mount/3&lt;/code&gt; with a small change, we set a layout dedicated to SPA. It simply delegates all the content to inner content and keeps a place for flash messages from server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;main&amp;gt;
 &amp;lt;.flash_group flash={@flash} /&amp;gt;
 &amp;lt;%= @inner_content %&amp;gt;
&amp;lt;/main&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;We do it in such a way as to leave a place for any future HTML that may be part of the layout for the page. This is useful in case we’d like to move more of the layout to server, some boring stuff like a footer, a navigation bar or a cookie prompt. We don’t change anything in &lt;code&gt;root.html.heex&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, we have &lt;code&gt;handle_params/3&lt;/code&gt;. It’s a no-op method because SPA is handling the routing, we don’t need to do anything here. It’s important for links using &lt;code&gt;navigate&lt;/code&gt;, in cases where they point to SPA and only this method will be called, not &lt;code&gt;mount&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code&gt;render/1&lt;/code&gt;. We cannot simply put &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags in &lt;code&gt;~H&lt;/code&gt; block, as instructed by Vite’s backend integration guide. This would work on the initial render, but if there’s navigation between different LiveView controlled pages, these scripts won’t be executed! LiveView under the hood is using &lt;a href="https://github.com/patrick-steele-idem/morphdom"&gt;morphdom&lt;/a&gt; and DOM is set via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML"&gt;innerHTML&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;HTML specifies that a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script"&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;&lt;/a&gt; tag inserted with &lt;code&gt;innerHTML&lt;/code&gt; &lt;a href="https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0"&gt;should not execute&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a reason why we need to take a different approach, &lt;code&gt;phx-hook&lt;/code&gt;. We’ll use &lt;a href="https://hexdocs.pm/phoenix_live_view/js-interop.html"&gt;# JavaScript interoperability&lt;/a&gt; to load SPA.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// required by Vite
// error handling of import is left as another exercise
const setupReactPatching = (callback) =&amp;gt; {
 if (window.$RefreshReg$) {
 callback()
 return;
 }

 import("http://localhost:5173/@react-refresh").then(RefreshRuntime =&amp;gt; {
 RefreshRuntime.default.injectIntoGlobalHook(window)
 window.$RefreshReg$ = () =&amp;gt; {}
 window.$RefreshSig$ = () =&amp;gt; (type) =&amp;gt; type
 window. __vite_plugin_react_preamble_installed__ = true
 callback()
 })
}

const LoadSPA = {
 mounted() {
 setupReactPatching(() =&amp;gt; {
 import("http://localhost:5173/@vite/client")
 import("http://localhost:5173/src/main.jsx").then(mount =&amp;gt; {
 this.unmount = mount.default(this.el)
 })
 })
 },

 destroyed() {
 if (this.unmount) {
 this.unmount()
 }
 }
}

export default LoadSPA;

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

&lt;/div&gt;



&lt;p&gt;In this hook, we follow Vite’s guide as before for backend integration and we load JS files from its own dev server.&lt;/p&gt;

&lt;p&gt;We don’t set up any additional communication between LiveView and SPA, frontend will still send requests to server as before (if it did). If you’re interested in communication between SPA and LiveView, make sure to read &lt;a href="https://stephenbussey.com/2022/04/13/react-in-liveview-how-and-why.html"&gt;React in LiveView: How and Why?&lt;/a&gt; blog post.&lt;/p&gt;

&lt;p&gt;Back to LiveView hook. This isn’t fully working yet, notice how import of &lt;code&gt;main.jsx&lt;/code&gt; exported a function to render SPA, we need to modify the most typical SPA set up to wait with rendering until we actually need it! Not on initial load.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// sample/src/main.jsx
const mount = (element) =&amp;gt; {
 const root = ReactDOM.createRoot(element);
 root.render(
 &amp;lt;React.StrictMode&amp;gt;
 &amp;lt;App /&amp;gt;
 &amp;lt;/React.StrictMode&amp;gt;,
 );

 return () =&amp;gt; {
 root.unmount();
 };
};

export default mount;

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

&lt;/div&gt;



&lt;p&gt;…and that’s it, or is it? What about links?&lt;/p&gt;

&lt;p&gt;We must be able to create links handled by LiveView on the client side. In a typical Phoenix heex template we’d use something like &lt;code&gt;&amp;lt;.link navigate={~p"/about"}&amp;gt;About&amp;lt;/.link&amp;gt;&lt;/code&gt;. LiveView then captures clicks (or fallbacks gracefully) and does client-side routing to seamlessly switch between pages. We need to do this for links on frontend. This can be done by adding &lt;code&gt;phx&lt;/code&gt; annotations, and exactly we mimic the behavior of this &lt;a href="https://github.com/phoenixframework/phoenix_live_view/blob/d41ea3b55b2a6a74c3a5634737f0a998042b32f3/lib/phoenix_component.ex#L2504-L2708"&gt;LiveView component&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sample component wrapping React Router link.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const LiveLink = ({ navigate, href, patch, replace, ...props }) =&amp;gt; {
 const phxDataProps = {}

 if (navigate) {
 phxDataProps["data-phx-link"] = "redirect";
 phxDataProps["data-phx-link-state"] = replace ? "replace" : "push";
 } else if (patch) {
 phxDataProps["data-phx-link"] = "patch";
 phxDataProps["data-phx-link-state"] = replace ? "replace" : "push";
 }

 return &amp;lt;Link {...props} {...phxDataProps} to={navigate || patch || href || "#"} /&amp;gt;;
};

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

&lt;/div&gt;



&lt;p&gt;For example, &lt;code&gt;/app/login&lt;/code&gt; is a LiveView page and everything else is still on SPA.&lt;/p&gt;

&lt;p&gt;Phoenix routing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; live "/app/login", LoginLive
 live "/app/*path", SpaLive

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

&lt;/div&gt;



&lt;p&gt;A sample menu on SPA then may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Full client rounting. LiveView won't see it at all
&amp;lt;Link to="/app/about"&amp;gt;About&amp;lt;/Link&amp;gt;

// LiveView captures click, handle_params/3 is called but SPA still renderd the page
&amp;lt;LiveLink patch="/app/contact"&amp;gt;Contact&amp;lt;/LiveLink&amp;gt;

// LiveView captures the click and navigates to a different LiveView dedicated to login page
&amp;lt;LiveLink navigate="/app/login"&amp;gt;Login&amp;lt;/LiveLink&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;When it comes to navigation to SPA views, on backend it works as with any other link. Sample menu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~H"""
 &amp;lt;.link navigate={~p"/app/about"}&amp;gt;About&amp;lt;/.link&amp;gt;
 &amp;lt;.link navigate={~p"/app/contact"}&amp;gt;Contact&amp;lt;/.link&amp;gt;
 &amp;lt;.link navigate={~p"/app/login"}&amp;gt;Login&amp;lt;/.link&amp;gt;
"""

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

&lt;/div&gt;



&lt;p&gt;That’s all there is to navigate between SPA and LiveView, for a demo.&lt;/p&gt;

&lt;p&gt;It wouldn’t work on deployment, for deployment, we need to load files from the &lt;a href="https://vitejs.dev/config/build-options#build-manifest"&gt;manifest&lt;/a&gt; instead of running dev server, so the LiveView hook presented earlier would be a bit more complex.&lt;/p&gt;




&lt;p&gt;Side note: if you’re using &lt;a href="https://reactrouter.com/"&gt;react-router&lt;/a&gt;, ensure you initialize the routes when SPA is mounting, not outside of it on initial load! Otherwise, when LiveView navigates browser’s history, react-router state will be out of date and it won’t render the correct page!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- const router = createBrowserRouter([...]);

const mount = (element) =&amp;gt; {
 const root = ReactDOM.createRoot(element);
+ const router = createBrowserRouter([...]);
 root.render(
 &amp;lt;React.StrictMode&amp;gt;
 &amp;lt;RouterProvider router={router} /&amp;gt;
 &amp;lt;/React.StrictMode&amp;gt;,
 );
 ....

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

&lt;/div&gt;



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

&lt;p&gt;I actually thought it would be much more difficult to connect both worlds and was pleasantly surprised how little glue was needed in very specific places.&lt;/p&gt;

&lt;p&gt;Such integration might be a practical option to explore for older applications, where LiveView looks very interesting to the team, but at the same time we need to maintain and develop our frontend too. We can still write SPA as before, we can create backend views like there’s no SPA and LiveView navigates between the two separate worlds.&lt;/p&gt;

&lt;p&gt;And this separation is concerning because there are some areas where the two will overlap.&lt;/p&gt;

&lt;h3&gt;
  
  
  UI Components
&lt;/h3&gt;

&lt;p&gt;It’s unlikely that client UI toolkit uses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components"&gt;Web Components&lt;/a&gt;. This means that we either have to re-implement UI components, using the same styling on backend, or migrate needed frontend components to web components.&lt;/p&gt;

&lt;p&gt;Currently on LiveView side, while basic web components would work, slots and styling for shadow DOM needs special consideration, see &lt;a href="https://elixirforum.com/t/adobe-spectrum-2-web-components-with-liveview/60395"&gt;this thread on elixirforum&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switch between two modes
&lt;/h3&gt;

&lt;p&gt;If you follow the demo at the beginning closely, there’s noticable rerender when page switches between the two modes. This is because almost the whole DOM is replaced when JS/LiveView takes over.&lt;/p&gt;

&lt;p&gt;This is very noticeable on elements that would be shared and must look the same in both SPA and LiveView. If it’s make-or-break, it might be worth considering moving these elements to server side and letting SPA just render the “inside” of the page, not the whole layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  There’s more
&lt;/h3&gt;

&lt;p&gt;There is more that needs to be done for great UX before you go ahead and deploy it for production. All invalid links now point to SPA, always. Maybe previously any user with the page left open, didn’t keep the WebSocket connection open and everything was cached. Now you need to handle this load on server side. And many more things, unique to each setup. If you would like to check the full code from the demo, &lt;a href="https://github.com/arathunku/sample-liveview-vite-spa"&gt;see this repository&lt;/a&gt;. Thanks for reading!&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Taming Slack notifications and channels</title>
      <dc:creator>arathunku</dc:creator>
      <pubDate>Sun, 26 Feb 2023 14:00:00 +0000</pubDate>
      <link>https://dev.to/arathunku/taming-slack-notifications-and-channels-9cf</link>
      <guid>https://dev.to/arathunku/taming-slack-notifications-and-channels-9cf</guid>
      <description>&lt;p&gt;I’ve heard this at least a dozen of times how Slack is overwhelming, distracting, and not actually helping in organizing any knowledge. I wholeheartedly agree! I’m a fan of async-first approach, with a call here and there - when needed, with clear goal. Slack is &lt;strong&gt;instant&lt;/strong&gt; messenger first, and by default facilitates very synchronous communication. Too synchronous for my taste, but it can be made not so &lt;em&gt;instant&lt;/em&gt;! My settings focus on this, and I’d like to share some of my tweaks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2de7giump3jpeprx3eqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2de7giump3jpeprx3eqw.png" alt="20201119-103911.png" width="800" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 1st offender is typing a message prompt! For me, that’s 100% noise that only wastes time, you may be waiting for the message that may never come! Noise, noise, noise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6r2pyn59mxg9wz674yf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6r2pyn59mxg9wz674yf2.png" alt="20201119-103837.png" width="681" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I never try to stay on top of all messages by being bombarded with sounds about every single one of them. I’ve muted &lt;strong&gt;all&lt;/strong&gt; sounds. If something is absolutely critical, there’s on-call rotation and in the worst case, a phone number to call.&lt;/p&gt;

&lt;p&gt;Did you know you can also receive a summary by email(bottom of the screenshot above)? Useful for catching up after holidays. Notice, how Slack encourages here more instant communication. It’s not like on a &lt;a href="https://www.discourse.org/" rel="noopener noreferrer"&gt;Discourse forum&lt;/a&gt; where you may select “summary” from a day/week/month, it’s &lt;strong&gt;ONCE AN HOUR&lt;/strong&gt; , and only when you’re &lt;strong&gt;not&lt;/strong&gt; active.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiuewy7neecp694yfivlf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiuewy7neecp694yfivlf.png" alt="Pasted_image_20230215153234.png" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sounds are muted - perfect. Now time for desktop notifications too, they’re enabled for keywords and I use different settings for other channels. This doesn’t mean I receive desktop notifications all the time.&lt;/p&gt;

&lt;p&gt;Notifications schedule:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9dzeyuwnaio3werjqtz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9dzeyuwnaio3werjqtz.png" alt="Pasted_image_20230215153434.png" width="585" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I get them when I start work and not earlier, not later. Anyone who’s messaging me gets information that my notifications are muted, but they’re welcome to click “Send now…” and bypass this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5m7x8m1hg8tmtefikewc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5m7x8m1hg8tmtefikewc.png" alt="20201119-103752.png" width="710" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Status: away, I always keep it that way and disable Slack’s prompt to toggle it back on. In the past, I’ve noticed people waiting for the dot to go green to send me a message instead of doing it whenever they wanted/needed to. Green dot, online presence, is a sign of synchronous communication. We don’t have presence tracking on email! (fortunately)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdrjf0t6263mrqff3102e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdrjf0t6263mrqff3102e.png" alt="Pasted_image_20230215154757.png" width="666" height="668"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inbox Zero fans love this setting, me included! Only unread channels are visible, keeps channels list tidy. You’re welcome to join even 1000 channels, but your sidebar won’t grow as much unless they’re all very active, then maybe reconsider few things… They’re only visible when there’s something new in them, and even in that case - it’s easy to tell if it’s a mention/keyword or just a normal message and catch up later, or never.&lt;/p&gt;

&lt;p&gt;You can always jump quickly to any channel via C-k shortcut, sidebar is not needed for that.&lt;/p&gt;

&lt;p&gt;And the last one, &lt;a href="https://nohello.net" rel="noopener noreferrer"&gt;https://nohello.net&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>slack</category>
    </item>
  </channel>
</rss>
