refactor: nixify / clean remaining dots scripts
This commit is contained in:
1 parent
9030f33842
commit
9110789527
53 files changed
+265
-655
No files matched your search
@@ -11,6 +11,10 @@ let
|
||||
in
|
||||
{
|
||||
options.anki.enable = lib.mkEnableOption "Anki";
|
||||
options.anki.reviewsCache = lib.mkOption {
|
||||
default = "/tmp/anki-reviews";
|
||||
type = lib.types.str;
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
secrets.files.anki = [
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
script =
|
||||
name: args:
|
||||
pkgs.writeShellApplication (
|
||||
{
|
||||
inherit name;
|
||||
text = builtins.readFile ./scripts/${name};
|
||||
}
|
||||
// args
|
||||
);
|
||||
pythonScript =
|
||||
name: runtimeInputs:
|
||||
pkgs.writeShellApplication {
|
||||
inherit name runtimeInputs;
|
||||
text = ''exec ${lib.getExe pkgs.python3} ${pkgs.writeTextDir name (builtins.readFile ./scripts/${name})}/${name} "$@"'';
|
||||
};
|
||||
ssh-hosts = script "ssh-hosts" {
|
||||
runtimeInputs = with pkgs; [
|
||||
gnugrep
|
||||
gawk
|
||||
];
|
||||
};
|
||||
scripts = with pkgs; {
|
||||
bak.runtimeInputs = [ coreutils ];
|
||||
notify.runtimeInputs = [ libnotify ];
|
||||
pdftitle.runtimeInputs = [
|
||||
poppler-utils
|
||||
gawk
|
||||
coreutils
|
||||
];
|
||||
fzf-book.runtimeInputs = [
|
||||
findutils
|
||||
fzf
|
||||
coreutils
|
||||
];
|
||||
fzf-ssh.runtimeInputs = [
|
||||
ssh-hosts
|
||||
fzf
|
||||
];
|
||||
save-ssh-host = {
|
||||
runtimeInputs = [
|
||||
ssh-hosts
|
||||
fzf
|
||||
];
|
||||
excludeShellChecks = [ "SC2029" ];
|
||||
};
|
||||
fzf-fontnames.runtimeInputs = [
|
||||
fontconfig
|
||||
fzf
|
||||
gawk
|
||||
coreutils
|
||||
];
|
||||
get-anki-decks.runtimeInputs = [
|
||||
curl
|
||||
jq
|
||||
];
|
||||
get-anki-reviews-status = {
|
||||
runtimeInputs = [ coreutils ];
|
||||
runtimeEnv.ANKI_REVIEWS = config.anki.reviewsCache;
|
||||
};
|
||||
};
|
||||
pythonScripts = with pkgs; {
|
||||
pomo = [ libnotify ];
|
||||
taskdeps = [ ];
|
||||
};
|
||||
in
|
||||
{
|
||||
options.bin.enable = lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
|
||||
config = lib.mkIf config.bin.enable {
|
||||
home.packages = [
|
||||
ssh-hosts
|
||||
]
|
||||
++ lib.mapAttrsToList script scripts
|
||||
++ lib.mapAttrsToList pythonScript pythonScripts;
|
||||
};
|
||||
}
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
# back up a file by copying it to a new file with a .bak extension
|
||||
# reference: https://askubuntu.com/questions/962489/is-there-any-way-to-create-backup-copy-of-a-file-without-type-its-name-twice
|
||||
|
||||
usage() {
|
||||
echo "Usage: bak [-t|--timestamp] <file to back up>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
timestamp=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-t|--timestamp)
|
||||
timestamp=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ $# -eq 1 ] || usage
|
||||
|
||||
if $timestamp; then
|
||||
date="$(date +%Y-%m-%d_%H-%M-%S)"
|
||||
cp -vpn "$1"{,."$date".bak}
|
||||
else
|
||||
cp -vpn "$1"{,.bak}
|
||||
fi
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
# Generate menu of book filenames and save paths
|
||||
# Preview window contains metadata
|
||||
|
||||
function get_book_paths {
|
||||
find "$HOME/doc/books/" -regex '.*\.\(pdf\|epub\|djvu\)' -type f | sort
|
||||
}
|
||||
|
||||
function select_file {
|
||||
get_book_paths | fzf --delimiter=/ --with-nth=-1
|
||||
}
|
||||
|
||||
function open {
|
||||
if [ -n "$1" ]; then
|
||||
echo "Opening \"$1\""
|
||||
zathura "$1" --fork
|
||||
else
|
||||
echo "No file selected"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--open) open "$(select_file)" ;;
|
||||
--help) printf "open \n" >&2 ;;
|
||||
*) open "$(select_file)" ;;
|
||||
esac
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
fn="/tmp/fontnames.txt"
|
||||
|
||||
contains_dash() {
|
||||
[[ "$1" =~ - ]]
|
||||
}
|
||||
|
||||
update() {
|
||||
: > "$fn"
|
||||
|
||||
font_list=$(fc-list -f "%{fullname}\n")
|
||||
|
||||
echo "$font_list" | while read -r line ; do
|
||||
|
||||
first="$(echo "$line" | cut -d',' -f1)"
|
||||
last="$(echo "$line" | cut -d',' -f2)"
|
||||
|
||||
if contains_dash "$first"; then
|
||||
echo "$last" >> "$fn"
|
||||
else
|
||||
echo "$first" >> "$fn"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--update) update;;
|
||||
*) sort -u "$fn" | awk 'NF' | fzf;;
|
||||
esac
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
ssh "$(ssh-hosts | fzf)"
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
curl 127.0.0.1:8765 -X POST -d '{"action": "deckNames", "version": 6}' | jq '.result[]' -r
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# Get Anki review status from exit code
|
||||
|
||||
num_reviews="$(cat "$ANKI_REVIEWS")"
|
||||
|
||||
if [ "$num_reviews" -ge "400" ]
|
||||
then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
# Pipe into notify-send
|
||||
#
|
||||
# e.g. `echo "Hello world" | notify`
|
||||
|
||||
read -r msg || true
|
||||
notify-send "$msg" "$@"
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
pdfinfo "$1" | head -n 1 | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}'
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim: set filetype=python:
|
||||
"""
|
||||
Pomodoro timer
|
||||
|
||||
- Writes pomodoro timer to temporary file so statusbar can read it
|
||||
- Notification on session finish
|
||||
- Notification on break finish
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
from argparse import ArgumentParser
|
||||
from time import sleep
|
||||
|
||||
POMO_PATH = os.path.join(
|
||||
os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share")), "pomo"
|
||||
)
|
||||
|
||||
|
||||
@atexit.register
|
||||
def clear():
|
||||
if os.path.exists(POMO_PATH):
|
||||
os.remove(POMO_PATH)
|
||||
|
||||
|
||||
def format_mins_secs(mins, secs):
|
||||
return f"{mins:02d}:{secs:02d}"
|
||||
|
||||
|
||||
def countdown(duration):
|
||||
while duration != 0:
|
||||
mins = duration // 60
|
||||
secs = duration % 60
|
||||
with open(POMO_PATH, "w") as f:
|
||||
f.write(format_mins_secs(mins, secs))
|
||||
sleep(1)
|
||||
duration -= 1
|
||||
|
||||
|
||||
def main(args):
|
||||
work_duration = args.work_duration * 60
|
||||
break_duration = args.break_duration * 60
|
||||
repeats = args.repeats
|
||||
|
||||
while repeats != 0:
|
||||
subprocess.run(["notify-send", "Get started"])
|
||||
countdown(work_duration)
|
||||
if break_duration != 0:
|
||||
subprocess.run(["notify-send", "Time for a break"])
|
||||
countdown(break_duration)
|
||||
subprocess.run(["notify-send", "Break is over, back to work"])
|
||||
repeats -= 1
|
||||
|
||||
|
||||
def handle_signal(signal, frame):
|
||||
# Wait for clear to finish
|
||||
clear()
|
||||
print("Exiting")
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-w", "--work-duration", type=int, help="Session duration", default=25
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b", "--break-duration", type=int, help="Break duration", default=5
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r", "--repeats", type=int, help="Numer of sessions", default=1
|
||||
)
|
||||
parser.add_argument("-c", "--clear", action="store_true", help="Clear timer")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
signal.signal(signal.SIGINT, handle_signal)
|
||||
signal.signal(signal.SIGTERM, handle_signal)
|
||||
|
||||
if args.clear:
|
||||
clear()
|
||||
else:
|
||||
main(args)
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
selected_hosts="$(ssh-hosts | fzf -m)"
|
||||
|
||||
for host in $selected_hosts; do
|
||||
echo "Saving $host"
|
||||
directories="$(ssh "$host" ls | fzf -m)"
|
||||
for directory in $directories; do
|
||||
echo "Saving $host:$directory"
|
||||
ssh "$host" "(tar cvzf - ~/$directory)" > "${host}_${directory}.tar.gz"
|
||||
done
|
||||
done
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
grep -E 'Host [a-z0-9\-]*$' ~/.ssh/config | awk '{print $2}'
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def get_task_data():
|
||||
command = ["task", "+PENDING", "or", "+WAITING", "-COMPLETED", "-DELETED", "export"]
|
||||
return json.loads(subprocess.check_output(command))
|
||||
|
||||
|
||||
def parse_task_data(data):
|
||||
dependency_graph = defaultdict(list)
|
||||
task_details = {}
|
||||
dependent_tasks = set()
|
||||
|
||||
for task in data:
|
||||
task_id = task["uuid"]
|
||||
task_details[task_id] = {
|
||||
"id": task.get("id", "?"),
|
||||
"description": task.get("description", "No description"),
|
||||
"status": task.get("status", "Unknown status"),
|
||||
}
|
||||
for dependency in task.get("depends", []):
|
||||
dependency_graph[dependency].append(task_id)
|
||||
dependent_tasks.add(task_id)
|
||||
|
||||
root_tasks = set(task_details.keys()) - dependent_tasks
|
||||
return task_details, dependency_graph, root_tasks
|
||||
|
||||
|
||||
def render_dependency_dag(task_details, dependency_graph, root_tasks, show_id):
|
||||
dag_lines = []
|
||||
global_visited = set()
|
||||
|
||||
def dfs(task_id, prefix="", is_last=True, visited=None):
|
||||
if visited is None:
|
||||
visited = set()
|
||||
|
||||
if task_id in visited:
|
||||
return
|
||||
|
||||
visited.add(task_id)
|
||||
global_visited.add(task_id)
|
||||
|
||||
task_info = task_details[task_id]
|
||||
task_line = f"{prefix}{'└── ' if is_last else '├── '}{str(task_info['id']) + ': ' if show_id else ''}{task_info['description']} ({task_info['status']})"
|
||||
dag_lines.append(task_line)
|
||||
|
||||
children = dependency_graph.get(task_id, [])
|
||||
for idx, child in enumerate(children):
|
||||
child_is_last = idx == len(children) - 1
|
||||
child_prefix = prefix + (" " if is_last else "│ ")
|
||||
dfs(child, child_prefix, child_is_last, visited.copy())
|
||||
|
||||
root_tasks_with_children = [
|
||||
root for root in root_tasks if dependency_graph.get(root, [])
|
||||
]
|
||||
for root in sorted(
|
||||
root_tasks_with_children,
|
||||
key=lambda x: len(dependency_graph.get(x, [])),
|
||||
reverse=True,
|
||||
):
|
||||
if root not in global_visited:
|
||||
dfs(root)
|
||||
dag_lines.append("")
|
||||
|
||||
return "\n".join(dag_lines).rstrip()
|
||||
|
||||
|
||||
def main(args):
|
||||
data = get_task_data()
|
||||
task_details, dependency_graph, root_tasks = parse_task_data(data)
|
||||
ascii_dag = render_dependency_dag(
|
||||
task_details, dependency_graph, root_tasks, show_id=args.show_id
|
||||
)
|
||||
print(ascii_dag)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generates a task dependency DAG for Taskwarrior tasks."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show-id",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Include task IDs in the output.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
main(args)
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
lib',
|
||||
...
|
||||
}:
|
||||
|
||||
@@ -22,11 +21,6 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
addBinToPath = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
extraInit = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
@@ -65,7 +59,5 @@ in
|
||||
${cfg.extraInit}
|
||||
'';
|
||||
};
|
||||
|
||||
home.sessionPath = lib.optional cfg.addBinToPath "${lib'.dotsPath}/.bin";
|
||||
};
|
||||
}
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
# Anki review percentage for statusbar
|
||||
|
||||
num_to_review=200
|
||||
|
||||
# Get current card count from Anki
|
||||
count=$(curl -s 127.0.0.1:8765 -X POST -d '{"action": "getNumCardsReviewedToday", "version": 6}' | jq '.result') || count=
|
||||
|
||||
if [ -n "$count" ]; then
|
||||
echo "$count" > "$ANKI_REVIEWS"
|
||||
elif [ ! -s "$ANKI_REVIEWS" ]; then
|
||||
echo "-1" > "$ANKI_REVIEWS"
|
||||
fi
|
||||
|
||||
num_reviews=$(cat "$ANKI_REVIEWS")
|
||||
echo -n " Reviews: $(awk -v n="$num_reviews" -v t="$num_to_review" 'BEGIN { printf "%.2f%%", 100 * n / t }') "
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
pomo="${XDG_DATA_HOME:-$HOME/.local/share}/pomo"
|
||||
if [ -f "$pomo" ]; then cat "$pomo"; fi
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
# Render contents of $HOME/.project if present
|
||||
if [ -f "$HOME/.project" ]; then cat "$HOME/.project"; fi
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
num_done="$(task end.after:today status:completed count)"
|
||||
num_pending="$(($(task count status:pending) + num_done))"
|
||||
num_waiting="$(task count status:waiting)"
|
||||
|
||||
echo "Tasks: $num_done/$num_pending+$num_waiting"
|
||||
@@ -1,59 +1,115 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
status =
|
||||
name: args:
|
||||
lib.getExe (
|
||||
pkgs.writeShellApplication (
|
||||
{
|
||||
inherit name;
|
||||
text = builtins.readFile ./scripts/${name};
|
||||
}
|
||||
// args
|
||||
)
|
||||
);
|
||||
in
|
||||
{
|
||||
programs.waybar.settings = [
|
||||
{
|
||||
height = 16;
|
||||
spacing = 4;
|
||||
modules-left = [ "niri/workspaces" ];
|
||||
modules-right = [
|
||||
"pulseaudio"
|
||||
"memory"
|
||||
"cpu"
|
||||
"network"
|
||||
"clock"
|
||||
"battery"
|
||||
];
|
||||
clock = {
|
||||
format = "W{:%V %d %b %H:%M}";
|
||||
tooltip-format = "{calendar}";
|
||||
format-alt = "{:%Y-%m-%d %H:%M:%S}";
|
||||
};
|
||||
battery = {
|
||||
bat = "BAT0";
|
||||
adapter = "ADP1";
|
||||
interval = 5;
|
||||
full-at = 99;
|
||||
states = {
|
||||
good = 80;
|
||||
warning = 20;
|
||||
critical = 10;
|
||||
(
|
||||
{
|
||||
height = 16;
|
||||
spacing = 4;
|
||||
modules-left = [ "niri/workspaces" ];
|
||||
modules-right = [
|
||||
"custom/project"
|
||||
"custom/pomo"
|
||||
]
|
||||
++ lib.optional config.taskwarrior.enable "custom/tasks"
|
||||
++ lib.optional config.anki.enable "custom/anki"
|
||||
++ [
|
||||
"pulseaudio"
|
||||
"memory"
|
||||
"cpu"
|
||||
"network"
|
||||
"clock"
|
||||
"battery"
|
||||
];
|
||||
clock = {
|
||||
format = "W{:%V %d %b %H:%M}";
|
||||
tooltip-format = "{calendar}";
|
||||
format-alt = "{:%Y-%m-%d %H:%M:%S}";
|
||||
};
|
||||
format = "{capacity}%--";
|
||||
format-charging = "{capacity}%++";
|
||||
format-plugged = "{capacity}%";
|
||||
format-alt = "{time} {power}W";
|
||||
};
|
||||
pulseaudio = {
|
||||
format = "VOL {volume}%";
|
||||
format-muted = "muted";
|
||||
on-click = "pavucontrol";
|
||||
};
|
||||
memory = {
|
||||
interval = 2;
|
||||
format = "RAM {percentage}%";
|
||||
format-alt = "RAM {used:0.1f}G/{total:0.1f}G";
|
||||
};
|
||||
cpu = {
|
||||
interval = 2;
|
||||
format = "CPU {usage}%";
|
||||
format-alt = "CPU {avg_frequency}GHz";
|
||||
};
|
||||
network = {
|
||||
interval = 5;
|
||||
format-wifi = "{ifname} {ipaddr} {essid}";
|
||||
format-ethernet = "{ifname} {ipaddr}";
|
||||
format-disconnected = "{ifname} disconnected";
|
||||
tooltip-format = "{ifname}: {ipaddr}/{cidr}";
|
||||
};
|
||||
}
|
||||
battery = {
|
||||
bat = "BAT0";
|
||||
adapter = "ADP1";
|
||||
interval = 5;
|
||||
full-at = 99;
|
||||
states = {
|
||||
good = 80;
|
||||
warning = 20;
|
||||
critical = 10;
|
||||
};
|
||||
format = "{capacity}%--";
|
||||
format-charging = "{capacity}%++";
|
||||
format-plugged = "{capacity}%";
|
||||
format-alt = "{time} {power}W";
|
||||
};
|
||||
pulseaudio = {
|
||||
format = "VOL {volume}%";
|
||||
format-muted = "muted";
|
||||
on-click = "pavucontrol";
|
||||
};
|
||||
memory = {
|
||||
interval = 2;
|
||||
format = "RAM {percentage}%";
|
||||
format-alt = "RAM {used:0.1f}G/{total:0.1f}G";
|
||||
};
|
||||
cpu = {
|
||||
interval = 2;
|
||||
format = "CPU {usage}%";
|
||||
format-alt = "CPU {avg_frequency}GHz";
|
||||
};
|
||||
network = {
|
||||
interval = 5;
|
||||
format-wifi = "{ifname} {ipaddr} {essid}";
|
||||
format-ethernet = "{ifname} {ipaddr}";
|
||||
format-disconnected = "{ifname} disconnected";
|
||||
tooltip-format = "{ifname}: {ipaddr}/{cidr}";
|
||||
};
|
||||
"custom/project" = {
|
||||
exec = status "sb-project" { runtimeInputs = [ pkgs.coreutils ]; };
|
||||
interval = 30;
|
||||
};
|
||||
"custom/pomo" = {
|
||||
exec = status "sb-pomo" { runtimeInputs = [ pkgs.coreutils ]; };
|
||||
interval = 1;
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs config.taskwarrior.enable {
|
||||
"custom/tasks" = {
|
||||
exec = status "sb-tasks" { runtimeInputs = [ config.programs.taskwarrior.package ]; };
|
||||
interval = 60;
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs config.anki.enable {
|
||||
"custom/anki" = {
|
||||
exec = status "sb-anki" {
|
||||
runtimeInputs = with pkgs; [
|
||||
coreutils
|
||||
curl
|
||||
gawk
|
||||
jq
|
||||
];
|
||||
runtimeEnv.ANKI_REVIEWS = config.anki.reviewsCache;
|
||||
};
|
||||
interval = 300;
|
||||
};
|
||||
}
|
||||
)
|
||||
];
|
||||
}
|
||||
Reference in new issue
Block a user