Compare commits

...
4 Commits
Author SHA1 Message Date
tangent 4cdf2a7322 books can be added
- debug function added
- updating progress added
- book select/add multifunction (ew sounds like corpo-speak)
- fixed initial priority assignment to be randomized
- pulled out preset function
- normal exit capability (lol)
2026-08-01 04:13:34 -06:00
tangent 6f0cbfcd2a fix data error in default sort, move selecting books to own function 2026-07-31 23:37:01 -06:00
tangent 942855d5f2 fix import, explicit data passing between functions 2026-07-31 23:22:46 -06:00
tangent 22e81227b9 reorg -> function split 2026-07-31 22:29:26 -06:00
2 changed files with 184 additions and 58 deletions
+3 -1
View File
@@ -5,12 +5,14 @@ CLI tracker to prioritize your limited time by ELO ranking media.
- [ ] Mark books read/unread/in-progress.
- [x] Show top 10 / bottom 10. Show only in-progress or unread.
- [ ] Rank only in-progress or unread.
- [ ] Track books by title, author, and series. Default is to recognize "by" or "(series)" and automatically sort into relevant fields.
- [x] Track books by title, author, and series. Default is to recognize "by" or "(series)" and automatically sort into relevant fields.
- [x] But also almost nothing is actually *needed*.
- [x] Store progress as a numerical value? (Unread = 0, read = 1, in-progress in-between? Too easy to forget how it works?)
- [x] Store pages as an option,
- [ ] allow a weighted priority based on remaining percentage or whatever.
- [x] Store items as an array instead of object, because that makes the JSON output more consistent -> smaller JSON commit differences.
- [ ] Re-implement ELO ranking.
- [ ] Display filter/sort options to output lists. (Focus only on top 10 items?)
## Contributions
+181 -57
View File
@@ -3,6 +3,8 @@ local json = require "lib.dkjson"
math.randomseed(os.time())
local default_file = "books.json"
local debug = function() end
local debug = function(...) print(...) end
local path_exists = function(path_name)
local file = io.open(path_name, "r")
@@ -27,6 +29,7 @@ local save_json = function(file_name, data)
file:write(json.encode(data, { indent = true, }))
file:write("\n")
file:close()
debug("Saved.")
end
local get_random_order = function(array)
@@ -93,7 +96,9 @@ sort_orders = {
if not (A.pages and B.pages) then -- if pages is unknown for both, fallback to percentage
return A.progress < B.progress
end
local average_page_count = data.total_pages / #data.books
-- TODO figure out an elegant way to pass the required values here
-- local average_page_count = data.total_pages / #data.books
local average_page_count = 200 -- an intentionally low default, intended to push them higher and make you pay attention
local a = (A.pages or average_page_count) - (A.pages or average_page_count) * A.progress
local b = (B.pages or average_page_count) - (B.pages or average_page_count) * B.progress
return a > b
@@ -140,61 +145,101 @@ local get_display_name = function(book)
return result
end
-- TODO this procedural code should be split into functions where feasible
local data
if path_exists(default_file) then
data = load_json(default_file)
-- TODO verify data structure
else
data = {
books = {},
elo = {
distance_constant = 5,
maximum_change = 2.5,
},
total_pages = 0,
defaults = {
launch = {
filter = "in_progress",
sort = "fewest_pages_remaining",
},
},
}
save_json(default_file, data)
end
-- main menu
-- TODO should be a loop/function to return to
local selected_books = {}
for i = 1, #data.books do
local book = data.books[i]
if filters[data.defaults.launch.filter](book) then
selected_books[#selected_books + 1] = book
local update_progress_prompt = function(book)
if not book then
print("Invalid selection.")
return false
end
print("Enter a value between 0 and 1 to update progress:")
print(" " .. get_display_name(book))
local input = io.read("*line")
local numerical = tonumber(input)
if numerical >= 0 and numerical <= 1 then
book.progress = numerical
return true
end
-- saving is handled by a higher function on the stack
end
table.sort(selected_books, sort_orders[data.defaults.launch.sort])
for i = 1, math.min(10, #selected_books) do
local book = selected_books[i]
print(" " .. (i == 10 and "0" or i) .. ". " .. get_display_name(book))
local get_book = function(data, input)
local title, author, series, genre
local i, j = input:find(" by ")
if i then
title = input:sub(1, i - 1)
author = input:sub(j + 1)
debug("get_book() Title by Author detected:", title, author)
else
local i = input:find(" (author)")
if i then
author = input:sub(1, i - 1)
end
local i = input:find(" (series)")
series = input:sub(1, i - 1)
if i then
end
local i = input:find(" (genre)")
genre = input:sub(1, i - 1)
if i then
end
if not (author or series or genre) then
title = input
end
end
local book
for i = 1, #data.books do
local current = data.books[i]
if title then
if current.title == title then
if author then
if current.author and current.author == author then
book = current
break
end
else
book = current
break
end
end
elseif author and current.author == author then
book = current
break
elseif series and current.series == series then
book = current
break
elseif genre and current.genre == genre then
book = current
break
end
end
if not book then
book = {
title = title,
author = author,
series = series,
genre = genre,
progress = 0,
priority = 0,
}
data.books[#data.books + 1] = book
end
-- this may or may not stay relevant to this placement
return update_progress_prompt(book)
end
print("Commands: " .. (#selected_books > 0 and "[0-9] to modify a book's progress. " or "") .. "[a <book>] to add a book. [i <file>] to import from a JSON file.")
-- TODO define what can go in <book> and how it will be interpreted
-- TODO actually implement modifying progress and adding books
-- TODO implement elo ranking
-- TODO type title to add or edit an extant book (this is better than the "a" command I wrote above)
local input = io.read("*line")
local update_book = function(book)
-- currently just a wrapper, because I'm not sure if this will stay exactly consistent with that
return update_progress_prompt(book)
end
if input:sub(1, 1) == "i" then
-- TODO this should all be broken out into a function
import = load_json(input:sub(3))
local import_json = function(data, file_name)
import = load_json(file_name)
if import.books then
for i = 1, #import.books do
@@ -225,13 +270,17 @@ if input:sub(1, 1) == "i" then
end
local i = title:find(" %(genre%)")
if i then
print("Skipping import of \"" .. title .. "\"")
return false
book.genre = title:sub(1, i - 1)
end
if not (book.series or book.author) then
-- TODO detect " by " and split if needed
book.title = title
if not (book.series or book.author or book.genre) then
local i, j = title:find(" by ")
if i then
book.title = title:sub(1, i - 1)
book.author = title:sub(j + 1)
else
book.title = title
end
end
if type(value) == "number" then
@@ -254,7 +303,82 @@ if input:sub(1, 1) == "i" then
end
end
save_json(default_file, data)
-- we must rely on parent function to save
end
-- TODO there should be a sort of main menu I am returned to
local get_books_by_preset = function(data, preset_name)
local selected_books = {}
for i = 1, #data.books do
local book = data.books[i]
if filters[data.defaults[preset_name].filter](book) then
selected_books[#selected_books + 1] = book
end
end
table.sort(selected_books, sort_orders[data.defaults[preset_name].sort])
return selected_books
end
local launch = function(file_name)
local data
if path_exists(file_name) then
data = load_json(file_name)
-- TODO verify data structure
else
data = {
books = {},
elo = {
distance_constant = 5,
maximum_change = 2.5,
},
total_pages = 0,
defaults = {
launch = {
filter = "in_progress",
sort = "fewest_pages_remaining",
},
},
}
save_json(file_name, data)
end
local selected_books = get_books_by_preset(data, "launch")
return data, selected_books
end
local main = function(data, selected_books)
for i = 1, math.min(10, #selected_books) do
local book = selected_books[i]
print(" " .. (i == 10 and "0" or i) .. ". " .. get_display_name(book))
end
print("Commands: " .. (#selected_books > 0 and "[0-9] to modify a book's progress. " or "") .. "[i <file>] to import from a JSON")
print(" file. Enter nothing to exit.")
print(" Adding/Selecting: Title OR Title by Author OR \"Name (type)\" for other types.")
-- TODO implement elo ranking
local input = io.read("*line")
local numerical = tonumber(input)
if #input == 0 then
os.exit(0)
elseif numerical and (numerical >= 0 and numerical <= 9) then
update_book(selected_books[numerical])
elseif input:sub(1, 1) == "i" then
import_json(data, input:sub(3))
else -- assumed we are trying to add/select a book
get_book(data, input)
-- but that just returns a book, we need to DO something with it ??
-- right now, it demands a progress update;
-- TODO add mode selection to select and update vs select only mode that THEN asks what to do?
-- no? these are completely separate modes, there should be quick add, update progress, rank mode, add pages mode, etc
end
save_json(default_file, data) -- this assumes it is equivalent with how launch() is called
end
local data, selected_books = launch(default_file) -- the file name doesn't make it to main :\
while true do
main(data, selected_books)
end