Compare commits
16
Commits
4cdf2a7322
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c7e66e38e | ||
|
|
e1fa8e1916 | ||
|
|
8dce67b2c4 | ||
|
|
ae484aa134 | ||
|
|
7e3b9f43d1 | ||
|
|
9c2d209de3 | ||
|
|
5fa8c1a974 | ||
|
|
20f70aed7f | ||
|
|
33e94e2ef0 | ||
|
|
dfe74510ef | ||
|
|
856e483570 | ||
|
|
8e2211fd04 | ||
|
|
ff674c0273 | ||
|
|
b458994149 | ||
|
|
39e7cdc8a8 | ||
|
|
362d332a71 |
@@ -2,26 +2,33 @@
|
|||||||
CLI tracker to prioritize your limited time by ELO ranking media.
|
CLI tracker to prioritize your limited time by ELO ranking media.
|
||||||
|
|
||||||
## Initial Ideas
|
## Initial Ideas
|
||||||
- [ ] Mark books read/unread/in-progress.
|
- [x] Mark books read/unread/in-progress.
|
||||||
- [x] Show top 10 / bottom 10. Show only in-progress or unread.
|
- [x] Show top 10 / bottom 10. Show only in-progress or unread.
|
||||||
- [ ] Rank only in-progress or unread.
|
- [ ] Rank only in-progress or unread.
|
||||||
- [x] 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] 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,
|
- [x] Store pages as an option,
|
||||||
- [ ] allow a weighted priority based on remaining percentage or whatever.
|
- [x] 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.
|
- [x] Store items as an array instead of object, because that makes the JSON output more consistent -> smaller JSON commit differences.
|
||||||
|
- [ ] I am not sure this actually helps much when EACH ITEM is still an
|
||||||
|
object. I need to make the JSON export sort its keys.
|
||||||
- [ ] Re-implement ELO ranking.
|
- [ ] Re-implement ELO ranking.
|
||||||
- [ ] Display filter/sort options to output lists. (Focus only on top 10 items?)
|
- [ ] Display filter/sort options to output lists. (Focus only on top 10 items?)
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
This repo is hosted at https://gitea.tangentfox.com/tangent/Asymtome and
|
This repo is hosted at https://gitea.tangentfox.com/tangent/Asymptome and
|
||||||
force-pushed to https://github.com/TangentFoxy/Asymtome when commits are made.
|
force-pushed to https://github.com/TangentFoxy/Asymptome when commits are made.
|
||||||
I accept pull requests and issues, but management of the code is handled there.
|
I accept pull requests and issues, but management of the code is handled there.
|
||||||
|
|
||||||
Licensing? I don't care. Do whatever you want. IP rights are stupid.
|
Licensing? I don't care. Do whatever you want. IP rights are stupid.
|
||||||
|
|
||||||
|
## Ideas
|
||||||
|
- [ ] When importing, allow user to make a choice when duplicates are detected
|
||||||
|
instead of rejecting them automatically.
|
||||||
|
- [ ] When importing, a duplicate should still be checked for missing data, and
|
||||||
|
merge rather than ignore.
|
||||||
|
|
||||||
## Book Data Structure
|
## Book Data Structure
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -30,19 +37,23 @@ Licensing? I don't care. Do whatever you want. IP rights are stupid.
|
|||||||
"series": "Series",
|
"series": "Series",
|
||||||
"genre": "Fiction",
|
"genre": "Fiction",
|
||||||
"pages": 100,
|
"pages": 100,
|
||||||
|
"hours": 1.30,
|
||||||
"priority": 0,
|
"priority": 0,
|
||||||
"progress": 0.5,
|
"progress": 0.5,
|
||||||
"hidden": true
|
"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.
|
- At least one of `title`, `author`, `series`, or `genre` must be defined.
|
||||||
- `priority` is the ELO ranking, initializing to `0`.
|
- `priority` is the ELO ranking, initializing to `0`.
|
||||||
- `progress` represents read/unread/in-progress status. `0` is unread, `1` is
|
- `progress` represents read/unread/in-progress status. `0` is unread, `1` is
|
||||||
read, and anything in-between in a percentage of progress.
|
read, and anything in-between in a percentage of progress.
|
||||||
- `hidden` is undefined or `true` to hide a book from ALL of the interface
|
- `hidden` is undefined or `true` to hide a book from ALL of the interface
|
||||||
without deleting its data.
|
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
|
Despite the script supporting otherwise, I highly recommend only using this for
|
||||||
*specific book titles*. In my experience, using entries representing multiple
|
*specific book titles*. In my experience, using entries representing multiple
|
||||||
|
|||||||
+158
-113
@@ -1,10 +1,12 @@
|
|||||||
#!/usr/bin/env luajit
|
#!/usr/bin/env luajit
|
||||||
|
local filters = require "lib.filters"
|
||||||
local json = require "lib.dkjson"
|
local json = require "lib.dkjson"
|
||||||
|
local sort_orders = require "lib.sort_orders"
|
||||||
math.randomseed(os.time())
|
math.randomseed(os.time())
|
||||||
|
|
||||||
local default_file = "books.json"
|
local default_file = "books.json"
|
||||||
local debug = function() end
|
local debug = function() end
|
||||||
local debug = function(...) print(...) end
|
-- local debug = function(...) print(...) end
|
||||||
|
|
||||||
local path_exists = function(path_name)
|
local path_exists = function(path_name)
|
||||||
local file = io.open(path_name, "r")
|
local file = io.open(path_name, "r")
|
||||||
@@ -20,16 +22,23 @@ local load_json = function(file_name)
|
|||||||
assert(file, error_message)
|
assert(file, error_message)
|
||||||
local result = json.decode(file:read("*all"))
|
local result = json.decode(file:read("*all"))
|
||||||
file:close()
|
file:close()
|
||||||
|
|
||||||
|
result._loaded_file_location = file_name
|
||||||
return result
|
return result
|
||||||
end
|
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")
|
local file, error_message = io.open(file_name, "w")
|
||||||
assert(file, error_message)
|
assert(file, error_message)
|
||||||
file:write(json.encode(data, { indent = true, }))
|
file:write(json.encode(data, { indent = true, }))
|
||||||
file:write("\n")
|
file:write("\n")
|
||||||
file:close()
|
file:close()
|
||||||
debug("Saved.")
|
debug("Saved.")
|
||||||
|
|
||||||
|
data._loaded_file_location = file_name
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_random_order = function(array)
|
local get_random_order = function(array)
|
||||||
@@ -43,71 +52,6 @@ local get_random_order = function(array)
|
|||||||
return order
|
return order
|
||||||
end
|
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
|
|
||||||
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
|
|
||||||
end,
|
|
||||||
fewest_pages_remaining = function(A, B)
|
|
||||||
return not sort_orders.most_pages_remaining(A, B)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
local get_display_name = function(book)
|
local get_display_name = function(book)
|
||||||
local result = ""
|
local result = ""
|
||||||
|
|
||||||
@@ -137,7 +81,10 @@ local get_display_name = function(book)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if book.pages then
|
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
|
elseif book.progress > 0 and book.progress < 1 then
|
||||||
result = result .. " ~" .. math.floor(book.progress * 100) .. "% read"
|
result = result .. " ~" .. math.floor(book.progress * 100) .. "% read"
|
||||||
end
|
end
|
||||||
@@ -164,6 +111,27 @@ local update_progress_prompt = function(book)
|
|||||||
-- saving is handled by a higher function on the stack
|
-- saving is handled by a higher function on the stack
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local detect_type = function(input)
|
||||||
|
local author, series, genre
|
||||||
|
local i = input:find(" %(.-series.-%)")
|
||||||
|
if i then
|
||||||
|
series = input:sub(1, i - 1)
|
||||||
|
end
|
||||||
|
local i = input:find(" %(trilogy%)")
|
||||||
|
if i then
|
||||||
|
series = input:sub(1, i - 1)
|
||||||
|
end
|
||||||
|
local i = input:find(" %(.-author%)")
|
||||||
|
if i then
|
||||||
|
author = input:sub(1, i - 1)
|
||||||
|
end
|
||||||
|
local i = input:find(" %(genre%)")
|
||||||
|
if i then
|
||||||
|
genre = input:sub(1, i - 1)
|
||||||
|
end
|
||||||
|
return author, series, genre
|
||||||
|
end
|
||||||
|
|
||||||
local get_book = function(data, input)
|
local get_book = function(data, input)
|
||||||
local title, author, series, genre
|
local title, author, series, genre
|
||||||
local i, j = input:find(" by ")
|
local i, j = input:find(" by ")
|
||||||
@@ -172,18 +140,7 @@ local get_book = function(data, input)
|
|||||||
author = input:sub(j + 1)
|
author = input:sub(j + 1)
|
||||||
debug("get_book() Title by Author detected:", title, author)
|
debug("get_book() Title by Author detected:", title, author)
|
||||||
else
|
else
|
||||||
local i = input:find(" (author)")
|
author, series, genre = detect_type(input)
|
||||||
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
|
if not (author or series or genre) then
|
||||||
title = input
|
title = input
|
||||||
@@ -224,7 +181,7 @@ local get_book = function(data, input)
|
|||||||
series = series,
|
series = series,
|
||||||
genre = genre,
|
genre = genre,
|
||||||
progress = 0,
|
progress = 0,
|
||||||
priority = 0,
|
priority = math.random(),
|
||||||
}
|
}
|
||||||
data.books[#data.books + 1] = book
|
data.books[#data.books + 1] = book
|
||||||
end
|
end
|
||||||
@@ -238,40 +195,58 @@ local update_book = function(book)
|
|||||||
return update_progress_prompt(book)
|
return update_progress_prompt(book)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local book_exists = function(data, book)
|
||||||
|
for i = 1, #data.books do
|
||||||
|
local current = data.books[i]
|
||||||
|
if not (book.title == current.title) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if not (book.author == current.author) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if not (book.series == current.series) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if not (book.genre == current.genre) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
return current -- we return the object instead of true so it can be updated if desired
|
||||||
|
end
|
||||||
|
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)
|
local import_json = function(data, file_name)
|
||||||
import = load_json(file_name)
|
import = load_json(file_name)
|
||||||
if import.books then
|
if import.books then
|
||||||
|
|
||||||
for i = 1, #import.books do
|
for i = 1, #import.books do
|
||||||
data.books[#data.books + 1] = import.books[i]
|
-- TODO allow updating metadata instead of just ignoring
|
||||||
-- TODO detect and ignore duplicates (would be better to provide user an option for what to do with duplicates)
|
if not book_exists(data, import.books[i]) then
|
||||||
-- default should accept highest progress of duplicates?
|
data.books[#data.books + 1] = import.books[i]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
else -- assume we are importing from decider.lua's format
|
else -- assume we are importing from decider.lua's format
|
||||||
for title, value in pairs(import) do
|
for title, value in pairs(import) do
|
||||||
local run = function()
|
local run = function()
|
||||||
local book = { -- minimum required book data strucutre
|
local book = { -- minimum required book data strucutre
|
||||||
priority = 0,
|
priority = math.random(),
|
||||||
progress = 0,
|
progress = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
local i = title:find(" %(.-series.-%)")
|
book.author, book.series, book.genre = detect_type(title)
|
||||||
if i then
|
|
||||||
book.series = title:sub(1, i - 1)
|
|
||||||
end
|
|
||||||
local i = title:find(" %(trilogy%)")
|
|
||||||
if i then
|
|
||||||
book.series = title:sub(1, i - 1)
|
|
||||||
end
|
|
||||||
local i = title:find(" %(.-author%)")
|
|
||||||
if i then
|
|
||||||
book.author = title:sub(1, i - 1)
|
|
||||||
end
|
|
||||||
local i = title:find(" %(genre%)")
|
|
||||||
if i then
|
|
||||||
book.genre = title:sub(1, i - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not (book.series or book.author or book.genre) then
|
if not (book.series or book.author or book.genre) then
|
||||||
local i, j = title:find(" by ")
|
local i, j = title:find(" by ")
|
||||||
@@ -296,13 +271,16 @@ local import_json = function(data, file_name)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO detect duplicates before adding them
|
-- TODO allow updating metadata (this and the above need to be split into its own function)
|
||||||
data.books[#data.books + 1] = book
|
if not book_exists(data, book) then
|
||||||
|
data.books[#data.books + 1] = book
|
||||||
|
end
|
||||||
end
|
end
|
||||||
run()
|
run()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
recalculate_total_pages(data)
|
||||||
-- we must rely on parent function to save
|
-- we must rely on parent function to save
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -316,15 +294,73 @@ local get_books_by_preset = function(data, preset_name)
|
|||||||
selected_books[#selected_books + 1] = book
|
selected_books[#selected_books + 1] = book
|
||||||
end
|
end
|
||||||
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
|
return selected_books
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local print_list = function(data, options)
|
||||||
|
local gsplit = function(s, delimiter)
|
||||||
|
local function escape_special_characters(s)
|
||||||
|
local special_characters = "[()%%.[^$%]*+%-?]"
|
||||||
|
if s == nil then return end
|
||||||
|
return (s:gsub(special_characters, "%%%1"))
|
||||||
|
end
|
||||||
|
|
||||||
|
delimiter = delimiter or ","
|
||||||
|
if s:sub(-#delimiter) ~= delimiter then s = s .. delimiter end
|
||||||
|
return s:gmatch("(.-)" .. escape_special_characters(delimiter))
|
||||||
|
end
|
||||||
|
local split = function(s, delimiter)
|
||||||
|
local result = {}
|
||||||
|
for item in gsplit(s, delimiter) do
|
||||||
|
result[#result + 1] = item
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
options = split(options, " ")
|
||||||
|
|
||||||
|
local selected_books = {}
|
||||||
|
for i = 1, #data.books do
|
||||||
|
local book = data.books[i]
|
||||||
|
if filters[options[1]](book) then
|
||||||
|
selected_books[#selected_books + 1] = book
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(selected_books, function(A, B) return sort_orders[options[2]](A, B, data) end)
|
||||||
|
|
||||||
|
local limit = 10
|
||||||
|
if options[3] == "all" then
|
||||||
|
limit = #selected_books
|
||||||
|
end
|
||||||
|
|
||||||
|
print("")
|
||||||
|
for i = 1, limit do
|
||||||
|
local book = selected_books[i]
|
||||||
|
print(i .. ". " .. get_display_name(book))
|
||||||
|
end
|
||||||
|
|
||||||
|
print(" Press enter to continue.")
|
||||||
|
io.read("*line")
|
||||||
|
end
|
||||||
|
|
||||||
local launch = function(file_name)
|
local launch = function(file_name)
|
||||||
local data
|
local data
|
||||||
if path_exists(file_name) then
|
if path_exists(file_name) then
|
||||||
data = load_json(file_name)
|
data = load_json(file_name)
|
||||||
-- TODO verify data structure
|
-- 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
|
else
|
||||||
data = {
|
data = {
|
||||||
books = {},
|
books = {},
|
||||||
@@ -333,6 +369,7 @@ local launch = function(file_name)
|
|||||||
maximum_change = 2.5,
|
maximum_change = 2.5,
|
||||||
},
|
},
|
||||||
total_pages = 0,
|
total_pages = 0,
|
||||||
|
books_with_page_count = 0,
|
||||||
defaults = {
|
defaults = {
|
||||||
launch = {
|
launch = {
|
||||||
filter = "in_progress",
|
filter = "in_progress",
|
||||||
@@ -340,7 +377,7 @@ local launch = function(file_name)
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
save_json(file_name, data)
|
save_json(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
local selected_books = get_books_by_preset(data, "launch")
|
local selected_books = get_books_by_preset(data, "launch")
|
||||||
@@ -349,13 +386,15 @@ local launch = function(file_name)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local main = function(data, selected_books)
|
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]
|
local book = selected_books[i]
|
||||||
print(" " .. (i == 10 and "0" or i) .. ". " .. get_display_name(book))
|
print(" " .. i .. ". " .. get_display_name(book))
|
||||||
end
|
end
|
||||||
print("Commands: " .. (#selected_books > 0 and "[0-9] to modify a book's progress. " or "") .. "[i <file>] to import from a JSON")
|
print("Commands: " .. (#selected_books > 0 and "[#] to modify a book's progress. " or "") .. "[i <file>] to import from a JSON")
|
||||||
print(" file. Enter nothing to exit.")
|
print(" file. [recalculate pages] Enter nothing to exit.")
|
||||||
print(" Adding/Selecting: Title OR Title by Author OR \"Name (type)\" for other types.")
|
print(" Adding/Selecting: Title OR Title by Author OR \"Name (type)\" for other types.")
|
||||||
|
print(" Listing: list [filter] [sort] (all)")
|
||||||
-- TODO implement elo ranking
|
-- TODO implement elo ranking
|
||||||
|
|
||||||
local input = io.read("*line")
|
local input = io.read("*line")
|
||||||
@@ -363,11 +402,16 @@ local main = function(data, selected_books)
|
|||||||
|
|
||||||
if #input == 0 then
|
if #input == 0 then
|
||||||
os.exit(0)
|
os.exit(0)
|
||||||
elseif numerical and (numerical >= 0 and numerical <= 9) then
|
elseif numerical then
|
||||||
update_book(selected_books[numerical])
|
update_book(selected_books[numerical])
|
||||||
elseif input:sub(1, 1) == "i" then
|
elseif input:sub(1, 1) == "i" then
|
||||||
import_json(data, input:sub(3))
|
import_json(data, input:sub(3))
|
||||||
else -- assumed we are trying to add/select a book
|
elseif input:find("list ") == 1 then
|
||||||
|
print_list(data, input:sub(6))
|
||||||
|
return true
|
||||||
|
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)
|
get_book(data, input)
|
||||||
-- but that just returns a book, we need to DO something with it ??
|
-- but that just returns a book, we need to DO something with it ??
|
||||||
-- right now, it demands a progress update;
|
-- right now, it demands a progress update;
|
||||||
@@ -375,10 +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
|
-- no? these are completely separate modes, there should be quick add, update progress, rank mode, add pages mode, etc
|
||||||
end
|
end
|
||||||
|
|
||||||
save_json(default_file, data) -- this assumes it is equivalent with how launch() is called
|
save_json(data)
|
||||||
|
return true
|
||||||
end
|
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
|
while true do
|
||||||
main(data, selected_books)
|
main(data, selected_books)
|
||||||
end
|
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