<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Julio</title>
    <description>The latest articles on DEV Community by Julio (@julio_ui).</description>
    <link>https://dev.to/julio_ui</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F88722%2Fba68fa4d-3c6a-4716-8bb8-71beea4e3fce.jpg</url>
      <title>DEV Community: Julio</title>
      <link>https://dev.to/julio_ui</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/julio_ui"/>
    <language>en</language>
    <item>
      <title>Human Friendly Data Science Interviews</title>
      <dc:creator>Julio</dc:creator>
      <pubDate>Sun, 22 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/julio_ui/human-friendly-data-science-interviews-46ed</link>
      <guid>https://dev.to/julio_ui/human-friendly-data-science-interviews-46ed</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR. We focused on a holistic view of our candidates (technical and interpersonal skills) while trying to be fair with everyone’s time and life experiences. We could identify the outstanding people and those that weren’t a good fit and have had a great experience working with our hires!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After reading &lt;a href="https://www.neilwithdata.com/developer-hiring"&gt;The software industry’s greatest sin: hiring&lt;/a&gt; by Neil Sainsbury and &lt;a href="https://andrewrondeau.com/blog/2020/04/take-home-vs-whiteboard-coding-the-problem-is-bad-interviews"&gt;Take-home vs. whiteboard coding: The problem is bad interviews&lt;/a&gt; by Andrew Rondeau, several critical points about interviewing software developers stood out to me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Software developers are usually assessed based on technical aspects ignoring their personal and organizational qualities. This might produce technically correct software with good performance, but that might be far from fulfilling users’ needs.&lt;/li&gt;
&lt;li&gt;Someone can be technically excellent but lack the skills to understand and interact with your users and the rest of the team.&lt;/li&gt;
&lt;li&gt;Someone can be technically excellent but keep using technologies they find interesting but are not aligned with the company’s goals.&lt;/li&gt;
&lt;li&gt;There are tradeoffs between whiteboard and take-home questions: time invested by both parties, different development environment/conditions, visibility of the candidate’s technical and personal qualities, and the feedback loop between the examiner and the applicant.&lt;/li&gt;
&lt;li&gt;A key aspect is to plan a good interview with coding assignments that consider the company’s needs and are fair for everyone involved.&lt;/li&gt;
&lt;li&gt;Presenting existing code is briefly discussed by Andrew Rondeau as an alternative to whiteboard and take-home questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am a postdoctoral researcher at a group that explores mobile data’s role in monitoring or supporting people with different health conditions. Broadly speaking, we collect smartphone and wearable sensor data, process it, and use it to create statistical and machine learning models that provide relevant behavioral or clinical insights. This is possible thanks to our team’s multi-disciplinary nature with expertise in psychology, statistics, computer science, software engineering, and data science.&lt;/p&gt;

&lt;p&gt;Recently, we needed to hire a couple of data science interns from the local master’s program, and I was in charge of leading the technical part of the interviews. This was an excellent opportunity for me to pilot the type of technical interview that I’d like to experience based on the points I summarized above and the lab’s needs.&lt;/p&gt;

&lt;p&gt;I divided our interviews into two 30-minute stages, one to talk about one of the candidate’s past projects and the other to find out how they would approach a data science problem that represents the kind of work we do.&lt;/p&gt;

&lt;p&gt;For the first stage, we asked applicants to submit in advance a past data science project that they would like to discuss with us. I want to clarify that we accepted any industry, school, or hobby code repository and did not judge its purpose or complexity. We don’t expect that everyone will have the time to work on side-projects in their free time or disclose code from a previous employer. However, their sample project allowed us to understand some of the person’s experience with data science and software engineering practices like data cleaning, modeling, documentation, version control, variable and function naming, code comments, code formatting, and code refactoring. If any of these aspects were missing or seemed unsatisfactory, we made a note and ask about them during the interview.&lt;/p&gt;

&lt;p&gt;When the time came for the first part of our face-to-face chat (where we talked about their chosen project), we focused on their hard skills (technical expertise, domain knowledge, and problem-solving abilities), soft skills (communication, multi-disciplinary collaboration, feedback reception), and traits like proactiveness, enthusiasm, motivation, clarity of thought, independence and attention to detail. This is our take on what Neil refers to as a candidate’s “holistic” view. Crucially, having technical and non-technical members from our team present made it easier to discuss and evaluate our candidates. More specifically, we inquired people about their role in previous teams (if any), their approach to learning, and their thought process to choose the best tool for the job. We also prompted them to explain complex non-technical concepts to everyone in the interview panel and to talk more about their experience interacting with past “clients” (teachers, fellow students, or any other stakeholders for those with experience in Industry). One of the advantages of this setup was that it allowed everyone to interact in a work environment very similar to what we experience every day while planning, implementing, executing, analyzing, and publishing a health intervention or monitoring study.&lt;/p&gt;

&lt;p&gt;In the second part of the interview, we asked participants the following question: how would you implement a sleep classifier based on smartphone and Fitbit data? Even if this problem appears simple at first sight, numerous decisions and considerations can be taken into account along the way. For example, we can talk about missing data, feature engineering, data resampling, data imputation, class imbalance, type of model (population or individual), hyper-parameter tuning, model choice, baselines, cross-validation, evaluation metrics, etc. Consequently and to foster the discussion, we always dropped clues, clarifications, and follow up questions.&lt;/p&gt;

&lt;p&gt;We did not expect our interviewees to reach a comprehensive solution or write any code (it took us weeks to finish a publishable solution, and the first part of the interview already would have given us an idea of their programming skills). Instead, we wanted to know more about their thinking process. We paid particular attention to the candidate’s understanding of the problem (do they ask relevant questions?), creativity (how do they suggest tackling this problem?), experience (are they levering solutions to past problems?), technical expertise (what programming language, libraries, or methods would they like to use?), and communication skills (can they engage the whole team in the discussion?).&lt;/p&gt;

&lt;p&gt;This process fits well within our workflow and our team’s characteristics, and we hope that by sharing it, you can adapt it to your needs and provide a better experience for your candidates.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Setting up Travis CI to test R and Python scripts in MacOS and Ubuntu</title>
      <dc:creator>Julio</dc:creator>
      <pubDate>Thu, 28 May 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/julio_ui/setting-up-travis-ci-to-test-r-and-python-scripts-in-macos-and-ubuntu-1c</link>
      <guid>https://dev.to/julio_ui/setting-up-travis-ci-to-test-r-and-python-scripts-in-macos-and-ubuntu-1c</guid>
      <description>&lt;p&gt;A colleague and I configured Travis CI to run the tests of a project that relies on R and Python scripts. This project supports both macOS and Linux, so it was essential to test it in both environments. After some trial and error, we got this working with the &lt;code&gt;travis.yaml&lt;/code&gt; file below. Our deployment manages the following project requirements: MySQL, Python 3.7, miniconda with a virtual environment, R and a cached virtual environment with renv, and slack notifications.&lt;/p&gt;

&lt;p&gt;For Linux (Ubuntu 16.04) we do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install brew, linuxbrew-wrapper and linuxbrew/xorg&lt;/li&gt;
&lt;li&gt;Install R using brew&lt;/li&gt;
&lt;li&gt;Install miniconda using their provided script installer&lt;/li&gt;
&lt;li&gt;Restore our conda virtual env&lt;/li&gt;
&lt;li&gt;Cache &lt;a href="https://rstudio.github.io/renv/index.html"&gt;renv&lt;/a&gt;‘s library. We use renv to keep a reproducible R environment with 161 packages; however, renv had to build them from source in Ubuntu 16.04 and our travis build was timing out. The solution we implemented was building the renv library (and therefore the travis’ cache) in three steps, first committing a small renv.lock with 40 packages, then 100, then all 161.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For MacOS (10.14.4)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up the OS image with &lt;code&gt;osx_image: xcode11.3&lt;/code&gt; and &lt;code&gt;language: generic&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install MySQL, R and miniconda using brew (brew and Python 3.7 are already installed)&lt;/li&gt;
&lt;li&gt;Restore our conda virtual env&lt;/li&gt;
&lt;li&gt;Cache renv’s library. We faced and solved the same problem we had in Linux with renv’s building times timing out our travis build (see above). In addition, in MacOS, renv’s library path contains a space &lt;code&gt;~/Library/Application Support/renv&lt;/code&gt; which was causing issues with travis’ cache mechanism, as a quick fix we disabled renv’s global cache &lt;code&gt;R -e 'renv::settings$use.cache(FALSE)'&lt;/code&gt; for both Linux and MacOS (for consistency) and cached our project’s renv library folder &lt;code&gt;$TRAVIS_BUILD_DIR/renv/library&lt;/code&gt; as it now contains the actual packages instead of symbolic links.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mysql&lt;/span&gt;
&lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Python&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3.7&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Xenial&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Linux"&lt;/span&gt;
      &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;
      &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
      &lt;span class="na"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.7&lt;/span&gt;
      &lt;span class="na"&gt;before_install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH=/home/linuxbrew/.linuxbrew/bin:$PATH&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;source ~/.bashrc&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sudo apt-get install linuxbrew-wrapper&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;brew tap --shallow linuxbrew/xorg&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;brew install r&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;R --version&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bash miniconda.sh -b -p $HOME/miniconda&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;source "$HOME/miniconda/etc/profile.d/conda.sh"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;hash -r&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;conda config --set always_yes yes --set changeps1 no&lt;/span&gt;
      &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/home/travis/.linuxbrew&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$HOME/.local/share/renv&lt;/span&gt; &lt;span class="c1"&gt;# global renv cache in linux (not used)&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$TRAVIS_BUILD_DIR/renv/library&lt;/span&gt; &lt;span class="c1"&gt;# local renv cache&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Python&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3.7&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;macOS"&lt;/span&gt;
      &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;osx&lt;/span&gt;
      &lt;span class="na"&gt;osx_image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xcode11.3&lt;/span&gt;  &lt;span class="c1"&gt;# Python 3.7 running on macOS 10.14.4&lt;/span&gt;
      &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;generic&lt;/span&gt;
      &lt;span class="na"&gt;before_install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;brew install mysql&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;brew services start mysql&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;brew install r&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;R --version&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;brew cask install miniconda&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;eval "$(/usr/local/bin/conda shell.bash hook)"&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RENV_PATHS_ROOT="$HOME/renv/cache"&lt;/span&gt;
      &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/usr/local/lib/R&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$RENV_PATHS_ROOT&lt;/span&gt; &lt;span class="c1"&gt;# global renv cache in MacOS (not used)&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$TRAVIS_BUILD_DIR/renv/library&lt;/span&gt; &lt;span class="c1"&gt;# local renv cache&lt;/span&gt;

&lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;conda init&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;conda update -q --all --yes conda&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;conda env create -q -n test-environment python=$TRAVIS_PYTHON_VERSION --file environment.yml&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;conda activate test-environment&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;snakemake renv_install&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;R -e 'renv::settings$use.cache(FALSE)'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;snakemake renv_restore&lt;/span&gt; 

&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python -m unittest discover tests/scripts/ -v&lt;/span&gt;

&lt;span class="na"&gt;notifications&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;slack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SLACK_SECURE_KEY&lt;/span&gt;
    &lt;span class="na"&gt;on_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Repo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;`%{repository_slug}`&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*%{result}*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(&amp;lt;%{build_url}|#%{build_number}&amp;gt;)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;commit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(&amp;lt;%{compare_url}|%{commit}&amp;gt;)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;branch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;`%{branch}`."&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Execution&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;time:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*%{duration}*"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Message:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%{message}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Quickly exploring CSV files with wc and awk</title>
      <dc:creator>Julio</dc:creator>
      <pubDate>Sat, 18 Apr 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/julio_ui/quickly-exploring-csv-files-with-wc-and-awk-4jed</link>
      <guid>https://dev.to/julio_ui/quickly-exploring-csv-files-with-wc-and-awk-4jed</guid>
      <description>&lt;p&gt;This week I was extracting high-intensity activity episodes from the Fitbit data of 150 people. The first thing I wanted to know after processing all participants was how many people had at least 1 episode. I am using &lt;a href="https://rapidspitt.readthedocs.io/en/latest/"&gt;RAPIDS&lt;/a&gt; to process the data, which means that the activity episodes for each participant are stored one per line in CSV files in individual folders. As I was looking for a quick and short solution, I went for Bash instead of Python or R.&lt;/p&gt;

&lt;p&gt;Then, the problem is reduced to three steps: list all files in a subfolder with names that match a pattern, count the lines on each file, and filter those files with at least 2 lines (all files have at least the header row). For that, we can use &lt;code&gt;wc&lt;/code&gt; and &lt;code&gt;awk&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wc $(find . -name 'p*_fitbit_mvpa_episodes.csv') | awk '{if (($1 &amp;gt; 1) &amp;amp;&amp;amp; ($4 ~ /^\.\/data/)) { print }}' | wc -l

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



&lt;p&gt;The first part &lt;code&gt;wc $(find . -name 'p*_fitbit_mvpa_episodes.csv')&lt;/code&gt; executes the &lt;code&gt;wc&lt;/code&gt; command on the output of the &lt;code&gt;find&lt;/code&gt; command, which retrieves all files in the current directory and any subdirectories with a name that matches the regular expression between quotes. The &lt;a href="https://www.mkssoftware.com/docs/man1/wc.1.asp"&gt;default output&lt;/a&gt; of the &lt;code&gt;wc&lt;/code&gt; command has four columns for each file: line count, word count, byte count, and its path. These are piped into &lt;code&gt;awk '{if (($1 &amp;gt; 1) &amp;amp;&amp;amp; ($4 ~ /^\.\/data/)) { print }}'&lt;/code&gt; which filters and prints those lines where the value of the first column &lt;code&gt;$1&lt;/code&gt; (line count) is bigger than one and the fourth column &lt;code&gt;$4&lt;/code&gt; (file path) starts with &lt;code&gt;./data&lt;/code&gt;. The first part of the filter gets all the files with at least one activity episode (header + episode line), and the second part excludes the total count that &lt;code&gt;wc&lt;/code&gt; appends. Finally, to obtain the &lt;em&gt;number&lt;/em&gt; of files with at least one activity episode, I piped the previous list to &lt;code&gt;wc&lt;/code&gt; with the &lt;code&gt;-l&lt;/code&gt; flag that counts the number of lines (files) that &lt;code&gt;awk&lt;/code&gt; printed. It turns out that out of 150 participants, only 20 have high-intensity activity episodes (this lead us to discover a problem with the data I was working with that is a matter for another post).&lt;/p&gt;

&lt;p&gt;As an extra bit of information useful for our collaborators, I wanted to know the average number of episodes across all participants. For this I followed a similar process but instead of the second &lt;code&gt;wc -l&lt;/code&gt;, I piped the output to &lt;code&gt;awk&lt;/code&gt; where it is possible to keep a counter and sum of the values of each line, obtaining the average for the first column (line count) as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wc $(find . -name 'p*_fitbit_mvpa_episodes.csv') | awk '{if (($1 &amp;gt; 1) &amp;amp;&amp;amp; ($4 ~ /^\.\/data/)) { print $1}}' | awk '{ total += $1; count++ } END { print total/count }'

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



&lt;p&gt;We have an average of 17.3 episodes across 20 people.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Polishing my blog's appearance and performance</title>
      <dc:creator>Julio</dc:creator>
      <pubDate>Wed, 08 Apr 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/julio_ui/polishing-my-blog-s-appearance-and-performance-2n69</link>
      <guid>https://dev.to/julio_ui/polishing-my-blog-s-appearance-and-performance-2n69</guid>
      <description>&lt;p&gt;When I was setting up this blog with Hugo and Netlify, I found five themes that I liked for their simplicity and aesthetics. I chose &lt;a href="https://cupper-hugo-theme.netlify.com/"&gt;Cupper&lt;/a&gt; because it focuses on content, is accessible, posts are grouped by tags and not categories, it supports multiple shortcodes (like notes, warnings, code, among others), it includes minimal javascript, and it provides a dark theme.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cupper-hugo-theme.netlify.com/"&gt;https://cupper-hugo-theme.netlify.com/&lt;/a&gt; (the one I went for)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://themes.gohugo.io//theme/harbor/"&gt;https://themes.gohugo.io//theme/harbor/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://themes.gohugo.io/theme/kiss/"&gt;https://themes.gohugo.io/theme/kiss/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://themes.gohugo.io/theme/hugo-ink/"&gt;https://themes.gohugo.io/theme/hugo-ink/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.bespinian.io/"&gt;https://blog.bespinian.io/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As good as the theme is, I tweaked some things, and this post is a compilation of them for my future reference and for other people that might find them useful. A neat tip is that you don’t need to modify the original theme to make changes, instead you can add it as a submodule in the &lt;code&gt;themes&lt;/code&gt; folder, create the files with the modifications you need in the folders at the top level of your blog and &lt;a href="https://gohugo.io/templates/lookup-order/"&gt;Hugo will use them first&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The TL;DR list of changes is below, but you can keep reading for more details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support for more highlighted languages in code snippets by Prism JS&lt;/li&gt;
&lt;li&gt;CSS changes for text readability&lt;/li&gt;
&lt;li&gt;Make the blog’s post list its homepage&lt;/li&gt;
&lt;li&gt;Support for static comments using Staticman&lt;/li&gt;
&lt;li&gt;Support for web analytics using GoatCounter&lt;/li&gt;
&lt;li&gt;RSS feed with full posts instead of a short description&lt;/li&gt;
&lt;li&gt;Image, CSS, and JavaScript optimization in Netlify&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, I updated the code highlighting languages supported by Prism JS, the highlighting library used by Cupper. This is the &lt;a href="https://prismjs.com/download.html#themes=prism&amp;amp;languages=markup+css+clike+javascript+bash+git+java+json+latex+markdown+python+r+rest+sql+toml+yaml&amp;amp;plugins=toolbar+copy-to-clipboard"&gt;URL&lt;/a&gt; of my configuration that includes Markup, CSS, JS, Bash, Git, Java, JSON, Latex, Markdown, Python, R, SQL, YAML, and TOML. Don’t forget you need to update Prism’s JS and CSS.&lt;/p&gt;

&lt;p&gt;I swapped the original logo for a text title, and made some small CSS adjustments to make the text more readable: decreased the contrast of the dark theme by 15%, changed the general line height, letter spacing of titles, and top margin of paragraphs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* The dark theme settings are a separate style tag in the header*/&lt;/span&gt;
&lt;span class="nc"&gt;.intro-and-nav&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.main-and-footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;invert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;85%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;src&lt;/span&gt;&lt;span class="o"&gt;*=&lt;/span&gt;&lt;span class="s1"&gt;".svg"&lt;/span&gt;&lt;span class="o"&gt;]),&lt;/span&gt; &lt;span class="nc"&gt;.colors&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;iframe&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.demo-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;invert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;85%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* This is the site's main CSS */&lt;/span&gt;
&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1em&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;0.33vw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Helvetica&lt;/span&gt; &lt;span class="n"&gt;Neue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#111&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fefefe&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;h4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Miriam&lt;/span&gt; &lt;span class="n"&gt;Libre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.125&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;letter-spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.logo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I substituted the original homepage for the blog’s post list as I want them to be the focus of visitors, and made the about section an external link to my personal website. For the first change, I had to move the theme’s post template from &lt;code&gt;post/single.html&lt;/code&gt; to &lt;code&gt;_default/single.html&lt;/code&gt;, so all .md files in the &lt;code&gt;content&lt;/code&gt; folder are rendered with it, and moved &lt;code&gt;_default/list.html&lt;/code&gt; to &lt;code&gt;layouts/index.html&lt;/code&gt;, so the original list of posts is now at the homepage. For the second change, I added a conditional to the navigation links rendering to make it open in a new tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight liquid"&gt;&lt;code&gt;&amp;lt;a href="&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;" &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nv"&gt;active&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;aria-current="page"&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"About"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;target="_blank"&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I added support for Staticman comments instead of Disqus to have a git-backed, lightweight, ethical comment provider. You can read more about the whole process &lt;a href="https://dev.to/julio_ui/configuring-staticman-comments-with-hugo-317g"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I added support for &lt;a href="https://www.goatcounter.com/"&gt;GoatCounter&lt;/a&gt; for web analytics instead of Google Analytics for &lt;a href="https://plausible.io/blog/remove-google-analytics"&gt;these reasons&lt;/a&gt;; &lt;a href="https://mentalpivot.com/ethical-web-analytics-alternatives-google/"&gt;this post&lt;/a&gt; by David Papandrew made the search for an alternative easier. GoatCounter gives me the level of detail just right to know what are the most visited posts, referrals, and visitors’ platforms, it is GDPR friendly as it does not rely on cookies, just around 1.5Kb, open-source, and free for under 100k pageviews a month. You can support them in &lt;a href="https://github.com/sponsors/arp242"&gt;GitHub Sponsors&lt;/a&gt; and &lt;a href="https://www.patreon.com/arp242"&gt;Patreon&lt;/a&gt;. Installing GC was really easy, all I had to do was to create an account there and add the following script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;goatcounter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://MY_SITE.goatcounter.com/count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;//gc.zgo.at/count.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I updated the RSS template to include full posts instead of just descriptions to make them work better with readers like Feedly, which I use a lot. I added &lt;a href="https://github.com/JulioV/blog/commit/81a11583027f5d4b83a00ea2808ab88e87adb731"&gt;this file&lt;/a&gt; to &lt;code&gt;layouts/rss.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, I activated the asset optimization in Netlify to compress images and minify and bundle CSS and JS files using my &lt;code&gt;netlify.toml&lt;/code&gt; file. For the latter to work, all their links need to be relative, so I modified the following lines in my templates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight liquid"&gt;&lt;code&gt;&amp;lt;link rel="stylesheet" href="&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"css/prism.css"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;relURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;" media="none" onload="this.media='all';"&amp;gt;
&amp;lt;link rel="stylesheet" type="text/css" href="&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nv"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;RelPermalink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;"&amp;gt;
&amp;lt;script src="&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"js/prism.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;relURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src="&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"js/dom-scripts.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;relURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;Append&lt;/span&gt; &lt;span class="err"&gt;this&lt;/span&gt; &lt;span class="err"&gt;to&lt;/span&gt; &lt;span class="err"&gt;your&lt;/span&gt; &lt;span class="err"&gt;netlify.toml&lt;/span&gt;
&lt;span class="nn"&gt;[build.processing]&lt;/span&gt;
&lt;span class="py"&gt;skip_processing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nn"&gt;[build.processing.css]&lt;/span&gt;
&lt;span class="py"&gt;bundle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;minify&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;[build.processing.js]&lt;/span&gt;
&lt;span class="py"&gt;bundle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;minify&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;[build.processing.images]&lt;/span&gt;
&lt;span class="py"&gt;compress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;All these changes gave the blog a &lt;a href="https://gtmetrix.com/reports/jvblog.net/XsWRPzvq"&gt;PageSpeed&lt;/a&gt; score of 100%, Ylow Score of 94% with a Fully Loaded Time of 1.7s and a Total Page Size of 109Kb.&lt;/p&gt;

</description>
      <category>web</category>
      <category>hugo</category>
      <category>netlify</category>
    </item>
    <item>
      <title>Configuring Staticman Comments with Hugo</title>
      <dc:creator>Julio</dc:creator>
      <pubDate>Sun, 05 Apr 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/julio_ui/configuring-staticman-comments-with-hugo-317g</link>
      <guid>https://dev.to/julio_ui/configuring-staticman-comments-with-hugo-317g</guid>
      <description>&lt;p&gt;I wanted to add comments to my blog, and Disqus seemed like a good option as the &lt;a href="https://github.com/zwbetz-gh/cupper-hugo-theme"&gt;theme&lt;/a&gt; I’m using supports it out of the box. However, as things stand, I am happy with a solution that doesn’t require storing people’s data in third party databases and doesn’t add &lt;a href="https://replyable.com/2017/03/disqus-is-your-data-worth-trading-for-convenience/"&gt;ads and unnecessary tracking scripts&lt;/a&gt; that could make the reading experience slower or cluttered.&lt;/p&gt;

&lt;p&gt;After searching for open-source/ethical comment suppliers, I found out about &lt;a href="https://staticman.net/"&gt;Staticman&lt;/a&gt;, and I am giving it a try since it integrates with Hugo blogs, it uses a git repository to store and triage comments, it’s been around since 2015, and has good documentation. I just had to work around some constraints. In essence, you need to deploy your own instance of Staticman to Heroku as the official Staticman API hits its quota frequently (Heroku’s free tier is enough tho’), I wanted to keep this blog’s comments on a separate repository, and I am using Staticman API V2 since everything is hosted on GitHub (V3 supports other providers like Gitlab).&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://petersen.pro/blog/staticman-comments-in-separate-repository/"&gt;post&lt;/a&gt; by Arne Petersen was of great help to put everything together. After some tweaks, my deployment works like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I’m only collecting people’s names and comments&lt;/li&gt;
&lt;li&gt;I’m using reCaptcha 2 to avoid spam&lt;/li&gt;
&lt;li&gt;I only load reCaptcha’s JS script when you click the “Show Comments” button&lt;/li&gt;
&lt;li&gt;I accept/reject comments using pull requests.&lt;/li&gt;
&lt;li&gt;After accepting a comment, my blog is re-build and published automatically in Netlify using webhooks&lt;/li&gt;
&lt;li&gt;After someone submits a comment, they get redirected to the original blog post with a message explaining their comment will go live after approval. No AJAX or popups are required, and you can try it leaving a comment!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the instructions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a repository for your comments in your &lt;code&gt;main&lt;/code&gt; GitHub account; we will call it &lt;code&gt;blog_comments&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a secondary GitHub account; we will call it &lt;code&gt;account2&lt;/code&gt;. This is for security reasons as Arne pointed out, you are creating a Personal Access Token and keeping it in your Heroku instance which could give anyone who gets hold of it full access to your GitHub account.&lt;/li&gt;
&lt;li&gt;Create a Personal Access Token in &lt;code&gt;account2&lt;/code&gt; at &lt;a href="https://github.com/settings/tokens"&gt;https://github.com/settings/tokens&lt;/a&gt;. Save it because you can only see it once, and you will need it in a bit.&lt;/li&gt;
&lt;li&gt;Invite &lt;code&gt;account2&lt;/code&gt; as a collaborator to &lt;code&gt;blog_comments&lt;/code&gt; going to &lt;code&gt;https://github.com/YOUR_MAIN_GITHUB_ACCOUNT/blog_comments/settings/access&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deploy Staticman to Heroku using the purple button in the project’s &lt;a href="https://github.com/eduardoboucas/staticman"&gt;README&lt;/a&gt; (make sure it’s in the master branch)&lt;/li&gt;
&lt;li&gt;Create a private key for Staticman (you can do this in your Heroku instance going to “More” -&amp;gt; “Run console”): &lt;code&gt;openssl genrsa -out key.pem&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following three Config vars to your Heroku instance in &lt;code&gt;https://dashboard.heroku.com/apps/YOUR_INSTANCE_NAME/settings&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;NODE_ENV         production&lt;/span&gt;
&lt;span class="s"&gt;GITHUB_TOKEN     YOUR PERSONAL ACCESS TOKEN&lt;/span&gt;
&lt;span class="s"&gt;RSA_PRIVATE_KEY  CONTENT OF key.pem&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you want to use reCaptcha to avoid spam, do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register your blog &lt;a href="https://www.google.com/recaptcha/admin"&gt;here&lt;/a&gt;. You can add &lt;code&gt;localhost&lt;/code&gt; to the domain list to be able to test everything in your local machine. Save your &lt;code&gt;siteKey&lt;/code&gt; and &lt;code&gt;secret&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Encrypt your reCaptcha &lt;code&gt;secret&lt;/code&gt; obtained before by querying your Heroku instance in this URL: &lt;code&gt;https://YOUR_HEROKU_APP_NAME.herokuapp.com/v2/encrypt/YOUR_UNENCRYPTED_RECAPTCHA_SECRET&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add &lt;a href="https://github.com/JulioV/blog/blob/205b5b4f0408ca2748b093ad3a7c98c11ace37d2/layouts/partials/staticman.html#L1"&gt;this partial&lt;/a&gt; to your blog and call &lt;code&gt;{{ partial "staticman.html" . }}&lt;/code&gt; where you want to load your comments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the CSS styles from this &lt;a href="https://github.com/JulioV/blog/blob/9e23be1e2cd987af170fba3a60876254bce465df/assets/css/template-styles.css#L1045"&gt;line onwards&lt;/a&gt; to your blog.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add this &lt;a href="https://github.com/JulioV/blog/blob/9e23be1e2cd987af170fba3a60876254bce465df/layouts/partials/staticman-js-common.js"&gt;JS script&lt;/a&gt; to your &lt;strong&gt;partials&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Add the following lines to the &lt;code&gt;params&lt;/code&gt; list in your Hugo blog’s &lt;code&gt;config.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;staticman&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://&amp;lt;YOUR_HEROKU_APP_NAME&amp;gt;.herokuapp.com/v2/entry/YOUR_MAIN_GITHUB_ACCOUNT/blog_comments/master/comments&lt;/span&gt;
    &lt;span class="na"&gt;recaptcha&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;sitekey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;RECAPTCHA&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;KEY"&lt;/span&gt;
        &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENCRYPTED&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;RECAPTCHA&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;SECRET"&lt;/span&gt;

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




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Add your &lt;a href="https://staticman.net/docs/configuration"&gt;Staticman configuration file&lt;/a&gt;, &lt;code&gt;staticman.yaml&lt;/code&gt;, to the root of &lt;code&gt;blog_comments&lt;/code&gt;. You can use the one below or &lt;a href="https://raw.githubusercontent.com/eduardoboucas/staticman/master/staticman.sample.yml"&gt;this other one&lt;/a&gt; as a reference if you want to collect more data like emails or personal websites.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;comments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;allowedFields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;comment"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;master"&lt;/span&gt;
&lt;span class="na"&gt;commitMessage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;New&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;comment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{options.slug}"&lt;/span&gt;
&lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;comment-{@timestamp}"&lt;/span&gt;
&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yaml"&lt;/span&gt;
&lt;span class="na"&gt;generatedFields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;date&lt;/span&gt;
&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iso8601"&lt;/span&gt;
&lt;span class="na"&gt;moderation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;SITES&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;NAME"&lt;/span&gt;
&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{options.slug}"&lt;/span&gt;
&lt;span class="na"&gt;requiredFields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;comment"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;transforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;md5&lt;/span&gt;
&lt;span class="s"&gt;// Delete this if you do not want to use reCaptcha&lt;/span&gt;
&lt;span class="na"&gt;reCaptcha&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;siteKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;RECAPTCHA&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;KEY"&lt;/span&gt;
&lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENCRYPTED&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;RECAPTCHA&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;SECRET"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add your &lt;code&gt;blog_comments&lt;/code&gt; repo as a submodule to your main repo in the &lt;code&gt;data/comments&lt;/code&gt; folder: &lt;code&gt;git submodule add &lt;a href="https://github.com/YOUR_MAIN_GITHUB_ACCOUNT/blog-comments.git"&gt;https://github.com/YOUR_MAIN_GITHUB_ACCOUNT/blog-comments.git&lt;/a&gt; data/comments&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;I use Netlify to publish my blog, so I had to modify my Netlify build command to pull the latest version of &lt;code&gt;blog_comments&lt;/code&gt; to render any new comments. You can do this using Netlify’s website or by adding a &lt;code&gt;netlify.toml&lt;/code&gt; file to the root of your blog repo with the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[build]&lt;/span&gt;
&lt;span class="py"&gt;publish&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"public"&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"git submodule update --remote data/comments &amp;amp;&amp;amp; hugo --gc --minify"&lt;/span&gt;

&lt;span class="nn"&gt;[context.production.environment]&lt;/span&gt;
&lt;span class="py"&gt;HUGO_VERSION&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v0.68.3"&lt;/span&gt;
&lt;span class="py"&gt;HUGO_ENV&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"production"&lt;/span&gt;
&lt;span class="py"&gt;HUGO_ENABLEGITINFO&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every time someone comments on a blog post, Staticman creates a new branch and a Pull Request (PR) in &lt;code&gt;blog_comments&lt;/code&gt; which you can accept or reject to publish it or not. Branches will start to pile up, so, for those PRs you reject, you have to delete their branches using GitHub’s UI manually. Still, for those PRs you accept, GitHub can automatically delete them by activating &lt;a href="https://help.github.com/en/github/administering-a-repository/managing-the-automatic-deletion-of-branches"&gt;this feature&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At this point, you can submit your first comment from your computer or, commit everything to GitHub and do it online.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;em&gt;Optional&lt;/em&gt;. If you want to avoid triggering a new Netlify build manually every time you accept a comment, you can automatize it by using &lt;a href="https://www.integromat.com"&gt;Integromat’s webhooks&lt;/a&gt;. You could also use Zappier, but you have to switch to their paid tier.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Got to Netlify’s &lt;code&gt;Build hooks&lt;/code&gt; section in &lt;code&gt;https://app.netlify.com/sites/YOUR_NETLIFY_DOMAIN/settings/deploys#build-hooks&lt;/code&gt; and click on &lt;code&gt;Add build hook&lt;/code&gt;. Save the generated URL&lt;/li&gt;
&lt;li&gt;Create a new Scenario in Integromat&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;Custom Webhook trigger&lt;/code&gt;. Inside, add a new &lt;code&gt;Webhook&lt;/code&gt; and copy its URL. Click on &lt;code&gt;Determine data structure&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;https://github.com/YOUR_MAIN_GITHUB_ACCOUNT/blog_comments/settings/hooks&lt;/code&gt;. Click on &lt;code&gt;Add webhook&lt;/code&gt;, in &lt;code&gt;Payload URL&lt;/code&gt; add the URL of the Integromat &lt;code&gt;Custom Webhook trigger&lt;/code&gt;, in &lt;code&gt;Content type&lt;/code&gt; select &lt;code&gt;application/json&lt;/code&gt;, and under &lt;code&gt;Let me select individual events&lt;/code&gt; check &lt;code&gt;Pull requests&lt;/code&gt;. Click on &lt;code&gt;Add Webhook&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Submit a comment in your blog, so Staticman creates a new Pull Request in &lt;code&gt;blog_comments&lt;/code&gt;, and Integromat infers its content. You should see a confirmation message in the &lt;code&gt;Custom Webhook trigger&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;HTTP action&lt;/code&gt; in Integromat. Connect this to the &lt;code&gt;Custom Webhook trigger&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the connection between the &lt;code&gt;HTTP action&lt;/code&gt; and the &lt;code&gt;Custom Webhook trigger&lt;/code&gt;, add two conditions joined by an &lt;code&gt;AND&lt;/code&gt; operator: &lt;code&gt;action&lt;/code&gt; = &lt;code&gt;closed&lt;/code&gt; and &lt;code&gt;pul_request: merged&lt;/code&gt; = &lt;code&gt;true&lt;/code&gt;. They should be autocompleted if Integromat was able to infer the PR’s content&lt;/li&gt;
&lt;li&gt;Click in the &lt;code&gt;HTTP action&lt;/code&gt;, add the Netlify hook’s URL you got earlier to the action’s &lt;code&gt;URL&lt;/code&gt; field, and change its &lt;code&gt;Method&lt;/code&gt; to &lt;code&gt;POST&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Turn the scenario ON using the switch at the bottom left and set &lt;code&gt;Schedule setting&lt;/code&gt; to &lt;code&gt;Immediatly&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;From now on, the scenario should trigger a Netlify build every time you accept a Staticman’s Pull Request&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Feel free to leave a comment if you have issues or questions!&lt;/p&gt;

</description>
      <category>web</category>
      <category>hugo</category>
      <category>comments</category>
      <category>disqus</category>
    </item>
    <item>
      <title>Organizing ideas with the Zettelkasten method</title>
      <dc:creator>Julio</dc:creator>
      <pubDate>Fri, 03 Apr 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/julio_ui/organizing-ideas-with-the-zettelkasten-method-4m83</link>
      <guid>https://dev.to/julio_ui/organizing-ideas-with-the-zettelkasten-method-4m83</guid>
      <description>&lt;p&gt;I came across the Zettelkasten (ZK) method as a flexible way of organizing knowledge. I have read different descriptions, and most of them describe it as a second brain, a single text-based repository where you can dump all your ideas and link them to not only store but also generate knowledge. This is what resonated with me the most, as I know that I am the most creative once I know any topic(s) in-depth and can make connections between its different components.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://zettelkasten.de/posts/overview/#principles"&gt;this website&lt;/a&gt;, keeping a ZK repository has multiple benefits like improving your thinking, writing, memory, and learning. I do think that keeping one will help me resurface ideas that I have after reading papers or technical content and retain concepts for longer as you are supposed to add notes in your own words instead of copy and pasting. That said, I need to check if the recommendation of writing long pieces in a ZK works for me. The idea is that you outline your text in a note and then add subsections in other notes linked to the original. I usually follow this iterative approach to writing the difference is that I like to have a quick overview of what I have written to expand and reorganize content, so I have to see if I can get used to not having this.&lt;/p&gt;

&lt;p&gt;For the actual implementation of my ZK repo, I chose to type all my notes in markdown files stored in a single directory backed up in Github and &lt;a href="https://www.sublimetext.com"&gt;Sublime&lt;/a&gt; with the &lt;a href="(https://github.com/renerocksai/sublime_zk#zettelkasten-mode)"&gt;ZK plugin&lt;/a&gt; in Mac OS (it should be cross-platform).&lt;/p&gt;

&lt;h2&gt;
  
  
  Sublime as editor
&lt;/h2&gt;

&lt;p&gt;I set up my ZK repository in Mac OS using Sublime and a few extra plugins as suggested &lt;a href="https://github.com/renerocksai/sublime_zk#zettelkasten-mode"&gt;here&lt;/a&gt;. Most of the steps below are taken from that project’s README, but I replicate some of them here for future reference (I find the official docs a bit overwhelming).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the &lt;a href="(https://github.com/renerocksai/sublime_zk#zettelkasten-mode)"&gt;ZK plugin&lt;/a&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;In Sublime’s command palette (&lt;code&gt;cmd + P&lt;/code&gt;) run &lt;em&gt;Install Package Control&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;In Sublime’s command palette run &lt;em&gt;Package Control: Add Repository&lt;/em&gt; and paste this URL when prompted &lt;code&gt;https://github.com/renerocksai/sublime_zk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In Sublime’s command palette run &lt;em&gt;Package Control: Install Package&lt;/em&gt; and search for &lt;code&gt;sublime_zk&lt;/code&gt; when prompted&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Install the Silver Searcher plugin using: &lt;code&gt;brew install the_silver_searcher&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install Pandoc using: &lt;code&gt;brew install pandoc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Re-start Sublime&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Initializing my ZK folder and creating my first note
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In Sublime’s command palette run &lt;em&gt;ZK: New Zettel Note&lt;/em&gt; and type a name for your new note&lt;/li&gt;
&lt;li&gt;When prompted choose or create a new folder as your ZK repository (I added mine to git source control)&lt;/li&gt;
&lt;li&gt;In Sublime’s command palette run &lt;em&gt;ZK: Enter in Zettelkasten Mode&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Now you can create a new note by typing &lt;code&gt;Shift + Enter&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Configuring my ZK folder
&lt;/h3&gt;

&lt;p&gt;I am using the default file extension (&lt;code&gt;.md&lt;/code&gt;), link notation (&lt;code&gt;[[link]]&lt;/code&gt;) and ID precision in minutes (&lt;code&gt;YYYYMMDDHHMM&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;I configured ZK to insert the title of a note next to its ID when they are linked from other notes. In addition, I changed the color scheme, set the bib citation format to pandoc’s, and modified the template for new notes to insert the note ID, title, date, and tags at the beginning of the file. To do all this, go to Sublime’s &lt;em&gt;Preferences&lt;/em&gt; &amp;gt; &lt;em&gt;Package Settings&lt;/em&gt; &amp;gt; &lt;em&gt;Sublime ZK&lt;/em&gt; &amp;gt; &lt;em&gt;Settings User&lt;/em&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Insert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;note&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;titles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;links&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;linking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;note&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;frome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;another&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"insert_links_with_titles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;notes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"new_note_template"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"---&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;note-id: {id}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;title: {title}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;date: {timestamp: %Y-%m-%d}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;tags: &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;---&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"color_scheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Packages/sublime_zk/Monokai Extended-zk.tmTheme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"citations-mmd-style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When you create a new note with the config above, its header will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;note-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;202003281428&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My second note&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2020-03-28&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

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



&lt;h3&gt;
  
  
  Taking notes in my ZK
&lt;/h3&gt;

&lt;p&gt;I create notes with the following &lt;a href="https://zettelkasten.de/posts/overview/#principles"&gt;principles&lt;/a&gt; in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each note is atomic and self-contained. This means that a note is related to a single idea, and I don’t need anything else to remember what a note means.&lt;/li&gt;
&lt;li&gt;A link is a stronger connection than a tag. In other words, a single idea is developed throughout different notes connected by links, and multiple ideas related to the same broad topic are grouped with tags.&lt;/li&gt;
&lt;li&gt;If a new note is related to an existing note, I link the parent note in the child note (&lt;code&gt;[[parent ID]]&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;I try to use specific tags, and before adding a new one, I list all existent tags to make sure I am not duplicating any. Using the &lt;code&gt;# + ?&lt;/code&gt; shortcut helps me avoid typos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, I found the following shortcuts the most useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new note &lt;code&gt;shift + enter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open the note pointed by a link &lt;code&gt;opt + double-click on link&lt;/code&gt; or &lt;code&gt;cursor on link + ctrl + enter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Insert a link to a note &lt;code&gt;[ + [&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Find all notes referencing another note &lt;code&gt;cursor on link + opt + enter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;View all tags &lt;code&gt;# + !&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;View all notes &lt;code&gt;[ + !&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Autocomplete tag &lt;code&gt;# + ?&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Find all notes tagged by a tag &lt;code&gt;cursor on tag + ctrl + enter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Expand note link inline &lt;code&gt;ctrl + .&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Expand tag inline (all referencing notes) &lt;code&gt;ctrl + .&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Expand citekey inline (all referencing notes) &lt;code&gt;ctrl + .&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Insert pandoc citation &lt;code&gt;[ + @&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will update my experience using ZK in a few months.&lt;/p&gt;

</description>
      <category>research</category>
      <category>productivity</category>
    </item>
    <item>
      <title>PaperStream: collecting data from multiple-answer questions documents</title>
      <dc:creator>Julio</dc:creator>
      <pubDate>Mon, 27 Aug 2018 14:43:22 +0000</pubDate>
      <link>https://dev.to/julio_ui/paperstream-collecting-data-from-multiple-answer-question-documents-50c5</link>
      <guid>https://dev.to/julio_ui/paperstream-collecting-data-from-multiple-answer-question-documents-50c5</guid>
      <description>&lt;p&gt;Previously published at the &lt;a href="https://www.software.ac.uk/blog/2018-08-02-paperstream-software-collects-data-multiple-answer-questions-documents"&gt;Software Sustainability Institute's blog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F73rKUUc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/5i1jonpic5cubbtt3q6k.png" class="article-body-image-wrapper"&gt;&lt;img alt="image1_0.png" src="https://res.cloudinary.com/practicaldev/image/fetch/s--F73rKUUc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/5i1jonpic5cubbtt3q6k.png"&gt;&lt;/a&gt; &lt;/strong&gt;

&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As part of my PhD where we are researching if we can use smartphone data to monitor the progression of Parkinson’s Disease, we found out that we had to go &lt;a href="https://doi.org/10.1145/3173574.3173648"&gt;“Back to Analogue”&lt;/a&gt; as a paper diary was the best tool for patients to self-report their symptoms. This was excellent for the study, but it gave us another thing to worry about, I would have to manually transcribe participants’ answers from paper into electronic files. We were aiming for ten participants that needed to complete a diary with 365 pages over a year; if it had taken 45 seconds (being very optimistic) to transcribe each page, encoding all ten diaries would have taken ~114 hours, or ~19 days of work!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Being a computer scientist and wanting to save a good 114 hours for when I have to write my dissertation, I searched the Internet for a tool that would have allowed me to create and encode paper diaries automatically (maybe some sort of software to mark multiple-answer exams?). To my dismay, the few options available were software projects no longer supported, not well documented, not free or open source, and not very user friendly. I decided then to take this as an opportunity to contribute back to the community because many of the tools we use for data analysis in our lab are freely available thanks to the work of others. This is how &lt;a href="https://paperstream.netlify.com/"&gt;PaperStream&lt;/a&gt; was born.&lt;/p&gt;

&lt;p&gt;PaperStream is a software that researchers and academics can use to create paper diaries, surveys, quizzes or any other document with multiple-answer questions that people can respond using pen and paper and that can then be encoded automatically into a CSV file. PaperStream is free, open source, available for Windows, Mac OS, and Linux, and fully documented as it was designed to be used by anyone without a technical background.&lt;/p&gt;

&lt;p&gt;For the sake of this post, I will write about PaperStream using diaries and surveys as two examples to showcase its features. If you are working with quizzes or other types of documents, what I describe here shouldn't be too different from what you'd need to do. So, in our case, a diary is a questionnaire that one or more people must answer every day for several days, weeks, or months. Similarly, a survey is a questionnaire that one or more people have to answer only once.&lt;/p&gt;

&lt;p&gt;For both a diary and a survey, you only need a one-page PDF document that will work as a template for every page. For diaries, PaperStream will label each page with a unique date, like the next figure, and for surveys, PaperStream will enumerate them with a unique ID. Once PaperStream has processed your templates, you will get a zip file with your diaries or surveys in both A4 and A5 size ready to print and bind.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5yGEWmNl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.software.ac.uk/sites/default/files/image2_0.png" class="article-body-image-wrapper"&gt;&lt;img alt="image2_0.png" src="https://res.cloudinary.com/practicaldev/image/fetch/s--5yGEWmNl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.software.ac.uk/sites/default/files/image2_0.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After your participants have answered these printouts using a pen, you need to scan them as a multi-page TIF image, or as single PNG images compressed on a ZIP file. Once this is ready, you need to tell PaperStream where and what answers to look for through a marking rubric. A marking rubric is nothing more than a group of circles that indicate what areas of a page participants can mark with a pen and what those mark/answers mean, for example, the hour of the day or a point in a likert scale. Since you used a single template to create your diaries or surveys, you only need to design a marking rubric once, and that’s it! When the rubric is ready, PaperStream will give you a zip file with a CSV file containing all the answers of each diary or survey that you wanted to process. What is also useful, is that PaperStream can detect duplicates, missing data, and is very forgiving, as it will detect an answer when at least 15% of the answer area is filled in and has no problems when the pen goes outside it. This means that your participants don’t have to worry about how to respond the questions, it is as easy as using pen and paper.&lt;/p&gt;

&lt;p&gt;From a technical perspective, Python was the best language to develop PaperStream on. It has multiplatform support, it can process PDF files thanks to the &lt;a href="https://pythonhosted.org/PyPDF2/"&gt;pypdf2&lt;/a&gt; library and images via &lt;a href="https://opencv.org/"&gt;OpenCV&lt;/a&gt;. The first prototype of PaperStream was a script that converted a PDF template into a PDF booklet based in the &lt;a href="https://bitbucket.org/spookylukey/booklet-maker/"&gt;booklet-maker&lt;/a&gt; project of &lt;a href="https://bitbucket.org/spookylukey/"&gt;Luke Plant&lt;/a&gt;. The second prototype, which needed to encode the answers from paper to an electronic file, was slightly more complicated. I wanted to maintain Python’s multi-platform capabilities while at the same time giving users a graphical interface that would not take too much time to develop.  There are many options to create a GUI in Python. First, I tried &lt;a href="https://docs.python.org/3/library/tk.html"&gt;Tkinter&lt;/a&gt; but canvas support and geometric shapes manipulation (like drag and drop) was not straight forward. For this reason, I decided to think of PaperStream as a desktop web app, meaning that the GUI would be HTML/CSS/Javascript based, taking advantage of HTML, SVG and the rich Javascript ecosystem, while relying on a local web server to route all calls from the web GUI to the processing scripts. &lt;a href="https://falconframework.org/"&gt;Falcon&lt;/a&gt; was my choice for the web server due to its light weight, extensive documentation, and simple implementation, &lt;a href="http://fabricjs.com/"&gt;Fabric.js&lt;/a&gt; for the geometric manipulation, plus &lt;a href="http://caolan.github.io/async/"&gt;Async.JS&lt;/a&gt; for asynchronous calls and &lt;a href="https://ned.im/noty/"&gt;Noty.js&lt;/a&gt; for notifications. Then, for the actual encoding logic, I adapted the &lt;a href="https://github.com/rbaron/omr"&gt;work done&lt;/a&gt; by &lt;a href="https://github.com/rbaron/"&gt;Raphael Baron&lt;/a&gt; that used OpenCV to extract parts of a page framed by markers, complementing it with the answer extraction functionality that works by comparing black pixels between two paper sheets. All this open source software made PaperStream development faster and easier.&lt;/p&gt;

&lt;p&gt;Finally, the last sprint for the first version of PaperStream was its testing, distribution and documentation. I implemented a few unit tests using Python’s unit test library for the core functionality of the scripts that create and encode documents. Then, I considered &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; and other similar options to make PaperStream available but in the end I went with &lt;a href="https://www.pyinstaller.org/"&gt;PyInstaller&lt;/a&gt; which allows developers to distribute a Python project (including firing up a Falcon server) as a single executable or as a single zip file that works on all major OS. I also deployed PaperStream in a pip repository, so it could be installed with a single line by developers and other technical users. Finally, for the documentation I decided to give &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; a try for the first time; writing it in Markdown was simple, and automatically publishing the static website to &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; with every &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt; commit was super convenient.&lt;/p&gt;

&lt;p&gt;I learnt a lot during the development of this project and I’d love if the community finds PaperStream useful and takes its development forward. Future cool functionality could include detecting different pen colours, shape marks, and even handwritten text. In the meantime, you can get PaperStream and its source code for free in &lt;a href="https://github.com/JulioV/paperstream"&gt;GitHub&lt;/a&gt; and how-to guides to create and encode documents over &lt;a href="https://paperstream.netlify.com/"&gt;Netlify&lt;/a&gt;. Oh, and in case you were wondering, with PaperStream I encoded all ~3650 pages in about 5 minutes; a whopping 1300% faster.&lt;/p&gt;

</description>
      <category>python</category>
      <category>survey</category>
      <category>web</category>
      <category>omr</category>
    </item>
  </channel>
</rss>
