aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/by-name/ne/neorg/functions/add.sh23
-rw-r--r--pkgs/by-name/ne/neorg/functions/context.sh43
-rw-r--r--pkgs/by-name/ne/neorg/functions/dmenu.sh14
-rw-r--r--pkgs/by-name/ne/neorg/functions/f_start.sh7
-rw-r--r--pkgs/by-name/ne/neorg/functions/f_stop.sh7
-rw-r--r--pkgs/by-name/ne/neorg/functions/inputs.sh62
-rw-r--r--pkgs/by-name/ne/neorg/functions/list.sh8
-rw-r--r--pkgs/by-name/ne/neorg/functions/project.sh41
-rw-r--r--pkgs/by-name/ne/neorg/functions/review.sh12
-rw-r--r--pkgs/by-name/ne/neorg/functions/utils.sh40
-rw-r--r--pkgs/by-name/ne/neorg/functions/workspace.sh9
-rwxr-xr-xpkgs/by-name/ne/neorg/main.sh189
-rwxr-xr-xpkgs/by-name/ne/neorg/neorg_id_function.sh16
-rw-r--r--pkgs/by-name/ne/neorg/package.nix75
-rw-r--r--pkgs/by-name/ts/tskm/.envrc8
-rw-r--r--pkgs/by-name/ts/tskm/.gitignore2
-rw-r--r--pkgs/by-name/ts/tskm/Cargo.lock994
-rw-r--r--pkgs/by-name/ts/tskm/Cargo.toml86
-rw-r--r--pkgs/by-name/ts/tskm/build.rs49
-rw-r--r--pkgs/by-name/ts/tskm/flake.lock27
-rw-r--r--pkgs/by-name/ts/tskm/flake.nix25
-rw-r--r--pkgs/by-name/ts/tskm/package.nix32
-rw-r--r--pkgs/by-name/ts/tskm/src/cli.rs115
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/input/handle.rs112
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/input/mod.rs257
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/mod.rs4
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs79
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/neorg/mod.rs18
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/open/handle.rs137
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/open/mod.rs2
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/project/handle.rs87
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/project/mod.rs73
-rw-r--r--pkgs/by-name/ts/tskm/src/main.rs66
-rw-r--r--pkgs/by-name/ts/tskm/src/rofi/mod.rs37
-rw-r--r--pkgs/by-name/ts/tskm/src/task/mod.rs322
-rwxr-xr-xpkgs/by-name/ts/tskm/update.sh3
36 files changed, 2535 insertions, 546 deletions
diff --git a/pkgs/by-name/ne/neorg/functions/add.sh b/pkgs/by-name/ne/neorg/functions/add.sh
deleted file mode 100644
index 5a830a10..00000000
--- a/pkgs/by-name/ne/neorg/functions/add.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env dash
-
-add0open_taskwarrior_project_file() {
- task_project_file="%TASK_PROJECT_FILE"
-
- cd "$(dirname $task_project_file)" || die "BUG: task_project_file ('$task_project_file') can't be accessed"
-
- git_dir="$(search_flake_base_dir)"
- [ "$git_dir" ] || die "(BUG): No git directory?"
- cd "$git_dir" || die "Unreachable, this MUST exists"
-
- nvim "$task_project_file"
- git add "$task_project_file"
-
- base_task_project_file_path="$(awk "{ gsub(\"$git_dir/\", \"\", \$0); print }" "$(ptmp "$task_project_file")")"
- git add $task_project_file
-
- # Check that only the project file has been added (and that our file is actually
- # modified)
- if git status --porcelain=v2 | awk -v path="$base_task_project_file_path" 'BEGIN { hit = 0 } { if ($2 ~ /A./ || $2 ~ /M./) { if ($NF ~ path) { hit = 1 } else { hit = 0; exit 1 } } } END { if (hit == 1) { exit 0 } else { exit 1 } }'; then
- git commit --verbose --message="chore($(dirname "$base_task_project_file_path")): Update"
- fi
-}
diff --git a/pkgs/by-name/ne/neorg/functions/context.sh b/pkgs/by-name/ne/neorg/functions/context.sh
deleted file mode 100644
index 7095847d..00000000
--- a/pkgs/by-name/ne/neorg/functions/context.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env dash
-
-context0open_current_task_context() {
- current_context="$(utils0get_current_context)"
-
- if [ "$current_context" ]; then
- context_path="$(utils0get_current_context_path "$current_context")"
-
- extended_neorg_project_dir="$(utils0get_neorg_project_dir)"
- cd "$extended_neorg_project_dir" || die "(BUG?): Can not access the project dir: $extended_neorg_project_dir"
-
- nvim "$extended_neorg_project_dir/$context_path"
-
- git add .
- git commit --message="chore($(dirname "$context_path")): Update" --no-gpg-sign
- else
- warn "No context active"
- fi
-}
-
-context0open_current_task_context_at_task_id() {
- task_id="$1"
- current_context="$(utils0get_current_context)"
-
- if [ "$current_context" ]; then
- context_path="$(utils0get_current_context_path "$current_context")"
- extended_neorg_project_dir="$(utils0get_neorg_project_dir)"
- task_uuid="$(task "$task_id" uuids)"
-
- cd "$extended_neorg_project_dir" || die "(BUG?): Can not access the project dir: $extended_neorg_project_dir"
-
- if ! grep -q "% $task_uuid" "$extended_neorg_project_dir/$context_path"; then
- echo "* TITLE (% $task_uuid)" >>"$extended_neorg_project_dir/$context_path"
- fi
-
- nvim "$extended_neorg_project_dir/$context_path" -c "/% $task_uuid"
-
- git add .
- git commit --message="chore($(dirname "$context_path")): Update" --no-gpg-sign
- else
- warn "No context active"
- fi
-}
diff --git a/pkgs/by-name/ne/neorg/functions/dmenu.sh b/pkgs/by-name/ne/neorg/functions/dmenu.sh
deleted file mode 100644
index 5a138982..00000000
--- a/pkgs/by-name/ne/neorg/functions/dmenu.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env dash
-
-dmenu0open_context_in_browser() {
- project="$(echo "%ALL_PROJECTS_PIPE" | rofi -sep "|" -dmenu)"
-
- if [ "$project" ]; then
- [ -d "%NEORG_REVIEW_PATH" ] || mkdir --parents "%NEORG_REVIEW_PATH"
- [ -f "%NEORG_REVIEW_PATH/$project.lock" ] || touch "%NEORG_REVIEW_PATH/$project.lock"
- project0open_project_in_browser "$project"
- else
- notify-send "(neorg/dmenu) No project selected"
- exit 1
- fi
-}
diff --git a/pkgs/by-name/ne/neorg/functions/f_start.sh b/pkgs/by-name/ne/neorg/functions/f_start.sh
deleted file mode 100644
index 2423dd44..00000000
--- a/pkgs/by-name/ne/neorg/functions/f_start.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env dash
-
-fstart0start_new_task() {
- task_id="$1"
- fstop0stop_current_task
- task start "$task_id"
-}
diff --git a/pkgs/by-name/ne/neorg/functions/f_stop.sh b/pkgs/by-name/ne/neorg/functions/f_stop.sh
deleted file mode 100644
index e4ff0b94..00000000
--- a/pkgs/by-name/ne/neorg/functions/f_stop.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env dash
-
-fstop0stop_current_task() {
- # we ensured that only one task may be active
- active="$(task +ACTIVE _ids)"
- [ "$active" ] && task stop "$active"
-}
diff --git a/pkgs/by-name/ne/neorg/functions/inputs.sh b/pkgs/by-name/ne/neorg/functions/inputs.sh
deleted file mode 100644
index d47b129a..00000000
--- a/pkgs/by-name/ne/neorg/functions/inputs.sh
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env dash
-
-_git_commit() {
- message="$1"
-
- cd "$(dirname "%NEORG_INPUTS_STORAGE_FILE")" || die "BUG. This should exist."
- [ -d .git ] || git init
- git add .
- git commit --message "$message" --no-gpg-sign
-}
-
-inputs0add() {
- url_file="$1"
-
- mkdir --parents "$(dirname "%NEORG_INPUTS_STORAGE_FILE")"
-
- {
- # Add another newline
- echo ""
- # echo "# $url_file "
-
- clean "$url_file"
- } >>"%NEORG_INPUTS_STORAGE_FILE" &&
- msg2 "Successfully added file '$url_file' with $(clean "$url_file" | wc -l) entries to the url list"
-
- _git_commit "Add entries from '$url_file' ($(clean "$url_file" | wc -l))"
-}
-
-inputs0review() {
- base_profile="$1"
-
- [ -f "%NEORG_INPUTS_STORAGE_FILE" ] || die "'%NEORG_INPUTS_STORAGE_FILE' is not a file. Have you added something with 'inputs_add' yet?"
-
- done_urls="$(mktmp)"
-
- # We assume that the project is not yet open.
- firefox -P "$base_profile" &
- # Give it some time to start up.
- sleep 2
-
- if [ "$(wc -l <"%NEORG_INPUTS_STORAGE_FILE")" -gt 100 ]; then
- echo "Your would want to review more than 100 inputs. Limiting it to the first 100 entries."
- fi
-
- head --lines=100 "%NEORG_INPUTS_STORAGE_FILE" | while read -r url; do
- echo "-> '$url'"
- firefox -P "$base_profile" "$url"
-
- echo "$url" >>"$done_urls"
- done
-
- # Wait for the Firefox process from above to finish.
- wait
-
- tmp="$(mktmp)"
- # source: https://stackoverflow.com/a/24324455
- awk 'NR==FNR {a[$0]=1; next} !a[$0]' "$done_urls" "%NEORG_INPUTS_STORAGE_FILE" >"$tmp"
-
- mv "$tmp" "%NEORG_INPUTS_STORAGE_FILE"
-
- _git_commit "Dump entries into firefox profile '$base_profile'"
-}
diff --git a/pkgs/by-name/ne/neorg/functions/list.sh b/pkgs/by-name/ne/neorg/functions/list.sh
deleted file mode 100644
index 10659457..00000000
--- a/pkgs/by-name/ne/neorg/functions/list.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env dash
-
-list0list_all_contexts_newline() {
- print "%ALL_PROJECTS_NEWLINE"
-}
-list0list_all_contexts_comma() {
- print "%ALL_PROJECTS_COMMA"
-}
diff --git a/pkgs/by-name/ne/neorg/functions/project.sh b/pkgs/by-name/ne/neorg/functions/project.sh
deleted file mode 100644
index 64591850..00000000
--- a/pkgs/by-name/ne/neorg/functions/project.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env dash
-
-project0open_current_context_in_browser() {
- current_context="$(utils0get_current_context)"
- [ "$current_context" ] || die "No current context to use"
- project0open_context_in_browser "$(utils0context2project "$current_context")"
-}
-
-project0open_project_in_browser() {
- project="$1"
- [ "$project" ] || die "BUG: No context supplied to project0open_context_in_browser"
-
- old_context="$(utils0get_current_context)"
- # We have ensured that only one task may be active
- old_started_task="$(task +ACTIVE _ids)"
-
- tracking="$(mktmp)"
- task "project:$project" _ids | xargs --no-run-if-empty task _zshids >"$tracking"
- task context "$(utils0project2context "$project")"
-
- while read -r description; do
- desc="$(echo "$description" | awk -F: '{print $2}')"
- if [ "$desc" = "tracking" ]; then
- task_id="$(echo "$description" | awk -F: '{print $1}')"
- notify-send "(Neorg)" "Starting task $project -> $desc"
- task start "$task_id"
- break
- fi
- done <"$tracking"
-
- firefox -P "$project"
-
- task stop "$task_id"
- [ "$old_started_task" ] && task start "$old_started_task"
-
- if [ "$old_context" ]; then
- task context "$old_context"
- else
- task context none
- fi
-}
diff --git a/pkgs/by-name/ne/neorg/functions/review.sh b/pkgs/by-name/ne/neorg/functions/review.sh
deleted file mode 100644
index a0a9ab8d..00000000
--- a/pkgs/by-name/ne/neorg/functions/review.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env dash
-
-review0start() {
- for project in $(list0list_all_contexts_newline); do
- if [ -f "%NEORG_REVIEW_PATH/$project.lock" ]; then
- msg "Reviewing '$project'"
- notify-send "Neorg" "Reviewing '$project'"
- firefox -P "$project"
- rm "%NEORG_REVIEW_PATH/$project.lock"
- fi
- done
-}
diff --git a/pkgs/by-name/ne/neorg/functions/utils.sh b/pkgs/by-name/ne/neorg/functions/utils.sh
deleted file mode 100644
index c3843e8e..00000000
--- a/pkgs/by-name/ne/neorg/functions/utils.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env dash
-
-# Runs it's first argument and then the second, regardless if the first failed or
-# succeeded
-utils0chain() {
- eval "$1"
- eval "$2"
-}
-
-utils0get_current_context() {
- current_context="$(task _get rc.context)"
- printf "%s\n" "$current_context"
-}
-
-utils0get_current_context_path() {
- current_context="$1"
- context_path="$(task _get rc.context."$current_context".rc.neorg_path 2>/dev/null)"
- if ! [ "$context_path" ]; then
- context_path="$(grep "context.$current_context.rc.neorg_path" "%HOME_TASKRC" | awk 'BEGIN {FS="="} {print $2}')"
- [ "$context_path" ] || die "All contexts should have a 'neorg_path' set!"
- fi
- printf "%s\n" "$context_path"
-}
-
-utils0get_neorg_project_dir() {
- # Perform shell expansion of Tilde
- neorg_project_dir="$(sed "s|^~|$HOME|" "$(ptmp "%DEFAULT_NEORG_PROJECT_DIR")")"
- printf "%s\n" "$neorg_project_dir"
-}
-
-utils0project2context() {
- project="$1"
- context="$(sed 's|\.|_|g' "$(ptmp "$project")")"
- printf "%s\n" "$context"
-}
-utils0context2project() {
- context="$1"
- project="$(sed 's|_|\.|g' "$(ptmp "$context")")"
- printf "%s\n" "$project"
-}
diff --git a/pkgs/by-name/ne/neorg/functions/workspace.sh b/pkgs/by-name/ne/neorg/functions/workspace.sh
deleted file mode 100644
index d5eb2fca..00000000
--- a/pkgs/by-name/ne/neorg/functions/workspace.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env dash
-
-workspace0open_neorg_workspace() {
- workspace="$1"
- nvim -c "NeorgStart" -s "$(ptmp ":Neorg workspace $workspace\n")"
-}
-workspace0open_neorg_workspace_prompt() {
- nvim -c "NeorgStart" -s "$(ptmp ":Neorg workspace ")"
-}
diff --git a/pkgs/by-name/ne/neorg/main.sh b/pkgs/by-name/ne/neorg/main.sh
deleted file mode 100755
index b4f0f0e6..00000000
--- a/pkgs/by-name/ne/neorg/main.sh
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/usr/bin/env dash
-
-# shellcheck source=/dev/null
-SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
-
-# load dependencies
-. ./functions/add.sh
-. ./functions/context.sh
-. ./functions/dmenu.sh
-. ./functions/f_start.sh
-. ./functions/f_stop.sh
-. ./functions/inputs.sh
-. ./functions/list.sh
-. ./functions/project.sh
-. ./functions/review.sh
-. ./functions/utils.sh
-. ./functions/workspace.sh
-
-# these are used in version()
-# shellcheck disable=2034
-AUTHORS="Soispha"
-# shellcheck disable=2034
-YEARS="2023"
-
-NAME="neorg"
-
-help() {
- cat <<EOF
-This is the core interface to the system-integrated task management
-
-USAGE:
- $NAME [OPTIONS] [COMMAND]
-
-OPTIONS:
- --help | -h
- Display this help and exit.
-
- --version | -v
- Display version and copyright information and exit.
-COMMANDS:
- task [ID]
- Open the neorg context associated with the current context and
- the uuid of the task with id ID. Without ID, it'll open the
- current context's norg file.
- If no context is set, drops you to the selection prompt
-
- dmenu
- Select a project in dmenu mode. This will give you all projects
- and exectute the selected one as in 'neorg projects <selected>'
-
- workspace [WS]
- The neorg workspace (WS) to open at startup, an empty value drops
- you at a prompt to enter the workspace yourself.
-
- project [P]
- Opens the webbrowser with either the context (P) or
- the current active context as argument if no context is supplied
-
- list
- Lists all available contexts
-
- add
- Allows you to quickly add projects
-
- fstart ID
- Starts the task (ID) but only after it stooped
- the previous active task, if it existed.
-
- fstop
- Stops the current active task
-
- review
- Review all firefox tabs
-
- inputs_add F
- Add all urls from the file F as inputs to be catogorized
-
- inputs_review P
- Like 'review', but for the inputs that have previously been added.
- It takes a project in which to open the urls.
-ARGUMENTS:
- ID | *([0-9]) := [[%ID_GENERATION_FUNCTION]]
- The function displays all possible IDs of the eligable tasks.
-
- WS := %ALL_WORKSPACES
- All possible workspaces.
-
- P := %ALL_PROJECTS_PIPE
- The possible project.
-
- F := [[fd . --max-depth 3]]
- A URL-Input file to use as source.
-
-EOF
-}
-
-for arg in "$@"; do
- case "$arg" in
- "--help" | "-h")
- help
- exit 0
- ;;
- "--version" | "-v")
- version
- exit 0
- ;;
- esac
-done
-
-while [ "$#" -ne 0 ]; do
- case "$1" in
- "t"*) # task
- shift 1
- task_id="$1"
- [ "$task_id" ] || utils0chain context0open_current_task_context "exit 0"
- context0open_current_task_context_at_task_id "$task_id"
- exit 0
- ;;
- "w"*) # workspace
- shift 1
- workspace_to_open="$1"
- # TODO: Exit with 1 on error, instead of the 0 <2023-10-20>
- [ "$workspace_to_open" ] || utils0chain workspace0open_neorg_workspace_prompt "exit 0"
- workspace0open_neorg_workspace "$workspace_to_open"
- exit 0
- ;;
- "p"*) # project
- shift 1
- project_to_open="$1"
- # TODO: Exit with 1 on error, instead of the 0 <2023-10-20>
- [ "$project_to_open" ] || utils0chain project0open_current_context_in_browser "exit 0"
- if ! grep -q "$project_to_open" "$(ptmp "%ALL_PROJECTS_NEWLINE")"; then
- die "Your project ('$project_to_open') is not in the list of available projects:
-%ALL_PROJECTS_COMMA"
- fi
- project0open_project_in_browser "$project_to_open"
- exit 0
- ;;
- "l"*) # list
- list0list_all_contexts_newline
- exit 0
- ;;
- "a"*) # add-project
- add0open_taskwarrior_project_file
- exit 0
- ;;
- "d"*) # dmenu
- dmenu0open_context_in_browser
- exit 0
- ;;
- "fsta"*) # fstart
- shift 1
- task_id="$1"
- [ "$task_id" ] || die "No task id provided to fstart"
- fstart0start_new_task "$task_id"
- exit 0
- ;;
- "fsto"*) # fstop
- fstop0stop_current_task
- exit 0
- ;;
- "r"*) # review
- shift 1
- review0start
- exit 0
- ;;
- "inputs_a"*) # inputs_add
- shift 1
- url_file="$1"
- [ -f "$url_file" ] || die "Url file ('$url_file') is not a valid file."
- inputs0add "$url_file"
- exit 0
- ;;
- "inputs_r"*) # inputs_review
- shift 1
- base_project="$1"
- [ -z "$base_project" ] && die "'inputs_review' requires a project."
- inputs0review "$base_project"
- exit 0
- ;;
- *)
- die "Command '$1' does not exist! Please look at:\n $NAME --help"
- exit 0
- ;;
- esac
-done
-
-context0open_current_task_context
-# vim: ft=sh
diff --git a/pkgs/by-name/ne/neorg/neorg_id_function.sh b/pkgs/by-name/ne/neorg/neorg_id_function.sh
deleted file mode 100755
index 865ecacf..00000000
--- a/pkgs/by-name/ne/neorg/neorg_id_function.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#! /usr/bin/env dash
-
-# shellcheck source=/dev/null
-SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
-
-context="$(task _get rc.context)"
-if [ "$context" ]; then
- filter="project:$context"
-else
- filter="0-10000"
-fi
-tasks="$(task "$filter" _ids)"
-
-if [ "$tasks" ]; then
- echo "$tasks" | xargs task _zshids | awk -F: -v q="'" '{gsub(/'\''/, q "\\" q q ); print $1 ":" q $2 q}'
-fi
diff --git a/pkgs/by-name/ne/neorg/package.nix b/pkgs/by-name/ne/neorg/package.nix
deleted file mode 100644
index ad39290a..00000000
--- a/pkgs/by-name/ne/neorg/package.nix
+++ /dev/null
@@ -1,75 +0,0 @@
-{
- lib,
- sysLib,
- # dependencies
- cocogitto,
- rofi,
- libnotify,
- taskwarrior,
- gawk,
- findutils,
- # config
- defaultNeorgProjectDir ? "/no-default-dir", # homeConfig.programs.nixvim.plugins.neorg.settings.load."core.dirman".config.workspaces.projects
- allProjectsNewline ? "", # homeConfig.soispha.taskwarrior.projects.projects_newline
- allProjectsComma ? "", # homeConfig.soispha.taskwarrior.projects.projects_comma
- allProjectsPipe ? "", # homeConfig.soispha.taskwarrior.projects.projects_pipe
- allWorkspaces ? {}, # homeConfig.programs.nixvim.plugins.neorg.settings.load."core.dirman".config.workspaces
- xdgConfigHome ? builtins.getEnv "XDG_CONFIG_HOME",
- xdgDataHome ? builtins.getEnv "XDG_DATA_HOME",
-}:
-(sysLib.writeShellScriptMultiPart {
- name = "neorg";
- src = ./.;
- generateCompletions = true;
- keepPath = true;
-
- baseName = "main.sh";
- cmdPrefix = "functions";
- cmdNames = [
- "add.sh"
- "context.sh"
- "dmenu.sh"
- "f_start.sh"
- "f_stop.sh"
- "inputs.sh"
- "list.sh"
- "project.sh"
- "review.sh"
- "utils.sh"
- "workspace.sh"
- ];
-
- dependencies = [
- cocogitto
- rofi
- libnotify
- ];
- replacementStrings = {
- DEFAULT_NEORG_PROJECT_DIR = defaultNeorgProjectDir;
- HOME_TASKRC = "${xdgConfigHome}/task/home-manager-taskrc";
- NEORG_REVIEW_PATH = "${xdgDataHome}/neorg/review";
-
- NEORG_INPUTS_STORAGE_FILE = "${xdgDataHome}/neorg/inputs/url_list.txt";
-
- ALL_PROJECTS_NEWLINE = allProjectsNewline;
- ALL_PROJECTS_COMMA = allProjectsComma;
- ALL_PROJECTS_PIPE = allProjectsPipe;
- ALL_WORKSPACES = "${lib.strings.concatStringsSep "|" (builtins.attrNames allWorkspaces)}";
-
- ID_GENERATION_FUNCTION = "${sysLib.writeShellScript {
- name = "neorg_id_function";
- src = ./neorg_id_function.sh;
- dependencies = [
- taskwarrior
- gawk
- findutils # source of xargs
- ];
- }}/bin/neorg_id_function";
-
- # TODO: Replace the hard-coded path here with some reference <2023-10-20>
- TASK_PROJECT_FILE = "/home/soispha/repos/nix/nixos-config/hm/soispha/conf/taskwarrior/projects/default.nix";
- };
-})
-// {
- meta.mainProgram = "neorg";
-}
diff --git a/pkgs/by-name/ts/tskm/.envrc b/pkgs/by-name/ts/tskm/.envrc
new file mode 100644
index 00000000..d21a17fc
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/.envrc
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+
+SHELL_COMPLETION_DIR="$(pwd)/target/shell"
+export SHELL_COMPLETION_DIR
+
+export TSKM_PROJECT_FILE=/home/soispha/repos/nix/config/modules/common/projects.json
+
+use flake
diff --git a/pkgs/by-name/ts/tskm/.gitignore b/pkgs/by-name/ts/tskm/.gitignore
new file mode 100644
index 00000000..2d5df85d
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/.gitignore
@@ -0,0 +1,2 @@
+/target
+.direnv
diff --git a/pkgs/by-name/ts/tskm/Cargo.lock b/pkgs/by-name/ts/tskm/Cargo.lock
new file mode 100644
index 00000000..879cbf57
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/Cargo.lock
@@ -0,0 +1,994 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
+dependencies = [
+ "anstyle",
+ "once_cell",
+ "windows-sys",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "bitflags"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+
+[[package]]
+name = "bumpalo"
+version = "3.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+
+[[package]]
+name = "cc"
+version = "1.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_complete"
+version = "4.5.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06f5378ea264ad4f82bbc826628b5aad714a75abf6ece087e923010eb937fb6"
+dependencies = [
+ "clap",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.62"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "icu_collections"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform_data"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d"
+
+[[package]]
+name = "icu_normalizer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7"
+
+[[package]]
+name = "icu_properties"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2"
+
+[[package]]
+name = "icu_provider"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_macros"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.171"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
+
+[[package]]
+name = "libredox"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[package]]
+name = "litemap"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
+dependencies = [
+ "getrandom",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "smallvec"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "stderrlog"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c910772f992ab17d32d6760e167d2353f4130ed50e796752689556af07dc6b"
+dependencies = [
+ "chrono",
+ "is-terminal",
+ "log",
+ "termcolor",
+ "thread_local",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tskm"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "clap_complete",
+ "dirs",
+ "log",
+ "serde",
+ "serde_json",
+ "stderrlog",
+ "url",
+ "walkdir",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "url"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf16_iter"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "write16"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+
+[[package]]
+name = "writeable"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+
+[[package]]
+name = "yoke"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/pkgs/by-name/ts/tskm/Cargo.toml b/pkgs/by-name/ts/tskm/Cargo.toml
new file mode 100644
index 00000000..3344e378
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/Cargo.toml
@@ -0,0 +1,86 @@
+[package]
+name = "tskm"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.97"
+clap = { version = "4.5.34", features = ["derive"] }
+dirs = "6.0.0"
+log = "0.4.27"
+serde = { version = "1.0.219", features = ["derive"] }
+serde_json = "1.0.140"
+stderrlog = "0.6.0"
+url = { version = "2.5.4" }
+walkdir = "2.5.0"
+
+[profile.release]
+lto = true
+codegen-units = 1
+panic = "abort"
+split-debuginfo = "off"
+
+[lints.rust]
+# rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html
+warnings = "warn"
+future_incompatible = { level = "warn", priority = -1 }
+let_underscore = { level = "warn", priority = -1 }
+nonstandard_style = { level = "warn", priority = -1 }
+rust_2018_compatibility = { level = "warn", priority = -1 }
+rust_2018_idioms = { level = "warn", priority = -1 }
+rust_2021_compatibility = { level = "warn", priority = -1 }
+unused = { level = "warn", priority = -1 }
+# rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html
+# missing_docs = "warn"
+macro_use_extern_crate = "warn"
+meta_variable_misuse = "warn"
+missing_abi = "warn"
+missing_copy_implementations = "warn"
+missing_debug_implementations = "warn"
+non_ascii_idents = "warn"
+noop_method_call = "warn"
+single_use_lifetimes = "warn"
+trivial_casts = "warn"
+trivial_numeric_casts = "warn"
+unreachable_pub = "warn"
+unsafe_op_in_unsafe_fn = "warn"
+unused_crate_dependencies = "warn"
+unused_import_braces = "warn"
+unused_lifetimes = "warn"
+unused_qualifications = "warn"
+variant_size_differences = "warn"
+
+[lints.rustdoc]
+# rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html
+broken_intra_doc_links = "warn"
+private_intra_doc_links = "warn"
+missing_crate_level_docs = "warn"
+private_doc_tests = "warn"
+invalid_codeblock_attributes = "warn"
+invalid_rust_codeblocks = "warn"
+bare_urls = "warn"
+
+[lints.clippy]
+# clippy allowed by default
+dbg_macro = "warn"
+# clippy categories https://doc.rust-lang.org/clippy/
+all = { level = "warn", priority = -1 }
+correctness = { level = "warn", priority = -1 }
+suspicious = { level = "warn", priority = -1 }
+style = { level = "warn", priority = -1 }
+complexity = { level = "warn", priority = -1 }
+perf = { level = "warn", priority = -1 }
+pedantic = { level = "warn", priority = -1 }
+
+[build-dependencies]
+anyhow = "1.0.97"
+clap = { version = "4.5.34", features = ["derive"] }
+clap_complete = "4.5.47"
+dirs = "6.0.0"
+log = "0.4.27"
+serde = { version = "1.0.219", features = ["derive"] }
+serde_json = "1.0.140"
+url = "2.5.4"
+walkdir = "2.5.0"
diff --git a/pkgs/by-name/ts/tskm/build.rs b/pkgs/by-name/ts/tskm/build.rs
new file mode 100644
index 00000000..8dfb213b
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/build.rs
@@ -0,0 +1,49 @@
+use anyhow::{Context, Result};
+use clap::{CommandFactory, ValueEnum};
+use clap_complete::generate_to;
+use clap_complete::Shell;
+
+use std::env;
+use std::fs;
+use std::path::PathBuf;
+
+use crate::cli::CliArgs;
+
+pub mod task {
+ include!("src/task/mod.rs");
+}
+
+pub mod interface {
+ pub mod input {
+ include!("src/interface/input/mod.rs");
+ }
+ pub mod project {
+ include!("src/interface/project/mod.rs");
+ }
+}
+
+pub mod cli {
+ include!("src/cli.rs");
+}
+
+fn main() -> Result<()> {
+ let outdir = match env::var_os("SHELL_COMPLETION_DIR") {
+ None => return Ok(()),
+ Some(outdir) => outdir,
+ };
+
+ if !PathBuf::from(&outdir).exists() {
+ fs::create_dir_all(&outdir)?;
+ }
+
+ let mut cmd = CliArgs::command();
+
+ for &shell in Shell::value_variants() {
+ let path = generate_to(shell, &mut cmd, "tskm", &outdir).with_context(|| {
+ format!("Failed to output shell completion for {shell} to {outdir:?}")
+ })?;
+ println!("cargo:warning=completion file for {shell} is generated at: {path:?}");
+ }
+
+ Ok(())
+}
diff --git a/pkgs/by-name/ts/tskm/flake.lock b/pkgs/by-name/ts/tskm/flake.lock
new file mode 100644
index 00000000..82448387
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/flake.lock
@@ -0,0 +1,27 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1743076231,
+ "narHash": "sha256-yQugdVfi316qUfqzN8JMaA2vixl+45GxNm4oUfXlbgw=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "6c5963357f3c1c840201eda129a99d455074db04",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/pkgs/by-name/ts/tskm/flake.nix b/pkgs/by-name/ts/tskm/flake.nix
new file mode 100644
index 00000000..4b1142d6
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/flake.nix
@@ -0,0 +1,25 @@
+{
+ description = "This is the core interface to the system-integrated task management";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+ };
+
+ outputs = {nixpkgs, ...}: let
+ system = "x86_64-linux";
+ pkgs = nixpkgs.legacyPackages."${system}";
+ in {
+ devShells."${system}".default = pkgs.mkShell {
+ packages = with pkgs; [
+ cargo
+ clippy
+ rustc
+ rustfmt
+
+ cargo-edit
+ ];
+ };
+ };
+}
+# vim: ts=2
+
diff --git a/pkgs/by-name/ts/tskm/package.nix b/pkgs/by-name/ts/tskm/package.nix
new file mode 100644
index 00000000..a57dc4aa
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/package.nix
@@ -0,0 +1,32 @@
+{
+ rustPlatform,
+ installShellFiles,
+}:
+rustPlatform.buildRustPackage {
+ pname = "tskm";
+ version = "0.1.0";
+
+ src = ./.;
+ cargoLock = {
+ lockFile = ./Cargo.lock;
+ };
+
+ env = {
+ SHELL_COMPLETION_DIR = "./shell";
+ };
+
+ nativeBuildInputs = [
+ installShellFiles
+ ];
+
+ postInstall = ''
+ installShellCompletion --cmd tskm \
+ --bash ./shell/tskm.bash \
+ --fish ./shell/tskm.fish \
+ --zsh ./shell/_tskm
+ '';
+
+ meta = {
+ mainProgram = "tskm";
+ };
+}
diff --git a/pkgs/by-name/ts/tskm/src/cli.rs b/pkgs/by-name/ts/tskm/src/cli.rs
new file mode 100644
index 00000000..958033b3
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/cli.rs
@@ -0,0 +1,115 @@
+use std::path::PathBuf;
+
+use clap::{Parser, Subcommand};
+
+use crate::{
+ interface::{input::Input, project::ProjectName},
+ task,
+};
+
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about, verbatim_doc_comment)]
+/// This is the core interface to the system-integrated task management
+///
+/// `tskm` effectively combines multiple applications together:
+/// - `taskwarrior` projects are raised connected to `firefox` profiles, making it possible to “open”
+/// a project.
+/// - Every `taskwarrior` project has a determined `neorg` path, so that extra information for a
+/// `project` can be stored in this `norg` file.
+/// - `tskm` can track inputs for you. These are URLs with optional tags which you can that
+/// “review” to open tasks based on them.
+pub struct CliArgs {
+ #[command(subcommand)]
+ pub command: Command,
+}
+
+#[derive(Subcommand, Debug)]
+pub enum Command {
+ /// Interact with projects.
+ Projects {
+ #[command(subcommand)]
+ command: ProjectCommand,
+ },
+
+ /// Manage the input queue.
+ Inputs {
+ #[command(subcommand)]
+ command: InputCommand,
+ },
+
+ /// Access the associated `neorg` workspace for the project/task.
+ Neorg {
+ #[command(subcommand)]
+ command: NeorgCommand,
+ },
+
+ /// Interface with the Firefox profile of each project.
+ Open {
+ #[command(subcommand)]
+ command: OpenCommand,
+ },
+}
+
+#[derive(Subcommand, Debug)]
+pub enum ProjectCommand {
+ /// Lists all available projects.
+ List,
+
+ /// Allows you to quickly add projects.
+ Add {
+ /// The name of the new project.
+ #[arg(value_parser = ProjectName::try_from_project)]
+ new_project_name: ProjectName,
+ },
+}
+
+#[derive(Subcommand, Debug, Clone, Copy)]
+pub enum NeorgCommand {
+ /// Open the `neorg` project associated with id of the task.
+ Task { id: task::Id },
+}
+
+#[derive(Subcommand, Debug)]
+pub enum OpenCommand {
+ /// Open each project's Firefox profile consecutively, that was opened since the last review.
+ ///
+ /// This allows you to remove stale opened tabs and to commit open tabs to the `inputs`.
+ Review,
+
+ /// Opens Firefox with either the supplied project or the currently active project profile.
+ Project {
+ /// The project to open.
+ #[arg(value_parser = task::Project::from_project_string)]
+ project: Option<task::Project>,
+ },
+
+ /// Open a selected project in it's Firefox profile.
+ ///
+ /// This will use rofi's dmenu mode to select one project from the list of all registered
+ /// projects.
+ Select,
+}
+
+#[derive(Subcommand, Debug)]
+pub enum InputCommand {
+ /// Add URLs as inputs to be categorized.
+ Add { inputs: Vec<Input> },
+ /// Remove URLs
+ Remove { inputs: Vec<Input> },
+
+ /// Add all URLs in the file as inputs to be categorized.
+ ///
+ /// This expects each line to contain one URL.
+ File { file: PathBuf },
+
+ /// Like 'review', but for the inputs that have previously been added.
+ /// It takes a project in which to open the URLs.
+ Review {
+ /// Opens all the URLs in this project.
+ #[arg(value_parser = task::Project::from_project_string)]
+ project: task::Project,
+ },
+
+ /// List all the previously added inputs.
+ List,
+}
diff --git a/pkgs/by-name/ts/tskm/src/interface/input/handle.rs b/pkgs/by-name/ts/tskm/src/interface/input/handle.rs
new file mode 100644
index 00000000..0ff0e56e
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/interface/input/handle.rs
@@ -0,0 +1,112 @@
+use std::{
+ fs, process,
+ str::FromStr,
+ thread::{self, sleep},
+ time::Duration,
+};
+
+use anyhow::{Context, Result};
+use log::{error, info};
+
+use crate::cli::InputCommand;
+
+use super::Input;
+
+/// # Errors
+/// When command handling fails.
+///
+/// # Panics
+/// When internal assertions fail.
+pub fn handle(command: InputCommand) -> Result<()> {
+ match command {
+ InputCommand::Add { inputs } => {
+ for input in inputs {
+ input.commit().with_context(|| {
+ format!("Failed to add input ('{input}') to the input storage.")
+ })?;
+ }
+ }
+ InputCommand::Remove { inputs } => {
+ for input in inputs {
+ input.remove().with_context(|| {
+ format!("Failed to remove input ('{input}') from the input storage.")
+ })?;
+ }
+ }
+ InputCommand::File { file } => {
+ let file = fs::read_to_string(file)?;
+ for line in file.lines() {
+ let input = Input::from_str(line)?;
+ input.commit().with_context(|| {
+ format!("Failed to add input ('{input}') to the input storage.")
+ })?;
+ }
+ }
+ InputCommand::Review { project } => {
+ let project = project.to_project_display();
+
+ let local_project = project.clone();
+ let handle = thread::spawn(move || {
+ // We assume that the project is not yet open.
+ let mut firefox = process::Command::new("firefox")
+ .args(["-P", local_project.as_str(), "about:newtab"])
+ .spawn()?;
+
+ Ok::<_, anyhow::Error>(firefox.wait()?)
+ });
+ // Give Firefox some time to start.
+ info!("Waiting on firefox to start");
+ sleep(Duration::from_secs(4));
+
+ let project_str = project.as_str();
+ 'outer: for all in Input::all()?.chunks(100) {
+ info!("Starting review for the first hundred URLs.");
+
+ for input in all {
+ info!("-> '{input}'");
+ let status = process::Command::new("firefox")
+ .args(["-P", project_str, input.url().to_string().as_str()])
+ .status()?;
+
+ if status.success() {
+ input.remove()?;
+ } else {
+ error!("Adding `{input}` to Firefox failed!");
+ }
+ }
+
+ {
+ use std::io::{stdin, stdout, Write};
+
+ let mut s = String::new();
+ eprint!("Continue? (y/N) ");
+ stdout().flush()?;
+
+ stdin()
+ .read_line(&mut s)
+ .expect("Did not enter a correct string");
+
+ if let Some('\n') = s.chars().next_back() {
+ s.pop();
+ }
+ if let Some('\r') = s.chars().next_back() {
+ s.pop();
+ }
+
+ if s != "y" {
+ break 'outer;
+ }
+ }
+ }
+
+ info!("Waiting for firefox to stop");
+ handle.join().expect("Should be joinable")?;
+ }
+ InputCommand::List => {
+ for url in Input::all()? {
+ println!("{url}");
+ }
+ }
+ }
+ Ok(())
+}
diff --git a/pkgs/by-name/ts/tskm/src/interface/input/mod.rs b/pkgs/by-name/ts/tskm/src/interface/input/mod.rs
new file mode 100644
index 00000000..9ece7a3a
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/interface/input/mod.rs
@@ -0,0 +1,257 @@
+use std::{
+ collections::HashSet,
+ fmt::Display,
+ fs::{self, read_to_string, File},
+ io::Write,
+ path::PathBuf,
+ process::Command,
+ str::FromStr,
+};
+
+use anyhow::{bail, Context, Result};
+use url::Url;
+use walkdir::WalkDir;
+
+pub mod handle;
+pub use handle::handle;
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct NoWhitespaceString(String);
+
+impl NoWhitespaceString {
+ /// # Panics
+ /// If the input contains whitespace.
+ #[must_use]
+ pub fn new(input: String) -> Self {
+ if input.contains(' ') {
+ panic!("Your input '{input}' contains whitespace. I did not expect that.")
+ } else {
+ Self(input)
+ }
+ }
+}
+
+impl Display for NoWhitespaceString {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl NoWhitespaceString {
+ #[must_use]
+ pub fn as_str(&self) -> &str {
+ &self.0
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Input {
+ url: Url,
+ tags: HashSet<NoWhitespaceString>,
+}
+
+impl FromStr for Input {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ if s.contains(' ') {
+ let (url, tags) = s.split_once(' ').expect("Should work");
+ Ok(Self {
+ url: Url::from_str(url)?,
+ tags: {
+ tags.trim()
+ .split(' ')
+ .map(|tag| {
+ if let Some(tag) = tag.strip_prefix('+') {
+ Ok(NoWhitespaceString::new(tag.to_owned()))
+ } else {
+ bail!("Your tag '{tag}' does not start with the required '+'");
+ }
+ })
+ .collect::<Result<_, _>>()?
+ },
+ })
+ } else {
+ Ok(Self {
+ url: Url::from_str(s)?,
+ tags: HashSet::new(),
+ })
+ }
+ }
+}
+
+impl Display for Input {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if self.tags.is_empty() {
+ self.url.fmt(f)
+ } else {
+ write!(
+ f,
+ "{} {}",
+ self.url,
+ self.tags
+ .iter()
+ .fold(String::new(), |mut acc, tag| {
+ acc.push('+');
+ acc.push_str(tag.as_str());
+ acc.push(' ');
+ acc
+ })
+ .trim()
+ )
+ }
+ }
+}
+
+impl Input {
+ fn base_path() -> PathBuf {
+ dirs::data_local_dir()
+ .expect("This should be set")
+ .join("tskm/inputs")
+ }
+
+ fn url_path(url: &Url) -> Result<PathBuf> {
+ let base_path = Self::base_path();
+
+ let url_path = base_path.join(url.to_string());
+ fs::create_dir_all(&url_path)
+ .with_context(|| format!("Failed to open file: '{}'", url_path.display()))?;
+
+ Ok(url_path.join("url_value"))
+ }
+
+ #[must_use]
+ pub fn url(&self) -> &Url {
+ &self.url
+ }
+
+ /// Commit this constructed [`Input`] to storage.
+ ///
+ /// # Errors
+ /// If IO operations fail.
+ pub fn commit(&self) -> Result<()> {
+ let url_path = Self::url_path(&self.url)?;
+
+ let url_content = {
+ if url_path.exists() {
+ read_to_string(&url_path)?
+ } else {
+ String::new()
+ }
+ };
+
+ let mut file = File::create(&url_path)
+ .with_context(|| format!("Failed to open file: '{}'", url_path.display()))?;
+ writeln!(file, "{url_content}{self}")?;
+
+ Self::git_commit(&format!("Add new url: '{self}'"))?;
+
+ Ok(())
+ }
+
+ /// Remove this constructed [`Input`] to storage.
+ ///
+ /// Beware that this does not take tags into account.
+ ///
+ /// # Errors
+ /// If IO operations fail.
+ pub fn remove(&self) -> Result<()> {
+ let url_path = Self::url_path(&self.url)?;
+
+ fs::remove_file(&url_path)
+ .with_context(|| format!("Failed to remove file: '{}'", url_path.display()))?;
+
+ let mut url_path = url_path.as_path();
+ while let Some(parent) = url_path.parent() {
+ if fs::read_dir(parent)?.count() == 0 {
+ fs::remove_dir(parent)?;
+ }
+ url_path = parent;
+ }
+
+ Self::git_commit(&format!("Remove url: '{self}'"))?;
+ Ok(())
+ }
+
+ /// Commit your changes
+ fn git_commit(message: &str) -> Result<()> {
+ let status = Command::new("git")
+ .args(["add", "."])
+ .current_dir(Self::base_path())
+ .status()?;
+ if !status.success() {
+ bail!("Git add . failed!");
+ }
+
+ let status = Command::new("git")
+ .args(["commit", "--message", message, "--no-gpg-sign"])
+ .current_dir(Self::base_path())
+ .status()?;
+ if !status.success() {
+ bail!("Git commit failed!");
+ }
+
+ Ok(())
+ }
+
+ /// Get all previously [`Self::commit`]ed inputs.
+ ///
+ /// # Errors
+ /// When IO handling fails.
+ ///
+ /// # Panics
+ /// If internal assertions fail.
+ pub fn all() -> Result<Vec<Self>> {
+ let mut output = vec![];
+ for entry in WalkDir::new(Self::base_path())
+ .min_depth(1)
+ .into_iter()
+ .filter_entry(|e| {
+ let s = e.file_name().to_str();
+ s != Some(".git")
+ })
+ {
+ let entry = entry?;
+
+ if !entry.file_type().is_file() {
+ continue;
+ }
+
+ let url_value_file = entry
+ .path()
+ .to_str()
+ .expect("All of these should be URLs and thus valid strings");
+ assert!(url_value_file.ends_with("/url_value"));
+
+ let url = {
+ let base = url_value_file
+ .strip_prefix(&format!("{}/", Self::base_path().display()))
+ .expect("This will exist");
+
+ let (proto, path) = base.split_once(':').expect("This will countain a :");
+
+ let path = path.strip_suffix("/url_value").expect("Will exist");
+
+ Url::from_str(&format!("{proto}:/{path}"))
+ .expect("This was a URL, it should still be one")
+ };
+ let tags = {
+ let url_values = read_to_string(PathBuf::from(url_value_file))?;
+ url_values
+ .lines()
+ .map(|line| {
+ let input = Self::from_str(line)?;
+ Ok::<_, anyhow::Error>(input.tags)
+ })
+ .collect::<Result<Vec<HashSet<NoWhitespaceString>>, _>>()?
+ .into_iter()
+ .flatten()
+ .collect()
+ };
+
+ output.push(Self { url, tags });
+ }
+
+ Ok(output)
+ }
+}
diff --git a/pkgs/by-name/ts/tskm/src/interface/mod.rs b/pkgs/by-name/ts/tskm/src/interface/mod.rs
new file mode 100644
index 00000000..1a0d934c
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/interface/mod.rs
@@ -0,0 +1,4 @@
+pub mod input;
+pub mod neorg;
+pub mod open;
+pub mod project;
diff --git a/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs b/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs
new file mode 100644
index 00000000..ea11f1e2
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs
@@ -0,0 +1,79 @@
+use std::{
+ env,
+ fs::{self, read_to_string, OpenOptions},
+ io::Write,
+ process::Command,
+};
+
+use anyhow::{bail, Result};
+
+use crate::cli::NeorgCommand;
+
+pub fn handle(command: NeorgCommand) -> Result<()> {
+ match command {
+ NeorgCommand::Task { id } => {
+ let project = id.project()?;
+ let path = dirs::data_local_dir()
+ .expect("This should exists")
+ .join("notes")
+ .join(project.get_neorg_path()?);
+
+ fs::create_dir_all(path.parent().expect("This should exist"))?;
+
+ {
+ let contents = read_to_string(&path)?;
+ if contents.contains(format!("% {}", id.to_uuid()?).as_str()) {
+ let mut options = OpenOptions::new();
+ options.append(true).create(true);
+
+ let mut file = options.open(&path)?;
+ file.write_all(format!("* TITLE (% {})", id.to_uuid()?).as_bytes())?;
+ }
+ }
+
+ let editor = env::var("EDITOR").unwrap_or("nvim".to_owned());
+ let status = Command::new(editor)
+ .args([
+ path.to_str().expect("Should be a utf-8 str"),
+ "-c",
+ format!("/% {}", id.to_uuid()?).as_str(),
+ ])
+ .status()?;
+ if !status.success() {
+ bail!("$EDITOR fail with error code: {status}");
+ }
+
+ {
+ let status = Command::new("git")
+ .args(["add", "."])
+ .current_dir(&path)
+ .status()?;
+ if !status.success() {
+ bail!("Git add . failed!");
+ }
+
+ let status = Command::new("git")
+ .args([
+ "commit",
+ "--message",
+ format!(
+ "chore({}): Update",
+ path.parent().expect("Should have a parent").display()
+ )
+ .as_str(),
+ "--no-gpg-sign",
+ ])
+ .current_dir(&path)
+ .status()?;
+ if !status.success() {
+ bail!("Git commit failed!");
+ }
+ }
+
+ {
+ id.annotate("[neorg data]]")?;
+ }
+ }
+ }
+ Ok(())
+}
diff --git a/pkgs/by-name/ts/tskm/src/interface/neorg/mod.rs b/pkgs/by-name/ts/tskm/src/interface/neorg/mod.rs
new file mode 100644
index 00000000..dc5cdf19
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/interface/neorg/mod.rs
@@ -0,0 +1,18 @@
+use std::path::PathBuf;
+
+use anyhow::Result;
+
+use crate::task::{run_task, Project};
+
+pub mod handle;
+pub use handle::handle;
+
+impl Project {
+ pub(super) fn get_neorg_path(&self) -> Result<PathBuf> {
+ let project_path = run_task(&[
+ "_get",
+ format!("rc.context.{}.rc.neorg_path", self.to_context_display()).as_str(),
+ ])?;
+ Ok(PathBuf::from(project_path.as_str()))
+ }
+}
diff --git a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
new file mode 100644
index 00000000..5738d232
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
@@ -0,0 +1,137 @@
+use std::process;
+
+use anyhow::{bail, Context, Result};
+use log::{error, info};
+
+use crate::{cli::OpenCommand, rofi, task};
+
+pub fn handle(command: OpenCommand) -> Result<()> {
+ match command {
+ OpenCommand::Review => {
+ for project in task::Project::all()? {
+ if project.is_touched() {
+ info!("Reviewing project: '{}'", project.to_project_display());
+ open_in_browser(project).with_context(|| {
+ format!(
+ "Failed to open project ('{}') in Firefox",
+ project.to_project_display()
+ )
+ })?;
+ project.untouch().with_context(|| {
+ format!(
+ "Failed to untouch project ('{}')",
+ project.to_project_display()
+ )
+ })?;
+ }
+ }
+ }
+ OpenCommand::Project { project } => {
+ let project = if let Some(p) = project {
+ p
+ } else if let Some(p) = task::Project::get_current()? {
+ p
+ } else {
+ bail!("You need to either supply a project or have a project active!");
+ };
+
+ project.touch().context("Failed to touch project")?;
+ open_in_browser(&project).context("Failed to open project")?;
+ }
+ OpenCommand::Select => {
+ let selected_project: task::Project = task::Project::from_project_string(
+ &rofi::select(
+ task::Project::all()
+ .context("Failed to get all registered projects")?
+ .iter()
+ .map(task::Project::to_project_display)
+ .collect::<Vec<_>>()
+ .as_slice(),
+ )
+ .context("Failed to get selected project")?,
+ )
+ .expect("This should work, as we send only projects in");
+
+ selected_project
+ .touch()
+ .context("Failed to touch project")?;
+
+ open_in_browser(&selected_project).context("Failed to open project")?;
+ }
+ }
+ Ok(())
+}
+
+fn open_in_browser(selected_project: &task::Project) -> Result<()> {
+ let old_project: Option<task::Project> =
+ task::Project::get_current().context("Failed to get currently active project")?;
+ // We have ensured that only one task may be active
+ let old_task: Option<task::Id> =
+ task::Id::get_current().context("Failed to get currently active task")?;
+
+ selected_project.activate().with_context(|| {
+ format!(
+ "Failed to active project: '{}'",
+ selected_project.to_project_display()
+ )
+ })?;
+
+ let tracking_task = {
+ let all_tasks = selected_project.get_tasks().with_context(|| {
+ format!(
+ "Failed to get assoctiated tasks for project: '{}'",
+ selected_project.to_project_display()
+ )
+ })?;
+
+ let tracking_task = all_tasks.into_iter().find(|t| {
+ let maybe_desc = t.description();
+ if let Ok(desc) = maybe_desc {
+ desc == "tracking"
+ } else {
+ error!(
+ "Getting task description returned error: {}",
+ maybe_desc.expect_err("We already check for Ok")
+ );
+ false
+ }
+ });
+
+ if let Some(task) = tracking_task {
+ info!(
+ "Starting task {} -> tracking",
+ selected_project.to_project_display()
+ );
+ task.start()?;
+ }
+ tracking_task
+ };
+
+ let status = process::Command::new("firefox")
+ .args([
+ "-P",
+ selected_project.to_project_display().as_str(),
+ "about:newtab",
+ ])
+ .status()
+ .context("Failed to start firefox")?;
+
+ if !status.success() {
+ error!("Firefox run exited with error.");
+ }
+
+ if let Some(task) = tracking_task {
+ task.stop()?;
+ }
+ if let Some(task) = old_task {
+ task.start()?;
+ }
+
+ if let Some(project) = old_project {
+ project.activate()?;
+ } else {
+ task::Project::clear()?;
+ }
+
+ Ok(())
+}
diff --git a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
new file mode 100644
index 00000000..a70793bc
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
@@ -0,0 +1,2 @@
+pub mod handle;
+pub use handle::handle;
diff --git a/pkgs/by-name/ts/tskm/src/interface/project/handle.rs b/pkgs/by-name/ts/tskm/src/interface/project/handle.rs
new file mode 100644
index 00000000..d1cc0b1e
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/interface/project/handle.rs
@@ -0,0 +1,87 @@
+use std::{env, fs::File, io::Write};
+
+use anyhow::{anyhow, Context, Result};
+use log::trace;
+
+use crate::{cli::ProjectCommand, task};
+
+use super::{ProjectDefinition, ProjectList, SortAlphabetically};
+
+/// # Panics
+/// If internal expectations fail.
+///
+/// # Errors
+/// If IO operations fail.
+pub fn handle(command: ProjectCommand) -> Result<()> {
+ match command {
+ ProjectCommand::List => {
+ for project in task::Project::all()? {
+ println!("'{}'", project.to_project_display());
+ }
+ }
+ ProjectCommand::Add {
+ mut new_project_name,
+ } => {
+ let project_file = env::var("TSKM_PROJECT_FILE")
+ .map_err(|err| anyhow!("The `TSKM_PROJECT_FILE` env var is unset: {err}"))?;
+
+ let mut projects_content: ProjectList =
+ serde_json::from_reader(File::open(&project_file).with_context(|| {
+ format!("Failed to open project file ('{project_file:?}') for reading")
+ })?)?;
+
+ let first = new_project_name.project_segments.remove(0);
+ if let Some(mut definition) = projects_content.0.get_mut(&first) {
+ for segment in new_project_name.project_segments {
+ if definition.subprojects.contains_key(&segment) {
+ definition = definition
+ .subprojects
+ .get_mut(&segment)
+ .expect("We checked");
+ } else {
+ let new_definition = ProjectDefinition::default();
+ let output = definition
+ .subprojects
+ .insert(segment.clone(), new_definition);
+
+ assert_eq!(output, None);
+
+ definition = definition
+ .subprojects
+ .get_mut(&segment)
+ .expect("Was just inserted");
+ }
+ }
+ } else {
+ let mut orig_definition = ProjectDefinition::default();
+ let mut definition = &mut orig_definition;
+ for segment in new_project_name.project_segments {
+ trace!("Adding segment: {segment}");
+
+ let new_definition = ProjectDefinition::default();
+
+ assert!(definition
+ .subprojects
+ .insert(segment.clone(), new_definition)
+ .is_none());
+
+ definition = definition
+ .subprojects
+ .get_mut(&segment)
+ .expect("Was just inserted");
+ }
+ assert!(projects_content.0.insert(first, orig_definition).is_none());
+ };
+
+ let mut file = File::create(&project_file).with_context(|| {
+ format!("Failed to open project file ('{project_file:?}') for writing")
+ })?;
+ serde_json::to_writer_pretty(
+ &file,
+ &SortAlphabetically::<ProjectList>(projects_content),
+ )?;
+ writeln!(file)?;
+ }
+ }
+ Ok(())
+}
diff --git a/pkgs/by-name/ts/tskm/src/interface/project/mod.rs b/pkgs/by-name/ts/tskm/src/interface/project/mod.rs
new file mode 100644
index 00000000..62069746
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/interface/project/mod.rs
@@ -0,0 +1,73 @@
+use std::collections::HashMap;
+
+use anyhow::Result;
+use serde::{Deserialize, Serialize};
+
+pub mod handle;
+pub use handle::handle;
+
+#[derive(Deserialize, Serialize)]
+struct ProjectList(HashMap<String, ProjectDefinition>);
+
+#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
+struct ProjectDefinition {
+ #[serde(default)]
+ #[serde(skip_serializing_if = "is_default")]
+ name: String,
+
+ #[serde(default)]
+ #[serde(skip_serializing_if = "is_default")]
+ prefix: String,
+
+ #[serde(default)]
+ #[serde(skip_serializing_if = "is_default")]
+ subprojects: HashMap<String, ProjectDefinition>,
+}
+
+fn is_default<T: Default + PartialEq>(input: &T) -> bool {
+ input == &T::default()
+}
+
+#[derive(Debug, Clone)]
+pub struct ProjectName {
+ project_segments: Vec<String>,
+}
+
+impl ProjectName {
+ #[must_use]
+ pub fn segments(&self) -> &[String] {
+ &self.project_segments
+ }
+
+ /// # Errors
+ /// Never.
+ pub fn try_from_project(s: &str) -> Result<Self> {
+ Ok(Self::from_project(s))
+ }
+ pub fn from_project(s: &str) -> Self {
+ let me = Self {
+ project_segments: s.split('.').map(ToOwned::to_owned).collect(),
+ };
+ me
+ }
+ pub fn from_context(s: &str) -> Self {
+ let me = Self {
+ project_segments: s.split('_').map(ToOwned::to_owned).collect(),
+ };
+ me
+ }
+}
+
+// Source: https://stackoverflow.com/a/67792465
+fn sort_alphabetically<T: Serialize, S: serde::Serializer>(
+ value: &T,
+ serializer: S,
+) -> Result<S::Ok, S::Error> {
+ let value = serde_json::to_value(value).map_err(serde::ser::Error::custom)?;
+ value.serialize(serializer)
+}
+
+#[derive(Serialize)]
+pub(super) struct SortAlphabetically<T: Serialize>(
+ #[serde(serialize_with = "sort_alphabetically")] T,
+);
diff --git a/pkgs/by-name/ts/tskm/src/main.rs b/pkgs/by-name/ts/tskm/src/main.rs
new file mode 100644
index 00000000..7fc9c0d4
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/main.rs
@@ -0,0 +1,66 @@
+#![allow(clippy::missing_panics_doc)]
+#![allow(clippy::missing_errors_doc)]
+
+use anyhow::Result;
+use clap::Parser;
+
+use crate::interface::{input, neorg, open, project};
+
+pub mod cli;
+pub mod interface;
+pub mod rofi;
+pub mod task;
+
+use crate::cli::{CliArgs, Command};
+
+fn main() -> Result<(), anyhow::Error> {
+ // TODO: Support these completions for the respective types <2025-04-04>
+ //
+ // ID_GENERATION_FUNCTION
+ // ```sh
+ // context="$(task _get rc.context)"
+ // if [ "$context" ]; then
+ // filter="project:$context"
+ // else
+ // filter="0-10000"
+ // fi
+ // tasks="$(task "$filter" _ids)"
+ //
+ // if [ "$tasks" ]; then
+ // echo "$tasks" | xargs task _zshids | awk -F: -v q="'" '{gsub(/'\''/, q "\\" q q ); print $1 ":" q $2 q}'
+ // fi
+ // ```
+ //
+ // ARGUMENTS:
+ // ID | *([0-9]) := [[%ID_GENERATION_FUNCTION]]
+ // The function displays all possible IDs of the eligible tasks.
+ //
+ // WS := %ALL_WORKSPACES
+ // All possible workspaces.
+ //
+ // P := %ALL_PROJECTS_PIPE
+ // The possible project.
+ //
+ // F := [[fd . --max-depth 3]]
+ // A URL-Input file to use as source.
+ let args = CliArgs::parse();
+
+ stderrlog::new()
+ .module(module_path!())
+ .quiet(false)
+ .show_module_names(true)
+ .color(stderrlog::ColorChoice::Auto)
+ .verbosity(5)
+ .timestamp(stderrlog::Timestamp::Off)
+ .init()
+ .expect("Let's just hope that this does not panic");
+
+ match args.command {
+ Command::Inputs { command } => input::handle(command)?,
+ Command::Neorg { command } => neorg::handle(command)?,
+ Command::Open { command } => open::handle(command)?,
+ Command::Projects { command } => project::handle(command)?,
+ }
+
+ Ok(())
+}
diff --git a/pkgs/by-name/ts/tskm/src/rofi/mod.rs b/pkgs/by-name/ts/tskm/src/rofi/mod.rs
new file mode 100644
index 00000000..a0591b7f
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/rofi/mod.rs
@@ -0,0 +1,37 @@
+use std::{
+ io::Write,
+ process::{Command, Stdio},
+};
+
+use anyhow::{Context, Result};
+
+pub fn select(options: &[String]) -> Result<String> {
+ let mut child = Command::new("rofi")
+ .args(["-sep", "\n", "-dmenu"])
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()
+ .context("Failed to spawn rofi")?;
+
+ let mut stdin = child
+ .stdin
+ .take()
+ .expect("We piped this, so should be available");
+
+ stdin
+ .write_all(options.join("\n").as_bytes())
+ .context("Failed to write to rofi's stdin")?;
+
+ let output = child
+ .wait_with_output()
+ .context("Failed to wait for rofi's output")?;
+
+ let selected = String::from_utf8(output.stdout.clone()).with_context(|| {
+ format!(
+ "Failed to decode '{}' as utf8",
+ String::from_utf8_lossy(&output.stdout)
+ )
+ })?;
+
+ Ok(selected.trim_end().to_owned())
+}
diff --git a/pkgs/by-name/ts/tskm/src/task/mod.rs b/pkgs/by-name/ts/tskm/src/task/mod.rs
new file mode 100644
index 00000000..c3a6d614
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/src/task/mod.rs
@@ -0,0 +1,322 @@
+use std::{
+ fmt::Display,
+ fs::{self, read_to_string, File},
+ path::PathBuf,
+ process::Command,
+ str::FromStr,
+ sync::OnceLock,
+};
+
+use anyhow::{bail, Context, Result};
+use log::{debug, info, trace};
+
+use crate::interface::project::ProjectName;
+
+/// The `taskwarrior` id of a task.
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
+pub struct Id {
+ id: u64,
+}
+impl Id {
+ /// # Errors
+ /// When `task` execution fails
+ pub fn get_current() -> Result<Option<Self>> {
+ // We have ensured that only one task may be active
+ let self_str = run_task(&["+ACTIVE", "_ids"])?;
+
+ if self_str.is_empty() {
+ Ok(None)
+ } else {
+ Self::from_str(&self_str).map(Some)
+ }
+ }
+
+ /// # Errors
+ /// When `task` execution fails
+ pub fn to_uuid(&self) -> Result<String> {
+ let uuid = run_task(&[self.to_string().as_str(), "uuids"])?;
+
+ Ok(uuid)
+ }
+
+ /// # Panics
+ /// When internal assertions fail.
+ /// # Errors
+ /// When `task` execution fails
+ pub fn annotate(&self, message: &str) -> Result<()> {
+ run_task(&["annotate", self.to_string().as_str(), "--", message])?;
+ Ok(())
+ }
+
+ /// # Panics
+ /// When internal assertions fail.
+ /// # Errors
+ /// When `task` execution fails
+ pub fn start(&self) -> Result<()> {
+ info!("Activating {self}");
+
+ let output = run_task(&["start", self.to_string().as_str()])?;
+ assert!(output.is_empty());
+ Ok(())
+ }
+ /// # Panics
+ /// When internal assertions fail.
+ /// # Errors
+ /// When `task` execution fails
+ pub fn stop(&self) -> Result<()> {
+ info!("Stopping {self}");
+
+ let output = run_task(&["stop", self.to_string().as_str()])?;
+ assert!(output.is_empty());
+ Ok(())
+ }
+
+ /// # Panics
+ /// When internal assertions fail.
+ /// # Errors
+ /// When `task` execution fails
+ pub fn description(&self) -> Result<String> {
+ let output = run_task(&["rc.context=none", "_zshids", self.to_string().as_str()])?;
+ let (id, desc) = output
+ .split_once(':')
+ .expect("The output should always contain one colon");
+ assert_eq!(id.parse::<Id>().expect("This should be a valid id"), *self);
+ Ok(desc.to_owned())
+ }
+
+ /// # Panics
+ /// When internal assertions fail.
+ /// # Errors
+ /// When `task` execution fails
+ pub fn project(&self) -> Result<Project> {
+ let output = run_task(&[
+ "rc.context=none",
+ "_get",
+ format!("{self}.project").as_str(),
+ ])?;
+ let project = Project::from_project_string(output.as_str())
+ .expect("This comes from tw, it should be valid");
+ Ok(project)
+ }
+}
+
+impl Display for Id {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.id.fmt(f)
+ }
+}
+
+impl FromStr for Id {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let id = u64::from_str(s)?;
+ Ok(Self { id })
+ }
+}
+
+/// A registered task Project
+#[derive(Debug, Clone, PartialEq)]
+pub struct Project {
+ /// The project name.
+ /// For example:
+ /// ```no_run
+ /// &["trinitrix", "testing", "infra"]
+ /// ```
+ name: Vec<String>,
+}
+
+static ALL_CACHE: OnceLock<Vec<Project>> = OnceLock::new();
+impl Project {
+ #[must_use]
+ pub fn to_project_display(&self) -> String {
+ self.name.join(".")
+ }
+ #[must_use]
+ pub fn to_context_display(&self) -> String {
+ self.name.join("_")
+ }
+
+ /// # Errors
+ /// - When the string does not encode a previously registered project.
+ /// - When the string does not adhere to the project syntax.
+ pub fn from_project_string(s: &str) -> Result<Self> {
+ Self::from_input(s, ProjectName::from_project)
+ }
+
+ /// # Errors
+ /// - When the string does not encode a previously registered project.
+ /// - When the string does not adhere to the context syntax.
+ pub fn from_context_string(s: &str) -> Result<Self> {
+ Self::from_input(s, ProjectName::from_context)
+ }
+
+ fn from_input<F>(s: &str, f: F) -> Result<Self>
+ where
+ F: Fn(&str) -> ProjectName,
+ {
+ if s.is_empty() {
+ bail!("Your project is empty")
+ }
+
+ let all = Self::all()?;
+ let me = Self::from_project_name_unchecked(&f(s));
+ if all.contains(&me) {
+ Ok(me)
+ } else {
+ bail!(
+ "Your project '{}' is not registered!",
+ me.to_project_display()
+ );
+ }
+ }
+ fn from_project_name_unchecked(pn: &ProjectName) -> Self {
+ Self {
+ name: pn.segments().to_owned(),
+ }
+ }
+
+ /// Return all known valid projects.
+ ///
+ /// # Errors
+ /// When file operations fail.
+ ///
+ /// # Panics
+ /// Only when internal assertions fail.
+ pub fn all<'a>() -> Result<&'a [Project]> {
+ // Inlined from `OnceLock::get_or_try_init`
+ {
+ let this = &ALL_CACHE;
+ let f = || {
+ let file = dirs::config_local_dir()
+ .expect("Should be some")
+ .join("tskm/projects.list");
+ let contents = read_to_string(&file)
+ .with_context(|| format!("Failed to read file: '{}'", file.display()))?;
+
+ Ok::<_, anyhow::Error>(
+ contents
+ .lines()
+ .map(|s| Self::from_project_name_unchecked(&ProjectName::from_project(s)))
+ .collect::<Vec<_>>(),
+ )
+ };
+
+ // Fast path check
+ // NOTE: We need to perform an acquire on the state in this method
+ // in order to correctly synchronize `LazyLock::force`. This is
+ // currently done by calling `self.get()`, which in turn calls
+ // `self.is_initialized()`, which in turn performs the acquire.
+ if let Some(value) = this.get() {
+ return Ok(value);
+ }
+
+ this.set(f()?).expect(
+ "This should always be able to take our value, as we initialize only once.",
+ );
+
+ Ok(this.get().expect("This was initialized"))
+ }
+ }
+
+ fn touch_dir(&self) -> PathBuf {
+ let lock_dir = dirs::data_dir()
+ .expect("Should be found")
+ .join("tskm/review");
+ lock_dir.join(format!("{}.opened", self.to_project_display()))
+ }
+
+ /// Mark this project as having been interacted with.
+ ///
+ /// # Errors
+ /// When IO operations fail.
+ pub fn touch(&self) -> Result<()> {
+ let lock_file = self.touch_dir();
+
+ File::create(&lock_file)
+ .with_context(|| format!("Failed to create lock_file at: {}", lock_file.display()))?;
+
+ Ok(())
+ }
+ /// Returns [`true`] if it was previously [`Self::touch`]ed.
+ #[must_use]
+ pub fn is_touched(&self) -> bool {
+ let lock_file = self.touch_dir();
+ lock_file.exists()
+ }
+ /// Mark this project as having not been interacted with.
+ ///
+ /// # Errors
+ /// When IO operations fail.
+ pub fn untouch(&self) -> Result<()> {
+ let lock_file = self.touch_dir();
+
+ fs::remove_file(&lock_file)
+ .with_context(|| format!("Failed to create lock_file at: {}", lock_file.display()))?;
+
+ Ok(())
+ }
+
+ /// # Errors
+ /// When `task` execution fails.
+ pub fn get_tasks(&self) -> Result<Vec<Id>> {
+ let output = run_task(&[
+ "rc.context=none",
+ format!("project:{}", self.to_project_display()).as_str(),
+ "_ids",
+ ])?;
+
+ if output.is_empty() {
+ Ok(vec![])
+ } else {
+ output
+ .lines()
+ .map(Id::from_str)
+ .collect::<Result<Vec<Id>>>()
+ }
+ }
+
+ /// # Errors
+ /// When `task` execution fails.
+ pub fn activate(&self) -> Result<()> {
+ debug!("Setting project {}", self.to_context_display());
+
+ run_task(&["context", self.to_context_display().as_str()]).map(|_| ())
+ }
+ /// # Errors
+ /// When `task` execution fails.
+ pub fn clear() -> Result<()> {
+ debug!("Clearing active project");
+
+ run_task(&["context", "none"]).map(|_| ())
+ }
+
+ /// # Errors
+ /// When `task` execution fails.
+ pub fn get_current() -> Result<Option<Self>> {
+ let self_str = run_task(&["_get", "rc.context"])?;
+
+ if self_str.is_empty() {
+ Ok(None)
+ } else {
+ Self::from_context_string(&self_str).map(Some)
+ }
+ }
+}
+
+pub(crate) fn run_task(args: &[&str]) -> Result<String> {
+ debug!("Running task command: `task {}`", args.join(" "));
+
+ let output = Command::new("task")
+ .args(args)
+ .output()
+ .with_context(|| format!("Failed to run `task {}`", args.join(" ")))?;
+
+ let stdout = String::from_utf8(output.stdout).context("Failed to read task output as utf8")?;
+ let stderr = String::from_utf8(output.stderr).context("Failed to read task output as utf8")?;
+
+ trace!("Output (stdout): '{}'", stdout.trim());
+ trace!("Output (stderr): '{}'", stderr.trim());
+
+ Ok(stdout.trim().to_owned())
+}
diff --git a/pkgs/by-name/ts/tskm/update.sh b/pkgs/by-name/ts/tskm/update.sh
new file mode 100755
index 00000000..9268caf2
--- /dev/null
+++ b/pkgs/by-name/ts/tskm/update.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cargo update && cargo upgrade