Compare commits
10
Commits
856e483570
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c7e66e38e | ||
|
|
e1fa8e1916 | ||
|
|
8dce67b2c4 | ||
|
|
ae484aa134 | ||
|
|
7e3b9f43d1 | ||
|
|
9c2d209de3 | ||
|
|
5fa8c1a974 | ||
|
|
20f70aed7f | ||
|
|
33e94e2ef0 | ||
|
|
dfe74510ef |
@@ -37,19 +37,23 @@ Licensing? I don't care. Do whatever you want. IP rights are stupid.
|
||||
"series": "Series",
|
||||
"genre": "Fiction",
|
||||
"pages": 100,
|
||||
"hours": 1.30,
|
||||
"priority": 0,
|
||||
"progress": 0.5,
|
||||
"hidden": true
|
||||
}
|
||||
```
|
||||
|
||||
- `title`, `author`, `series`, `pages`, `genre`, and `hidden` are optional.
|
||||
- `title`, `author`, `series`, `pages`, `hours`, `genre`, and `hidden` are
|
||||
optional.
|
||||
- At least one of `title`, `author`, `series`, or `genre` must be defined.
|
||||
- `priority` is the ELO ranking, initializing to `0`.
|
||||
- `progress` represents read/unread/in-progress status. `0` is unread, `1` is
|
||||
read, and anything in-between in a percentage of progress.
|
||||
- `hidden` is undefined or `true` to hide a book from ALL of the interface
|
||||
without deleting its data.
|
||||
- `hours` is hours.minutes rather than the fractional part being percentage of
|
||||
an hour.
|
||||
|
||||
Despite the script supporting otherwise, I highly recommend only using this for
|
||||
*specific book titles*. In my experience, using entries representing multiple
|
||||
|
||||
+54
-79
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env luajit
|
||||
local filters = require "lib.filters"
|
||||
local json = require "lib.dkjson"
|
||||
local sort_orders = require "lib.sort_orders"
|
||||
math.randomseed(os.time())
|
||||
|
||||
local default_file = "books.json"
|
||||
@@ -20,16 +22,23 @@ local load_json = function(file_name)
|
||||
assert(file, error_message)
|
||||
local result = json.decode(file:read("*all"))
|
||||
file:close()
|
||||
|
||||
result._loaded_file_location = file_name
|
||||
return result
|
||||
end
|
||||
|
||||
local save_json = function(file_name, data)
|
||||
local save_json = function(data)
|
||||
local file_name = data._loaded_file_location
|
||||
data._loaded_file_location = nil
|
||||
|
||||
local file, error_message = io.open(file_name, "w")
|
||||
assert(file, error_message)
|
||||
file:write(json.encode(data, { indent = true, }))
|
||||
file:write("\n")
|
||||
file:close()
|
||||
debug("Saved.")
|
||||
|
||||
data._loaded_file_location = file_name
|
||||
end
|
||||
|
||||
local get_random_order = function(array)
|
||||
@@ -43,72 +52,6 @@ local get_random_order = function(array)
|
||||
return order
|
||||
end
|
||||
|
||||
local filters = {
|
||||
all_books = function(book)
|
||||
if book.hidden then return false end
|
||||
return true
|
||||
end,
|
||||
unread = function(book)
|
||||
if book.hidden then return false end
|
||||
if book.progress == 0 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
read = function(book)
|
||||
if book.hidden then return false end
|
||||
if book.progress == 1 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
in_progress = function(book)
|
||||
if book.hidden then return false end
|
||||
if book.progress > 0 and book.progress < 1 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
exclude_series = function(book)
|
||||
if book.hidden then return false end
|
||||
if book.series and not (book.title or book.author) then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end,
|
||||
}
|
||||
|
||||
local sort_orders
|
||||
sort_orders = {
|
||||
most_progress = function(A, B)
|
||||
return A.progress > B.progress
|
||||
end,
|
||||
least_progress = function(A, B)
|
||||
return A.progress < B.progress
|
||||
end,
|
||||
highest_priority = function(A, B)
|
||||
return A.priority > B.priority
|
||||
end,
|
||||
lowest_priority = function(A, B)
|
||||
return A.priority < B.priority
|
||||
end,
|
||||
most_pages_remaining = function(A, B)
|
||||
if not (A.pages and B.pages) then -- if pages is unknown for both, fallback to percentage
|
||||
return A.progress < B.progress
|
||||
end
|
||||
-- TODO figure out an elegant way to pass the required values here
|
||||
-- local average_page_count = data.total_pages / #data.books
|
||||
-- the previous default wasn't working for my desires, so I'm using a realistic default now
|
||||
local average_page_count = 300
|
||||
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
|
||||
end,
|
||||
fewest_pages_remaining = function(A, B)
|
||||
return not sort_orders.most_pages_remaining(A, B)
|
||||
end,
|
||||
}
|
||||
|
||||
local get_display_name = function(book)
|
||||
local result = ""
|
||||
|
||||
@@ -138,7 +81,10 @@ local get_display_name = function(book)
|
||||
end
|
||||
|
||||
if book.pages then
|
||||
result = result .. " ~" .. math.floor(book.progress * book.pages) .. "/" .. book.pages .. " pages read"
|
||||
result = result .. " ~" .. math.floor(book.progress * book.pages) .. "/" .. book.pages .. " pages read (" .. math.floor(book.progress * 100) .. "%)"
|
||||
elseif book.hours then
|
||||
-- TODO correct for the fractional part being minutes instead of percent
|
||||
result = result .. " ~" .. math.floor(book.progress * book.hours * 10) / 10 .. "/" .. book.hours .. " hours listened (" .. math.floor(book.progress * 100) .. "%)"
|
||||
elseif book.progress > 0 and book.progress < 1 then
|
||||
result = result .. " ~" .. math.floor(book.progress * 100) .. "% read"
|
||||
end
|
||||
@@ -269,6 +215,18 @@ local book_exists = function(data, book)
|
||||
return false
|
||||
end
|
||||
|
||||
local recalculate_total_pages = function(data)
|
||||
data.total_pages = 0
|
||||
data.books_with_page_count = 0
|
||||
for i = 1, #data.books do
|
||||
local book = data.books[i]
|
||||
if book.pages then
|
||||
data.total_pages = data.total_pages + book.pages
|
||||
data.books_with_page_count = data.books_with_page_count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local import_json = function(data, file_name)
|
||||
import = load_json(file_name)
|
||||
if import.books then
|
||||
@@ -322,6 +280,7 @@ local import_json = function(data, file_name)
|
||||
end
|
||||
end
|
||||
|
||||
recalculate_total_pages(data)
|
||||
-- we must rely on parent function to save
|
||||
end
|
||||
|
||||
@@ -335,7 +294,7 @@ local get_books_by_preset = function(data, preset_name)
|
||||
selected_books[#selected_books + 1] = book
|
||||
end
|
||||
end
|
||||
table.sort(selected_books, sort_orders[data.defaults[preset_name].sort])
|
||||
table.sort(selected_books, function(A, B) return sort_orders[data.defaults[preset_name].sort](A, B, data) end)
|
||||
return selected_books
|
||||
end
|
||||
|
||||
@@ -368,7 +327,7 @@ local print_list = function(data, options)
|
||||
selected_books[#selected_books + 1] = book
|
||||
end
|
||||
end
|
||||
table.sort(selected_books, sort_orders[options[2]])
|
||||
table.sort(selected_books, function(A, B) return sort_orders[options[2]](A, B, data) end)
|
||||
|
||||
local limit = 10
|
||||
if options[3] == "all" then
|
||||
@@ -390,6 +349,18 @@ local launch = function(file_name)
|
||||
if path_exists(file_name) then
|
||||
data = load_json(file_name)
|
||||
-- TODO verify data structure
|
||||
local data_modified = false
|
||||
if not data.books_with_page_count then
|
||||
recalculate_total_pages(data)
|
||||
data_modified = true
|
||||
end
|
||||
if not data.defaults.launch.max_displayed then
|
||||
data.defaults.launch.max_displayed = 10
|
||||
data_modified = true
|
||||
end
|
||||
if data_modified then
|
||||
save_json(data)
|
||||
end
|
||||
else
|
||||
data = {
|
||||
books = {},
|
||||
@@ -398,6 +369,7 @@ local launch = function(file_name)
|
||||
maximum_change = 2.5,
|
||||
},
|
||||
total_pages = 0,
|
||||
books_with_page_count = 0,
|
||||
defaults = {
|
||||
launch = {
|
||||
filter = "in_progress",
|
||||
@@ -405,7 +377,7 @@ local launch = function(file_name)
|
||||
},
|
||||
},
|
||||
}
|
||||
save_json(file_name, data)
|
||||
save_json(data)
|
||||
end
|
||||
|
||||
local selected_books = get_books_by_preset(data, "launch")
|
||||
@@ -414,12 +386,13 @@ local launch = function(file_name)
|
||||
end
|
||||
|
||||
local main = function(data, selected_books)
|
||||
for i = 1, math.min(10, #selected_books) do
|
||||
local book_display_count = data.defaults.launch.max_displayed == 0 and #selected_books or data.defaults.launch.max_displayed
|
||||
for i = 1, book_display_count do
|
||||
local book = selected_books[i]
|
||||
print(" " .. (i == 10 and "0" or i) .. ". " .. get_display_name(book))
|
||||
print(" " .. 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("Commands: " .. (#selected_books > 0 and "[#] to modify a book's progress. " or "") .. "[i <file>] to import from a JSON")
|
||||
print(" file. [recalculate pages] Enter nothing to exit.")
|
||||
print(" Adding/Selecting: Title OR Title by Author OR \"Name (type)\" for other types.")
|
||||
print(" Listing: list [filter] [sort] (all)")
|
||||
-- TODO implement elo ranking
|
||||
@@ -429,14 +402,16 @@ local main = function(data, selected_books)
|
||||
|
||||
if #input == 0 then
|
||||
os.exit(0)
|
||||
elseif numerical and (numerical >= 0 and numerical <= 9) then
|
||||
elseif numerical then
|
||||
update_book(selected_books[numerical])
|
||||
elseif input:sub(1, 1) == "i" then
|
||||
import_json(data, input:sub(3))
|
||||
elseif input:find("list ") == 1 then
|
||||
print_list(data, input:sub(6))
|
||||
return true
|
||||
else -- assumed we are trying to add/select a book
|
||||
elseif input:find("recalculate pages") == 1 then
|
||||
recalculate_total_pages(data)
|
||||
else -- assume 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;
|
||||
@@ -444,11 +419,11 @@ local main = function(data, selected_books)
|
||||
-- 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
|
||||
save_json(data)
|
||||
return true
|
||||
end
|
||||
|
||||
local data, selected_books = launch(default_file) -- the file name doesn't make it to main :\
|
||||
local data, selected_books = launch(default_file)
|
||||
while true do
|
||||
main(data, selected_books)
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
local filters = {
|
||||
all_books = function(book)
|
||||
if book.hidden then return false end
|
||||
return true
|
||||
end,
|
||||
unread = function(book)
|
||||
if book.hidden then return false end
|
||||
if book.progress == 0 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
read = function(book)
|
||||
if book.hidden then return false end
|
||||
if book.progress == 1 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
in_progress = function(book)
|
||||
if book.hidden then return false end
|
||||
if book.progress > 0 and book.progress < 1 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
exclude_series = function(book)
|
||||
if book.hidden then return false end
|
||||
if book.series and not (book.title or book.author) then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end,
|
||||
audiobooks_only = function(book)
|
||||
if book.hidden then return false end
|
||||
if book.hours then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
}
|
||||
|
||||
return filters
|
||||
@@ -0,0 +1,43 @@
|
||||
local sort_orders
|
||||
sort_orders = {
|
||||
most_progress = function(A, B)
|
||||
return A.progress > B.progress
|
||||
end,
|
||||
least_progress = function(A, B)
|
||||
return A.progress < B.progress
|
||||
end,
|
||||
highest_priority = function(A, B)
|
||||
return A.priority > B.priority
|
||||
end,
|
||||
lowest_priority = function(A, B)
|
||||
return A.priority < B.priority
|
||||
end,
|
||||
most_pages_remaining = function(A, B, data)
|
||||
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_with_page_count
|
||||
if not (average_page_count == average_page_count) then
|
||||
average_page_count = 300 -- based on average novel length
|
||||
end
|
||||
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
|
||||
end,
|
||||
fewest_pages_remaining = function(A, B, data)
|
||||
return not sort_orders.most_pages_remaining(A, B, data)
|
||||
end,
|
||||
most_time_remaining = function(A, B)
|
||||
if not (A.hours and B.hours) then -- fallback to percentage
|
||||
return A.progress < B.progress
|
||||
end
|
||||
local a = A.hours - A.hours * A.progress
|
||||
local b = B.hours - B.hours * B.progress
|
||||
return a > b
|
||||
end,
|
||||
least_time_remaining = function(A, B)
|
||||
return not sort_orders.most_time_remaining(A, B)
|
||||
end,
|
||||
}
|
||||
|
||||
return sort_orders
|
||||
Reference in New Issue
Block a user