mirror of
https://github.com/elenapan/dotfiles.git
synced 2026-01-21 23:29:30 +08:00
253 lines
8.3 KiB
Lua
253 lines
8.3 KiB
Lua
-- Provides:
|
|
-- evil::tasks
|
|
-- tasks array
|
|
local awful = require("awful")
|
|
local gears = require("gears")
|
|
local helpers = require("helpers")
|
|
|
|
local org_deadline_warning_days = 14
|
|
local org_schedule_warning_days = 7
|
|
|
|
local home = os.getenv("HOME")
|
|
local org_files = {
|
|
home.."/notes/todo.org",
|
|
home.."/notes/birthdays.org",
|
|
home.."/notes/finance.org",
|
|
home.."/notes/health.org",
|
|
home.."/notes/home.org"
|
|
}
|
|
local file_list_str = table.concat(org_files, " ")
|
|
|
|
local function lines_in_string(s)
|
|
local lines = {}
|
|
for line in s:gmatch("([^\n]*)\n?") do
|
|
if line ~= "" then
|
|
table.insert(lines, line)
|
|
end
|
|
end
|
|
return lines
|
|
end
|
|
|
|
local function calculate_recurrence(date, recurrence)
|
|
if not recurrence or recurrence == "" then return nil end
|
|
local amount, unit = recurrence:match("%+(%d+)([dmy])")
|
|
amount = tonumber(amount)
|
|
if unit == "d" then
|
|
return os.time({year = os.date("*t", date).year, month = os.date("*t", date).month, day = os.date("*t", date).day + amount})
|
|
elseif unit == "m" then
|
|
local year = os.date("*t", date).year
|
|
local month = os.date("*t", date).month + amount
|
|
year = year + math.floor((month - 1) / 12)
|
|
month = (month - 1) % 12 + 1
|
|
local day = math.min(os.date("*t", date).day, os.date("*t", os.time({year = year, month = month + 1, day = 0})).day)
|
|
return os.time({year = year, month = month, day = day})
|
|
elseif unit == "y" then
|
|
return os.time({year = os.date("*t", date).year + amount, month = os.date("*t", date).month, day = os.date("*t", date).day})
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function pretty_print_date(input_date)
|
|
-- Get dates as objects and zero out hour, min and sec to get the timestamp
|
|
-- at midnight
|
|
local today = os.time()
|
|
local today_obj = os.date("*t", today)
|
|
today_obj.hour, today_obj.min, today_obj.sec = 0, 0, 0
|
|
local today_midnight = os.time(today_obj)
|
|
|
|
local input_date_obj = os.date("*t", input_date)
|
|
input_date_obj.hour, input_date_obj.min, input_date_obj.sec = 0, 0, 0
|
|
local input_date_midnight = os.time(input_date_obj)
|
|
|
|
local delta = os.difftime(input_date_midnight, today_midnight) / (24 * 3600)
|
|
if delta == 0 then
|
|
return "today"
|
|
elseif delta == 1 then
|
|
return "tomorrow"
|
|
elseif delta == -1 then
|
|
return "yesterday"
|
|
-- elseif delta >= -5 and delta <= -2 then
|
|
-- return math.abs(delta) .. " days ago"
|
|
-- elseif delta >= 2 and delta <= 5 then
|
|
-- return "in " .. delta .. " days"
|
|
else
|
|
return os.date("%A %d %B", input_date):lower()
|
|
end
|
|
end
|
|
|
|
local fields_to_compare = {
|
|
"file",
|
|
"title",
|
|
"type",
|
|
"date_timestamp",
|
|
"recurrence",
|
|
"time_start",
|
|
"time_end",
|
|
}
|
|
local function compare_tasks(task1, task2)
|
|
for _, field in ipairs(fields_to_compare) do
|
|
if task1[field] ~= task2[field] then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function compare_task_arrays(array1, array2)
|
|
if #array1 ~= #array2 then
|
|
return false
|
|
end
|
|
|
|
for i = 1, #array1 do
|
|
if not compare_tasks(array1[i], array2[i]) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local old_tasks = {}
|
|
local function update_tasks()
|
|
-- Search for the pattern and also print the line before it (task title)
|
|
local search_entries_cmd = 'grep -B 1 -E "^(SCHEDULED|DEADLINE):" '..file_list_str..' | grep -v -E "^--$"'
|
|
|
|
awful.spawn.easy_async_with_shell(search_entries_cmd, function(out)
|
|
local entries = {}
|
|
local lines = lines_in_string(out)
|
|
|
|
-- Every entry is 2 lines (title and date)
|
|
for i=1,#lines,2 do
|
|
local title_line = lines[i]
|
|
local event_line = lines[i+1]
|
|
|
|
local full_path, _, status_and_title = title_line:match("^(.+)%-(%*+)%s*(.*)")
|
|
local file = full_path:match("^.*/([^/]+)%.%w+$")
|
|
|
|
local status, title
|
|
local first_word, remaining = status_and_title:match("^(%S+)%s*(.*)")
|
|
if first_word and first_word:match("^[A-Z]+$") then
|
|
status = first_word
|
|
title = remaining
|
|
else
|
|
title = status_and_title
|
|
end
|
|
|
|
local event_type, date_str = event_line:match(":([A-Z]+): <(%d%d%d%d%-%d%d%-%d%d)")
|
|
|
|
-- Match optional time and recurrence
|
|
local time_start = event_line:match(" (%d%d:%d%d)")
|
|
local time_end = event_line:match("-(%d%d:%d%d)")
|
|
local recurrence = event_line:match("(%+[%d%w]*)>")
|
|
|
|
local date_timestamp = os.time({
|
|
year = tonumber(date_str:sub(1,4)),
|
|
month = tonumber(date_str:sub(6,7)),
|
|
day = tonumber(date_str:sub(9,10)),
|
|
hour = time_start and tonumber(time_start:sub(1,2)),
|
|
min = time_start and tonumber(time_start:sub(4,5))
|
|
})
|
|
|
|
table.insert(entries, {
|
|
file = file,
|
|
title = title,
|
|
status = status,
|
|
event_type = event_type,
|
|
date = date_str,
|
|
date_timestamp = date_timestamp,
|
|
time_start = time_start,
|
|
time_end = time_end,
|
|
recurrence = recurrence
|
|
})
|
|
end
|
|
|
|
-- Generate tasks array
|
|
local tasks = {}
|
|
local time_range_start = os.time()
|
|
local time_range_end = time_range_start -- Can be adjusted as needed
|
|
local now = os.time({year = os.date("*t").year, month = os.date("*t").month, day = os.date("*t").day})
|
|
|
|
for _, entry in ipairs(entries) do
|
|
local date_timestamp = entry.date_timestamp
|
|
local status = entry.status
|
|
local event_type = entry.event_type
|
|
local title = entry.title
|
|
while true do
|
|
if not date_timestamp then break end
|
|
|
|
if (status == "DONE" or status == "KILL") and not (time_range_start <= date_timestamp and date_timestamp <= time_range_end) then
|
|
break
|
|
end
|
|
|
|
if event_type == "DEADLINE" then
|
|
if date_timestamp > (now + org_deadline_warning_days * 24 * 3600) then break end
|
|
else
|
|
if date_timestamp > (now + org_schedule_warning_days * 24 * 3600) then break end
|
|
end
|
|
|
|
table.insert(tasks, {
|
|
file = entry.file,
|
|
title = title,
|
|
type = event_type:lower(),
|
|
status = status and status:lower(),
|
|
recurrence = entry.recurrence,
|
|
overdue = date_timestamp < now,
|
|
date = os.date("%Y-%m-%d", date_timestamp),
|
|
date_timestamp = date_timestamp,
|
|
date_pretty = pretty_print_date(date_timestamp),
|
|
time_start = entry.time_start,
|
|
time_end = entry.time_end
|
|
})
|
|
|
|
if status == "DONE" or status == "KILL" then break end
|
|
|
|
date_timestamp = calculate_recurrence(date_timestamp, entry.recurrence)
|
|
end
|
|
end
|
|
|
|
table.sort(tasks, function(a, b)
|
|
return a.date_timestamp < b.date_timestamp
|
|
end)
|
|
|
|
if not compare_task_arrays(tasks, old_tasks) then
|
|
awesome.emit_signal("evil::tasks", tasks)
|
|
old_tasks = tasks
|
|
end
|
|
|
|
end)
|
|
end
|
|
|
|
-- Update tasks when the day changes
|
|
local old_day
|
|
gears.timer {
|
|
autostart = true,
|
|
timeout = 60,
|
|
single_shot = false,
|
|
call_now = true,
|
|
callback = function()
|
|
today = os.date("*t").day
|
|
if not (old_day == today) then
|
|
update_tasks()
|
|
old_day = today
|
|
else
|
|
end
|
|
end
|
|
}
|
|
|
|
-- Also update tasks when related files change
|
|
-- Requires inotify-tools
|
|
local org_files_subscribe_script = [[
|
|
bash -c "
|
|
while true; do inotifywait -qq -e modify ]]..file_list_str..[[; echo; done
|
|
"]]
|
|
|
|
-- Kill old inotifywait process
|
|
-- Search for "[w]hile" instead of "while" to prevent grep from matching itself
|
|
awful.spawn.easy_async_with_shell("ps x | grep \"[w]hile true; do inotifywait -qq -e modify "..org_files[1].."\" | awk '{print $1}' | xargs kill", function (_)
|
|
-- Update tasks with each line printed
|
|
awful.spawn.with_line_callback(org_files_subscribe_script, {
|
|
stdout = function(_)
|
|
update_tasks()
|
|
end
|
|
})
|
|
end)
|