skip to content
Cover Image

Implementing continuous Flag in Lazydraft

This is Part 5 in Building a CLI in Rust Series

I recently saw Kepano’s tweet where he announced the webViewer is coming to Obsidian, the tool I use for almost all of my note-taking. This was a great opportunity for me to finally add the feature I was dreading to add to lazydraft: --watch style continous stage mode where my local website would be updated constantly as I write.

I’ve tried something different this time. I decided to implement this feature fully using LLM’s and test their capabilities.

—continous Flag

The basic idea for continuously updating the draft I am writing is to create a watcher, attaching to the directory, and according to the changes, re-transfer the latest version of the draft and all of the related content. So It has to know the other used methods as well. ChatGPT offered a similar solution like this.

We first create a watcher, and start to watch the source directory.

let mut watcher = RecommendedWatcher::new(
tx,
NotifyConfig::default().with_poll_interval(Duration::from_secs(2)),
)
.expect("Failed to create file watcher");
// Watch the source directory
watcher
.watch(Path::new(&config.source_dir), RecursiveMode::Recursive)
.expect("Failed to start watching directory");

Then when the received event is a modification, we create the writing list using the config, find the modified writing, check if it is in a draft state or not, and update everything related to that writing.

match create_writing_list(&config) {
Ok(writing_list) => {
if let Some(modified_writing) = writing_list.iter().find(|w| {
event
.paths
.iter()
.any(|p| p.to_string_lossy().contains(&w.path))
}) {
if modified_writing.is_draft {
match get_asset_list_of_writing(modified_writing, &config) {
Ok(asset_list) => {
match transfer_asset_files(&config, &asset_list) {
Ok(_) => {
match update_writing_content_and_transfer(&config, modified_writing, &asset_list) {
Ok(_) => println!("Successfully staged changes for: {}", modified_writing.title),
Err(e) => eprintln!("Error updating content: {}", e),
}
}
Err(e) => eprintln!("Error transferring assets: {}", e),
}
}
Err(e) => eprintln!("Error getting asset list: {}", e),
}
}
}
}

Code could certainly be optimized but we are checking every 2 seconds, and for my usecase it is more than enough. Updates are instantaneous and I can immediately see what the article would look like on my website.

I though about changing the structure of the tool so that the default approach would be continous staging as it is. But both options have their own usecases. So I decided to add this as a flag to the stage command.

Terminal window
lazydraft stage --continuous

Here is my current workflow

Updating CI

After tackling this and realized that I forgot both how to release a new version, and serve a binary through Homebrew, I decided to upgrade its release pipeline and some extra.

To publish a brew formula, you have to have a seperate repository where you define your formulaes.

Previously I released a new version using Github Actions and manually copy paste the binary information to the formulae page. Using ChatGPT, I created these additional steps where I

  • Compute the SHA256 of the created binary
  • Clone my Formulae repository for updating
  • Change every related information inside the formulate to automatically publish a new release.

Here are the added steps, they can be tweaked for a similar setup

- name: Compute SHA256
if: matrix.os == 'macos-latest'
id: sha256
run: |
curl -L -o lazydraft.tar.gz https://github.com/yigitozgumus/lazydraft/releases/download/${{ env.tag_name }}/lazydraft-macos-amd64.tar.gz
shasum -a 256 lazydraft.tar.gz | awk '{ print $1 }' > sha256.txt
echo "::set-output name=sha256::$(cat sha256.txt)"
- name: Clone Homebrew Tap
if: matrix.os == 'macos-latest'
run: |
git clone https://x-access-token:${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/yigitozgumus/homebrew-formulae.git
cd homebrew-formulae
git config user.name "GitHub Actions"
git config user.email "[email protected]"
- name: Update Formula
if: matrix.os == 'macos-latest'
run: |
cd homebrew-formulae
cd Formula
# Update the version lines
sed -i '' "s|version \".*\"|version \"${{ env.tag_name }}\"|g" lazydraft.rb
# Update the URL
sed -i '' "s|url \".*\"|url \"https://github.com/yigitozgumus/lazydraft/releases/download/${{ env.tag_name }}/lazydraft-macos-amd64.tar.gz\"|" lazydraft.rb
# Update the sha256
sed -i '' "s|sha256 \".*\"|sha256 \"${{ steps.sha256.outputs.sha256 }}\"|" lazydraft.rb
git add lazydraft.rb
git commit -m "Update lazydraft formula for version ${{ env.tag_name }}"
git push https://x-access-token:${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/yigitozgumus/homebrew-formulae.git main

Now all I have to do is to implement the feature, add a tag to the end for the new version and push the changes. Everything is automatic. This was something I should have done a long time ago. Using LLM’s reduces the friction to start, you can just ask things and estimate how long it’s going to take.

Additional Improvements

Looking at the source code of lazydraft, I realized that I haven’t been using the feature I wanted to add so long. Adding automatic cover to the posts. Seeing that, I also implemented the same process for og image creation. By implementing these two functions, I no longer need to define a path which was the thing that annoyed me the most.

There is also a config command available for lazydraft, but since opening the config is saved on my local machine, I continously postphoned that feature as well. Running the lazydradt for the very first time creates the config ready to be updated. Now, config command opens the config file with your editor of choice:

fn execute_config_command() {
if let Ok(home) = env::var("HOME") {
let config_path = format!("{}/.config/lazydraft/lazydraft.json", home);
// Get the value of the $EDITOR environment variable
let editor = env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
let status = ProcessCommand::new(editor)
.arg(config_path)
.status()
.expect("Failed to open file with editor");
// Check if the editor exited successfully
if status.success() {
println!("Config edited successfully.");
} else {
eprintln!("Editor exited with an error.");
}
} else {
exit_with_message("HOME variable is not set");
}
}

And lastly, what this tool does was really ambiguous to everyone except me. I used development version on my machine without pushing a new feature or fixing that bug. You can also forget the commands of your own tool if you don’t use it long enough. That is my fault though. Still, I needed to add a simple explanatory info for lazydraft, one I won’t have to change for a long time, hopefully.

fn execute_info_command() {
let version = env!("CARGO_PKG_VERSION");
println!(
r#"
LazyDraft - Version {}
Available Commands:
status - Displays the current status of your drafts and writings.
stage - Stages drafts and transfers content to the target location.
Options:
--continuous: Enables continuous monitoring and staging.
config - Validates and manages configuration settings.
Documentation and Help:
Visit https://github.com/yigitozgumus/lazydraft for more details.
"#,
version
);
}

End of the road?

With these additions, main feature set of lazydraft is completed. There are still improvements I want to make though. Backlog is always full.

Changing the config to TOML or PKL (Currently not supported) might be a nice improvement for the future. If it was a big tool, I’d have to worry about migration but formats are pretty similar.

I might also add different types of writings here to my website in the future. Right now besides my blogposts there are also series (Transmission and this). To move them to Obsidian, I also need to implement the same folder config for them as well. This is not very optimal so changing the config to a list of folders might be on the horizon, depending on how many series I want to write :)