Blog posts about programming, new technologies, and video gamingComputer geek, gamer, drummerZola2023-08-07T00:00:00+00:00https://zolmok.org/atom.xmlPeaks.js: Navigating Audio Waveforms Like a Pro2023-08-07T00:00:00+00:002023-08-07T00:00:00+00:00https://zolmok.org/peaks-js/<iframe width="560" height="315" src="https://www.youtube.com/embed/gpDOYDCU0TA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen class="video-frame"></iframe>
<p>In today's ever-growing digital world, audio processing has become a significant part of various industries such as music, film, and broadcasting. Handling and manipulating audio data can be complex. That's where Peaks.js comes into play.</p>
<span id="continue-reading"></span><h2 id="what-is-peaks-js">What is Peaks.js?</h2>
<p>Peaks.js is an open-source JavaScript library that enables web developers to create interactive, navigable waveforms of audio content. Born out of a collaboration between BBC R&D and Chris Finch, Peaks.js offers an impressive set of tools to visualize audio waveforms, making it a go-to library for many audio-centric applications.</p>
<h3 id="key-features">Key Features:</h3>
<ol>
<li><strong>Waveform Visualization</strong>: Offers different views like waveform and spectrogram.</li>
<li><strong>Zooming and Scrolling</strong>: Users can zoom in/out and scroll to navigate the waveform.</li>
<li><strong>Segmentation and Point Markers</strong>: Define segments and point markers for annotation.</li>
<li><strong>Resizable</strong>: Automatically resizes based on the container's dimensions.</li>
<li><strong>Customizable</strong>: A highly customizable API that allows developers to modify the appearance.</li>
</ol>
<h2 id="setting-up-peaks-js">Setting Up Peaks.js</h2>
<p>Integration of Peaks.js into a project is quite simple. Here's a brief walkthrough:</p>
<h3 id="1-installation">1. Installation</h3>
<p>You can install Peaks.js through npm:</p>
<pre data-lang="bash" style="background-color:#fafafa;color:#383a42;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#e45649;">npm</span><span> install peaks.js
</span><span style="color:#e45649;">npm</span><span> install</span><span style="color:#e45649;"> --save</span><span> konva@8.4.3
</span><span style="color:#e45649;">npm</span><span> install</span><span style="color:#e45649;"> --save</span><span> waveform-data
</span></code></pre>
<p>NOTE: I specified a version for <code>konva</code> because as of this writing <code>peaks.js</code> doesn't like the current version of konva.</p>
<p>or include it from a CDN:</p>
<pre data-lang="html" style="background-color:#fafafa;color:#383a42;" class="language-html "><code class="language-html" data-lang="html"><span><</span><span style="color:#e45649;">script </span><span style="color:#c18401;">src</span><span>=</span><span style="color:#50a14f;">"https://cdn.jsdelivr.net/npm/peaks.js"</span><span>></</span><span style="color:#e45649;">script</span><span>>
</span></code></pre>
<h3 id="2-create-a-peaks-instance">2. Create a Peaks Instance</h3>
<p>First, create an HTML5 <code><audio></code> element, then initialize Peaks.js:</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#383a42;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#a626a4;">var </span><span style="color:#e45649;">audioElement </span><span style="color:#a626a4;">= </span><span>document.</span><span style="color:#0184bc;">getElementById</span><span>(</span><span style="color:#50a14f;">'audio'</span><span>);
</span><span style="color:#a626a4;">var </span><span style="color:#e45649;">peaksInstance </span><span style="color:#a626a4;">= </span><span style="color:#e45649;">Peaks</span><span>.</span><span style="color:#0184bc;">init</span><span>({
</span><span> container: document.</span><span style="color:#0184bc;">getElementById</span><span>(</span><span style="color:#50a14f;">'waveform-container'</span><span>),
</span><span> mediaElement: </span><span style="color:#e45649;">audioElement
</span><span>});
</span></code></pre>
<h3 id="3-customize-appearance">3. Customize Appearance</h3>
<p>You can further customize Peaks.js to fit your application’s look and feel:</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#383a42;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#e45649;">Peaks</span><span>.</span><span style="color:#0184bc;">init</span><span>({
</span><span> container: document.</span><span style="color:#0184bc;">getElementById</span><span>(</span><span style="color:#50a14f;">'waveform-container'</span><span>),
</span><span> mediaElement: </span><span style="color:#e45649;">audioElement</span><span>,
</span><span> waveformBuilderOptions: {
</span><span> scale: </span><span style="color:#c18401;">4
</span><span> },
</span><span> zoomLevels: [</span><span style="color:#c18401;">512</span><span>, </span><span style="color:#c18401;">1024</span><span>, </span><span style="color:#c18401;">2048</span><span>, </span><span style="color:#c18401;">4096</span><span>]
</span><span>});
</span></code></pre>
<h2 id="real-life-use-cases">Real-life Use Cases</h2>
<p>Peaks.js has been implemented in various scenarios:</p>
<ul>
<li><strong>Music Production Software</strong>: For navigating and editing audio files.</li>
<li><strong>Audio Transcription Services</strong>: Assisting in annotating and transcribing spoken content.</li>
<li><strong>Educational Platforms</strong>: Helping students visualize sound waves in physics or music studies.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Peaks.js is an indispensable library for anyone working with audio data in the browser. With its feature-rich toolkit, easy integration, and strong community support, it opens up new horizons for developers in the multimedia domain.</p>
<p>Whether you are building a professional audio editing software or a small tool for your personal project, Peaks.js provides the flexibility to cater to your needs. Explore more about Peaks.js and elevate your audio processing capabilities to a professional level.</p>
<p>The full documentation and examples can be found on <a rel="noopener" target="_blank" href="https://github.com/bbc/peaks.js">the Peaks.js GitHub page</a>. Happy coding!</p>
<p><a rel="noopener" target="_blank" href="https://github.com/Zolmok/example-peaksjs">Example project on GitHub</a></p>
GitUI - a git terminal UI2022-09-02T00:00:00+00:002022-09-02T00:00:00+00:00https://zolmok.org/gitui/<p>I've tried various git UI's over the years but I always end up right
back with the cli tool. I've been learning Rust and as a result I like
to find tools that have been written in Rust because I know they'll be
memory safe and generally always pretty fast. Recently I ran across a
tool called <a rel="noopener" target="_blank" href="https://github.com/extrawurst/gitui">GitUI</a> which is a
terminal based UI frontend for git.</p>
<span id="continue-reading"></span>
<p>The git cli tool will do absolutely everything you need it to, but
sometimes it would be nice to just mindless maneuver around without
having to remember the exact syntax for what you want to do, or maybe
you just want to quickly look around. Having a UI that you can
navigate with a few hot-keys would really be nice. There are
certainly several UIs that already do this quite well but if you're
like me and you live in the terminal all day, then it would be nice to
have a tool that you can run right where you are, possibly it lives in
a tmux window or something.</p>
<p>GitUI is not a complete replacement for the git cli tool. I did not
see a way to do merge or rebase for example but if you want to view
logs, view diffs, make commits, amend changes and manage stashes
GitUI will do that and more all without having to remember the syntax
for each task. It also feels very lightweight and its very
fast. GitUI has five tabs along the top, status, logs, files, stashing
and stashes. I'll describe each below.</p>
<h1 id="status">Status</h1>
<p><a href="/images/gitui-status.png"><img src="/images/gitui-status.png" title="Emacs-ng git repo"
width="730" /></a> I'll use the emacs-ng public
git repo for demonstration purposes. The status pane will show any
unstaged files you may have created as well as any files that you
might have changed, essentially what you would get with <code>git status</code>.
My first disappointment came when I realized there are no vim
bindings. It would have been really nice to be able to move around
with the <code>hjkl</code> keys, but alas you have move all the way down to the
arrow keys :(. That said there is a lot you can do from this tab. You
can view diffs, stage and unstage files, reset hunks, push, pull,
edit, blame, and view history. To navigate from one tab to the next
you can either hit the number corresponding to the tab, or use <code><tab></code>
and <code><shift-tab></code> to go back and forth. There are a list of hotkeys
displayed along the bottom that tell you what you can do on each tab.
The one thing I don't like is the commit message. Unless I'm missing
something you can only enter a single line. That said it will let
you drop to an editor and you can obviously enter multiple lines
there.</p>
<h1 id="logs">Logs</h1>
<p><a href="/images/gitui-log.png"><img src="/images/gitui-log.png" title="Emacs-ng commit list view"
width="730" /></a> The logs view obviously shows
you the list of commits that have been made. This is where you can
choose which hash you want to learn more about, hit <code><enter></code> and see
more details. You can also manage branches and tags from this tab,
and probably one of most favorite is just hitting <code><y></code> copies the
hash into your clipboard! The hotkey <code><shift-f></code> will show you the
list of files that were involved in the commit you have selected. You
can also revert a commit by selecting and using the hotkey
<code><shift-d></code>.</p>
<h1 id="files">Files</h1>
<p><a href="/images/gitui-files.png"><img src="/images/gitui-files.png" title="Emacs-ng files list view"
width="730" /></a> The files tab shows a list of
files associated with the repository and it honors <code>.gitignore</code> so
anything in there won't show up in the list. From here you can find,
but sadly it only matches filenames, it does not use <code>git grep</code>. Other
things you can do here is edit the file, although not in this UI, it
uses whatever editor you have in your <code>EDITOR</code> environment
variable. You can also view the history of a file or run <code>blame</code>.</p>
<h1 id="stashing">Stashing</h1>
<p><a href="/images/gitui-stashing.png"><img src="/images/gitui-stashing.png" title="Emacs-ng stashing list
view" width="730" /></a> The stashing tab is
where you can create a stash. You can also see history and blame.</p>
<h1 id="stashes">Stashes</h1>
<p><a href="/images/gitui-stashes.png"><img src="/images/gitui-stashes.png" title="Emacs-ng stashes list
view" width="730" /></a> The stashes tab
differs from stashing in that you get a list of stashes already
created that you can unstash. You can easily inspect the stashes
which is a nice touch so you can see whats in it in case you forget
which I often do.</p>
<h1 id="summary">Summary</h1>
<p>GitUI is a really nice tool that I'll be adding to my toolbox. I
can't say that I'll be using it in my everyday workflow but
occasionally it will be nice to be able to navigate around while
researching something or managing stashes. While there are a couple
features missing that I've pointed out, most of them appear to at
least be on the roadmap such as log search, and rebase. If you live
in the command line like I do give this tool a try and let me know
what you think.</p>
Emacs problem with tide-mode, eslint, and eldoc2022-08-06T00:00:00+00:002022-08-06T00:00:00+00:00https://zolmok.org/emacs-tide-mode-eslint-eldoc/<iframe width="560" height="315" src="https://www.youtube.com/embed/nS92d9J13XY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen class="video-frame"></iframe>
<p>I edit almost everything in Emacs. I primarily edit JavaScript or
TypeScript so I use the <a rel="noopener" target="_blank" href="https://github.com/ananthakumaran/tide">Tide
Mode</a> package for it's many
features that make editing JavaScript much easier. Two of those
features clashed on me, flymake and eldoc. I use flymake instead of
flycheck because it's built-in to Emacs so it's not another package I
have to install and configure. Flymake performs syntax checking using
a backend, for JavaScript that would be eslint. So if an error is
found, the text is highlighted and you can navigate all the errors in
a buffer and deal with each one as you go. Eldoc shows you
documentation about function calls, variables and such.</p>
<span id="continue-reading"></span>
<p>The problem I ran into is that if flymake found errors on a symbol
that also had documentation, the error would display for a second or
two in the minibuffer then be overwritten by the eldoc documentation.</p>
<p>I came up with a work-around, not really a fix. In my emacs
configuration I set the following:</p>
<pre data-lang="lisp" style="background-color:#fafafa;color:#383a42;" class="language-lisp "><code class="language-lisp" data-lang="lisp"><span> (</span><span style="color:#0184bc;">setq</span><span> eldoc</span><span style="color:#a626a4;">-</span><span style="color:#0184bc;">documentation</span><span style="color:#a626a4;">-</span><span>strategy </span><span style="color:#c18401;">#'eldoc-documentation-compose</span><span>)
</span></code></pre>
<p>This doesn't do what I thought it would. The documentation says the
following:</p>
<blockquote class="relative border-l-4 pl-4 sm:pl-6
dark:border-gray-700"> <p class="text-gray-800 sm:text-xl
dark:text-gray-500"><em> eldoc-documentation-compose: calls all
functions in the special hook and displays all of the resulting doc
strings together. Wait for all strings to be ready, and preserve
their relative order as specified by the order of functions in the
hook; </p></em>
</blockquote>
<p>I thought this would take the call from flymake and combine it the the
subsequent call from tide-mode which both use eldoc and just combine
the messages, but this is not the case. Instead it essential disables
eldoc under the point altogether. So I wanted another way to trigger
the documentation, but only when I wanted it, so it would no longer
conflict with errors under the point. So I made a hotkey ~<C-c C-.>~
that would trigger the documentation and it works very well. This is
probably the way it should have been all along really. Here is how I
created the keybind.</p>
<pre data-lang="lisp" style="background-color:#fafafa;color:#383a42;" class="language-lisp "><code class="language-lisp" data-lang="lisp"><span>(</span><span style="color:#0184bc;">use-package</span><span> tide
</span><span> :</span><span style="color:#a626a4;">if</span><span> config</span><span style="color:#a626a4;">-</span><span>enable</span><span style="color:#a626a4;">-</span><span>web</span><span style="color:#a626a4;">-</span><span>mode
</span><span> :bind ((</span><span style="color:#50a14f;">"C-c C-."</span><span> . tide</span><span style="color:#a626a4;">-</span><span style="color:#0184bc;">documentation</span><span style="color:#a626a4;">-</span><span>at</span><span style="color:#a626a4;">-</span><span>point))
</span><span> :after (typescript</span><span style="color:#a626a4;">-</span><span>mode flymake))
</span></code></pre>
<p>The <code>bind</code> property above is what creates the hotkey. I would love to
hear from you if you have alternative ways of handling this kind of
thing.</p>
/usr/local/src2022-01-04T00:00:00+00:002022-01-04T00:00:00+00:00https://zolmok.org/usr-local-src/<p>As of late last year, when my work gave me a System76 Linux machine
I'm now officially 100% on Linux both at work and at home. I'm also a
programmer and on both my home and work machines I have a directory
under my <code>$HOME</code> called <code>dev</code> where I store all of my coding projects.
Most of the apps I install come from a package manager like <code>apt</code>,
however from time to time I find the need to compile a project from
source. For example, I use Emacs as my coding editor and occasionally
there will be a new feature available on the main branch that is not
available in the current shipping package. Right now that is project
management that will be available in <code>v28</code> but the current shipping
version (as of this writing) is <code>v27</code>. If I want that feature then I
have to build Emacs myself. The question is, where do I put this
project because I don't really want it in my <code>$HOME/dev</code> directory
with all my own projects.</p>
<span id="continue-reading"></span>
<p>Linux has developed a standard directory structure called the
<a rel="noopener" target="_blank" href="https://refspecs.linuxfoundation.org/fhs.shtml">FHS</a> or Filesystem
Hierarchy Standard. In this standard they have defined a directory
<code>/usr/local</code> with the following definition "The /usr/local hierarchy
is for use by the system administrator when installing software
locally.". Below that directory they have <code>src</code>, and this directory
is designed for exactly this purpose. So if you're ever wondering
where to put 3rd party code that you want to build from source, or
maybe even some library that you found a bug in that you want to fork
and put up a PR, this is a great place to do it so it doesn't
interfere with your own projects. One thing I did do to make this a
little easier was to give ownership of this directory to my logged in
user.</p>
<pre data-lang="bash" style="background-color:#fafafa;color:#383a42;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#e45649;">sudo</span><span> chown $</span><span style="color:#e45649;">USER</span><span>:$</span><span style="color:#e45649;">USER</span><span> /usr/local/src
</span></code></pre>
<p>Now you don't have to run <code>sudo</code> every time you download source or run
a build command.</p>
Emacs NG2022-01-03T00:00:00+00:002022-01-03T00:00:00+00:00https://zolmok.org/emacs-ng/<p>I think I have definitely been assimilated into the Emacs lifestyle. I
find myself tinkering with it constantly, and I'm always trying out
new packages and waiting for new features. I'm a JavaScript developer
for employment and I have Emacs finely tuned for front-end JavaScript
development. In my spare time I love to play around with Rust. I
started my programming career writing C so I have an affinity for
compiled languages and I really like the promise that Rust gives as a
memory safe compiled language. So at some point I stumbled on Emacs
NG which has a few things going for it that really caught my
attention.</p>
<span id="continue-reading"></span>
<ul>
<li>It is a fork of Emacs proper, but with regular merges of the latest
master branch</li>
<li>The goal is to explore new development approaches, not re-create a
"better" Emacs</li>
<li>Integrates Deno's JavaScript runtime but it doesn't want to do
something strange like replace Elisp with it (Deno is a Rust
project, so this bullet gets 2 points)</li>
</ul>
<p>That said I have not really taken advantage of the Deno integration
just yet, but because <a rel="noopener" target="_blank" href="https://emacs-ng.github.io/emacs-ng/">emacs-ng</a>
is a fork of emacs master I have been able to try out the new project
management package that will ship with emacs 28 and it is really nice.
At any rate, I just wanted to show my support for emacs-ng. Keep up
the great work! I can't wait to see where this project goes.</p>
How I learn new technology2021-11-23T00:00:00+00:002021-11-23T00:00:00+00:00https://zolmok.org/how-i-learn-new-technology/<p>Over the years I've used several different editors and learned several
different programming languages and technologies. The problem often
seems to be that I'll only need it for a short while, just long enough
to figure out how it all works and then I shelve it, sometimes for a a
year or two, and then I'll need to use it again at which point I'll
have to re-learn it all over again. I'm a JavaScript developer by
trade and <a rel="noopener" target="_blank" href="https://d3js.org/">D3.js</a> seems to be one of those
projects. It is one of the more challenging libraries I have to use. I
really enjoy using it, and it only takes me a day or two before I
start remember how it all works again.</p>
<span id="continue-reading"></span><h2 id="using-google">Using google</h2>
<p>In the past I thought I was being clever by just relying on google to
store information that was not currently relevant to me, after all its
just a few search queries away. The problem is that the index is
constantly evolving and the sites that you might have referenced in
the past may no longer come up in the top 10 search (you know you
never go past the first page, if you do you know something is wrong)
so you may have to dig for a bit. The next thing you know an hour or
so has gone by as you've had to search for the half a dozen or more
concepts that you have forgotten. It's even worse if the content you
found was in a video because you have to scrub through hoping to find
it. I knew there had to be a better way, so here is what I came up
with. This is not revolutionary by any means but it's pretty simple,
here is how it works.</p>
<h2 id="create-a-cheat-sheet">Create a cheat sheet</h2>
<p>There are all sorts of cheat sheets that other people have created,
but that is not what I'm talking about. Those cheat sheets are
designed to help the person that created them. That is like
downloading someone else dotfiles, there is a bunch of stuff in there
you just won't use. You really want to create your own. Here is how I
do it. Find a good writing tool, preferably a plain text editor or
note taking tool. I like org-mode in Emacs. I'm not going to try and
sell you on org-mode or Emacs, I'll save that for another post. I'll
just say that the reason I've settled on org-mode is:</p>
<ol>
<li>I already use Emacs</li>
<li>I can jump to my notes with a hotkey</li>
<li>I can quickly capture notes without losing my train of thought</li>
<li>Since the notes are text based I can manipulate them in with my
editor</li>
</ol>
<h2 id="learn-your-new-technology">Learn your new technology</h2>
<p>Now comes the fun part, learn (or re-learn as the case may be) your
new technology. Step 1 is to create a new document or note in whatever
tool you have chosen, then start reading the tutorial, or watch the
video, of if you're the type that likes to just jump right in do
that. If you're reading a tutorial or watching a video, take notes,
make note of hotkeys that might be useful and save your notes
often. Once you're ready to start using what you've learned, this is
the most important part of the process, eventually you're going to
need to do something and you've forgotten how, and you're going to
need to look it up. This is the part that takes the discipline, but if
you stick to it this is where you will start to become super
productive. Here is what you do:</p>
<ol>
<li>Reference the notes you've been taking, there is a really good
chance you've already written it down</li>
<li>If you don't find it in your notes, go research, either re-read the
tutorial, google search, find a video, and figure out how to do
whatever if it is you need</li>
<li>Once you've figured it out, this is the most important part, WRITE
IT IN YOUR NOTES!</li>
</ol>
<p>You may actually forget how to do the thing again, but if you follow
the steps above the next time it you will find the answer in your
notes, and if you take notes in your editor, and you create a hotkey
to those notes, you'll have your answer in seconds! Eventually all the
things you want to be able to do will be nicely documented in your
notes, and you'll have your very own personal cheat sheet tailored
with only the information you need, not what someone else
needed. Yours may not be as beautiful as some of the ones you can find
online, but it will be practical, and it will make you successful.</p>
<p>Emacs has the concept of a register that you can program with a hotkey
that will take you to a document. So I have this in my configuration:</p>
<pre data-lang="lisp" style="background-color:#fafafa;color:#383a42;" class="language-lisp "><code class="language-lisp" data-lang="lisp"><span> (</span><span style="color:#0184bc;">set</span><span style="color:#a626a4;">-</span><span>register ?w '(file . </span><span style="color:#50a14f;">"~/org/wiki/index.org"</span><span>))
</span></code></pre>
<p>So all I have to do is <code><C-x r j w></code> and it takes me to an index of
different things that I've learned. Then I can jump to the one I want,
open it up and I have all the information I need ready to go. You
might be thinking, whoa, that is a crazy hotkey, how do you remember
that? It is sort of pnuemonic when you think about it. All you have to
remember is the C-x part. Then you know it is a "register" so you type
"r" and you want to "jump" to it so you type "j". Pretty soon you
don't even have to think about it any more. That said, you can creat
whatever hotkey you like, I've just chosen this route. Here is a
screenshot of part of the notes I've taken to learn Emacs.</p>
<p><a href="/images/emacs-cheat-sheet.png"><img src="/images/emacs-cheat-sheet.png" alt="Emacs cheat sheet" width="730" /></a></p>
<p>One last tip. I said in Emacs you can capture information instantly
without losing your train of thought. Sometimes something will just
come to you and you'll want to capture it, or you'll lookup that thing
you couldn't remember and you'll want to write it down quickly and
move on. Many people have a notepad near by for such things. However
emacs has <code>org-capture</code>. It is bound to <code><C-c c></code>. When you key that in
you can enter the information you want to capture, then <code><C-c C-c></code>. and it's gone, well not gone gone, but out of your way. It has
been saved off in the document you defined in your configuration. Then
later when you're not in the middle of your project you can go back to
it and organize it and put it in a more permanent place. Or maybe it's
a task you need to do, go do it, delete the note and move on.</p>
<p>I hope you've found this information helpful, if so please share
below. If you have ways that you like to learn things please share
those as well, I'm always looking for ways to improve my process.</p>
Manage multiple projects or sessions with tmux2021-11-08T00:00:00+00:002021-11-08T00:00:00+00:00https://zolmok.org/tmux-multiple-projects-sessions/<iframe width="560" height="315" src="https://www.youtube.com/embed/NCgSOiLqB04" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen class="video-frame"></iframe>
<p>This is my obligatory, "it's been a while" post. My apologies. Much
has happened since my last post, I've moved into a new house, a
pandemic, and I now have a new day job. At any rate, all that to say
I'm hoping to get back on track with my posts. As a special treat I've
created my first video content and I hope to do more of this as
well. Today's topic is on tmux and how I use it to manage my multiple
projects concurrently. I have a couple of scripts that I use to manage
all of this.</p>
<span id="continue-reading"></span>
<p>My workflow goes something like this:</p>
<ol>
<li>
<p>Script to easily create a project session
I've created a script called <code>dev </code>that will take the name of the
current directory, create a tmux session by that name, then create
the windows that I like to use, run a specific command in those
windows, then set the focus on the first window. If I detach from
the session, then later run the dev script a second time from the
same directory, instead of creating a new session it will attach
to the one that was already created.</p>
<pre data-lang="bash" style="background-color:#fafafa;color:#383a42;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#a0a1a7;">#! /usr/bin/env bash
</span><span>
</span><span style="color:#e45649;">directory_basename</span><span style="color:#a626a4;">=</span><span style="color:#50a14f;">${</span><span style="color:#e45649;">PWD</span><span style="color:#a626a4;">##*</span><span style="color:#50a14f;">/}
</span><span>
</span><span style="color:#a626a4;">if ! </span><span style="color:#e45649;">tmux</span><span> has-session</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"
</span><span style="color:#a626a4;">then
</span><span> </span><span style="color:#e45649;">tmux</span><span> new-session</span><span style="color:#e45649;"> -s </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span style="color:#e45649;"> -n</span><span> Server</span><span style="color:#e45649;"> -d
</span><span>
</span><span> </span><span style="color:#a0a1a7;"># create moar windows
</span><span> </span><span style="color:#e45649;">tmux</span><span> new-window</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:1</span><span style="color:#e45649;"> -n</span><span> UI
</span><span> </span><span style="color:#e45649;">tmux</span><span> new-window</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:2</span><span style="color:#e45649;"> -n</span><span> Tests
</span><span> </span><span style="color:#e45649;">tmux</span><span> new-window</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:3</span><span style="color:#e45649;"> -n</span><span> Code
</span><span> </span><span style="color:#e45649;">tmux</span><span> new-window</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:4</span><span style="color:#e45649;"> -n</span><span> Zsh
</span><span>
</span><span> </span><span style="color:#a0a1a7;"># run some command in the first window
</span><span> </span><span style="color:#a626a4;">if </span><span style="color:#0184bc;">[ </span><span style="color:#e45649;">-z </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">1</span><span style="color:#50a14f;">" </span><span style="color:#0184bc;">]
</span><span> </span><span style="color:#a626a4;">then
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:0.0 </span><span style="color:#50a14f;">"cd ${</span><span style="color:#e45649;">PWD</span><span style="color:#50a14f;">}"</span><span> C-m
</span><span> </span><span style="color:#a626a4;">else
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:0.0 </span><span style="color:#50a14f;">"cd ${</span><span style="color:#e45649;">PWD</span><span style="color:#50a14f;">} && ${</span><span style="color:#e45649;">1</span><span style="color:#50a14f;">}"</span><span> C-m
</span><span> </span><span style="color:#a626a4;">fi
</span><span>
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:1.0 </span><span style="color:#50a14f;">"cd ${</span><span style="color:#e45649;">PWD</span><span style="color:#50a14f;">}"</span><span> C-m
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:2.0 </span><span style="color:#50a14f;">"cd ${</span><span style="color:#e45649;">PWD</span><span style="color:#50a14f;">}"</span><span> C-m
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:3.0 </span><span style="color:#50a14f;">"cd ${</span><span style="color:#e45649;">PWD</span><span style="color:#50a14f;">}"</span><span> C-m
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:4.0 </span><span style="color:#50a14f;">"cd ${</span><span style="color:#e45649;">PWD</span><span style="color:#50a14f;">}"</span><span> C-m
</span><span>
</span><span> </span><span style="color:#a0a1a7;"># select the server window and pane
</span><span> </span><span style="color:#e45649;">tmux</span><span> select-window</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"</span><span>:0.0
</span><span style="color:#a626a4;">fi
</span><span style="color:#e45649;">tmux</span><span> attach</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">directory_basename</span><span style="color:#50a14f;">"
</span></code></pre>
</li>
<li>
<p>Script to easily attach to a project session</p>
<p>If I detach from a session, then do some work unrelated to any of
the sessions I've created and find myself in some distant
unrelated directory, my <code>dev</code> script won't help me get back to any
of my tmux sessions. Sure I could list them out and attach to them
manually but that is a lot of typing, and developers are
notoriously lazy, myself included. So I've created a script I call
tm that will list each tmux session and assign a numeric index to
each, then you can choose one of the numbers and it will attach to
that session, it's that simple. If you don't see the session you
want to attach to, any character other than one of the indexes
will exit the script. I generally choose q. You would put this
script in your .zshrc or .bashrc, then just call it like any other
command.</p>
<pre data-lang="bash" style="background-color:#fafafa;color:#383a42;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#a0a1a7;"># present a list of tmux sessions to choose from
</span><span style="color:#a626a4;">function </span><span style="color:#0184bc;">tm</span><span>() {
</span><span> </span><span style="color:#e45649;">select</span><span> sel in $(</span><span style="color:#e45649;">tmux</span><span> ls</span><span style="color:#e45649;"> -F </span><span style="color:#50a14f;">'#S'</span><span>)</span><span style="color:#a626a4;">; do
</span><span> </span><span style="color:#a626a4;">break;
</span><span> </span><span style="color:#a626a4;">done
</span><span>
</span><span> </span><span style="color:#a626a4;">if </span><span style="color:#0184bc;">[ </span><span style="color:#e45649;">-z </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">sel</span><span style="color:#50a14f;">" </span><span style="color:#0184bc;">]
</span><span> </span><span style="color:#a626a4;">then
</span><span> </span><span style="color:#0184bc;">echo </span><span style="color:#50a14f;">"You didn't select an appropriate choice"
</span><span> </span><span style="color:#a626a4;">else
</span><span> </span><span style="color:#e45649;">tmux</span><span> attach</span><span style="color:#e45649;"> -t </span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">sel</span><span style="color:#50a14f;">"
</span><span> </span><span style="color:#a626a4;">fi
</span><span>}
</span></code></pre>
</li>
</ol>
<pre style="background-color:#fafafa;color:#383a42;"><code></code></pre>
Do you lint your elisp files?2020-03-19T00:00:00+00:002020-03-19T00:00:00+00:00https://zolmok.org/do-you-lint-your-elisp-files/<p>This will be a short post this week. I just wanted to share something
I learned recently about flycheck and my elisp files. I use flycheck
to lint most of my code but I had it configured specifically for the
languages I use the most. I was having some problems with flycheck and
I discovered "global-flycheck-mode" and decided to use that rather
than individually configure it for each language. I was surprised to
see it lint my elisp files. I'm fairly new to emacs, only started
using it a couple of years ago, and I've since rolled my own
configuration.<span id="continue-reading"></span> Flycheck identified maybe 30 items it wanted me to
tweak in my configuration so I did. I also learned about "checkdoc"
which helped even more. I was able to get it down to 2 warnings which
don't exist on my Mac I think because they are mac specific
configurations:</p>
<pre data-lang="lisp" style="background-color:#fafafa;color:#383a42;" class="language-lisp "><code class="language-lisp" data-lang="lisp"><span> (</span><span style="color:#0184bc;">setq</span><span> mac</span><span style="color:#a626a4;">-</span><span>command</span><span style="color:#a626a4;">-</span><span>modifier 'meta)
</span><span> (</span><span style="color:#0184bc;">setq</span><span> mac</span><span style="color:#a626a4;">-</span><span>option</span><span style="color:#a626a4;">-</span><span>modifier 'super)
</span></code></pre>
<p>Those two lines give me the warning:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span> assignment to free variable 'mac-command-modifier'
</span><span> assignment to free variable 'mac-option-modifier'
</span></code></pre>
<p>And I'm not sure how to get rid of them on my Linux machine. At any rate if you don't already lint your elisp files I would highly recommend it. I feel like it is making me a stronger elisp coder! </p>
Django ImageField backed by DigitalOcean Spaces2020-03-10T00:00:00+00:002020-03-10T00:00:00+00:00https://zolmok.org/django-imagefield-backed-digitalocean-spaces/<p>Django has a wonderful tool for handling image uploads that has been
carefully thought out and works very well. Consider all the things you
would have to manage if you were to roll your own.</p>
<span id="continue-reading"></span>
<ol>
<li>Where to store it</li>
<li>How to handle the same image being uploaded twice but in different records</li>
<li>On change, remove the old image and replace with the new one</li>
<li>Deleting it when the record gets deleted</li>
</ol>
<p>Django handles some of these things for you right out of the
box. However it begins to break down if you have more than one node
because it keeps track of where they are stored on disk and if you
have more than one node, some images could be found on one node but
not the other so you would need a way to synchronize all the nodes or
use a shared store like S3 or DigitalOcean Spaces. If you do this then
the ImageField will need a little help. You can help it by either
downloading a library to take care of this for you, or you can roll
your own Storage. I opted for rolling my own storage and that is what
I'm going to talk about today.</p>
<p>My first attempt was not to create my own custom storage however, I
thought just hooking into the model might be "good enough"...and it
did kind of work, until I was making some edits and the load balancer
switched nodes on me for whatever reason and Django started throwing
500 errors. It turned out that it was because it was looking for the
image on disk but it was on another node, not the current one. That is
when I discovered custom storage. With a custom storage you can tell
the ImagField to use your new storage and you don't have to manage all
of that stuff in the model. Making a custom storage is not that hard,
you just need to subclass "Storage" and override the methods you
need. You can read more about the process here:
<a rel="noopener" target="_blank" href="https://docs.djangoproject.com/en/3.0/howto/custom-file-storage/">https://docs.djangoproject.com/en/3.0/howto/custom-file-storage/</a>. Here
is the storage I came up with for DigitalOcean Spaces.</p>
<pre data-lang="python" style="background-color:#fafafa;color:#383a42;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#a626a4;">import </span><span>boto3
</span><span style="color:#a626a4;">import </span><span>logging
</span><span style="color:#a626a4;">import </span><span>os
</span><span>
</span><span style="color:#a626a4;">from </span><span>botocore.exceptions </span><span style="color:#a626a4;">import </span><span>ClientError
</span><span style="color:#a626a4;">from </span><span>django.core.files.storage </span><span style="color:#a626a4;">import </span><span>Storage
</span><span style="color:#a626a4;">from </span><span>django.utils.deconstruct </span><span style="color:#a626a4;">import </span><span>deconstructible
</span><span>
</span><span>
</span><span style="color:#a626a4;">def </span><span style="color:#0184bc;">get_client</span><span>():
</span><span> </span><span style="color:#a0a1a7;">""" Initialize a session using DigitalOcean Spaces. """
</span><span> session </span><span style="color:#a626a4;">= </span><span>boto3.session.</span><span style="color:#e45649;">Session</span><span>()
</span><span> spaces_region </span><span style="color:#a626a4;">= </span><span>os.environ.</span><span style="color:#e45649;">get</span><span>(</span><span style="color:#50a14f;">'SPACES_REGION'</span><span>, </span><span style="color:#50a14f;">''</span><span>)
</span><span> spaces_endpoint </span><span style="color:#a626a4;">= </span><span>os.environ.</span><span style="color:#e45649;">get</span><span>(</span><span style="color:#50a14f;">'SPACES_HOST'</span><span>, </span><span style="color:#50a14f;">''</span><span>)
</span><span> spaces_access_key </span><span style="color:#a626a4;">= </span><span>os.environ.</span><span style="color:#e45649;">get</span><span>(</span><span style="color:#50a14f;">'SPACES_USER'</span><span>, </span><span style="color:#50a14f;">''</span><span>)
</span><span> spaces_password </span><span style="color:#a626a4;">= </span><span>os.environ.</span><span style="color:#e45649;">get</span><span>(</span><span style="color:#50a14f;">'SPACES_PASSWD'</span><span>, </span><span style="color:#50a14f;">''</span><span>)
</span><span>
</span><span> </span><span style="color:#a626a4;">return </span><span>session.</span><span style="color:#e45649;">client</span><span>(</span><span style="color:#50a14f;">'s3'</span><span>,
</span><span> </span><span style="color:#e45649;">region_name</span><span style="color:#a626a4;">=</span><span>spaces_region,
</span><span> </span><span style="color:#e45649;">endpoint_url</span><span style="color:#a626a4;">=</span><span>spaces_endpoint,
</span><span> </span><span style="color:#e45649;">aws_access_key_id</span><span style="color:#a626a4;">=</span><span>spaces_access_key,
</span><span> </span><span style="color:#e45649;">aws_secret_access_key</span><span style="color:#a626a4;">=</span><span>spaces_password)
</span><span>
</span><span>
</span><span>@</span><span style="color:#e45649;">deconstructible
</span><span style="color:#a626a4;">class </span><span style="color:#c18401;">SpacesStorage(Storage):
</span><span> bucket </span><span style="color:#a626a4;">= </span><span style="color:#50a14f;">"zol-images"
</span><span>
</span><span> </span><span style="color:#a626a4;">def </span><span style="color:#0184bc;">_save</span><span>(</span><span style="color:#e45649;">self</span><span>, </span><span style="color:#e45649;">name</span><span>, </span><span style="color:#e45649;">content</span><span>):
</span><span> </span><span style="color:#a0a1a7;">""" Save the image to a DigitalOcean Space """
</span><span> client </span><span style="color:#a626a4;">= </span><span style="color:#e45649;">get_client</span><span>()
</span><span> </span><span style="color:#a626a4;">try</span><span>:
</span><span> client.</span><span style="color:#e45649;">upload_fileobj</span><span>(</span><span style="color:#e45649;">Fileobj</span><span style="color:#a626a4;">=</span><span>content,
</span><span> </span><span style="color:#e45649;">Bucket</span><span style="color:#a626a4;">=</span><span style="color:#e45649;">self</span><span>.bucket,
</span><span> </span><span style="color:#e45649;">Key</span><span style="color:#a626a4;">=</span><span>name,
</span><span> </span><span style="color:#e45649;">ExtraArgs</span><span style="color:#a626a4;">=</span><span>{
</span><span> </span><span style="color:#50a14f;">'ACL'</span><span>: </span><span style="color:#50a14f;">'public-read'</span><span>,
</span><span> </span><span style="color:#50a14f;">'ContentType'</span><span>: content.content_type
</span><span> })
</span><span> </span><span style="color:#a626a4;">except </span><span>ClientError </span><span style="color:#a626a4;">as </span><span>e:
</span><span> logging.</span><span style="color:#e45649;">error</span><span>(e)
</span><span>
</span><span> </span><span style="color:#a626a4;">return </span><span>name
</span><span>
</span><span> </span><span style="color:#a626a4;">def </span><span style="color:#0184bc;">delete</span><span>(</span><span style="color:#e45649;">self</span><span>, </span><span style="color:#e45649;">name</span><span>):
</span><span> </span><span style="color:#a0a1a7;">""" Deletes image files on `post_delete` """
</span><span> </span><span style="color:#0184bc;">print</span><span>(</span><span style="color:#50a14f;">"spaces-delete: </span><span style="color:#c18401;">%s</span><span style="color:#50a14f;">" </span><span style="color:#a626a4;">% </span><span>name)
</span><span> client </span><span style="color:#a626a4;">= </span><span style="color:#e45649;">get_client</span><span>()
</span><span>
</span><span> </span><span style="color:#a626a4;">try</span><span>:
</span><span> client.</span><span style="color:#e45649;">delete_object</span><span>(</span><span style="color:#e45649;">Bucket</span><span style="color:#a626a4;">=</span><span style="color:#e45649;">self</span><span>.bucket, </span><span style="color:#e45649;">Key</span><span style="color:#a626a4;">=</span><span>name)
</span><span> </span><span style="color:#a626a4;">except </span><span>ClientError </span><span style="color:#a626a4;">as </span><span>e:
</span><span> logging.</span><span style="color:#e45649;">error</span><span>(e)
</span><span>
</span><span> </span><span style="color:#a626a4;">def </span><span style="color:#0184bc;">exists</span><span>(</span><span style="color:#e45649;">self</span><span>, </span><span style="color:#e45649;">name</span><span>):
</span><span> </span><span style="color:#a0a1a7;">""" Check if the image name already exists in the Space """
</span><span> client </span><span style="color:#a626a4;">= </span><span style="color:#e45649;">get_client</span><span>()
</span><span>
</span><span> </span><span style="color:#a626a4;">try</span><span>:
</span><span> client.</span><span style="color:#e45649;">get_object</span><span>(</span><span style="color:#e45649;">Bucket</span><span style="color:#a626a4;">=</span><span style="color:#e45649;">self</span><span>.bucket,
</span><span> </span><span style="color:#e45649;">Key</span><span style="color:#a626a4;">=</span><span>name)
</span><span> </span><span style="color:#a626a4;">except </span><span>ClientError </span><span style="color:#a626a4;">as </span><span>e:
</span><span> logging.</span><span style="color:#e45649;">error</span><span>(e)
</span><span> </span><span style="color:#a626a4;">return </span><span style="color:#c18401;">False
</span><span>
</span><span> </span><span style="color:#a626a4;">return </span><span style="color:#c18401;">True
</span><span>
</span><span> </span><span style="color:#a626a4;">def </span><span style="color:#0184bc;">url</span><span>(</span><span style="color:#e45649;">self</span><span>, </span><span style="color:#e45649;">name</span><span>):
</span><span> </span><span style="color:#a0a1a7;">""" Return the URL to access the image on the CDN """
</span><span> </span><span style="color:#a626a4;">return </span><span style="color:#50a14f;">"https://zol-images.zolmok.org/</span><span style="color:#c18401;">%s</span><span style="color:#50a14f;">" </span><span style="color:#a626a4;">% </span><span>name
</span></code></pre>
<p>So now your new storage knows how to read, write and delete images we
just need to tell the model about it.</p>
<pre data-lang="python" style="background-color:#fafafa;color:#383a42;" class="language-python "><code class="language-python" data-lang="python"><span>fs </span><span style="color:#a626a4;">= </span><span style="color:#e45649;">SpacesStorage</span><span>()
</span><span>
</span><span style="color:#a626a4;">class </span><span style="color:#c18401;">Post(models.Model):
</span><span> </span><span style="color:#c18401;">...
</span><span> hero_image </span><span style="color:#a626a4;">= </span><span style="color:#e45649;">ImageField</span><span>(
</span><span> </span><span style="color:#e45649;">blank</span><span style="color:#a626a4;">=</span><span style="color:#c18401;">True</span><span>,
</span><span> </span><span style="color:#e45649;">default</span><span style="color:#a626a4;">=</span><span style="color:#50a14f;">''</span><span>,
</span><span> </span><span style="color:#e45649;">storage</span><span style="color:#a626a4;">=</span><span>fs,
</span><span> </span><span style="color:#e45649;">upload_to</span><span style="color:#a626a4;">=</span><span style="color:#50a14f;">"gallery"
</span><span> )
</span></code></pre>
<p>That should be enough to upload your image but if you use the built-in
Django admin you want to replace the image with a new one you still
have a problem because the admin doesn't do that automatically. I
solved this by overriding "ImageField".</p>
<pre data-lang="python" style="background-color:#fafafa;color:#383a42;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#a626a4;">from </span><span>django.db </span><span style="color:#a626a4;">import </span><span>models
</span><span>
</span><span>
</span><span style="color:#a626a4;">class </span><span style="color:#c18401;">ImageField(models.ImageField):
</span><span> </span><span style="color:#a626a4;">def </span><span style="color:#0184bc;">save_form_data</span><span>(</span><span style="color:#e45649;">self</span><span>, </span><span style="color:#e45649;">instance</span><span>, </span><span style="color:#e45649;">data</span><span>):
</span><span> </span><span style="color:#a626a4;">if </span><span>data </span><span style="color:#a626a4;">is not </span><span style="color:#c18401;">None</span><span>:
</span><span> file </span><span style="color:#a626a4;">= </span><span style="color:#0184bc;">getattr</span><span>(instance, </span><span style="color:#e45649;">self</span><span>.attname)
</span><span>
</span><span> </span><span style="color:#a0a1a7;"># delete the image from the DigitalOcean space
</span><span> </span><span style="color:#a0a1a7;"># if the "clear" checkbox was checked
</span><span> </span><span style="color:#a626a4;">if </span><span>file.name </span><span style="color:#a626a4;">!= </span><span style="color:#50a14f;">'' </span><span style="color:#a626a4;">and </span><span>data </span><span style="color:#a626a4;">is </span><span style="color:#c18401;">False</span><span>:
</span><span> file.</span><span style="color:#e45649;">delete</span><span>(</span><span style="color:#e45649;">save</span><span style="color:#a626a4;">=</span><span style="color:#c18401;">False</span><span>)
</span><span>
</span><span> </span><span style="color:#0184bc;">super</span><span>(models.ImageField, </span><span style="color:#e45649;">self</span><span>).</span><span style="color:#e45649;">save_form_data</span><span>(instance, data)
</span></code></pre>
<p>You can see in the model code above that I'm already using the custom
"ImageField" and that should be all you need. Now you can add, edit,
and remove images backed by DigitalOcean Spaces. There may be a better
way and if you have any suggestions for improvement I would love to
hear about it.</p>
How to use multiple ssh-keys with multiple GitHub accounts2020-03-01T00:00:00+00:002020-03-01T00:00:00+00:00https://zolmok.org/how-use-multiple-ssh-keys-multiple-github-accounts/<p>For various reasons I have more than one GitHub account. Unfortunately
GitHub only allows you to use your ssh-key on a single account. So if
you have multiple accounts like I do then you need multiple ssh-keys
and it can be annoying to deal with unless you get a little creative
with your ssh configuration. Here is what I did for years before I
finally got annoyed enough to research a better way.</p>
<span id="continue-reading"></span>
<ol>
<li>Change to project directory, do some work</li>
<li>git push</li>
<li>Get error about not having access rights</li>
<li>ssh-add ~/.ssh/id_rsa_whatever_key</li>
<li>On rare occasions I might still get the error because I had another
key loaded that hadn't expired yet</li>
<li>ssh-add -D (to remove all keys)</li>
<li>ssh-add ~/.ssh/id_rsa_whatever_key</li>
<li>git push (finally it works!)</li>
</ol>
<p>The other thing that I would have liked to do is maybe even use my git
package from inside my editor, Emacs, to push and pull commits but
figuring out how to get that to work with various keys never seemed to
make it on my radar. It's amazing how much you can put up with while
you're in the middle of something and you just want to get it
done. But one day I had finally had enough and I had some extra time
so I decided to research how others have solved the problem and came
up with a solution. I thought git might have a configuration where you
could set a path to a key to be used for each repository, kind of like
you can with your email address if you want to use a different one for
different repositories for some reason. You can set repository level
configurations. But it turns out this is all handled at the ssh
level. The solution is quite simple. I have 3 ssh keys, so here is
what I did.</p>
<ol>
<li>Create 3 ssh-keys giving each one a unique name, (ssh-keygen -t rsa)</li>
<li>Create 3 <a rel="noopener" target="_blank" href="https://github.com">github.com</a> entries in your ~/.ssh/config file, one for each key you created<pre style="background-color:#fafafa;color:#383a42;"><code><span>Host github.com-key1
</span><span> HostName github.com
</span><span> User git
</span><span> IdentityFile ~/.ssh/id_rsa_key1
</span><span>Host github.com-key2
</span><span> HostName github.com
</span><span> User git
</span><span> IdentityFile ~/.ssh/id_rsa_key2
</span><span>Host github.com-key1
</span><span> HostName github.com
</span><span> User git
</span><span> IdentityFile ~/.ssh/id_rsa_key2
</span></code></pre>
</li>
<li>Visit the repo or repos you want to use the first key with and update the remote with the new ssh config entry<pre data-lang="bash" style="background-color:#fafafa;color:#383a42;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#e45649;">git</span><span> remote set-url origin git@github.com-key1:path-to/repo.git
</span></code></pre>
</li>
<li>Rinse and repeat for your other repos using the appropriate key</li>
</ol>
<p>Now that you have your keys in place, <code>git</code> push/pull will just work
without any key management whatsoever! You never have to worry about
them expiring, overlapping, or any of those things you might have had
to deal with before if you were hard-headed like me. It will also just
work with Magit or whatever editor plugin you might want to use in
your environment.</p>
GIMP as a screenshot tool for Linux2020-02-15T00:00:00+00:002020-02-15T00:00:00+00:00https://zolmok.org/gimp-screenshot-tool-linux/<p>As a Frontend Developer by trade, I take lots and lots of
screenshots. Windows and MacOS both have several good options for
taking screenshots. The one I like a lot on my Mac is
<a rel="noopener" target="_blank" href="https://monosnap.com/">Monosnap</a> but they did not have a solution for
Linux until recently. Now they have a browser addon that does just
about everything that the MacOS app does. One thing that is missing is
the ability to call it up with a hotkey and while taking screenshots
outside of the browser is possible, its not as seamless as the MacOS
app is. That said, as a Frontend Developer, 99 percent of my
screenshots are taken inside the browser anyway.<span id="continue-reading"></span> I tried
shutter which seems to be the current leader but it has some
drawbacks. I finally discovered GIMP had a screenshot mechanism and
I'm going to show you how to use it.</p>
<p>Because this post is about GIMP as a screenshot tool, we'll be using
it (v2.8.22) for all the screenshots taken to support this
article. GIMP stands for GNU Image Manipulation Program and when you
think of gimp, you generally think about it as a tool for editing a
screenshot or a photo , but it was not obvious to me that you could
also use it to take the screenshot as well. This is where a tool like
<a rel="noopener" target="_blank" href="https://shutter-project.org/">Shutter </a> really shines, but it doesn't
allow you to make any edits to the screenshot, you still need to open
it up in GIMP for that, so why not just skip the middle man and take
the screenshot as well? To take a screenshot in GIMP click the File
menu, then Create, then Screenshot.</p>
<p><a href="/images/take-screenshot.webp"><img src="/images/take-screenshot.webp" alt="How to take a screenshot" width="730" /></a></p>
<p>Once you have selected the screenshot tool you are presented with the
following options.</p>
<p><img src="/images/screenshot-options.webp" alt="Screenshot tool options" /></p>
<p><strong>Take a screenshot of a single window</strong>: What this means is that you can
simply click on any window you have open and it will take a screenshot
of that entire window. If you place a checkbox in the "Include window
decoration" Then it will include the toolbar, menu and such, otherwise
it only grabs the contents of the window.</p>
<p><strong>Take a screenshot of the entire screen</strong>: What this means is that when
you click it will take a screenshot of your entire desktop. If you
have the "Include mouse pointer" option checked it will also capture
your mouse pointer in the screenshot.</p>
<p><strong>Select a region to grab</strong>: This may be the most common option, at least
it tends to be the one I use the most. This option allows to you to
click and drag a rectangle around only the piece of the screen that
you want to capture in your screenshot.</p>
<p><strong>Delay</strong>: Normally you just click to capture the screenshot, but if you
add a non-zero value for delay then after that time has expired the
screenshot will be taken. This is useful if you want to take a
screenshot of a menu option like I did for the "File -> Create ->
Screenshot" image above.</p>
<p>Now that you have your screenshot captured, you might want to make
some minor edits to it before you share it. Common things you might
want to do would be:</p>
<h1 id="back-to-top"></h1>
<ol>
<li><a href="https://zolmok.org/gimp-screenshot-tool-linux/#draw-a-rectangle">Draw a rectangle</a></li>
<li><a href="https://zolmok.org/gimp-screenshot-tool-linux/#draw-an-ellipse">Draw ellipse</a></li>
<li><a href="https://zolmok.org/gimp-screenshot-tool-linux/#draw-a-line">Draw a line</a></li>
<li><a href="https://zolmok.org/gimp-screenshot-tool-linux/#draw-an-arrow">Draw an arrow</a></li>
<li><a href="https://zolmok.org/gimp-screenshot-tool-linux/#draw-free-hand">Draw free-hand</a></li>
<li><a href="https://zolmok.org/gimp-screenshot-tool-linux/#erase">Erase</a></li>
<li><a href="https://zolmok.org/gimp-screenshot-tool-linux/#crop">Crop</a></li>
<li><a href="https://zolmok.org/gimp-screenshot-tool-linux/#add-text">Add text</a></li>
<li><a href="https://zolmok.org/gimp-screenshot-tool-linux/#sharing-your-screenshots">Sharing your screenshots</a></li>
</ol>
<p>I'm going to show you how to do each one of these, but you're
certainly not limited here, you have the full power of GIMP at your
disposal, these are just a few of the basics. If you want more, there
is tons of information available on how to do just about anything you
can think of.</p>
<h2 id="draw-a-rectangle">Draw a rectangle</h2>
<p>Drawing a rectangle is basically two steps, dragging a selection
around where you would like the rectangle, then applying a stroke to
the selection you created.</p>
<ol>
<li>Before you create your selection, make sure you choose what color
you would like your rectangle. You choose your color by clicking on
the Window menu, then Dockable Dialogs, then Colors.</li>
<li>Once you have selected your color it's time to drag a selection
around what you want your rectangle. Click the Tools menu, then
Selection tools, then Rectangle select. Now you drag a rectangle
around what you want to have a rectangle around.</li>
<li>Now that you have chosen your color, and drug your rectangle out,
apply a stroke around the selection which will apply the color to
the selected area which will create your rectangle. Click the Edit
menu, then Stroke selection. You will be presented with a dialog
rectangle containing some choices. I generally just take the
defaults, but you can play around with your options until you find
something that works for you. You might for example want a thicker
or thinner rectangle, so adjust "Line width" up for a thicker
rectangle, or down for a thinner rectangle. Once you are satisfied
with your options click the Stroke button and should now have a
rectangle around your selection with the color you chose.</li>
</ol>
<p><a href="https://zolmok.org/gimp-screenshot-tool-linux/#back-to-top">Back to top</a></p>
<p><img src="/images/stroke-options.webp" alt="Stroke options" /></p>
<h2 id="draw-an-ellipse">Draw an ellipse</h2>
<p>Drawing an ellipse is exactly the same as drawing a rectangle in every
way with the exception of using the ellipse tool as opposed to the
rectangle tool.</p>
<ol>
<li>Before you create your selection, make sure you choose what color you would like your ellipse. You choose your color by clicking on the Window menu, then Dockable Dialogs, then Colors.</li>
<li>Once you have selected your color it's time to drag a selection around what you want your ellipse. Clicking the Tools menu then Selection tools, then Ellipse select. Now drag a ellipse around what you want to have an ellipse around.</li>
<li>Now that you have chosen your color, and drug your ellipse out, apply a stroke around the selection which will apply the color to the selected area which will create your ellipse. Click the Edit menu, then Stroke selection. You will be presented with a dialog ellipse containing some choices. I generally just take the defaults, but you can play around with your options until you find something that works for you. You might for example want a thicker or thinner ellipse, so adjust "Line width" up for a thicker ellipse, or down for a thinner ellipse. Once you are satisfied with your options click the Stroke button and should now have an ellipse around your selection with the color you chose.</li>
</ol>
<p><a href="https://zolmok.org/gimp-screenshot-tool-linux/#back-to-top">Back to top</a></p>
<h2 id="draw-a-line">Draw a line</h2>
<p>Drawing a line is slightly different than drawing a rectangle or
ellipse. This time you have several tools you can choose from that all
work very similar, the difference is the type of line you will end up
with.</p>
<ol>
<li>Before you choose your tool, make sure you choose what color you
would like your line. You choose your color by clicking on the
Window menu, then Dockable Dialogs, then Colors.</li>
<li>Once you have selected your color it's time to decide what type of
line you want. Your tool choices are paintbrush, pencil, airbrush,
or ink tool. You'll find these under the Tools menu, then Paint
tools.</li>
<li>Now that you have chosen your color, and decided what tool you will
use to draw your line you can either just drag it out free-hand, or
if you want a perfect line, click and quickly release where you
would like the line to start, hold down <code><shift></code> then quickly click
and release where you would like your line to end.</li>
</ol>
<p><a href="https://zolmok.org/gimp-screenshot-tool-linux/#back-to-top">Back to top</a></p>
<h2 id="draw-an-arrow">Draw an arrow</h2>
<p>Drawing an arrow is considerably more involved because what you need
doesn't come included in GIMP unless you would just like to try and
free-hand one out. If you want something a little more professional
looking you'll need an plug-in, and unfortunately GIMP used to host a
registry that is now defunct. However not all is lost. They have
kindly archived all of the plug-ins that were hosted under the old
registry up to a <a rel="noopener" target="_blank" href="https://github.com/pixlsus/registry.gimp.org_static/tree/ffcde7400f402728373ff6579947c6ffe87d1a5e/registry.gimp.org">GitHub
repository</a>. You
can find the arrow plug-in you need
<a rel="noopener" target="_blank" href="https://github.com/pixlsus/registry.gimp.org_static/blob/ffcde7400f402728373ff6579947c6ffe87d1a5e/registry.gimp.org/files/arrow.scm">here</a>.</p>
<p><strong>Install the plug-in</strong></p>
<ol>
<li>Download the <a rel="noopener" target="_blank" href="https://github.com/pixlsus/registry.gimp.org_static/blob/ffcde7400f402728373ff6579947c6ffe87d1a5e/registry.gimp.org/files/arrow.scm">arrow plug-in</a></li>
<li>In GIMP, click the Edit menu, then Preferences, then inside
Preferences on the left-hand menu click Folders, then
Plug-Ins. Take a note of the "Plug-In Folders", specifically the
one in your "home" folder.</li>
<li>Copy the arrow.scm file you downloaded to the plug-in folder
located in your "home" directory, then restart GIMP.</li>
</ol>
<p><strong>Create an arrow</strong></p>
<ol>
<li>Before you draw an arrow, make sure you choose what color you would
like it to be. You choose your color by clicking on the Window
menu, then Dockable Dialogs, then Colors.</li>
<li>Click the Windows menu then Dockable dialogs and open up both Tool
options and Paths.</li>
<li>Now select the Path tool by clicking the Tools menu then Paths.</li>
<li>In the Tool Options dialog make sure Polygonal is selected.</li>
<li>Now click the location where you would ultimately like the arrow
pointing, then click where you would like the line to end.</li>
<li>Now select the Arrow tool you installed earlier by clicking the
Tools menu then Arrow.</li>
</ol>
<p>You will be presented with a dialog of options you can use to
manipulate the arrow that is drawn. I recommend you just try the
defaults to see what you get, then play around with the options until
you find something that works for you. I had you open the Paths
dockable dialog in case you wanted to delete your arrow and start
over. You can see a list of "paths" in the Paths dialog and can easily
delete them from there.</p>
<p><img src="/images/arrow-options.webp" alt="Arrow plug-in options" /></p>
<p><img src="/images/path-tool-options.webp" alt="Path tool options" /></p>
<p>The arrow tool will use the path you drew out to figure out where to
put the arrow. The arrow will end up on the first point of the path
that you click. The arrow tool will also apply a stroke along the path
according to the color you selected.</p>
<p><a href="https://zolmok.org/gimp-screenshot-tool-linux/#back-to-top">Back to top</a></p>
<h2 id="draw-free-hand">Draw free-hand</h2>
<p>Drawing free-hand is almost identical to drawing a line, the only
difference is you don't hold down shift to get that perfectly straight
line. I'm going to copy the directions here again for your convenience
just in case you jumped to this area without reading about drawing a
line.</p>
<ol>
<li>Before you choose your tool, make sure you choose what color you
would like your line. You choose your color by clicking on the
Window menu, then Dockable Dialogs, then Colors.</li>
<li>Once you have selected your color it's time to decide what type of
line you want. Your tool choices are paintbrush, pencil, airbrush,
or ink tool. You'll find these under the Tools menu, then Paint
tools.</li>
<li>Now that you have chosen your color, and decided what tool you will
use to draw with, you can just click and drag your mouse about
until you get the shape you're looking for. Let go of the mouse
button to stop drawing, click it again to draw again, etc...</li>
</ol>
<p><a href="https://zolmok.org/gimp-screenshot-tool-linux/#back-to-top">Back to top</a></p>
<h2 id="erase">Erase</h2>
<p>Erasing is very similar to free-hand drawing except instead of
creating content, you are removing it, in a free-hand motion.</p>
<ol>
<li>Choose the erase tool by clicking the Tools menu, then choose Paint
tools, then Eraser.</li>
<li>Now just choose what you would like to erase by hold your mouse
over it, click and drag until you have removed that content from
your screenshot.</li>
</ol>
<p>There are many options you can choose from to modify how the Erase
tool works but the defaults work pretty well. Again experiment with
them and decide what works best for you. And different options might
work better for certain situations.</p>
<p><img src="/images/erase-tool-options.webp" alt="Erase tool options" /></p>
<p><a href="https://zolmok.org/gimp-screenshot-tool-linux/#back-to-top">Back to top</a></p>
<h2 id="crop">Crop</h2>
<p>Cropping a screenshot is when you want to trim it down by drawing a
selection rectangle inside of the screenshot around the area you want
to keep. Anything outside the selection will be removed.</p>
<ol>
<li>Choose the Crop tool from the menu by clicking Tools, then
Transform tools, then crop.</li>
<li>Now draw a rectangle around the area you want to keep and hit
enter.</li>
</ol>
<p>It's that simple!</p>
<p><a href="https://zolmok.org/gimp-screenshot-tool-linux/#back-to-top">Back to top</a></p>
<h2 id="add-text">Add text</h2>
<p>The final editing tool we are going to discuss is adding text. They
say a picture is worth a 1000 words, but sometimes you still need a
little text to augment it a bit.</p>
<ol>
<li>Choose the text tool by clicking the Tools menu, then choose Text.</li>
<li>There are several options you can select from the Text Tool option
dialog rectangle like the font, color, size, etc... so tweak all of
those things until you have what you want, then click an area in
the screenshot where you would like to place your text, then type
it out. When you have completed typing out your text hit ESC. You
might then want to choose the "move" tool to move it about slightly
if it didn't end up quite where you wanted it. Click the Tools
menu, then Transform tools, then Move if you would like to move it.</li>
</ol>
<p><img src="/images/text-options.webp" alt="Text tool options" /></p>
<p><a href="https://zolmok.org/gimp-screenshot-tool-linux/#back-to-top">Back to top</a></p>
<h2 id="sharing-your-screenshots">Sharing your screenshots</h2>
<p>There is one thing missing from this workflow. You've figured out how
to capture your screenshot and make the edits you need to, now its
perfect and you want to share it with the world, how do you do that?
This is another area where Shutter really stands out but I had
problems with this as well. My problem was that I was trying to use S3
and it seemed to want to make my files private. At any rate, I'm sure
it was probably something I had mis-configured but it led me to search
for an alternative which is how I came to find GIMP but I still needed
a way to share my creations. Enter imgur. I'm going to show you how
you can use imgur to share images semi-privately in that anyone that
has the link can get to it, but it won't show-up on the imgur
timeline. This is only one option, I'm sure there are others.</p>
<ol>
<li>Assuming you already have an account, <a rel="noopener" target="_blank" href="https://imgur.com/signin?redirect=%2F">login </a>to imgur, if not you'll
need to create one first.</li>
<li>Locate the button labeled "New Post". As of today it is in the top
left corner of on the home page, but I suppose they could move it
at some point. Click the "choose photo" and find the screenshot you
would like to upload and click submit.</li>
<li>And that's it, your image has been uploaded to what imgur refers to
as a "post". You can add more than a single image to a post, but
the post remains private until you click the "To Community"
button. If you instead click the "Hidden" button, you will be
presented with a dialog that will present to you a link that you
can pass around to share this post of images with and it will not
end up on the imgur feed. However do be aware that anyone who has
this link can access the post.</li>
</ol>
<p><img src="/images/imgur-new-post.webp" alt="New post on imgur" /></p>
<p><img src="/images/imgur-upload.webp" alt="Upload image to imgur" /></p>
<p><img src="/images/imgur-hidden.webp" alt="Obtain hidden link on imgur" /></p>
<p><a href="https://zolmok.org/gimp-screenshot-tool-linux/#back-to-top">Back to top</a></p>
<p>And there you go, this is how I use GIMP as my screenshot tool. I
would love to hear from you if you have other tools that you like, or
if you have suggestions for improving this workflow.</p>
Emacs org-mode for developers2020-02-08T00:00:00+00:002020-02-08T00:00:00+00:00https://zolmok.org/emacs-org-mode-developers/<p>tldr; I have created a Github Gist that captures the setup and
configuration, key-binds, formatting, and GTD chart if you just want a
reference.<span id="continue-reading"></span> Table of contents</p>
<h2 id="table-of-contents">Table of contents</h2>
<ul>
<li><a href="https://zolmok.org/emacs-org-mode-developers/#setup-and-configuration">Setup and configuration</a></li>
<li><a href="https://zolmok.org/emacs-org-mode-developers/#formatting">Formatting</a></li>
<li><a href="https://zolmok.org/emacs-org-mode-developers/#key-binds">Key-binds</a></li>
<li><a href="https://zolmok.org/emacs-org-mode-developers/#knowledge-base">Knowledge base</a></li>
<li><a href="https://zolmok.org/emacs-org-mode-developers/#note-taking">Note taking</a></li>
<li><a href="https://zolmok.org/emacs-org-mode-developers/#agenda">Agenda</a></li>
<li><a href="https://zolmok.org/emacs-org-mode-developers/#gtd-bonus">GTD (bonus)</a></li>
</ul>
<h2 id="setup-and-configuration">Setup and configuration</h2>
<p>The thing about Emacs is that it's not a one size fits all editor. I'm
not even sure you can call it an editor out of the box. It's basically
a box of parts that you can configure into your vision of what a text
editor should be. No two instances of Emacs should ever be the
same. So in order to utilize org-mode to its fullest potential you
need to configure some things first. Also Emacs is self referential. I
highly recommend you read the documentation not only for org-mode but
for Emacs itself. <code><C-h i></code> will get you to the info page that contains
documentation for both. You'll want to setup some hotkeys for quick
access when you need it and you'll need to give org-mode some default
locations for where to capture and look for it's data. Add these lines
to your init.el Emacs configuration.</p>
<pre data-lang="lisp" style="background-color:#fafafa;color:#383a42;" class="language-lisp "><code class="language-lisp" data-lang="lisp"><span style="color:#a0a1a7;">;; first setup a key-bind to access org-agenda, we'll talk more about what this is in a moment
</span><span>(global</span><span style="color:#a626a4;">-</span><span style="color:#0184bc;">set</span><span style="color:#a626a4;">-</span><span>key (kbd </span><span style="color:#50a14f;">"C-c a"</span><span>) 'org</span><span style="color:#a626a4;">-</span><span>agenda)
</span><span style="color:#a0a1a7;">;; next setup a key-bind to enable you to quickly capture ideas
</span><span>(global</span><span style="color:#a626a4;">-</span><span style="color:#0184bc;">set</span><span style="color:#a626a4;">-</span><span>key (kbd </span><span style="color:#50a14f;">"C-c c"</span><span>) 'org</span><span style="color:#a626a4;">-</span><span>capture)
</span><span>
</span><span style="color:#a0a1a7;">;; these next items are purely optional or comsmetic
</span><span style="color:#a0a1a7;">;; TODO and DONE are the default states
</span><span>(</span><span style="color:#0184bc;">setq</span><span> org</span><span style="color:#a626a4;">-</span><span>todo</span><span style="color:#a626a4;">-</span><span>keywords '(</span><span style="color:#50a14f;">"TODO" "STARTED" "WAITING" "DONE"</span><span>))
</span><span style="color:#a0a1a7;">;; set this to include entries from the Emacs diary into org-mode agenda
</span><span>(</span><span style="color:#0184bc;">defvar</span><span> org</span><span style="color:#a626a4;">-</span><span>agenda</span><span style="color:#a626a4;">-</span><span>include</span><span style="color:#a626a4;">-</span><span>diary </span><span style="color:#c18401;">t</span><span>)
</span><span style="color:#a0a1a7;">;; make source code look better
</span><span>(</span><span style="color:#0184bc;">defvar</span><span> org</span><span style="color:#a626a4;">-</span><span>src</span><span style="color:#a626a4;">-</span><span>fontify</span><span style="color:#a626a4;">-</span><span>natively </span><span style="color:#c18401;">t</span><span>)
</span><span>
</span><span style="color:#a0a1a7;">;; these next two are kind of important
</span><span style="color:#a0a1a7;">;; the files to be used in the agenda display
</span><span>(</span><span style="color:#0184bc;">setq</span><span> org</span><span style="color:#a626a4;">-</span><span>agenda</span><span style="color:#a626a4;">-</span><span>files (</span><span style="color:#0184bc;">directory</span><span style="color:#a626a4;">-</span><span>files</span><span style="color:#a626a4;">-</span><span>recursively </span><span style="color:#50a14f;">"~/org/agenda" "org$"</span><span>))
</span><span style="color:#a0a1a7;">;; default target for storing notes
</span><span>(</span><span style="color:#0184bc;">setq</span><span> org</span><span style="color:#a626a4;">-</span><span>default</span><span style="color:#a626a4;">-</span><span>notes</span><span style="color:#a626a4;">-</span><span>file </span><span style="color:#50a14f;">"~/org/agenda/organizer.org"</span><span>)
</span></code></pre>
<p>Remember, Emacs is highly customizable, you can set your key-binds and
paths to whatever you like, these are only suggestions.</p>
<p><a href="https://zolmok.org/emacs-org-mode-developers/#table-of-contents">Back to top</a></p>
<h2 id="formatting">Formatting</h2>
<p>org-mode is text based so while it does have some formatting, some of
it is for when you render it to a richer format like PDF. Exporting to
other formats is out of the scope of this article. Speaking of richer
formats however, one thing you may not be aware of is that Github will
render your org files similar to way it would say a markdown file. So
instead of making a README.md for example, you could make a README.org
and will parse its markup and render it accordingly. One thing to be
aware of though is that if you publish to NPM, npm will not recognize
your README.org, it is specifically looking for a README.md which is
quite unfortunate :( You can also create Github Gist's in org-mode as
well. That said this won't be an exhaustive list of formatting markup,
rather the things I find useful for my own documents. If you would
like to learn even more please reference the documentation.</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>_underline_
</span><span>
</span><span>/italics/
</span><span>
</span><span>*bold*
</span><span>
</span><span>=verbatim=
</span><span>
</span><span>+strike-through+
</span><span>
</span><span>- unorded list foo
</span><span>- unorded list bar
</span><span>- unorded list baz
</span><span>
</span><span>+ unorded list foo
</span><span>+ unorded list bar
</span><span>+ unorded list baz
</span><span>
</span><span>1. first item
</span><span>2. second item
</span><span>3. third item
</span><span>
</span><span>#+BEGIN_VERSE
</span><span>Great clouds overhead
</span><span>Tiny black birds rise and fall
</span><span>Snow covers Emacs
</span><span>
</span><span>-- AlexSchroeder
</span><span>#+END_VERSE
</span><span>
</span><span>#+BEGIN_QUOTE
</span><span>Everything should be made as simple as possible,
</span><span>but not any simpler -- Albert Einstein
</span><span>#+END_QUOTE
</span><span>
</span><span>#+BEGIN_CENTER
</span><span>Everything should be made as simple as possible, \\
</span><span>but not any simpler
</span><span>#+END_CENTER
</span><span>
</span><span>#+BEGIN_SRC javascript
</span><span>function foo() {
</span><span> console.log('bar');
</span><span>}
</span><span>#+END_SRC
</span></code></pre>
<p><a href="https://zolmok.org/emacs-org-mode-developers/#table-of-contents">Back to top</a></p>
<h2 id="key-binds">Key-Binds</h2>
<p>One of the reasons to choose an editor like Emacs or VIM is for it's
rich set of key-binds. org-mode has no shortage of key-binds and again
I don't even begin to scratch the surface of what all is available but
here is a set that I find useful:</p>
<table><thead><tr><th>Key-Binding</th><th>Function</th></tr></thead><tbody>
<tr><td><C-c C-l></td><td>Insert a link</td></tr>
<tr><td><C-c C-o></td><td>Follow a link</td></tr>
<tr><td><C-return></td><td>Insert heading</td></tr>
<tr><td><C-c C-b></td><td>Back a heading</td></tr>
<tr><td><C-c C-f></td><td>Forward a heading</td></tr>
<tr><td><TAB></td><td>Expand current heading</td></tr>
<tr><td><S-TAB></td><td>Expand all headings</td></tr>
<tr><td><C-c C-e l p></td><td>Org export as Latex and convert to PDF</td></tr>
<tr><td><strong>Table</strong></td><td></td></tr>
<tr><td><C-c pipe></td><td>Convert or edit a table</td></tr>
<tr><td><pipe-dash-tab></td><td>Insert a horizontal rule</td></tr>
<tr><td><C-^></td><td>Sort column</td></tr>
<tr><td><M-(left-right)></td><td>Move column left or right</td></tr>
<tr><td><M-(up-down)></td><td>Move row up or down</td></tr>
<tr><td><M-S down></td><td>Insert row</td></tr>
<tr><td><M-x org-table-insert-column></td><td>Insert column</td></tr>
<tr><td><M-S up></td><td>Delete row</td></tr>
<tr><td><M-x org-table-delete-column></td><td>Delete column</td></tr>
<tr><td><C-c }></td><td>Toggle the display of Row/Column numbers in tables</td></tr>
<tr><td><strong>Agenda</strong></td><td></td></tr>
<tr><td><C-c a a></td><td>(<C-c a> custom mapping) List all tasks (agenda)</td></tr>
<tr><td><C-c a t></td><td>List all tasks (todo)</td></tr>
<tr><td><C-c C-s></td><td>Schedule task</td></tr>
<tr><td><C-c C-d></td><td>Create a deadline</td></tr>
<tr><td><0-3 r></td><td>Review tasks by TODO status, list will be at top</td></tr>
<tr><td><F></td><td>Put list in “follow-mode”</td></tr>
<tr><td><S-(right-left arrow)></td><td>Toggle todo</td></tr>
<tr><td><C-c C-t></td><td>Create or toggle a todo</td></tr>
<tr><td><strong>Code block</strong></td><td></td></tr>
<tr><td><<-s-TAB></td><td>start a code block</td></tr>
<tr><td><C-c C-c></td><td>execute code block</td></tr>
<tr><td><C-c '></td><td>edit in prog-mode</td></tr>
<tr><td><strong>Time tracking</strong></td><td></td></tr>
<tr><td><C-c C-x TAB></td><td>org-clock-in</td></tr>
<tr><td><C-c C-x C-o></td><td>org-clock-out</td></tr>
<tr><td><C-c C-x C-d></td><td>org-clock-display</td></tr>
</tbody></table>
<p><a href="https://zolmok.org/emacs-org-mode-developers/#table-of-contents">Back to top</a></p>
<h2 id="knowledge-base">Knowledge Base</h2>
<p>OK, whew, now that you have made some configuration, learned a little
about formatting and a few key-binds we're finally ready to learn how
to make org-mode useful to someone who writes code either as a hobby
at home in your spare time or as your daily profession. One thing you
probably want is a place to store knowledge that you've learned over
time that you might not use every day. Before I started using Emacs I
was a hardcore VIM user and in VIM I used a plugin called "vimwiki"
(https://github.com/vimwiki/vimwiki) to hold my knowledge base. What
vimwiki did that I really liked was sort of emulate a wiki right from
with VIM. So you could use markdown or its own syntax, vimwiki, and
create links to other documents, then you could use a hotkey to
navigate to them just like clicking a link in a web page. You can
replicate this behavior in org-mode. So what I do is create an index
page that has links to all of my other documents, then I make a hotkey
that will take me directly to this page from right within Emacs.</p>
<p><code>(set-register ?o '(file . "~/org/agenda/organizer.org"))</code></p>
<p>This takes advantage of Emacs registers and the hotkey is <code><C-x r j (register-index)></code> where <code>register-index</code> is <code>o</code> in this case for
"organizer". Again this is the path to my file, you can make your path
whatever you like. Once you are in your "organizer.org" or whatever
you decide to call yours, you can create a new link with <code><C-c C-l></code>. Then choose the "file:" protocol and give it the path to your
file, and finally it will prompt you for a label and it will insert
the label into the document. Now you can navigate to the linked
document by placing your point in the linked text and keying <code><C-c C-o></code>.</p>
<p>Whenever I'm learning something new this is how I do it. I start using
whatever it is and the first time I have to research how to do
something I open up my organizer.org file, create the link, navigate
to the new file, then write it down. Then the next time I need to do
this same thing over again instead of searching for it, I look here
first because its just a hotkey away and so much faster. Eventually I
remember whatever it is and don't have to navigate as often but if
some time goes by and I forget again, I know where to find it.</p>
<p><a href="https://zolmok.org/emacs-org-mode-developers/#table-of-contents">Back to top</a></p>
<h2 id="note-taking">Note taking</h2>
<p>You may have used, or currently still use other note taking apps like
One Note or Evernote. However, as a developer, sometimes fantastic
ideas will pop into your head, or you'll remember something important
that you need to do and it would be nice to be able to capture that
knowledge quickly without breaking your flow. That's what org-capture
does for us. We made a key-bind for it <code><C-c c></code> up in the Setup and
configuration section of this article. So imagine a scenario where
you're deep in the middle of fixing a bug or implementing a feature on
one of your projects when this great idea for a new app enters your
consciousness. Just key <code><C-c c></code> and a window will popup prompting
you to create a task that it is going to save to your organizer.org
file that we also setup in the Setup and configuration section of this
article. You key "t" to create the task, enter the information about
it, key <code><C-c C-c></code> and the information is saved to your organizer.org
file, the window goes away and you're back in the buffer where you're
working on your bug. Emacs captures your idea, then gets out of your
way. Later you can filter through your organizer.org file and break
your idea down into more detail, maybe even creating it's own org file
for it in a projects folder or something so you can flesh the idea out
a little more, create tasks, etc... The ability to capture ideas
quickly and get out of your way is a subtle but very valuable feature
of Emacs. I highly recommend you give it a try.</p>
<p><a href="https://zolmok.org/emacs-org-mode-developers/#table-of-contents">Back to top</a></p>
<h2 id="agenda">Agenda</h2>
<p>As I mentioned in the beginning, org-mode is many things. Not only is
it really good at capturing information, formatting it, and making it
accessible through key-binds, it has the ability to "tag" information,
timestamp it, create a schedule, and create deadlines. These features
are extracted into a special buffer called the "agenda". We made a
hotkey for this in the Setup and configuration section <code><C-c a a></code> As
a programmer one thing you might want to do is create an org file for
each project you are working on. Then you can enumerate tasks that you
need to work through by prefixing each with the TODO keyword and
assign a schedule or deadline for each item. Now you can run the file
through the org-agenda and get a list you can filter by TODO type. The
default types are TODO and DONE, but in the Setup and configuration
section of this article we added 2 others, STARTED and WAITING. You
can add whatever states best fit your workflow or just leave the
defaults if that works for you. So a workflow might look something
like this.</p>
<ol>
<li>Create an org file to track a project.</li>
<li>Create a heading <code><C-return></code> and enter a description for a set of
tasks. At the end of the description place the following [/] and
key <code><C-c C-c></code>. This will let you track the progress of all of the
completed tasks.</li>
<li>Key <code><C-return></code> to create another heading, this time add a
second * to make it a sub-heading, enter the text for the task to
work on.</li>
<li>Make the item a TODO <code><C-c C-t></code>.</li>
<li>Schedule a time for this work to be completed <code><C-c C-s></code>.</li>
<li>Repeat steps 3-5 until you have a complete list of tasks.</li>
<li>Using the agenda view, pick a task to begin working on <code><C-c a></code>
key <code><</code> to scope agenda to the current org file, then choose t to
filter by the TODO list.</li>
<li>Track how long it takes to complete <code><C-c C-x TAB></code>. (starts a
clock to time how long your task takes)</li>
<li>When you've completed the task stop the time tracker <code><C-c C-x C-o></code>, and toggle it as complete <code><S->></code>.</li>
</ol>
<p>Now as you complete tasks the heading will show a count of completed
tasks, [1/3] for example. You can also key <code><C-c C-x C-d></code> to get a
summary of how long it took you to complete all of your tasks. When
you're done it will look something like this:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>* Set of tasks to complete [1/3]
</span><span>
</span><span>** DONE task 1
</span><span> SCHEDULED: &lt;2020-02-09 Sun&gt;
</span><span> :LOGBOOK:
</span><span> CLOCK: [2020-02-09 Sun 08:33]--[2020-02-09 Sun 08:36] =&gt; 0:03
</span><span> :END:
</span><span>
</span><span>** TODO task 2
</span><span>
</span><span>** TODO task 3
</span></code></pre>
<p><a href="https://zolmok.org/emacs-org-mode-developers/#table-of-contents">Back to top</a></p>
<h2 id="gtd-bonus">GTD (bonus)</h2>
<p>I call this a bonus section because while its an interesting practice,
its not really specific to developers. GTD stands for "getting things
done" and is a set of practices for tracking and actually completing
tasks. It has nothing to do with org-mode but I'm going to share with
you how you can use org-mode to practice GTD.</p>
<table><thead><tr><th>Phase</th><th>GTD</th><th>Org</th></tr></thead><tbody>
<tr><td>Collect</td><td>Capture everything you need to do.</td><td>Collect everything into the inbox</td></tr>
<tr><td>Process</td><td>Actionable?</td><td>Put tasks on list, track delegated</td></tr>
<tr><td></td><td>Yes: do, delegate or defer;</td><td></td></tr>
<tr><td></td><td>no: file, throw or incubate</td><td></td></tr>
<tr><td>Organize</td><td>Next actions, projects, waiting for, someday/maybe</td><td>Tag tasks, view tasks by tag</td></tr>
<tr><td>Review</td><td>Daily, weekly, etc...</td><td>Agenda view</td></tr>
<tr><td>Do</td><td>Actually do the work</td><td>Sadly Emacs can’t do this</td></tr>
</tbody></table>
<p>So there are 5 phases to completing tasks, collect them, process them,
organize them, review them, and then the hard part...do them! How can
you use Emacs to help with this process? In the Setup and
configuration section of this article we setup a hotkey for capturing
tasks and ideas <code><C-c c></code>. We also setup a file to store these ideas
called organizer.org, but you might want to call it inbox.org to fit
the paradigm. The idea is you collect everything in its raw form into
an inbox of sorts. Then you go through and "process" those ideas or
tasks into actions that you can either do yourself or delegate to
someone else. Next you tag the tasks so that you can view them by
"tag" a feature of the "agenda". Now you can review them in the agenda
which you will help you decide which tasks to do next...then you have
to do them, which Emacs can't do for you unfortunately. You can read
more about GTD here.</p>
<p>To make this work you need to learn about how to tag information, then
use the agenda-view to filter through it. Here is how you might go
about that:</p>
<ol>
<li><code><C-return></code> to create a heading</li>
<li><code><C-c C-q></code> to tag that heading</li>
<li>repeat steps 1 and 2 until you have everything captured</li>
<li><code><C-c a m></code> to enter the agenda by tag, enter the tag to filter on</li>
</ol>
<p>Now you will have a list of items filtered by a specific tag that you
can process through.</p>
<p>I hope you enjoyed the article. If I've made any mistakes, left out
crucial information, or you would just like to comment please do so in
the comment section below, I would love to hear from you.</p>
<p><a href="https://zolmok.org/emacs-org-mode-developers/#table-of-contents">Back to top</a></p>
Portfolio: Schedule an event2020-02-02T00:00:00+00:002020-02-02T00:00:00+00:00https://zolmok.org/portfolio-schedule-event/<p>I ran across a UI that looked interesting through a newsletter I get
called "UI Movement" (<a
href="https://uimovement.com/">https://uimovement.com/</a>). The UI is
pretty basic, it is a component that allows one to "schedule a demo"
based on a date and time. You can view the original concept here (<a
href="https://dribbble.com/shots/9357635-Schedule-Demo-Exploration">https://dribbble.com/shots/9357635-Schedule-Demo-Exploration</a>). It
looked like it would be a lot of fun to build and it was. I chose
React to build this because I don't get to use it as much as I would
like and I've not had a chance to play with "hooks" yet, so I took
this opportunity to figure out what they were all about.<span id="continue-reading"></span>
Also I'm not very good at CSS so I resisted the temptation to pull in
Bootstrap and tried to hand roll all of my styling. I did cheat a
little and started with the HTML5 boilerplate which brings in a CSS
reset (<a
href="https://html5boilerplate.com/">https://html5boilerplate.com/</a>).</p>
<h2 id="step-1">Step 1</h2>
<p><a href="/images/schedule-event-step-1.png"><img src="/images/schedule-event-step-1.png" alt="Step 1" /></a></p>
<p>The other reason I chose react is because with Create React App, you
can get all the things you need to begin your site up and running very
quickly. This project is basically a 3 step wizard, Step 1 has a day
picker and a time picker so that the user can quickly choose a date
and time for the demo. There is a slight animation that displays
schedule card when the date is picked. I attempted to recreate all the
animations just as they were. The only problem I ran into on this page
is the thin scroll-bar next to the time picker. I could not get it to
work in Chrome but it seems to work in Firefox just fine. Both
navigation buttons are grayed out until you choose both a date and
time and then the "Next step" button enables to allow you to proceeded
to the next step.</p>
<h2 id="step-2">Step 2</h2>
<p><a href="/images/schedule-event-step-2.png"><img src="/images/schedule-event-step-2.png" alt="Step 2" /></a></p>
<p>The next step is a form that prompts the user for name, email and
phone number so that the scheduling information can be emailed to the
user. I attempted to utilize a little bit of HTML5 form validation
here but I didn't do much more than make each field mandatory with a
slight bit of styling. The back button is enabled at this point but
the next button is grayed out again until the form validation is
complete at which point you can advance to the last and final step of
the wizard.</p>
<h2 id="step-3">Step 3</h2>
<p><a href="/images/schedule-event-step-3.png"><img src="/images/schedule-event-step-3.png" alt="Step 3" /></a></p>
<p>The final step is just a confirmation page that indicates the the user
was sent an email concerning the scheduling details with some links to
go to the home page or resend the email. I mostly utilized local state
and passed it from parent to child, however there was one set of
sibling components that needed to share some information so I utilized
Redux for that.</p>
<h2 id="testing">Testing</h2>
<p>I attempted to write some tests for everything as well since that is
another area I need work in. I'm not happy with my progress
here. There doesn't seem to be a way to test a form button "onClick"
event. Jest doesn't seem to support "onSubmit" and when you click the
button that seems to be the only thing it wants to do. Also I had to
rework things a bit to get Redux out of the way for some tests. I
would love to know how I can improve these so if you have any ideas
please leave comments below.</p>
<p>You can find the project here:
<a href="https://github.com/Zolmok/schedule-event">https://github.com/Zolmok/schedule-event</a>.</p>
Emacs vs Vscode2019-02-19T00:00:00+00:002019-02-19T00:00:00+00:00https://zolmok.org/emacs-vs-vscode/<p>I haven't really been using Emacs for all that long. I'm really a VIM
user when it comes down to it, but there is one thing that vim is
missing, a standardized package manager and a package repository. Sure
there are package managers and I guess you could count <a
href="https://www.vim.org/scripts/">vim.org/scripts</a> as somewhat of
a repository but generally speaking I find myself googling for some
functionality and finding a repository in Github that I can pull in
with one of the various different package managers that have sprung up
to fill the gap.<span id="continue-reading"></span> So what is a VIM user to do. I'm hooked
on the modal modes and keybindings, and I've tried several other
editors. My first requirement for a new editor is that it must have
VIM bindings or its a non-starter.</p>
<p>A few years ago I stumbled upon <a
href="http://spacemacs.org/">Spacemacs</a> which is exactly what I
didn't know that I was looking for. Spacemacs is called an Emacs
distribution, but really it is Emacs proper, it just has a massive
configuration that transforms it into... something else. Emacs has a
package repository called <a href="https://melpa.org/">melpa.org</a>
and it is quite tolerable and there is a package manager builtin to
interface with it. What was amazing about Spacemacs is that I have
tried Emacs before, but eventually gave up on it, however with
Spacemacs inclusion of "evil-mode", I was immediately productive! I
have tried many VIM emulators over the years but evil-mode is hands
down the closest thing to VIM other than VIM itself that I have
found. My fingers type in commands and evil-mode responds
appropriately. Usually there is that one thing that's missing or not
quite right that drives you crazy, but evil-mode does whats written on
the tin, and it does it very well. It wasn't long after using
Spacemacs that I discovered <a
href="https://orgmode.org/">org-mode</a> and now there is no turning
back. VIM has no solution for org-mode, in fact I dare say no one
does, there is no editor, or IDE even that has anything even close. I
was using a VIM add-on called <a
href="https://github.com/vimwiki/vimwiki">vimwiki</a> to keep notes on
before I found Spacemacs, but org-mode is so much better. I like using
tables to list keybindings and with vimwiki I was constantly
reformatting things as I would add new commands, vim-mode handles all
of that for you. Once I knew I was going to be living in Emacs for a
long while I decided that the Spacemacs configuration was a little too
heavy for me and I really wanted to learn more about Emacs so I
scrapped the Spacemacs config and rolled my own, and yes I did learn
quite a bit that I would never have learned otherwise.</p>
<p>About a year or so ago I discovered VS Code. Since it was all the buzz
I had to give it a try. VS Code has nice builtin package support and
an online repository they call the Marketplace. Of course step number
one was to find the VIM bindings which it does have, in fact there are
several extensions for that. The one I found was OK, but it had a few
quirks, I believe the most annoying ones have been addressed at this
point and things are coming along very well. VS Code is most certainly
a capable editor and I really can't say that I have anything against
it. For me it just comes down to the hotkeys. I try never to use the
mouse, I try to do as much as I can with the keyboard, that is why VIM
has always appealed so much to me. With it's philosophy on the home
row and its modal design, I can really get things done. Every editor
has a certain set of hotkeys for things and since I've been using
Emacs for the last couple of years, I've spent a lot of time learning
how it works. I can get 85% of what I need to done with VIM bindings
alone, but it's that other 15% that I now have down to muscle
memory. So, while I can get most of what I need to done with just the
VIM bindings in VS Code there are still some minor annoyances like :bd
doesn't close a buffer (or at least it didn't last I used it), which
means I have to either learn a new hotkey, or ugh...reach for the
mouse. So I had to sit down and decide whether or not there was a
reason to learn these new hotkeys or just stick it out with
Emacs. Mind you, I'm perfectly satisfied with Emacs in evil-mode, I
just wanted to see what all the buzz was about. VS Code has a page <a
href="https://code.visualstudio.com/docs/editor/whyvscode">Why VS
Code</a>, so I thought I would see if it offered anything I wanted or
needed that I didn't have in Emacs. Below I've made a list of items
from that page as well as features that I use that I find useful in
Emacs:</p>
<table><thead><tr><th>Feature</th><th>Vs Code</th><th>Emacs</th><th>Do I use it?</th></tr></thead><tbody>
<tr><td>macOS, Linux, and Windows</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>edit</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Build</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Debug</td><td>x</td><td></td><td>(yes, but I prefer Chrome devtools)</td></tr>
<tr><td>Syntax highlighting</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Bracket-matching</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Auto-indentation</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Box-selection</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Snippets</td><td>x</td><td>Extension</td><td>x</td></tr>
<tr><td>Code completion</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Code navigation</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Code refactoring</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Make it your own (addons)</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Support for JavaScript</td><td>x</td><td>Extension</td><td>x</td></tr>
<tr><td>Support for TypeScript</td><td>x</td><td>Extension</td><td>x</td></tr>
<tr><td>Support for JSX/React</td><td>x</td><td>Extension</td><td>x</td></tr>
<tr><td>Support for HTML</td><td>x</td><td>Extension</td><td>x</td></tr>
<tr><td>Support for CSS</td><td>x</td><td>Extension</td><td>x</td></tr>
<tr><td>Support for SCSS</td><td>x</td><td>Extension</td><td>x</td></tr>
<tr><td>Support for Less</td><td>x</td><td>Extension</td><td></td></tr>
<tr><td>Support for JSON</td><td>x</td><td>Extension</td><td>x</td></tr>
<tr><td>Support for other languages</td><td>(limited)</td><td>x</td><td>x</td></tr>
<tr><td>Uses Electron</td><td>x</td><td></td><td></td></tr>
<tr><td>Native App</td><td></td><td>x</td><td></td></tr>
<tr><td>Org-mode</td><td>Extension</td><td>x</td><td>x</td></tr>
<tr><td>Calculator</td><td>Extension</td><td>(advanced scientific expressions)</td><td>x</td></tr>
<tr><td>Dir-mode</td><td>Explorer</td><td>x</td><td>x</td></tr>
<tr><td>VIM bindings</td><td>Extension</td><td>evil-mode</td><td>x</td></tr>
<tr><td>Git integration</td><td>x</td><td>x</td><td>x</td></tr>
<tr><td>Self documenting</td><td></td><td>x</td><td>x</td></tr>
<tr><td>Configuration</td><td>JSON</td><td>Elisp</td><td>x</td></tr>
<tr><td>Configuration/Addon language</td><td>JavaScript</td><td>Elisp</td><td>x</td></tr>
<tr><td>View keystroke history</td><td>?</td><td>x</td><td>x</td></tr>
<tr><td>Hotkey window/buffer support</td><td>?</td><td>x</td><td>x</td></tr>
</tbody></table>
<p>Being primarily a frontend JavaScript developer I don't see any value
in the builtin debugger, however back before you could debug node with
the Chrome devtools I saw that as a huge feature. Now I think I would
prefer the Chrome devtools debugger even for node development simply
because I live there every day and I am very familiar with them and
they work very well. So needless to say I decided to stick it out with
Emacs because I didn't really find a compelling reason to switch to
something new, vim-bindings need to bake a little more as does support
for other languages. Who knows, maybe in 40 years VS Code will also be
an amazing editor :)</p>
Pi Wall Project2018-04-06T00:00:00+00:002018-04-06T00:00:00+00:00https://zolmok.org/pi-wall-project/<p>A while back I acquired a Raspberry PI from the recruiter that got me
my current job. I wasn't sure what to do with it for a while but I
eventually ran across a wall-board someone had made, I believe it was
this one <a href="https://imgur.com/gallery/z94Vr">https://imgur.com/gallery/z94Vr</a>. I
thought it was a fabulous idea so I set out to build my own. However I
made a few modifications.</p>
<span id="continue-reading"></span>
<ol>
<li>I made mine landscape instead of portrait</li>
<li>I used JavaScript</li>
<li>I rolled my own UI code</li>
<li>I used Node-Red (which ships with the PI) to make the buttons work</li>
</ol>
<h2 id="parts">Parts</h2>
<p>As I said, the Raspberry PI was given to me. I found a used monitor on
ebay that I paid around $40 for. I bought the buttons on Amazon and
the frame I built myself from spare wood I had.</p>
<h2 id="code">Code</h2>
<p>I believe the original used Python to manage the buttons and some
light HTML and JavaScript for the display. Python is a solid choice
but I'm a frontend developer so I wanted to see if I could work with
JavaScript. It turns out you can. The Raspberry PI ships with a tool
called Node-Red which allows you to capture the events from the
hardware and call your own code. I setup Socket.io in my UI so that I
could send events from Node-Red to change views when the buttons were
pushed.</p>
<p><a href="/images/000004-node-red.png"><img src="/images/000004-node-red.png" title="Node-Red configuration" width="730" /></a></p>
<p>When the UI receives the Socket.io event from the button push it
changes to the corresponding view. The UI is based on my Web Starter
Kit which includes both a frontend (written in React) and a backend
(powered by Loopback). Loopback uses express so I've added some code
to server.js to handle the Socket.io events.</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#383a42;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#a626a4;">if </span><span>(</span><span style="color:#e45649;">require</span><span>.</span><span style="color:#e45649;">main </span><span style="color:#a626a4;">=== </span><span>module) {
</span><span> </span><span style="color:#e45649;">app</span><span>.</span><span style="color:#e45649;">io </span><span style="color:#a626a4;">= </span><span style="color:#0184bc;">require</span><span>(</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#e45649;">socket</span><span>.</span><span style="color:#e45649;">io</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;)(</span><span style="color:#e45649;">app</span><span>.</span><span style="color:#0184bc;">start</span><span>());
</span><span> </span><span style="color:#e45649;">app</span><span>.</span><span style="color:#e45649;">io</span><span>.</span><span style="color:#0184bc;">on</span><span>(</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#e45649;">connection</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;, </span><span style="color:#a626a4;">function </span><span style="color:#0184bc;">onConnection</span><span>(</span><span style="color:#e45649;">socket</span><span>) {
</span><span> </span><span style="color:#e45649;">socket</span><span>.</span><span style="color:#0184bc;">on</span><span>(</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#e45649;">bus</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;, </span><span style="color:#a626a4;">function </span><span style="color:#0184bc;">onBus</span><span>() {
</span><span> </span><span style="color:#e45649;">socket</span><span>.</span><span style="color:#e45649;">broadcast</span><span>.</span><span style="color:#0184bc;">emit</span><span>(</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#e45649;">redirect</span><span style="color:#a626a4;">-</span><span style="color:#e45649;">bus</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;);
</span><span> });
</span><span> </span><span style="color:#e45649;">socket</span><span>.</span><span style="color:#0184bc;">on</span><span>(</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#e45649;">end</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;, </span><span style="color:#a626a4;">function </span><span style="color:#0184bc;">onEnd</span><span>() {
</span><span> </span><span style="color:#e45649;">socket</span><span>.</span><span style="color:#0184bc;">disconnect</span><span>();
</span><span> });
</span><span> </span><span style="color:#e45649;">socket</span><span>.</span><span style="color:#0184bc;">on</span><span>(</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#e45649;">weather</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;, </span><span style="color:#a626a4;">function </span><span style="color:#0184bc;">onWeather</span><span>() {
</span><span> </span><span style="color:#e45649;">socket</span><span>.</span><span style="color:#e45649;">broadcast</span><span>.</span><span style="color:#0184bc;">emit</span><span>(</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#e45649;">redirect</span><span style="color:#a626a4;">-</span><span style="color:#e45649;">weather</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;);
</span><span> });
</span><span> });
</span><span>}
</span></code></pre>
<p>Finally the frontend handles the events from the server to do the
actual navigation changes</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#383a42;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#a626a4;">export function </span><span style="color:#0184bc;">pushNavigation</span><span>() {
</span><span> </span><span style="color:#a626a4;">const </span><span style="color:#e45649;">socket </span><span style="color:#a626a4;">= </span><span style="color:#0184bc;">client</span><span>(</span><span style="color:#50a14f;">`http://${window.location.host}`</span><span>);
</span><span> </span><span style="color:#e45649;">socket</span><span>.</span><span style="color:#0184bc;">on</span><span>(</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#e45649;">redirect</span><span style="color:#a626a4;">-</span><span style="color:#e45649;">bus</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;, </span><span style="color:#a626a4;">function </span><span style="color:#0184bc;">redirectBus</span><span>() {
</span><span> window.location.href </span><span style="color:#a626a4;">= &</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#a626a4;">/</span><span style="color:#e45649;">bus</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;;
</span><span> });
</span><span>
</span><span> </span><span style="color:#e45649;">socket</span><span>.</span><span style="color:#0184bc;">on</span><span>(</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#e45649;">redirect</span><span style="color:#a626a4;">-</span><span style="color:#e45649;">weather</span><span style="color:#a626a4;">&</span><span>#</span><span style="color:#c18401;">39</span><span>;, </span><span style="color:#a626a4;">function </span><span style="color:#0184bc;">redirectWeather</span><span>() {
</span><span> window.location.href </span><span style="color:#a626a4;">= &</span><span>#</span><span style="color:#c18401;">39</span><span>;</span><span style="color:#a626a4;">/&</span><span>#</span><span style="color:#c18401;">39</span><span>;;
</span><span> });
</span><span>}
</span></code></pre>
<h2 id="weather">Weather</h2>
<p><a href="/images/000004-weather.jpeg"><img src="/images/000004-weather.jpeg" title="Weather view" width="730" /></a></p>
<p>For a long time I used the Open Weather API to provide the data for
the weather view. One of the items I display is the location I'm in,
Winchester VA. A month or so ago however Open Weather started to
return various other cities around Winchester even though I provided
the zip code to the API. When it started displaying a city in PA I
decided it was time to move on because not only was the city text
wrong, the weather was wrong as well. I've since moved over to
Wunderground and so far everything seems to be working well. I also
kind of like their forecast a little better and they also provide
icons with the API return that you can use if you like. The background
color changes depending on the current temperature. It ranges from
blue to orange depending on how cold or hot it is respectively. Today
you can see its pretty chilly so we have a nice blue background
color. The two big numbers on the left are the current temperatures,
the top one is in Fahrenheit and the bottom is in Celsius. I did this
for two reasons. First, it helps the kids learn about Celsius and
Fahrenheit and two, I work with some people in the UK and I would like
to tell them what the temperature is here in a format they can relate
too.</p>
<h2 id="bus">Bus</h2>
<p><a href="/images/000004-bus.jpeg"><img src="/images/000004-bus.jpeg" title="Bus view" width="730" /></a></p>
<p>The bus timer is pretty simple. There is a json file that contains a
schedule of events, the events being holidays and pickup times:</p>
<pre data-lang="json" style="background-color:#fafafa;color:#383a42;" class="language-json "><code class="language-json" data-lang="json"><span>{
</span><span> </span><span style="color:#50a14f;">"holidays"</span><span>: [
</span><span> {</span><span style="color:#50a14f;">"begin"</span><span>: [</span><span style="color:#c18401;">2018</span><span>, </span><span style="color:#c18401;">1</span><span>, </span><span style="color:#c18401;">15</span><span>], </span><span style="color:#50a14f;">"end"</span><span>: [</span><span style="color:#c18401;">2018</span><span>, </span><span style="color:#c18401;">1</span><span>, </span><span style="color:#c18401;">15</span><span>]}
</span><span> ],
</span><span>
</span><span> </span><span style="color:#50a14f;">"pickups"</span><span>: [
</span><span> </span><span style="color:#50a14f;">"07:06:00"</span><span>,
</span><span> </span><span style="color:#50a14f;">"07:22:00"
</span><span> ]
</span><span>}
</span></code></pre>
<p>The timer runs on the weather page (where it sits most of the time)
and when it gets 10 minutes before the next timer it automatically
switches over to the bus page and begins a 10 minute count down. When
the count down reaches 0 a bell sound rings and the kids know its time
to get out to the bus stop. There is also timer code on the bus page
so it automatically switches back to the weather 30 seconds after the
bell goes off (maybe its a minute I can't remember now). On holidays
and weekends, the page still switches to the bus timer, but it gives a
“No school today” message, then switches back to the
weather.</p>
<h2 id="calendar">Calendar</h2>
<p>I've added two extra buttons because I have an idea for two more items
I would like to add. The first is a calendar. My problem is I have not
found one that meets my requirements. It all begins from the fact that
decades ago, long before yahoo mail, gmail, hotmail, etc… you
used to get an email address from your Internet provider. I switched
providers several times and my email address kept changing. I got
tired of this and registered my own domain and started running my own
mail server. Eventually gmail came along, and shortly after that
Google Apps. Google Apps brought with it the power of gmail but you
could bring along your own domain. When it first came out you could
get it for free. They provided up to 50 accounts with it. They
eventually started weaning that down to 25, 10, 5, then eventually you
had to start paying for it. However all the early adopter like myself
were grandfather-ed in. So I have two domains on google apps that
still have up to 50 accounts for free. The problem is, Google Apps is
not quite the same as gmail. The Google calendar is one of those
areas. With the calendar in Google Apps, you can only give people read
only permission to the calendar if they are not one of the accounts in
your domain. With the regular Google calendar, you get a family
calendar that your family can share. I've just recently created a
gmail account because I wanted to purchase YouTube Red and you're
supposed to be able to share that with your family…but there is
no family concept on Google Apps so I had to register a gmail
account. Now that I have that I think I may use the Google calendar
for button 3. What we have been using is the iCloud calendar which
works wonderfully for our current use case. The problem with the
iCloud calendar is that it has no API so trying to create something
for my PI wall proved to be difficult.</p>
<h2 id="room-temperatures">Room Temperatures</h2>
<p>My idea for the fourth button will be an iot project. I would like to
put temperature sensors in every room of the house and display the
aggregates on button four of the PI-wall. I've built one sensor. My
current problem with the sensor is taking it from the breadboard and
figuring out some sort of housing for it. I think I could build
something for a 3D printer, but I don't have a 3D
printer…yet. I've even contemplated just putting them in each
room, breadboard, wires and all and just calling it electronic art :)
I think I have enough parts to make 2 more but I need like around 10
or 11 if I really want one in every room. I think I'll create a new
post for this project to give those that may be interested more in
depth information. Here is what I have so far:</p>
<p><a href="/images/000004-room-temp.jpeg"><img src="/images/000004-room-temp.jpeg" title="Room Temperature Hardware" width="730" /></a></p>
AWS CodeStar2018-02-28T00:00:00+00:002018-02-28T00:00:00+00:00https://zolmok.org/aws-codestar/<p>When I decided to start this blog I had to spend some time figuring
out a few things like where I wanted to host it. I've always been a
fan of AWS, I've been using it in some fashion ever since it came
out. So when I was registering my domain in Route53 this thing called
CodeStar under the Services list caught my eye. Then I read the first
line of the description.</p>
<span id="continue-reading"></span>
<blockquote>
<p>AWS CodeStar enables you to quickly develop, build, and deploy applications on AWS.</p>
</blockquote>
<p>That was really all I needed to get me to try it out. I know there are
other services out there that make similar claims. One I've used in
the past is Heroku. Back when I was playing with Ruby on Rails,
actually deploying your app could prove a bit challenging. Heroku was
one of the first players in the game to take that burden away. All you
had to do was a git push and Heroku would figure out how to deploy it
for you. It was really nice. Now to be honest, I have not looked at
Heroku in many years. It may be a completely different platform than
it was when it started. I think it only did Ruby back then, but these
days it will handle all the major languages.</p>
<p>CodeStar is very similar to that. You create a project, choose which
platform you would like to build on, whether or not you would prefer
AWS CodeCommit or GitHub to store your code, and you're done! From
there CodeStar creates all of the AWS resources you need to power your
application. I chose node.js with AWS CodeCommit and here are the
resources it created for my project.</p>
<ul>
<li>CloudFormation</li>
<li>CodeCommit</li>
<li>CodePipeline</li>
<li>Elastic Beanstalk</li>
<li>IAM Roles</li>
<li>S3</li>
</ul>
<p>CodeStar is just a means to tie a bunch of various AWS services
together and wrap it all together inside a nice dashboard where you
can monitor and manage it all. Like Heroku, the initial project will
get you up and running, all you have to do at this point is a git push
and the various different AWS services will deploy your
application. Once you're there you can take advantage of all of the
rest of AWS. For example I was already using Route53 but I needed a
database to host my blog data. I chose Amazon RDS to host a Postgres
database. This is nice because all I had to do was add the RDS
security group to my Elastic Beanstalk configuration, add some
environment variables for hostname, port, username, etc... and I had a
database complete with security baked in! I also utilize Simple Email
Service to deliver email for the contact page. One of the greatest
features of AWS is that you only pay for the resources you use. They
also have really nice tools for calculating what that might be so you
can make sure you're not getting in over your head.</p>
<p><a href="/images/000001-codestar.png"><img src="/images/000001-codestar.png" alt="CodeStar dashboard" width="730" /></a></p>
<h2 id="lessons-learned">Lessons Learned</h2>
<p>These tips are from my experience by creating a Node.js application
using Elastic Beanstalk.</p>
<ol>
<li>
<p>SSH: You can debug many problems by using SSH and logging into your
system. Elastic Beanstalk uses EC2 to create your environment. It
attaches an Elastic IP to the instance it creates, so navigate to the
EC2 services page, find the instance created by Elastic Beanstalk (it
will have an IAM role something like aws-elasticbeanstalk-ec2-role)
and note the Elastic IP. You will use this IP to SSH into your system.</p>
</li>
<li>
<p>Node.js version: The Elastic Beanstalk instance actually comes with
several versions of node installed. As of this writing they are
currently installed under <code>/opt/elasticbeanstalk/node-install</code>. I
think the default version on my project was <code>v6.12.2</code> but I don't
remember exactly. I wanted version <code>8.x</code>, there are two installed,
<code>v8.8.1</code> and <code>v8.9.3</code>, I chose <code>v8.9.3</code>. To set this bring up the
CodeStar dashboard:</p>
</li>
</ol>
<ol>
<li>Click the Deploy icon in the left navigation bar</li>
<li>Click on your environment</li>
<li>Click on configuration</li>
<li>Under the software card click Modify</li>
<li>In the Node.js version dropdown choose which version you would like</li>
<li>Click save</li>
<li>Click apply</li>
</ol>
<ol start="3">
<li>Customize deployments: Sometimes your basic git push deploy just
doesn't cut it and you need to customize a few things to get your
environment just right when you deploy. Maybe you need to install a
package that isn't already present, add a user, or create a
file. Luckily CodeStar can be configured through
ebextensions. Ebextensions are actually an Elastic Beanstalk concept
that you get for free since CodeStar uses Elastic Beanstalk to create
your environment. I found this page (Customizing Software on Linux
Servers) to be an invaluable source of information when configuring my
environment. There are several different keys you can configure from
packages, groups, users, etc... One thing I found confusing is what
order these various tasks would run. The documentation clears that
up. Right below the list of keys it says.</li>
</ol>
<blockquote>
<p>Keys are processed in the order that they are listed above.</p>
</blockquote>
<p>The two keys I ended up using the most are commands and container
commands. The difference between these two is when they run. Commands
run before container commands and are are generally used for system
wide things like installing packages and such. Container commands are
used for making changes in your project configuration “after” it has
been deployed to disk. You may want to run node or npm but you'll
quickly find that they're not on the path. To run a command you'll
need to prefix it with the path like so:</p>
<pre data-lang="bash" style="background-color:#fafafa;color:#383a42;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#e45649;">env</span><span> PATH=</span><span style="color:#50a14f;">"$</span><span style="color:#e45649;">PATH</span><span style="color:#50a14f;">:$</span><span style="color:#e45649;">NODE_HOME</span><span style="color:#50a14f;">/bin"</span><span> npm install
</span></code></pre>
<ol start="4">
<li>Debugging failed deployments: Eventually it will happen. You will
deploy your project and the application won't come up properly. It
could be for any number of reasons from a bug you've introduced in
your code to installing an npm module as a development dependency when
it needs to available in production. You can watch the events as the
deploy occurs in the Elastic Beanstalk dashboard. You get there by
clicking the Deploy icon in the left navigation, selecting your
environment, then clicking Events. This page will automatically
refresh as the events occur but if you get impatient there is a
Refresh button you can spam. Sometimes the messages get truncated if
they are too long and some things are just plain left out. I prefer to
ssh into my instance and tail the log file (<code>tail -f /var/log/eb-activity.log</code>). If the problem turns out to be a bug in
your code there is another file you'll want to look at the node.js
file instead (<code>tail -f /var/log/nodejs/nodejs.log</code>).</li>
</ol>
<p>At the end of the day you might find that you prefer another system
due to price or features or a combination of both. I'm kind of partial
to AWS if only because I've been using it for so long so its
familiar. But I also like all of the other AWS features I could tie
into if I wanted. However another system may have some added value
that you find useful for your situation. My goal is not to persuade
you one way or another but only to share with you what I've learned. I
hope you find it useful. If you feel I've missed out on something,
please feel free to share below.</p>
Starting a new blog2018-02-24T00:00:00+00:002018-02-24T00:00:00+00:00https://zolmok.org/starting-a-new-blog/<p>I'm constantly learning new things. I'm a Frontend Engineer by trade
and that space is constantly evolving. I've always liked the idea of
blogging, but I've never really done much of it. Part of the reason is
that I really like my editor (Emacs). I don't like the idea of typing
a bunch of text into a textarea as in for a blog post for a couple of
reasons. First, it doesn't have vim bindings. I can barely type
anymore if vim bindings are not present. Secondly, I have actually
lost form data before due to session timeouts and such so I just don't
like typing large amounts of text into forms. I was excited the other
day to find a Chrome addon that handles both of these concerns called
Atomic Chrome.<span id="continue-reading"></span> I've decided I would like to start
blogging about things that I learn about from day to day. My hope is
that I can help someone else that may be learning the same
thing. Secondly I'm hoping that as I go you guys can help me fill in
the gaps if I'm off track.</p>
<p>Choosing which platform upon which to build any sort of web
application is no small feat in 2018, and a blog platform is no less
overwhelming. Wordpress is the obvious choice, but what about
something like Medium? I considered all of these before I decided to
role my own. It basically boiled down to two things. I really enjoy
building stuff, and if I'm going to write a blog post I'd like to do
so from the comfort of my own editor, not some textarea provided by
some platform.</p>
<p>If you thought deciding on which blogging platform was a difficult
choice, it has nothing on choosing to roll your own thing from
scratch! Here are just a couple of questions you have to answer:</p>
<ul>
<li>Where will you host it?</li>
<li>Obviously there will be a UI, but what about a backend?</li>
<li>If you choose to have a backend, what language will it be in?</li>
<li>While we're on the backend, will you use a database?</li>
<li>Will it be a relational database or an object store?</li>
<li>Will you use a framework of some sort or roll something from
scratch...if a framework which one?</li>
<li>On the frontend, will you use a framework or roll it from scratch?</li>
</ul>
<p>I had some simple requirements for my blog. First, I wanted to be able
to use my own editor (current Emacs in Evil mode). Secondly I didn't
want to have to deal with a backend. I didn't get away from this
entirely, but I don't have a proper admin site. I have a couple of
scripts that I use to publish my posts and its all working pretty
great. I'm a UI developer by trade, I don't do any backend stuff at
work. However I kind of needed a backend for this because I wanted to
store my posts in a database. I chose a relational database because I
want to eventually setup tags and that requires a relation. I'm sure
that kind of thing can be done in an object store these days but call
me old fashioned, I feel like a relational database is the right tool
for this kind of job.</p>
<p>Here are the choices I ended up making to build my own blogging
platform:</p>
<ul>
<li>CodeStar</li>
<li>LoopBack</li>
<li>Postgres SQL</li>
<li>React</li>
</ul>
<p>I know there are several platforms out there that provide a good chunk
of this functionality and are ready to go out of the box. One that I
found that I tried and really liked for the frontend part at least is
Next.js. Next.js has a philosophy that works really well. They wanted
web development to work like it did back in the (pre-framework) php
days. That is files actually mapped to routes. When made a file on
disk, say foo.php, you could easily route to that in your browser,
http://example.com/foo.php. A similar mapping exists in Next.js. You
can make a file say, foo.js, and it will map to
http://example.com/foo. There is something to be said for this type of
simplicity. Sometimes I think we tend to make things overly
complex. While these platforms are great, for me, I just wanted to try
and build my own thing. I get two benefits from this. First I'll learn
something, maybe just a little, but maybe a lot. Secondly it will give
me something to write about :)</p>
<p>So in v1 of this new blogging platform I've created my workflow goes
something like this.</p>
<ol>
<li>./dev</li>
<li>./task/new-post</li>
<li>edit new-post</li>
<li>./task/publish.js</li>
<li>git commit</li>
<li>git push</li>
</ol>
<p>So what does all of that do? <code>./dev</code> is a tmux script. Did I tell you I
love the command line? Lots of people like various panes (think split
windows), I prefer separate windows (think tabs) for different
things. So my script creates 5 windows, 0) Server, 1) TestServer, 2)
Client, 3) TestClient 4) Zsh.</p>
<pre data-lang="bash" style="background-color:#fafafa;color:#383a42;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#a0a1a7;">#! /bin/sh
</span><span style="color:#e45649;">tmux</span><span> has-session</span><span style="color:#e45649;"> -t</span><span> Zolmok
</span><span> </span><span style="color:#a626a4;">if </span><span style="color:#0184bc;">[ </span><span>$</span><span style="color:#e45649;">? </span><span style="color:#a626a4;">!=</span><span> 0 </span><span style="color:#0184bc;">] </span><span style="color:#a626a4;">then
</span><span style="color:#e45649;">tmux</span><span> new-session</span><span style="color:#e45649;"> -s</span><span> Zolmok</span><span style="color:#e45649;"> -n</span><span> Server</span><span style="color:#e45649;"> -d
</span><span> </span><span style="color:#a0a1a7;"># create moar windows
</span><span> </span><span style="color:#e45649;">tmux</span><span> new-window</span><span style="color:#e45649;"> -t</span><span> Zolmok:1</span><span style="color:#e45649;"> -n</span><span> TestServer
</span><span> </span><span style="color:#e45649;">tmux</span><span> new-window</span><span style="color:#e45649;"> -t</span><span> Zolmok:2</span><span style="color:#e45649;"> -n</span><span> Client
</span><span> </span><span style="color:#e45649;">tmux</span><span> new-window</span><span style="color:#e45649;"> -t</span><span> Zolmok:3</span><span style="color:#e45649;"> -n</span><span> TestClient
</span><span> </span><span style="color:#e45649;">tmux</span><span> new-window</span><span style="color:#e45649;"> -t</span><span> Zolmok:4</span><span style="color:#e45649;"> -n</span><span> Zsh
</span><span>
</span><span> </span><span style="color:#a0a1a7;"># run the server in the first window
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t</span><span> Zolmok:0.0 :cd </span><span style="color:#e45649;">~</span><span>/dev/Zolmok </span><span style="color:#a626a4;">&& </span><span style="color:#0184bc;">source</span><span> env </span><span style="color:#a626a4;">&& </span><span style="color:#e45649;">npm</span><span> start: C-m
</span><span> </span><span style="color:#a0a1a7;"># run the server tests in the second window
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t</span><span> Zolmok:1.0 :cd </span><span style="color:#e45649;">~</span><span>/dev/Zolmok </span><span style="color:#a626a4;">&& </span><span style="color:#e45649;">npm</span><span> test: C-m
</span><span> </span><span style="color:#a0a1a7;"># run the client in the third window
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t</span><span> Zolmok:2.0 :cd </span><span style="color:#e45649;">~</span><span>/dev/Zolmok/frontend </span><span style="color:#a626a4;">&& </span><span style="color:#e45649;">npm</span><span> start: C-m
</span><span> </span><span style="color:#a0a1a7;"># run the client tests in the fourth window
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t</span><span> Zolmok:3.0 :cd </span><span style="color:#e45649;">~</span><span>/dev/Zolmok/frontend </span><span style="color:#a626a4;">&& </span><span style="color:#e45649;">npm</span><span> run test:client: C-m
</span><span> </span><span style="color:#a0a1a7;"># just get to the proper path in the last window
</span><span> </span><span style="color:#e45649;">tmux</span><span> send-keys</span><span style="color:#e45649;"> -t</span><span> Zolmok:4.0 :cd </span><span style="color:#e45649;">~</span><span>/dev/Zolmok: C-m
</span><span> </span><span style="color:#a0a1a7;"># select the server window and pane
</span><span> </span><span style="color:#e45649;">tmux</span><span> select-window</span><span style="color:#e45649;"> -t</span><span> Zolmok:0.0
</span><span style="color:#a626a4;">fi
</span><span style="color:#e45649;">tmux</span><span> attach</span><span style="color:#e45649;"> -t</span><span> Zolmok
</span></code></pre>
<p>The first thing it does is check to see if a tmux session named
"Zolmok" already exists. If it does it attaches to that session, if it
does not, then it builds the session.</p>
<p>new-post looks in the post directory and determines what the next post
id will be and copies over a template from which I'll use to create
the next post. I came up with an id system that uses 6 digits, so
000000, 000001, 000002, etc... as directory names. The template that
is copied over contains exactly 2 files, index.html and
meta.json. index.html is where I'll type up the post, and meta.json
contains various attributes about the post like title, tags (not
implemented yet) summary, card-image (image used on the list of
posts), a publish flag, etc...</p>
<p>I'll then edit the index.html file until I'm happy with it at which
point I set the publish flag to true in its meta.json file. I then run
the publish.js script which will walk the post directory looking for
meta.json files that have the publish flag set to true. If it is true
it tries to figure out if it is a new post, if so, push the index.html
and it's various properties from the meta.json into the database. If
not then it tries to figure out if any changes have been made to
either the post itself or it's meta data, if so push those changes to
the database. Then I do a git commit followed by git push at which
point everything "should" be live.</p>
<p>It's not perfect...yet, lol, but I really enjoyed building it. I also
have several more features in mind that I would like to add like an
auto-refresher to automatically refresh the browser as I'm writing my
post. I hope this is the first post of many and I hope that you learn
with me and help me to learn more as well.</p>
About me2018-02-18T00:00:00+00:002018-02-18T00:00:00+00:00https://zolmok.org/pages/about/<p>I am a Senior Engineer at <a rel="noopener" target="_blank" href="https://getpicnic.com/">Picnic</a> by day, and
generally trying to learn new things or gaming at night. I’ve been a
technical professional since 1996 and have been writing code in one
language or another since 2000. I’m currently fully invested in
JavaScript but considering dabling with Elm, Rust or both. I really
enjoy keeping up with new technologies that are coming out, devops
type things, and tweaking my development environment.</p>
<p>This is a place where I would like to document things that I’m
learning about. Generally speaking I tend to learn about technical
things like programming and devops. You may find blatent innacuracy’s
in some of my post’s but I assure you that’s not intentional. If you
find something that you believe to be innacurate please let me know so
that I can get it corrected.</p>
<p>My goal is to try and post something at least weekly. I may even try
to do some recording and host the videos on YouTube. If there is
something that you would like for me to look into, let me know.</p>
<div class="flex gap-x-3">
<div data-iframe-width="150" data-iframe-height="270" data-share-badge-id="3f5ade72-1f9b-4ca7-b59b-75ff3fc8ee22" data-share-badge-host="https://www.youracclaim.com"></div><script type="text/javascript" async src="//cdn.youracclaim.com/assets/utilities/embed.js"></script>
</div>
<div>
<a href="https://github.com/Zolmok?tab=followers" class="flex gap-x-3 items-center">
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 496 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
Follow me on GitHub
</a>
</div>
Contact me2018-02-18T00:00:00+00:002018-02-18T00:00:00+00:00https://zolmok.org/pages/contact/Using dtrace to fix git2014-03-14T00:00:00+00:002014-03-14T00:00:00+00:00https://zolmok.org/using-dtrace-to-fix-git/<blockquote>
<p>I am closing down my old blog on
<a rel="noopener" target="_blank" href="https://www.socketwiz.com">socketwiz.com</a> and I didn't have much
content over there and this was and article I felt like preserving.</p>
</blockquote>
<p>First off the problem. I like to develop on
<a rel="noopener" target="_blank" href="http://smartos.org/">smartos</a> because it is based on Solaris and has
all of the conveniences of Linux with a few extras namely zfs, zones
and <code>dtrace</code>. It’s <code>dtrace</code> that I’m going to discuss today. I run a
very specialized version of vim. By specialized I mean I have a bunch
of
<a rel="noopener" target="_blank" href="https://github.com/socketwiz/dotfiles/blob/main/.config/nvim/init.vim">addons</a>. In
particular, <a rel="noopener" target="_blank" href="https://github.com/ycm-core/YouCompleteMe">YouCompleteMe</a>
and <a rel="noopener" target="_blank" href="https://github.com/ycm-core/YouCompleteMe">UltiSnips</a> requires
that vim be a particular version and have python support compiled
in.<span id="continue-reading"></span> The vim for smartos unfortunately does not so I build
my own. This leads us to my problem. I also use git and when I do
commits I want it to use vim, specifically my custom vim, otherwise it
throws an error on startup complaining about the lack of python
support.</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>YouCompleteMe unavailable: requires Vim 7.3.584+
</span><span>UltiSnips requires py &gt;= 2.6 or any py3
</span><span>Press ENTER or type command to continue
</span></code></pre>
<p>To fix this there are two options:</p>
<ol>
<li>git config –global core.editor "/path/to/vim"</li>
<li>set the GIT_EDITOR, VISUAL, or EDITOR environment variables.</li>
</ol>
<p>I opted for the later because I share my $HOME/.gitconfig on 3
different systems, smartos, mac, and windows, and <code>vim</code> lives in
different locations on each of them. The environment variable would be
the ideal solution but it seems to be ignored because I have <code>EDITOR</code>
set already. I tried setting <code>GIT_EDITOR</code> to no avail. So what’s going
on and more importantly how can I fix it? One option might be to
download the source code for git and try to debug it and figure out
what the problem is, but that seems like a lot of work for such a
small annoyance. I chose to utilize <code>dtrace</code>, after all theres no since
running on smartos if you’re not going to take advantage of its
conveniences. My goal was to try and figure out where git was
searching for <code>vim</code> in an attempt to try and figure out why it was not
finding my <code>vim</code>, and instead using the system <code>vim</code>. My path has my <code>vim</code>
ahead of the system <code>vim</code> so in theory it should have found it. Here is
the command I came up with:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>$ dtrace -n 'syscall::exec*:entry /execname=="git"/ { printf("%s\n", copyinstr(arg0)); }'
</span></code></pre>
<p>I fired up tmux because I run smartos remotely and I wanted to take
advantage of the multiple windows so I didn’t have to make 2 ssh
connections. In the first window I ran sudo -i because <code>dtrace</code> wants to
run as root, then I ran the command above. In the second window I ran
<code>git commit</code> and here is what <code>dtrace</code> output:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>dtrace: description 'syscall::exec*:entry ' matched 1 probe
</span><span>CPU ID FUNCTION:NAME
</span><span>3 7129 exece:entry /opt/local/libexec/git-core/vi
</span><span>
</span><span>3 7129 exece:entry /opt/local/bin/vi
</span><span>
</span><span>3 7129 exece:entry /opt/local/bin/vi
</span><span>
</span><span>3 7129 exece:entry /usr/local/sbin/vi
</span><span>
</span><span>3 7129 exece:entry /usr/local/bin/vi
</span><span>
</span><span>3 7129 exece:entry /opt/local/sbin/vi
</span><span>
</span><span>3 7129 exece:entry /opt/local/bin/vi
</span><span>
</span><span>3 7129 exece:entry /usr/sbin/vi
</span><span>
</span><span>3 7129 exece:entry /usr/bin/vi
</span></code></pre>
<p>Aha! I don’t understand why, but for some reason it’s not looking for
<code>vim</code> at all, it’s looking for vi. I could fix this in one of two
ways. Download the source for git and try and figure out why it’s
doing this, or simply make a symbolic link from vi to my <code>vim</code> and
call it a day. I chose the latter:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>sudo ln -s /opt/local/bin/vim /opt/local/bin/vi
</span><span>
</span><span>Now when I run “git commit” I get:
</span><span>
</span><span>dtrace: description 'syscall::exec*:entry ' matched 1 probe
</span><span>CPU ID FUNCTION:NAME
</span><span>1 7129 exece:entry /opt/local/libexec/git-core/vi
</span><span>
</span><span>1 7129 exece:entry /opt/local/bin/vi
</span></code></pre>
VIM cheat sheet2012-05-05T00:00:00+00:002012-05-05T00:00:00+00:00https://zolmok.org/vim-cheat-sheet/<blockquote>
<p>I am closing down my old blog on
<a rel="noopener" target="_blank" href="https://www.socketwiz.com">socketwiz.com</a> and I didn't have much
content over there and this was and article I felt like preserving.</p>
</blockquote>
<p>Since I have been using Vim almost exclusively lately I made myself a
cheat sheet to help me remember some stuff. This is certainly not an
exhaustive list, just some things that I don’t know how to do off the
top of my head that I wrote down and ended up getting a little carried
away. Just thought I would share:</p>
<span id="continue-reading"></span><h2 id="motions">Motions</h2>
<table><thead><tr><th>motion</th><th>description</th></tr></thead><tbody>
<tr><td>h</td><td>Count characters left</td></tr>
<tr><td>l</td><td>Count characters right</td></tr>
<tr><td>^</td><td>To the first character of the line.</td></tr>
<tr><td>$</td><td>To the last character of the line.</td></tr>
<tr><td>f<char></td><td>To the counth character occurrence to the right. F<gchar> to the counth character occurrence to the left.</td></tr>
<tr><td>t<char></td><td>To 1 character just before the counth character occurrence to the right.</td></tr>
<tr><td>T<char></td><td>To 1 character just before the counth character occurrence to the left.</td></tr>
<tr><td>w</td><td>Count words forward.</td></tr>
<tr><td>W</td><td>Count words forward (different definition for what a word is, includes special characters and such.)</td></tr>
<tr><td>e</td><td>Count forward to the end of word.</td></tr>
<tr><td>b</td><td>Count words backward.</td></tr>
</tbody></table>
<table><thead><tr><th>trigger</th><th>effect</th></tr></thead><tbody>
<tr><td>c{motion}</td><td>Change</td></tr>
<tr><td>d{motion}</td><td>Delete</td></tr>
<tr><td>y{motion}</td><td>Yank into register</td></tr>
<tr><td>g~{motion}</td><td>Toggle case</td></tr>
<tr><td>gu{motion}</td><td>Make lowercase</td></tr>
<tr><td>gU{motion}</td><td>Make uppercase</td></tr>
<tr><td>>{motion}</td><td>Shift right</td></tr>
<tr><td><g{motion}</td><td>Shift left</td></tr>
<tr><td>={motion}</td><td>Auto-indent</td></tr>
</tbody></table>
<h2 id="buffer-management">Buffer management</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td><C-^> or <C-6></td><td>Switch to the buffer you just left.</td></tr>
</tbody></table>
<h2 id="split-windows">Split windows</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td><C-w>s or :sp[lit] <file></td><td>Split file horizontally.</td></tr>
<tr><td><C-w>v or :vsp[lit] <file></td><td>Split file vertically.</td></tr>
<tr><td><C-w>w</td><td>Cycle between open windows.</td></tr>
<tr><td><C-w>h</td><td>Focus the window to the left.</td></tr>
<tr><td><C-w>j</td><td>Focus the window below.</td></tr>
<tr><td><C-w>k</td><td>Focus the window above.</td></tr>
<tr><td><C-w>l</td><td>Focus the window to the right.</td></tr>
<tr><td>:cl[ose] <C-w>c</td><td>Close the active window.</td></tr>
<tr><td>:on[ly] <C-w>o</td><td>Keep only the active window, closing all others.</td></tr>
</tbody></table>
<h2 id="spelling">Spelling</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>:setlocal spell</td><td>Enable spell checker</td></tr>
<tr><td>]s</td><td>Jump to next spelling error.</td></tr>
<tr><td>[s</td><td>Jump to previous spelling error.</td></tr>
<tr><td>z=</td><td>Suggest corrections for current word.</td></tr>
<tr><td>zg</td><td>Add the current word to spell file.</td></tr>
<tr><td>zw</td><td>Remove the current word from spell file.</td></tr>
</tbody></table>
<h2 id="code-folding">Code folding</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>zf{motion}</td><td>Folds code when "foldmethod" set to manual or "marker".</td></tr>
<tr><td>za</td><td>Toggle fold at cursor.</td></tr>
<tr><td>zo</td><td>Opens fold at cursor.</td></tr>
<tr><td>zc</td><td>Close fold at cursor.</td></tr>
<tr><td>zR</td><td>Open all.</td></tr>
<tr><td>zM</td><td>Close all.</td></tr>
</tbody></table>
<h2 id="search-and-replace">Search and replace</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>:args **/*.txt</td><td>Multi-file Step 1) populate the argument list with the files you want to search.</td></tr>
<tr><td>:argdo %s/search/replace/gc</td><td>Mult-file Step 2) replace all occurrences of search with replace but prompt before doing so.</td></tr>
<tr><td>*</td><td>Search for the word under the cursor.</td></tr>
</tbody></table>
<h2 id="jumps">Jumps</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>:jumps</td><td>Display the jump list.</td></tr>
<tr><td><C-o></td><td>Jump backwards through the jump list.</td></tr>
<tr><td><C-i></td><td>Jump forwards through the jump list.</td></tr>
<tr><td>:changes</td><td>Display the change list.</td></tr>
<tr><td>g;</td><td>Jump backwards through the changes list.</td></tr>
<tr><td>g,</td><td>Jump forwards through the changes list.</td></tr>
<tr><td>gf</td><td>jump to the file name under the cursor.</td></tr>
</tbody></table>
<h2 id="marks">Marks</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>:marks</td><td>Display the marks list.</td></tr>
<tr><td>m<gupper case></td><td>Set a file bookmark.</td></tr>
<tr><td>m<glower case></td><td>Set a buffer bookmark.</td></tr>
<tr><td>'<gcharacter></td><td>Jump to the mark.</td></tr>
<tr><td>''</td><td>Jump to the line in the current buffer where jumped from.</td></tr>
<tr><td>:delmarks <gcharacter></td><td>Delete specified mark.</td></tr>
<tr><td>:delmarks a-d</td><td>Delete marks a through d</td></tr>
<tr><td>:delmarks a,b,x,y</td><td>Delete only marks a,b,x and y.</td></tr>
<tr><td>:delmarks!</td><td>Delete all lower case marks.</td></tr>
</tbody></table>
<h2 id="registers">Registers</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>:registers</td><td>Display the register list.</td></tr>
<tr><td>"<glower case register>{motion}</td><td>Overwrite or use contents of register.</td></tr>
<tr><td>"<gupper case register>{motion}</td><td>Append or use contents of register.</td></tr>
<tr><td>0</td><td>Populated with last yanked text.</td></tr>
</tbody></table>
<h2 id="macros">Macros</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>q{register}</td><td>Start recording and store it in the specified register.</td></tr>
<tr><td>q</td><td>Stop recording.</td></tr>
<tr><td>{count}@{register}</td><td>Execute specified macro count times.</td></tr>
</tbody></table>
<h2 id="sessions">Sessions</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>:mksession /path/to/session.vim</td><td>A Session keeps the Views for all windows, plus the global settings.</td></tr>
<tr><td>:source /path/to/session.vim</td><td>Open a session from within Vim.</td></tr>
<tr><td>vim -S /path/to/session.vim</td><td>Start Vim with a session.</td></tr>
</tbody></table>
<h2 id="quickfix-window">Quickfix window</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>:copen</td><td>Open the quickfix window.</td></tr>
<tr><td>:ccl</td><td>Close it.</td></tr>
<tr><td>:cw</td><td>Open it if there are "errors", close it otherwise (some people prefer this).</td></tr>
<tr><td>:cn</td><td>Go to the next error in the window.</td></tr>
<tr><td>:cnf</td><td>Go to the first error in the next file.</td></tr>
<tr><td>:cc{num}</td><td>Go to the error by number.</td></tr>
</tbody></table>
<h2 id="navigation">Navigation</h2>
<table><thead><tr><th>key</th><th>move to</th></tr></thead><tbody>
<tr><td>%</td><td>End of construct</td></tr>
<tr><td>[[</td><td>Backwards to the beginning of the current function.</td></tr>
<tr><td>][</td><td>Forwards to the beginning of the current function.</td></tr>
<tr><td>]}</td><td>Beginning of the current block.</td></tr>
<tr><td>[{</td><td>End of the current block.</td></tr>
<tr><td>}[</td><td>Beginning of the current comment block.</td></tr>
<tr><td>}]</td><td>End of the current comment block.</td></tr>
<tr><td>gd</td><td>First usage of the current variable name. (Mnemonic: go to definition).</td></tr>
<tr><td>gD</td><td>Go to the first global usage of the current variable name.</td></tr>
<tr><td>gg</td><td>Beginning of file.</td></tr>
<tr><td>G</td><td>End of file.</td></tr>
</tbody></table>
<h2 id="help">Help</h2>
<table><thead><tr><th>key</th><th>move to</th></tr></thead><tbody>
<tr><td>~<C-]>~</td><td>follow link</td></tr>
<tr><td>~<C-t>~</td><td>jump back</td></tr>
</tbody></table>
<h2 id="addon-references">Addon References</h2>
<p><strong>ctrlp.vim <a rel="noopener" target="_blank" href="https://github.com/kien/ctrlp.vim">https://github.com/kien/ctrlp.vim</a></strong></p>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>CtrlP</td><td>Invoke ctrl-p in file mode.</td></tr>
<tr><td>F5</td><td>Purge the cache for the current directory.</td></tr>
<tr><td><C-f><C-b></td><td>Cycle between modes.</td></tr>
<tr><td><C-d></td><td>Switch to filename instead of full path.</td></tr>
<tr><td><C-r></td><td>Switch to regex mode.</td></tr>
<tr><td><C-j><C-k></td><td>Navigate the result list.</td></tr>
<tr><td>..</td><td>To go up the directory tree.</td></tr>
<tr><td>:<gcommand></td><td>Excute command against the file.</td></tr>
<tr><td>:<gnumber></td><td>Jump to line number.</td></tr>
</tbody></table>
<p><strong>Tcomment <a rel="noopener" target="_blank" href="https://github.com/tomtom/tcomment_vim">https://github.com/tomtom/tcomment_vim</a></strong></p>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>gc{motion}</td><td>Toggle comments (for small comments within one line the &filetype_inline style will be used, if defined).</td></tr>
<tr><td>gcc</td><td>Toggle comment for the current line.</td></tr>
<tr><td>C{motion}</td><td>Comment region.</td></tr>
<tr><td>Cc</td><td>Comment the current line.</td></tr>
<tr><td><C- _ ><C- _ ></td><td>:TComment.</td></tr>
<tr><td><C- _ >b</td><td>:TCommentBlock.</td></tr>
</tbody></table>
<p><strong>vim-fugitive <a rel="noopener" target="_blank" href="https://github.com/tpope/vim-fugitive">https://github.com/tpope/vim-fugitive</a></strong></p>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>Gstatus</td><td>git status (- to add/reset changes)</td></tr>
<tr><td>Gcommit</td><td>git commit</td></tr>
<tr><td>Gmove</td><td>git move</td></tr>
<tr><td>Gremove</td><td>git rm</td></tr>
<tr><td>Ggrep</td><td>git grep</td></tr>
<tr><td>Glog</td><td>git log</td></tr>
<tr><td>Gbrowse</td><td>open current file on Github</td></tr>
<tr><td>Git <gcommand></td><td>run arbitrary git command</td></tr>
</tbody></table>
<p><strong>vim-surround <a rel="noopener" target="_blank" href="https://github.com/tpope/vim-surround">https://github.com/tpope/vim-surround</a></strong></p>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>cs<gcurrent><new></td><td>Change old delimeter to new.</td></tr>
<tr><td>ds</td><td>Remove delimeters.</td></tr>
<tr><td>ysiw<gdelimiter></td><td>Wrap current word.</td></tr>
<tr><td>S<gdelimiter></td><td>While in visual mode.</td></tr>
</tbody></table>
<p><strong>gitgutter <a rel="noopener" target="_blank" href="https://github.com/airblade/vim-gitgutter">https://github.com/airblade/vim-gitgutter</a></strong></p>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>[h</td><td>Previous hunk</td></tr>
<tr><td>]h</td><td>Next hunk</td></tr>
</tbody></table>
<h2 id="miscellaneous">Miscellaneous</h2>
<table><thead><tr><th>command</th><th>description</th></tr></thead><tbody>
<tr><td>v</td><td>Enable characterwise Visual mode.</td></tr>
<tr><td><S-v></td><td>Enable linewise Visual mode.</td></tr>
<tr><td><C-v></td><td>Enable blockwise Visual mode.</td></tr>
<tr><td>gv</td><td>Reselect the last visual selection.</td></tr>
<tr><td>o</td><td>Go to other end of highlighted text.</td></tr>
<tr><td>"+p</td><td>Paste from system clipboard.</td></tr>
<tr><td>:0,$d</td><td>Delete every line in a file.</td></tr>
<tr><td><C-a></td><td>Increment number.</td></tr>
<tr><td><C-x></td><td>Decrement number.</td></tr>
<tr><td>vit</td><td>Select contents inside of an HTML tag.</td></tr>
<tr><td>:map</td><td>List all currently define mappings.</td></tr>
<tr><td>:verbose map</td><td>Display where mapping was defined.</td></tr>
<tr><td>g/{pattern}/d</td><td>Delete every line matching pattern.</td></tr>
<tr><td>v/{pattern}/d</td><td>Delete every line <em>not</em> matching pattern.</td></tr>
<tr><td>:w !sudo tee %</td><td>Save a file with sudo that you opened with proper permissions.</td></tr>
<tr><td>bufdo {command}</td><td>Execute specified command on every buffer.</td></tr>
<tr><td>{trigger}i{container}</td><td>Inside some container like brackets, tag, or parenthesis for example.</td></tr>
<tr><td>:e</td><td>Refresh or reload buffer</td></tr>
</tbody></table>
<h2 id="merge-conflicts">Merge Conflicts</h2>
<p>(requires the fugitive plugin)</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>:Gdiff
</span></code></pre>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>Target | Working | Merge
</span><span>//2 //3
</span></code></pre>
<blockquote>
<p><code>[c</code> and <code>]c</code> to navigate between conflicts</p>
</blockquote>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>:diffget //2 or //3
</span><span>:diffupdate
</span><span>:only
</span><span>:wq
</span></code></pre>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>$ git add .
</span><span>$ git commit -m 'message'
</span></code></pre>
<h2 id="build-from-source">Build from source</h2>
<pre data-lang="bash" style="background-color:#fafafa;color:#383a42;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#e45649;">$</span><span> cd </span><span style="color:#e45649;">~
</span><span style="color:#e45649;">$</span><span> hg clone https://code.google.com/p/vim/
</span><span style="color:#e45649;">$</span><span> hg tags </span><span style="color:#a626a4;">| </span><span style="color:#e45649;">grep</span><span> less </span><span style="color:#a0a1a7;"># find out what latest stable build is
</span><span style="color:#e45649;">$</span><span> hg checkout </span><span style="color:#a626a4;"><</span><span>latest_stable</span><span style="color:#a626a4;">>
</span><span style="color:#e45649;">$</span><span> cd vim
</span><span style="color:#e45649;">$</span><span> ./configure</span><span style="color:#e45649;"> --with-features</span><span style="color:#a626a4;">=</span><span>huge \
</span><span style="color:#e45649;"> --enable-rubyinterp </span><span>\
</span><span style="color:#e45649;"> --enable-pythoninterp </span><span>\
</span><span style="color:#e45649;"> --enable-gui</span><span style="color:#a626a4;">=</span><span>gtk2</span><span style="color:#e45649;"> --enable-cscope --prefix</span><span style="color:#a626a4;">=</span><span>/usr
</span><span style="color:#e45649;">$</span><span> make VIMRUNTIMEDIR=/opt/local/share/vim/vim73
</span><span style="color:#e45649;">$</span><span> make install
</span></code></pre>
How to recover data from a memory card for free2010-07-21T00:00:00+00:002010-07-21T00:00:00+00:00https://zolmok.org/how-to-recover-data-from-a-memory-card-for-free/<blockquote>
<p>I am closing down my old blog on
<a rel="noopener" target="_blank" href="https://www.socketwiz.com">socketwiz.com</a> and I didn't have much
content over there and this was and article I felt like preserving.</p>
</blockquote>
<p>So a friend of mine took some pictures on her camera which contains an
SD card but for some reason was unable to retrieve the pictures off of
it. I have a similar camera and a Mac she has Windows. Many times I’ve
been able to access data on removable drives on my Mac that were
inaccessible on Windows but not this time.</p>
<span id="continue-reading"></span>
<p>So my first hurdle was how to mount this drive under the file
system. I put the card in my camera but it does a great job of masking
that there is a removable drive inside of it. Normally I don’t care. I
just plug it in and up comes iPhoto ready to pull the images off and
remove them from my camera. So when I plugged her card in, iPhoto
dutifully came up…but no pictures. So I found a free piece of software
called <a rel="noopener" target="_blank" href="https://www.cgsecurity.org/wiki/PhotoRec">PhotoRec </a> that
would supposedly extract the data from the memory card if I could only
see it. So with iPhoto up and running I ran the app, but there was no
drive to be seen. For some reason I decided to look around on my Mac
to see if by any chance there might be a card reader of some sort and
lo and behold right under the CD drive is an SD card reader. Who knew?
So I plugged her card into the reader and finally it was properly
mounted.</p>
<p>The website for PhotoRec says that as long as you can see the card and
the proper size of the card is detected, then there is a very good
chance that the data can be recovered. Luckily this card was reporting
the proper size. Now PhotoRec is a free application and as such it
doesn’t have a fancy user interface that just anybody can click around
and figure out. You have to run this utility from the command line.</p>
<p>The drive that is 1000GB is my hard drive, the one that is 513MB is
the memory card. So I choose the first one /dev/disk2, not the
/dev/rdisk2. And I’m given a screen with more options:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>Disk /dev/disk2 - 513 MB / 489 MiB (RO)
</span><span>
</span><span>Please select the partition table type, press Enter when done.
</span><span>[Intel ] Intel/PC partition
</span><span>[EFI GPT] EFI GPT partition map (Mac i386, some x86_64…)
</span><span>[Mac ] Apple partition map
</span><span>[None ] Non partitioned media
</span><span>[Sun ] Sun Solaris partition
</span><span>[XBox ] XBox partition
</span><span>[Return ] Return to disk selection
</span></code></pre>
<p>Intel seems like the most reasonable out of the choices so I select
that one. I’m then prompted with more choices:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>Disk /dev/disk2 - 513 MB / 489 MiB (RO)
</span><span>
</span><span>Partition Start End Size in sectors
</span><span>No partition 0 1002495 1002496 [Whole disk]
</span><span>1 P FAT16 &gt;32M 233 1002495 1002263 [NO NAME]
</span></code></pre>
<p>I’m pretty sure that memory sticks for most cameras are formatted as
FAT so I choose that. I’m then prompted with more choices:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>1 P FAT16 >32M 233 1002495 1002263 [NO NAME]
</span></code></pre>
<p>To recover lost files, PhotoRec need to know the filesystem type where
the file were stored:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>[ ext2/ext3 ] ext2/ext3/ext4 filesystem
</span><span>[ Other ] FAT/NTFS/HFS+/ReiserFS/...
</span></code></pre>
<p>Again, I choose anything that says FAT, and again I prompted with more
choices:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>1 P FAT16 >32M 233 1002495 1002263 [NO NAME]
</span></code></pre>
<p>Please choose if all space need to be analysed:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>[ Free ] Scan for files from FAT16 unallocated space only
</span><span>[ Whole ] Extract files from whole partition
</span></code></pre>
<p>You know me, I’m going with FAT. Now I’m asked where I would like to
save the recovered files. This is starting to look promising.</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>Do you want to save recovered files in /Users/socketwiz/bin ? [Y/N]
</span></code></pre>
<p>Do not choose to write the files to the same partition they were
stored on. I choose to store the files on my desktop and then select Y
and it begins to attempt to restore the files…FINALLY. A folder is
created on my desktop called recup_dir.1 and PhotoRec appears to be
recovering the files:</p>
<pre style="background-color:#fafafa;color:#383a42;"><code><span>Disk /dev/disk2 - 513 MB / 489 MiB (RO)
</span><span>Partition Start End Size in
</span><span>sectors
</span><span>1 P FAT16 &gt;32M 233 1002495 1002263
</span><span>[NO NAME]
</span><span>
</span><span>
</span><span>Pass 1 - Reading sector 384119/1002263, 151 files found
</span><span>Elapsed time 0h01m41s - Estimated time for achievement 0h02m42
</span><span>jpg: 151 recovered
</span></code></pre>
<p>After around 10-15 minutes all 412 files are properly recovered. Your
mileage may vary but I would say if you can mount your card, and the
operating system is reporting back the proper size of your card, then
you have a pretty good chance of recovering you data from it. This
software also runs on Windows and I would expect you would receive the
same choices I described above. At any rate, if you or a friend end up
with a corrupted memory card give these steps a try, I hope this
helps.</p>