<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>emre şahin's digital garden 🍃 - automation</title>
    <link>https://emresahin.net/tags/automation/</link>
    <description>Posts in the automation tag</description>
    <language>en</language>
    <managingEditor>contact@emresahin.net (Emre Şahin)</managingEditor>
    <lastBuildDate>Tue, 29 Sep 2026 14:57:43 +0000</lastBuildDate>
    <atom:link href="https://emresahin.net/tags/automation/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Adaptive Video Playback via Eye Tracking</title>
      <published>2024-11-25T17:23:35+00:00</published>
      <updated>2024-11-25T17:23:35+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 25 Nov 2024 17:23:35 +0000</pubDate>
      <link>https://emresahin.net/idea-11-25/</link>
      <guid isPermaLink="true">https://emresahin.net/idea-11-25/</guid>
      <description>Imagine an eye tracker that monitors a viewer’s level of interest and automatically adjusts the playback speed of a video. If the viewer appears bored or disengaged, the system could speed up the video; conversely, if they struggle to keep up, it could slow down the playback to match their pace.</description>
      <category>ideas</category>
      <category>technology</category>
      <category>video</category>
      <category>eye-tracking</category>
      <category>automation</category>
      <category>ux</category>
      <content:encoded><![CDATA[<p>Imagine an eye tracker that monitors a viewer’s level of interest and automatically adjusts the playback speed of a video. If the viewer appears bored or disengaged, the system could speed up the video; conversely, if they struggle to keep up, it could slow down the playback to match their pace.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 7</title>
      <published>2024-08-06T06:43:01+00:00</published>
      <updated>2024-08-06T06:43:01+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 06 Aug 2024 06:43:01 +0000</pubDate>
      <link>https://emresahin.net/devlog-7/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-7/</guid>
      <description>I noticed that I often forget to update the Xvc CHANGELOG. To fix this, I added a pre-push hook that checks the files I’m pushing. If the CHANGELOG is not among them and there are changes to Rust files, it prevents the push. I hope this will remind me to update the logs more frequently. #!/bin/ba...</description>
      <category>devlog</category>
      <category>Software Development</category>
      <category>Git</category>
      <category>git-hooks</category>
      <category>automation</category>
      <category>changelog</category>
      <category>xvc</category>
      <category>bash</category>
      <content:encoded><![CDATA[<p>I noticed that I often forget to update the Xvc CHANGELOG. To fix this, I added
a pre-push hook that checks the files I’m pushing. If the CHANGELOG is not among
them and there are changes to Rust files, it prevents the push. I hope this will
remind me to update the logs more frequently.</p>
<pre><code class="language-bash">#!/bin/bash

# Git pre-push hook to check if CHANGELOG.md is included in the push and if the branch is develop

# Get the current branch name

current_branch=$(git rev-parse --abbrev-ref HEAD)

# Check if the branch is develop

if [ "$current_branch" == "main" ]; then
	echo "You are on the main branch. Skipping CHANGELOG.md check."
	exit 0
fi

# remote="$1"
# url="$2"

# Get the list of commits to be pushed
commits=$(git rev-list '@{u}..HEAD')

has_rust_files=$(false)

# TODO: We can iterate to get file list only once
# Get the list of files that are going to be pushed
for commit in $commits; do
	if git diff-tree --no-commit-id --name-only -r "${commit}" | grep -q "\\.rs$"; then
		has_rust_files=$(true)
	fi
done

if [[ ! $has_rust_files ]]; then
	echo "No .rs files in the push, no need to check CHANGELOG"
	exit 0
fi

# Check if CHANGELOG.md is among the files in the commits
for commit in $commits; do
	if git diff-tree --no-commit-id --name-only -r "${commit}" | grep -q "CHANGELOG.md"; then
		echo "CHANGELOG.md is included in the push."
		exit 0
	fi
done

echo "ERROR: CHANGELOG.md is not included in the push."
exit 1
</code></pre>
<p><strong>Edit (2025-02-08):</strong> Added a check for when only code files are changed.</p>]]></content:encoded>
    </item>
    <item>
      <title>Merging all Tmux windows in a single session</title>
      <published>2024-06-12T21:24:15+00:00</published>
      <updated>2024-06-12T21:24:15+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 12 Jun 2024 21:24:15 +0000</pubDate>
      <link>https://emresahin.net/merging-all-tmux-windows-in-a-single-session/</link>
      <guid isPermaLink="true">https://emresahin.net/merging-all-tmux-windows-in-a-single-session/</guid>
      <description>I use a new Tmux session for each day. Over time, I noticed that sessions proliferate, and I often need windows from earlier sessions in a central location. This script merges all windows from all Tmux sessions into a single session. You must specify the target session as a parameter. If you save...</description>
      <category>Shell</category>
      <category>Terminal</category>
      <category>Tmux</category>
      <category>Script</category>
      <category>Zsh</category>
      <category>Automation</category>
      <category>Productivity</category>
      <content:encoded><![CDATA[<p>I use a new Tmux session for each day. Over time, I noticed that sessions proliferate, and I often need windows from earlier sessions in a central location.</p>
<p>This script merges all windows from all Tmux sessions into a single session. You must specify the target session as a parameter. If you save the script as <code>merge-tmux-sessions.zsh</code>, you can run it like:</p>
<pre><code class="language-bash">merge-tmux-sessions.zsh my-current-session
</code></pre>
<p>It will then move all windows into the session you specify.</p>
<pre><code class="language-zsh">#!/bin/zsh

# set -vuex
#
#  Specify the target session

target_session=$1

# Get the list of all sessions

sessions=$(tmux list-sessions -F '#S')

# Iterate through each session

for session in ${(@f)sessions}; do
    if [ "$session" != "$target_session" ]; then
        # Get the list of windows in the current session
        windows=$(tmux list-windows -t $session -F '#I')

        # Move each window to the target session
        for window in ${(@f)windows}; do
            tmux move-window -s ${session}:${window} -t ${target_session}
        done
    fi

done
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Shell function to open URLs in Kagi Summarizer</title>
      <published>2024-04-30T20:07:00+00:00</published>
      <updated>2024-04-30T20:07:00+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 30 Apr 2024 20:07:00 +0000</pubDate>
      <link>https://emresahin.net/shell-function-to-open-urls-in-kagi-summarizer/</link>
      <guid isPermaLink="true">https://emresahin.net/shell-function-to-open-urls-in-kagi-summarizer/</guid>
      <description>I’m a fan and an avid user of Kagi Search. I’m an Ultimate plan subscriber and use their Summarizer frequently. I needed a way to quickly open URLs in the Kagi Summarizer from my terminal. I wrote a small shell function to do that: ksum() { local urlenc=$(jq -rn --arg x "$1" '$x|@uri') open "http...</description>
      <category>Tools</category>
      <category>shell</category>
      <category>kagi</category>
      <category>search</category>
      <category>summarizer</category>
      <category>zsh</category>
      <category>automation</category>
      <content:encoded><![CDATA[<p>I’m a fan and an avid user of Kagi Search. I’m an Ultimate plan subscriber and use their Summarizer frequently.</p>
<p>I needed a way to quickly open URLs in the Kagi Summarizer from my terminal. I wrote a small shell function to do that:</p>
<pre><code class="language-zsh">ksum() {
  local urlenc=$(jq -rn --arg x "$1" '$x|@uri')
  open "https://kagi.com/summarizer/index.html?target_language=&amp;summary=takeaway&amp;url=${urlenc}"
}
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Xvc Devlog - 221108</title>
      <published>2022-11-09T10:42:00+00:00</published>
      <updated>2022-11-09T10:42:00+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 09 Nov 2022 10:42:00 +0000</pubDate>
      <link>https://emresahin.net/xvc-devlog---221108/</link>
      <guid isPermaLink="true">https://emresahin.net/xvc-devlog---221108/</guid>
      <description>🐇 I think we can begin by checking GitHub PRs . What do you have today? 🐢 The tests for yesterday’s Git integration PR failed. I’ll begin by checking the logs. 🐇 Maybe run the tests locally to see. They might fail on your machine as well. It might be a simple thing. 🐢 Probably, yes. I’ve started ...</description>
      <category>devlog</category>
      <category>Software Development</category>
      <category>xvc</category>
      <category>git</category>
      <category>testing</category>
      <category>trace</category>
      <category>rsync</category>
      <category>storage</category>
      <category>github actions</category>
      <category>automation</category>
      <content:encoded><![CDATA[<p>🐇 I think we can begin by checking <a href="https://github.com/pulls">GitHub PRs</a>. What do you have today?</p>
<p>🐢 The tests for yesterday’s <a href="https://github.com/iesahin/xvc/pull/105">Git integration PR</a> failed. I’ll begin by checking the logs.</p>
<p>🐇 Maybe run the tests locally to see. They might fail on your machine as well. It might be a simple thing.</p>
<p>🐢 Probably, yes. I’ve started the tests now. I shouldn’t forget to run them before testing the PR; it may save some time.</p>
<p>🐇 Ah, yeah, maybe. Yesterday you were already at the end of the workday, so it didn’t matter much. But you can shorten the testing time by reducing the number of files, etc. I think a separate benchmark suite might be good to have. Tests should be shorter; benchmarks should only run for tags.</p>
<p>🐢 Yup. I should shorten the tests. I should make them use a shorter list of files, maybe.</p>
<p>🐇 Local tests have passed. You’ll have to check the logs.</p>
<p>🐢 It seems <code>git diff --name-only --cached</code> returns files not yet added. That’s causing an error in new repositories with <code>stash</code>. Stashing shouldn’t run at all when there are no staged files.</p>
<p>🐇 Git behavior between the local version and the remote version seems different.</p>
<p>🐢 I think the trace should also show the Git version string.</p>
<p>🐇 You should also require a minimum Git version for the commands. You’re using some newer options, and not all users may have them.</p>
<p>🐢 Yep. Let’s see the version on the CI now.</p>
<hr>
<p>🐢 Ah, I see—the problem is not about versions. It’s that the CI doesn’t configure <code>git config --global user.email</code> and <code>user.name</code>. Commits don’t work without these.</p>
<p>🐇 You should remove the Git version report, then. It’s one more process call for no reason.</p>
<p>🐢 I think it should be in <code>trace!</code>; I can check the verbosity level and call it only if it’s trace.</p>
<hr>
<p>🐢 I completed the xvc-config docs as well. I think I can merge it before the tests finish, as it’s just a documentation update.</p>
<p>🐇 You seem to be rushing for the release.</p>
<p>🐢 Yeah, I want to really start testing this on the servers.</p>
<p>🐇 Then go ahead; let’s release a new version.</p>
<hr>
<p>🐇 It looks like you’re back for an evening session. I think it’s time to start using Xvc on your torrent server.</p>
<p>🐢 Ah, yeah. Let’s see how it goes.</p>
<p>🐇 Create a repository for torrents. Then you can add files to it with cache-type = symlink or cache-type = hardlink.</p>
<p>🐢 I think creating a local storage may also help. I can use it to store files to retrieve them later.</p>
<p>🐇 Umm. We don’t have a “garbage collection” facility yet, you know. There are no file deletions at the moment. It may be better to have a repository-to-repository transfer feature, using SSH.</p>
<p>🐢 Yeah, we don’t have SSH storage either. I think this highlights a lack of features.</p>
<p>🐇 It’s possible to mimic Rsync storage with <code>xvc storage new generic</code>, but it doesn’t feel quite the same.</p>
<p>🐢 Then I’m adding these three tickets.</p>
<p>🐇 I think for 0.3.4, the command we add might be <code>xvc file delete</code>. You can work on this and add <code>rsync</code> support as well.</p>
<p>🐢 There is a <a href="https://docs.rs/librsync/latest/librsync/">librsync</a> bindings library for Rust. But it looks like it doesn’t allow transferring file contents between hosts. There is also <a href="https://github.com/your-tools/rusync">rusync</a>, which is similar to rsync and implemented in Rust. There is also <a href="https://lib.rs/crates/fast_rsync">fast_rsync</a> in pure Rust. It uses MD4 to calculate deltas, though, I think.</p>
<p>🐇 None of these seem to have network capability, though.</p>
<p>🐢 I think it’s better just to use the process for now. We are trying to come up with the simplest solution <em>for now.</em> We are just trying to be more general.</p>
<p>🐇 Yes, I think we can just use the process for the time being.</p>
<p>🐢 Then let’s start by adding it.</p>
<p>🐇 We should also begin to create release notes. GitHub can generate them from PRs. <a href="https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes">Automatically generated release notes</a></p>
<p>🐢 Created an issue for that. Now we’re starting <a href="https://github.com/iesahin/xvc/issues/111"><code>xvc storage new rsync</code> #111</a>.</p>
<p>🐇 Ok. Let’s do this.</p>]]></content:encoded>
    </item>
    <item>
      <title>Sending i3 messages from shell scripts</title>
      <published>2021-01-28T12:35:21+00:00</published>
      <updated>2021-01-28T12:35:21+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 28 Jan 2021 12:35:21 +0000</pubDate>
      <link>https://emresahin.net/sending-i3-messages-from-shell/</link>
      <guid isPermaLink="true">https://emresahin.net/sending-i3-messages-from-shell/</guid>
      <description>It’s possible to send messages to the i3 window manager from shell scripts using i3-msg . For example: i3-msg workspace "n"</description>
      <category>Scripting</category>
      <category>i3</category>
      <category>X</category>
      <category>desktop</category>
      <category>automation</category>
      <category>shell</category>
      <category>til</category>
      <content:encoded><![CDATA[<p>It’s possible to send messages to the i3 window manager from shell scripts using <code>i3-msg</code>. For example:</p>
<pre><code class="language-bash">i3-msg workspace "n"
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Deleting duplicate files in Google Drive using rclone</title>
      <published>2021-01-06T21:31:08+00:00</published>
      <updated>2022-08-06T00:00:00+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 06 Jan 2021 21:31:08 +0000</pubDate>
      <link>https://emresahin.net/rclone-google-drive-deletion/</link>
      <guid isPermaLink="true">https://emresahin.net/rclone-google-drive-deletion/</guid>
      <description>I’m a Google One user, and my Google Drive has about 1TB of content from various sources. A few years ago, I used a third-party utility to sync my Linux boxes to Drive, which created many duplicate files. I had around 3-4 different versions of some directories with different sets of files. As a t...</description>
      <category>Scripts</category>
      <category>Cloud Storage</category>
      <category>drive</category>
      <category>duplicates</category>
      <category>rclone</category>
      <category>google-drive</category>
      <category>automation</category>
      <category>zsh</category>
      <content:encoded><![CDATA[<p>I’m a Google One user, and my Google Drive has about 1TB of content from various sources.
A few years ago, I used a third-party utility to sync my Linux boxes to Drive, which created many duplicate files.
I had around 3-4 different versions of some directories with different sets of files.
As a true procrastinator, I postponed addressing the problem until Google One alerted me about my quota.</p>
<p>Nowadays, I’m using <a href="https://rclone.org/"><code>rclone</code></a>.
It has become my primary way of using Drive on Linux after that half-baked sync application.
I discovered that <code>rclone</code> supports server-side MD5 hashes for files.
I decided to write a script to delete the duplicate files in Drive.</p>
<p>First, I obtain the MD5 hashes of all files in Drive using:</p>
<pre><code>$ rclone md5hash drive:/ &gt; $HOME/Google-Drive-md5-$(date +%F).txt
</code></pre>
<p>This may take some time depending on the number of files, but it finished more quickly than I expected.</p>
<p>The file contents look like this:</p>
<pre><code>39044094333de4a47d7478227cfa22a9  Facebin/vgg-clean/clea_duvall/00000501.jpg
398d44f0b32d1b4855e838754c2c49fc  Facebin/vgg-clean/Bob_Barker/00000445.jpg
c2090f9412b93d71fed884bb45b26518  Facebin/vgg-clean/Adam_Goldberg/00000432.jpg
cd6971f809a534ab02ee1b03eb6c1183  CHECK Uploads/Google Photos/2017/06/IMG_1461.JPG
1fb80cc9ef622410557cb42c4abf26a8  Facebin/vgg-clean/Angell_Conwell/00000510.jpg
160793fd8f24fcf27efb9a2b3698a9c8  Facebin/makimface-artifacts/dataset-images/user-test-v4/00056--/img-5c1fdc5d4fd82c1e12a7d49937d7f47b.png
bcddf6ebbc1eed83950d908c2ccbac4a  Facebin/vgg-clean/danny_pino/00000102.jpg
eb2746a9559e7a93fd0002f4bfd90517  Facebin/makimface-artifacts/dataset-images/user-test/00009--/ds-767cd16bfdf4e31860d597708a586979.png
9605d69aaf5fb83d309f10f9e2544630  Facebin/vgg-clean/Adam_Beach/00000953.jpg
68e6d3ec3cca8a728a78fb60c9a1ddba  Facebin/vgg-clean/corey_stoll/00000137.jpg
</code></pre>
<p>The first 32 characters are the MD5 hash of the file, and the rest of the line is the file path.</p>
<p>You’ll probably see some blanks for the MD5 hashes of certain files.
<strong>It’s important</strong> to remove these from the list, as these are your Google Docs, spreadsheets, etc.</p>
<pre><code>$ grep '[^0-9a-f]' $HOME/Google-Drive-md5-$(date +%F).txt &gt; $HOME/Google-Drive-md5-cleaned.txt
</code></pre>
<p>Next, we sort the file using:</p>
<pre><code>$ sort $HOME/Google-Drive-md5-cleaned.txt &gt; $HOME/Google-Drive-md5-sorted.txt
</code></pre>
<p>We keep all intermediate files because you might want to review the differences after the cleanup.</p>
<p>Then, we write the following script and run it:</p>
<pre><code class="language-zsh">#!/bin/zsh

PREV_MD5=""
PREV_PATH=""
CURRENT_MD5=""
CURRENT_PATH=""
MD5_FILE=$HOME/Google-Drive-md5-sorted.txt
cat $MD5_FILE | while read current_line ; do
    # echo $current_line
    CURRENT_MD5=$(echo "$current_line" | cut -c -32)
    CURRENT_PATH=$(echo "$current_line" | cut -c 35-)
    # echo $CURRENT_MD5
    # echo "$CURRENT_PATH"
    if [[ "$CURRENT_MD5" == "$PREV_MD5" ]] ; then
        echo "EQUAL: $CURRENT_MD5 $PREV_MD5"
        echo "DELETE: drive:/$CURRENT_PATH"
        rclone -v delete "drive:/$CURRENT_PATH"
    else
        PREV_MD5=$CURRENT_MD5
        PREV_PATH=$CURRENT_PATH
    fi
done
</code></pre>
<p>You can save this script as <code>google-drive-delete-duplicates.sh</code> and run:</p>
<pre><code>$ chmod +x google-drive-delete-duplicates.sh
$ ./google-drive-delete-duplicates.sh
</code></pre>
<p>The script checks each line one by one; if two duplicate MD5 hashes are found consecutively, the second file is deleted.
It keeps only one of the duplicates, even if there are more than two copies.</p>
<p>I have gained about 200GB by running this script.</p>]]></content:encoded>
    </item>
    <item>
      <title>telegram-send</title>
      <published>2020-05-12T18:47:24+00:00</published>
      <updated>2020-05-12T21:47:34+03:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 12 May 2020 18:47:24 +0000</pubDate>
      <link>https://emresahin.net/telegram-send-6932/</link>
      <guid isPermaLink="true">https://emresahin.net/telegram-send-6932/</guid>
      <description>A little Python CLI app for Telegram messages</description>
      <category>CLI</category>
      <category>Telegram</category>
      <category>Tools</category>
      <category>Python</category>
      <category>Automation</category>
      <category>Messaging</category>
      <category>Bot</category>
      <content:encoded><![CDATA[<p>There is a small Python command-line program called <code>telegram-send</code> that allows you to send messages to your
Telegram account.</p>
<p>First, you need to register a new bot with <a href="https://t.me/BotFather">@BotFather</a> and get an API token. Then,
run <code>pip3 install --user telegram-send</code> and prepare a config file at <code>~/.config/telegram-send.conf</code>:</p>
<pre><code class="language-ini">[telegram]
token = &lt;TOKEN_YOU_GET_FROM_BOT_FATHER&gt;
chat_id = &lt;CHAT_OR_USER_ID&gt;
</code></pre>
<p>You’ll need to start a conversation with the bot and find your user ID (which is identical to the chat
ID for the conversation you start with the bot).</p>
<p>After that, you can send yourself messages from the CLI like this:</p>
<pre><code class="language-bash">telegram-send "Hello Telegram."
</code></pre>
<p>You can also send Markdown-formatted messages, audio, stickers, and more using various command-line options:</p>
<pre><code class="language-text">usage: telegram-send [-h] [--format {text,markdown,html}] [--stdin] [--pre]
                     [--disable-web-page-preview] [--silent] [-c]
                     [--configure-channel] [--configure-group]
                     [-f FILE [FILE ...]] [-i IMAGE [IMAGE ...]]
                     [-s STICKER [STICKER ...]]
                     [--animation ANIMATION [ANIMATION ...]]
                     [--video VIDEO [VIDEO ...]] [--audio AUDIO [AUDIO ...]]
                     [-l LOCATION [LOCATION ...]]
                     [--caption CAPTION [CAPTION ...]] [--config CONF] [-g]
                     [--file-manager] [--clean] [--timeout TIMEOUT]
                     [--version]
                     [message [message ...]]
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Adding version information to executables in CMake projects</title>
      <published>2018-02-16T11:04:16+00:00</published>
      <updated>2018-02-16T11:04:16+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Fri, 16 Feb 2018 11:04:16 +0000</pubDate>
      <link>https://emresahin.net/versioning-through-cmake-14095-76258/</link>
      <guid isPermaLink="true">https://emresahin.net/versioning-through-cmake-14095-76258/</guid>
      <description>In programming, versioning your code files is of immense importance. Most files need to be constantly updated, renamed, and merged. You also need backups, as everyone learns after losing work due to various computer problems. Another problem we face is establishing a connection between an executa...</description>
      <category>Development</category>
      <category>C/C++</category>
      <category>CMake</category>
      <category>CMake</category>
      <category>C</category>
      <category>Versioning</category>
      <category>Git</category>
      <category>Build Systems</category>
      <category>Automation</category>
      <content:encoded><![CDATA[<p>In programming, versioning your code files is of immense importance. Most
files need to be constantly updated, renamed, and merged. You also need
backups, as everyone learns after losing work due to various computer problems.</p>
<p>Another problem we face is establishing a connection between an
executable file or library and its source code. We normally don’t add executable
files to version control, as they are produced from code files. A common
solution to this is writing version information to an “About” page or
something similar.</p>
<p>When I was using Subversion some 15 years ago, I would create hooks to change the
code files for the necessary versioning info, but this is not a recommended
approach in Git because of its distributed nature. I have never tried it, but it
would likely create more problems than it solves. The recommended way is to use the
build system’s facilities to retrieve the versioning information and add it to
the necessary places.</p>
<p>While developing the C library for dervaze, I wanted to add descriptive
versioning information, as the library will also contain wordlists, and more words
will be added over time.</p>
<p>In CMake, it’s possible to set versioning information and supply it through
compiler options. It’s also possible to replace strings formatted as
<code>@CHANGE_THIS@</code> in source files. To supply version information to the executable,
you can use these facilities:</p>
<pre><code class="language-cmake">
set (DERVAZE_VERSION_MAJOR 1)
set (DERVAZE_VERSION_MINOR 0)
string(TIMESTAMP DERVAZE_TIMESTAMP "%y%m%d%H%M%S")
# current branch
execute_process(
  COMMAND git rev-parse --abbrev-ref HEAD
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  OUTPUT_VARIABLE DERVAZE_GIT_BRANCH
  OUTPUT_STRIP_TRAILING_WHITESPACE
)

# abbreviated commit hash
execute_process(
  COMMAND git log -1 --format=%h
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  OUTPUT_VARIABLE DERVAZE_GIT_COMMIT_HASH
  OUTPUT_STRIP_TRAILING_WHITESPACE
)

</code></pre>
<p>This information can be supplied to the C files by creating a header file that
will be used as a template.</p>
<pre><code class="language-c">
#define DERVAZE_VERSION_MAJOR       "@DERVAZE_VERSION_MAJOR@"
#define DERVAZE_VERSION_MINOR       "@DERVAZE_VERSION_MINOR@"
#define DERVAZE_TIMESTAMP           "@DERVAZE_TIMESTAMP@"
#define DERVAZE_LIB_GIT_BRANCH      "@DERVAZE_GIT_BRANCH@"
#define DERVAZE_LIB_GIT_COMMIT_HASH "@DERVAZE_GIT_COMMIT_HASH@"
</code></pre>
<p>Suppose this file is named <code>version.h.in</code>; the following command creates the
actual <code>version.h</code> for each build.</p>
<pre><code class="language-cmake"># configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/version.h.in"
  "${PROJECT_SOURCE_DIR}/version.h"
  )

</code></pre>
<p>It’s also possible to write this file only during the build by using
<code>${PROJECT_BINARY_DIR}/version.h</code> as the second argument, but in my experience,
keeping such a file in the source directory is sometimes needed by build tools.
When you keep it in the source directory, it’s better to ignore the generated
<code>version.h</code> by adding it to <code>.gitignore</code>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Backup Script for Recent Files</title>
      <published>2014-02-01T22:00:00+00:00</published>
      <updated>2014-02-01T22:00:00+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sat, 01 Feb 2014 22:00:00 +0000</pubDate>
      <link>https://emresahin.net/backup-script-for-recent-files/</link>
      <guid isPermaLink="true">https://emresahin.net/backup-script-for-recent-files/</guid>
      <description>I decided to write a script to back up only recent files. There are solutions based on unison that work periodically for all files, but as I change projects, I need to configure new backups for these projects as well. This is cumbersome and error-prone; it is easy to forget to add new artifacts t...</description>
      <category>cli</category>
      <category>tools</category>
      <category>devops</category>
      <category>backup</category>
      <category>rsync</category>
      <category>unison</category>
      <category>bash</category>
      <category>script</category>
      <category>cron</category>
      <category>linux</category>
      <category>automation</category>
      <content:encoded><![CDATA[<p>I decided to write a script to back up only recent files. There are solutions based on <a href="http://www.cis.upenn.edu/~bcpierce/unison/">unison</a> that work periodically for all files, but as I change projects, I need to configure new backups for these projects as well. This is cumbersome and error-prone; it is easy to forget to add new artifacts to backup scripts and lose them in an emergency.</p>
<p>Therefore, I decided that a small Bash script using <a href="http://rsync.samba.org/">rsync</a> and <a href="http://en.wikipedia.org/wiki/Find">find</a> would work better. It monitors my entire home directory and backs up recent files.</p>
<p>The following script does exactly that:</p>
<pre><code class="language-bash">#!/bin/bash

if [ "x$1" = "x" ] ; then
    TARGET=/media/augustus/backup-recent-`hostname`
else
    TARGET=$1
fi

PERIOD=15

mkdir -p $TARGET

# Delete files older than $PERIOD days
find $TARGET -ctime +$PERIOD -print -delete

# Copy files newer than $PERIOD under ~. Ignore files under .hg
for d in ~/*/ ; do
    find $d -path '*/.hg/*' -prune -o -type f -ctime -$PERIOD -print  -exec rsync -aRv {} $TARGET/ \;
done
</code></pre>
<p>It checks whether a command-line option is provided as the target; otherwise, it sets a default target. <code>/media/augustus/</code> is an NFS mount in my case, but you can specify any path.</p>
<p><code>PERIOD</code> is the number of days that the script considers <em>recent</em>. Currently, it backs up files changed in the last 15 days.</p>
<p>The script deletes older files from the backup. Since I use other solutions for long-term storage, I don’t want to keep them here, especially since this script runs via <code>cron</code> every two hours.</p>
<p>Note that the script only checks directories under the home directory, so files located directly in the home directory are not backed up.</p>
<p>It also skips files under <code>.hg</code> directories. You can add more <code>-prune</code> options to the <code>find</code> command to exclude other irrelevant directories from your backup.</p>]]></content:encoded>
    </item>
    <item>
      <title>This Site's RSS Generator</title>
      <published>2013-04-22T21:13:06+00:00</published>
      <updated>2013-04-22T21:13:06+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 22 Apr 2013 21:13:06 +0000</pubDate>
      <link>https://emresahin.net/this-sites-rss-generator/</link>
      <guid isPermaLink="true">https://emresahin.net/this-sites-rss-generator/</guid>
      <description>This is an ancient post from 2013. I’m not using any of these now. Previously with Pandoc , I was using a simple setup to create RSS feeds. Markdown files were converted to plain, headerless HTML, and they were collected together to build an XML file. The obvious drawback is that all HTML files s...</description>
      <category>Software Development</category>
      <category>Python</category>
      <category>RSS</category>
      <category>automation</category>
      <category>static site generator</category>
      <category>web development</category>
      <content:encoded><![CDATA[<p><strong>This is an ancient post from 2013. I’m not using any of these now.</strong></p>
<p>Previously with <a href="http://johnmacfarlane.net/pandoc/">Pandoc</a>, I was using a simple setup to create RSS feeds. <code>Markdown</code> files were converted to plain, headerless HTML, and they were collected together to build an XML file. The obvious drawback is that all HTML files should be generated by Pandoc; anything that doesn’t fit that route does not appear in the feeds.</p>
<p>However, when I began to use <a href="http://orgmode.org">Org Mode</a> for data analysis and other tasks, I stopped using Pandoc. Org Mode has extensive facilities for exporting into HTML and other document formats, so I would not mess with Pandoc for this.</p>
<p>I thought RSS could be produced by parsing HTML files after they are produced. This requires parsing the HTML file, but it’s simple, and there are parsers for all programming languages out there. My previous RSS generator was in Python, and I decided to modify it to fit my needs. I think producing RSS for a static HTML site is a common need, and I tried to solve this problem as simply as possible.</p>
<p>Let’s begin with the ubiquitous shebang line. This tells the system that the script is in Python.</p>
<pre><code class="language-python">#!/usr/bin/env python
</code></pre>
<p>The following are the imports for this script. Apart from <a href="http://www.dalkescientific.com/Python/PyRSS2Gen.html">PyRSS2Gen</a>, all modules are present in Python 2.7.</p>
<pre><code class="language-python">import argparse
import codecs
import os
import datetime
from HTMLParser import HTMLParser
import PyRSS2Gen as rssgen
import operator as op
import re
import subprocess as proc
</code></pre>
<p>I use <a href="https://mercurial.selenic.com">Mercurial</a> to track the site’s files. I once thought about using the Mercurial public API to check the status of files, but it proved to be overkill because only the modification time of files is necessary, and retrieving them using a standard command-line call is much simpler. Hence, I removed the following imports for the time being.</p>
<pre><code class="language-python"># from mercurial import commands as cmd
# from mercurial import hg
# from mercurial import ui as hgui
</code></pre>
<p>The following function returns a valid HTML tag string, given the tag and its attributes in a list. <code>HTMLParser</code> sends the tags in a list form, and I use this function to reconvert them to usual HTML tags.</p>
<pre><code class="language-python">def make_tag(tag, attrs):
    content_list = [ tag ]
    content_list += [ "%s=\"%s\"" % (k, v) for (k, v) in attrs]
    return "&lt;" + " ".join(content_list) + "&gt;"
</code></pre>
<p><code>TitleBodyExtractor</code> is an <code>HTMLParser</code> subclass. It collects the body of a page in a string and also keeps the title. These two are the only requirements. It might be possible to parse meta tags to get publish date and author information as well, but I prefer to keep simple things simple.</p>
<pre><code class="language-python">class TitleBodyExtractor(HTMLParser):

    def __init__(self):
        HTMLParser.__init__(self)
        self.in_body = False
        self.in_title = False
        self.body = ""
        self.title = ""


    def handle_data(self, data):
        if self.in_body:
            self.body += data
        if self.in_title:
            self.title += data

    def handle_starttag(self, tag, attrs):
        if self.in_body:
            self.body += make_tag(tag, attrs)

        if tag == "body":
            self.in_body = True
        if tag == "title":
            self.in_title = True

    def handle_endtag(self, tag):
        if tag == "body":
            self.in_body = False

        if tag == "title":
            self.in_title = False

        if self.in_body:
            self.body += "&lt;/%s&gt;" % (tag)
</code></pre>
<p>Getting contents of a file in <em>UTF-8</em> encoding is a common task. The following two functions retrieve and store the contents in UTF-8 using the <code>codecs</code> module.</p>
<pre><code class="language-python">def get_content(filename):
    f = codecs.open(filename, "r", "utf-8")
    cont = f.read()
    f.close()
    return cont

def write_content(filename, content):
    f = codecs.open(filename, "w", "utf-8")
    f.write(content)
    f.close()
</code></pre>
<p>Mercurial allows running commands for a repository outside of that repository with the <code>-R</code> command-line switch. However, it requires the exact path of the repository and does not accept a child path. The following function finds the repository path of a file by recursively checking whether parent paths contain an <code>.hg/</code> directory.</p>
<pre><code class="language-python">def get_repo_path(dir):
    if dir == "/" or dir == "":
        return ""
    if os.path.exists(os.path.join(dir, ".hg")):
        return dir
    else:
        return get_repo_path(os.path.dirname(dir))
</code></pre>
<p>The <code>FileObject</code> class keeps the required data of an HTML file. It stores the path, modification time, body, and title.</p>
<pre><code class="language-python">class FileObject:
    def __init__(self, path, mtime):
        self.path = path
        self.mtime = int(mtime)
        self._body = ""
        self._title = ""

    def parse(self):
        content = get_content(self.path)
        tbe = TitleBodyExtractor()
        tbe.feed(content)
        self._body = tbe.body
        self._title = tbe.title

    def body(self):
        if self._body == "":
            self.parse()
        return self._body

    def title(self):
        if self._title == "":
            self.parse()
        return self._title

    def __str__(self):
        return str(self.path) + " " + str(self.mtime)
</code></pre>
<p>The modification time of a file should be retrieved from the Mercurial repository. The following function calls <code>hg log</code> with a specific template, then parses the date to get the last commit time of a file. If the file is not registered to a repository, it simply returns the filesystem modification time.</p>
<pre><code class="language-python">def get_mtime(full_path):
    if os.path.exists(full_path):
        repo_path = get_repo_path(full_path)
        if repo_path != "":
            logcmd = "/usr/bin/hg log -R %s --template='{date|hgdate}' -l 1 %s " % (repo_path, full_path)
            # print logcmd

            proc_res = proc.check_output(logcmd, shell=True).split()
            if len(proc_res) &gt; 0:
                filetime = int(proc_res[0])
            else:
                filetime = os.path.getmtime(full_path)
        else:
            filetime = os.path.getmtime(full_path)
        return filetime
    else:
        return 0
</code></pre>
<p>We need a list of files as <code>FileObject</code> objects, given the directory name and extension. The function also takes a repository path and excludes filenames that match a given regex.</p>
<pre><code class="language-python">
    def file_list(dirname, extension, repo_path, exclude_regex=None):
        results = []
        for root, dirs, files in os.walk(dirname):
            # print "Dirs:", dirs
            for d in dirs:
                if exclude_regex == None or (not re.match(exclude_regex, d)):
                    results += file_list(os.path.join(root, d), extension, repo_path)
                else:
                    print "Skipping", d
            # print "Files:", files
            for f in files:
                if (exclude_regex == None or (not re.match(exclude_regex, f))) and f.endswith(extension):
                    fullname = os.path.join(root, f)
                    filetime = get_mtime(fullname)
                    results.append(FileObject(fullname, filetime))
        return results
</code></pre>
<p>Given a local file in the site, we need to create a link that shows the URL of that file relative to the site’s URL. The following function finds the relative path with respect to an input directory and returns the complete URL by concatenating it to the site’s URL.</p>
<pre><code class="language-python">
    def make_link(site_url, input_dir, file_path):
        rel_path = os.path.relpath(file_path, input_dir)
        return site_url + rel_path
</code></pre>
<p>Given a <code>FileObject</code> that points to an HTML file, we need a function that builds an RSS item from it. It obtains the URL of the file and fills the rest using the attributes of the <code>FileObject</code>.</p>
<pre><code class="language-python">
    def get_rss_item(file_object, input_dir, site_url):
        the_link = make_link(site_url, input_dir, file_object.path)
        item = rssgen.RSSItem(title=file_object.title(),
                              link=the_link,
                              description=file_object.body(),
                              guid=rssgen.Guid(the_link),
                              pubDate=datetime.datetime.fromtimestamp(file_object.mtime))
        return item
</code></pre>
<p>The function that creates an RSS file from the files in a given directory is the topmost function. It takes all variables that are set from the command line and returns the RSS object.</p>
<p>It first lists all files with the given extension (default being <code>.html</code>) in the input directory. Then it compares the modification time of the RSS file with the modification times of these listed files. If there is no previous RSS file or there are newer HTML files, the RSS is generated again.</p>
<p>Note that a certain amount of <em>edit time</em> can be set, so that the script doesn’t consider files as <em>new</em> if they are modified within <em>edit time</em> minutes. This can be set to prevent too frequent generation of files during an edit session.</p>
<p>To generate the RSS, each file is supplied to the previous function and an RSS item is obtained; then these are fed into the <code>RSS2</code> function of <code>PyRSS2Gen</code> to get the resulting object.</p>
<pre><code class="language-python">def generate_rss(input_dir, extension = ".html", output = "rss/rss.xml", site_title = "Title", site_description = "Description", max_items = 20, site_url = "http://example.com", edit_time=0, exclude_regex = None):
        if not site_url.endswith("/"):
            site_url += "/"
        files = file_list(input_dir, extension, get_repo_path(input_dir), exclude_regex)
        rssmtime = get_mtime(output)
        files_up = [f for f in files if f.mtime &gt;= (rssmtime + edit_time)]
        if len(files_up) &gt; 0:
            print "Before", files_up
            files_up.sort(key=lambda x: x.mtime, reverse=True)
            print "After", files_up
            rss_items = [get_rss_item(fo, input_dir, site_url) for fo in files_up[:max_items]]
            rssobj = rssgen.RSS2(title = site_title,
                                 link = site_url,
                                 description = site_description,
                                 lastBuildDate = datetime.datetime.now(),
                                 items = rss_items)
            return rssobj
        return None
</code></pre>
<p>The main function uses <code>argparse</code> to handle options. The input directory, site title, site URL, and number of items are mandatory; other options have sensible default values.</p>
<p>The function also builds the <code>exclude_regex</code> object to supply to file listings. The regex is built here from the supplied string, and all other functions use this compiled regex.</p>
<p>After generating the RSS, it writes the file with the <code>write_xml</code> function.</p>
<pre><code class="language-python">
    def main():

        parser = argparse.ArgumentParser(description='Generate RSS feed from a set of HTML files')

        parser.add_argument('--input-dir', help="input directory", required=True)
        parser.add_argument("--extension", help="file extension to collect", default=".html")
        parser.add_argument("--output", help="output filename to write the results", default="rss/rss.xml")
        parser.add_argument('--title', help="title of the RSS feed", required=True)
        parser.add_argument("--description", help="site description", default="")
        parser.add_argument("--items", help="max items included in the feed", required=True, type=int)
        parser.add_argument("--site-url", help="site url of items", required=True)
        parser.add_argument("--exclude-regex", help="regex to set skipped files", default="")
        parser.add_argument("--edit-time", help="minutes to wait before putting an item into rss", default=0, type=int)

        args = vars(parser.parse_args())

        if args["exclude_regex"] == "":
            exclude_regex = None
        else:
            exclude_regex = re.compile(args["exclude_regex"])

        rssresults = generate_rss(args["input_dir"],
                                  args["extension"],
                                  args["output"],
                                  args["title"],
                                  args["description"],
                                  args["items"],
                                  args["site_url"],
                                  args["edit_time"],
                                  exclude_regex)

        if rssresults != None:
            rssresults.write_xml(open(args["output"], "w"))



    if __name__ == "__main__":
        main()
</code></pre>
<p>You can get the resulting Python script from <code>rss-generator.py</code>.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
