From 94436701a85e036654ae4172e90b4858d484fd26 Mon Sep 17 00:00:00 2001 From: sloumdrone Date: Sat, 5 Jan 2019 22:17:15 -0800 Subject: [PATCH] Added tabs and created a forked lite version --- README.md | 60 +++++--- stubb | 226 +++++++++++++++++++++------- stubb-lite | 432 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 643 insertions(+), 75 deletions(-) create mode 100755 stubb-lite diff --git a/README.md b/README.md index 7acf48b..67d4886 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ In an effort to streamline the browsing experience for text only, gophermap rend Support for search is currently under consideration. I have not generally found Gopher search to be particularly useful. The lack of tagging and organization makes most search results from Veronica2 seem like playing the lottery. We'll see. +The command bar lists the current tab number, out of how many total tabs. It also lists the current place in history (for the tab you are currently on). + ### Setup @@ -18,27 +20,45 @@ That should take care of dependencies. You just need to make sure that __stubb__ +### Versions + +This repo contains two files: `stubb` and `stubb-lite`. The later has a simpler menu structure and lacks support for having multiple tabs open. The filesize and memory usage should be similar, but the navigation may involve fewer keypresses in the lite version. + + ### Commands -Stub responds to single key commands without requiring the user to press enter. Some commands will request further information, such as a link or bookmark id: (5) or (f2) respectively. +Stub responds to single key commands without requiring the user to press enter. Some commands will request further information, such as a link or bookmark id. -| Key | Effect | -| :-------: | :------: | -| B | Back | -| F | Forward | -| G | Go to url | -| R | Reload current url | -| V | Visit link/bookmark | -| A | Add bookmark | -| D | Delete bookmark | -| L | List bookmarks | -| U | Update bookmark title | -| H | Help | -| Q | Quit | -| S | Save file to disk | +* __Navigation__ + * (B)ack + * (F)orward + * (G)o to + * (B)ookmark + * (L)ink + * (T)ab + * (U)rl + * (R)eload current url -When a gophermap is loaded, links will be enumerated. Pressing 'V' will prompt you for what link you would like to follow. Links are always based on the most recently loaded URL. However, bookmarks, which take the format 'f#' where # is the bookmark id, can be accessed at any time (you do not have to list bookmarks before visiting one, you just have to include the 'f' before the number). +* __Bookmarks/Tabs__ + * (A)dd + * (B)ookmark + * (T)ab + * (D)elete + * (B)ookmark + * (T)ab + * (L)ist + * (B)ookmark + * (T)ab + * (U)pdate bookmark title + +* __System__ + * (H)elp + * (Q)uit + * (S)ave current page + + +When a gophermap is loaded, links will be enumerated. Pressing (G) and then (L) will prompt you for what link you would like to follow. Links are always based on the most recently loaded url for your current tab. File save is always based out of the home directory of the user that launched Stubb. @@ -49,5 +69,11 @@ File save is always based out of the home directory of the user that launched St If Stubb's slim and light style is not fitting your needs, or you just want to check out some other gopher client projects, I highly recomend the following: - [Burrow](https://tildegit.org/sloum/burrow): My gui based browser with a wider featureset (primarily for *nix system) -- [VF-1](https://github.com/solderpunk/VF-1): A great/stable gopher client written by Solderpunk, who maintains one of the best gopher communities out there, [The People's Circumlunar Zaibatsu, in Circumluanr Space](gopher://zaibatsu.circumlunar.space:70/). +- [VF-1](https://github.com/solderpunk/VF-1): A great/stable gopher client written by Solderpunk, who maintains one of the best gopher communities out there, [The People's Circumlunar Zaibatsu](gopher://zaibatsu.circumlunar.space:70/) (a part of [Circumlunar Space](http://circumlunar.space/)). + + +### Notes + + Stubb is free, as in freedom, software. Modify it as you see fit, give it to whoever you want. It would be great if you referenced the original or cross linked, but it is not required. + diff --git a/stubb b/stubb index 60858c4..2767723 100755 --- a/stubb +++ b/stubb @@ -1,11 +1,11 @@ #!/usr/bin/env lua -local version = 'v0.6.0' +local version = 'v0.8.5' local socket = require("socket") local urlparser = require("socket.url") local separator = '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n' local valid_types = {'0', '1', 'i'} -session = { history={ loc=0, items={} }, current_links = {}, favorites={} } +session = { tabs = { } } local save_file_location = string.format('%s/.stubbfaves', assert(os.getenv('HOME'), 'Unable to get home directory')) @@ -92,6 +92,7 @@ end function display_gophermap_row(row) + local t = session.tabs[session.current_tab] local val = string.split(row, '\t') local gophertype = string.sub(val[1], 1, 1) local text = string.sub(val[1], 2) @@ -101,8 +102,8 @@ function display_gophermap_row(row) elseif table.has(valid_types, gophertype) then local leader = gophertype == '0' and 'TXT' or 'MAP' local url = string.format('%s:%s/%s%s', val[3] or '', val[4] or '', gophertype, val[2] or '/') - table.insert(session.current_links, url) - local linknum = string.format('(%d)', #session.current_links) + table.insert(t.current_links, url) + local linknum = string.format('(%d)', #t.current_links) print(string.format(' %s %5s %s', leader, linknum, text)) end end @@ -119,9 +120,10 @@ end function mainloop() + local th = session.tabs[session.current_tab].history print('\n') while true do - local bar = string.format('\27[7m o--[ S t u b b ]--> (H)elp, (Q)uit %d/%d \27[0m', session.history.loc, #session.history.items) + local bar = string.format('\27[7m o--[ S t u b b ]--> (H)elp, (Q)uit TAB:%d/%d HST: %d/%d \27[0m', session.current_tab, #session.tabs,th.loc, #th.items) print(bar) -- print('\27[7m (G)o to url, (B)ack, (V)isit link, (Q)uit \27[0m') local key = string.lower(getch()) @@ -130,45 +132,81 @@ function mainloop() os.exit(0) elseif key == 'h' then print_help() + elseif key == 's' then + save_file() elseif key == 'b' then back() elseif key == 'f' then forward() - elseif key == 'a' then - add_favorite() - elseif key == 'l' then - show_favorites() elseif key == 'u' then update_favorite() - elseif key == 'd' then - remove_favorite() - elseif key == 's' then - save_file() elseif key == 'r' then refresh() - elseif key == 'g' then - io.write('Enter gopher url > ') - local url = io.read() - if url ~= '0' and url ~= '' then - go_to_url(url, true) - else - print('\n') + elseif key == 'a' then + io.write('Add: (B)ookmark, (T)ab') + local subkey = string.lower(getch()) + if subkey == 'b' then + add_favorite() + elseif subkey == 't' then + new_tab() + end + elseif key == 'l' then + io.write('List: (B)ookmarks, (T)abs') + local subkey = string.lower(getch()) + if subkey == 'b' then + show_favorites() + elseif subkey == 't' then + show_tabs() + end + elseif key == 'd' then + io.write('Delete: (B)ookmark, (T)ab') + local subkey = string.lower(getch()) + if subkey == 'b' then + remove_favorite() + elseif subkey == 't' then + remove_tab() + end + elseif key == 'g' then + io.write('Go to: (B)ookmark, (L)ink, (T)ab, (U)rl') + local subkey = string.lower(getch()) + if subkey == 'b' then + visit_link(true) + elseif subkey == 'l' then + visit_link() + elseif subkey == 't' then + io.write('Enter tab id (! to cancel): ') + local t = io.read() + local tabid = tonumber(t) + if t ~= '!' and tabid and session.tabs[tabid] then + session.current_tab = tabid + refresh() + else + local x = tabid and print('Invalid tab id') or print('Cancelled') + end + elseif subkey == 'u' then + io.write('Enter gopher url (! to cancel): ') + local url = io.read() + if url ~= '!' and url ~= '' then + go_to_url(url, true) + else + print('\n') + end end - elseif key == 'v' then - visit_link() end end end function refresh() - if session.history.loc > 0 then - go_to_url(session.history.items[session.history.loc]) + local th = session.tabs[session.current_tab].history + if th.loc > 0 then + go_to_url(th.items[th.loc]) end end function add_favorite() + local th = session.tabs[session.current_tab].history if #session.history.items == 0 then return false end local title repeat @@ -176,7 +214,7 @@ function add_favorite() title = io.read() until title if title == '!' then return print('Cancelled') end - table.insert(session.favorites,{display = title, link = session.history.items[session.history.loc]}) + table.insert(session.favorites,{display = title, link = th.items[th.loc]}) print('Bookmark added') save_favorites() end @@ -184,7 +222,7 @@ end function update_favorite() local title - io.write('Enter the bookmark id (! to cancel) > ') + io.write('Enter the bookmark id (! to cancel): ') local favid = io.read() if favid == '!' then return print('Cancelled') end local id = tonumber(favid) or tonumber(string.sub(favid,2)) @@ -210,7 +248,7 @@ end function remove_favorite() - io.write('Enter the bookmark id (! to cancel) > ') + io.write('Enter the bookmark id to remove (! to cancel): ') local favid = io.read() if favid == '!' then return print('Cancelled') end local id = tonumber(favid) or tonumber(string.sub(favid,2)) @@ -229,18 +267,35 @@ function remove_favorite() end -function visit_link() +function remove_tab() + io.write('Enter the tab id to remove (! to cancel): ') + local tabid = io.read() + if tabid == '!' then return print('Cancelled') end + local id = tonumber(tabid) + if id then + local item = session.tabs[id] + if item then + table.remove(session.tabs, id) + print('Tab has been removed') + else + print('Invalid tab id') + end + else + print('Invalid tab id') + end +end + + +function visit_link(favorite) + local t = session.tabs[session.current_tab] local linkid - local favorite repeat - favorite = false - io.write('Enter link id (! to cancel) > ') + io.write('Enter link id (! to cancel): ') local inp = io.read() if inp == '!' then return print('Cancelled') end linkid = tonumber(inp) if not linkid and string.sub(inp, 1, 1) == 'f' then linkid = tonumber(string.sub(inp, 2)) - favorite = true end until linkid local linkurl @@ -252,10 +307,10 @@ function visit_link() return print('Invalid bookmark id') end else - linkurl = session.current_links[linkid] + linkurl = t.current_links[linkid] end if linkurl then - table.truncate(session.history.items, session.history.loc + 1) + table.truncate(t.history.items, t.history.loc + 1) go_to_url(linkurl, true) else local out = string.format('Link id %d does not exist...', linkid) @@ -263,21 +318,25 @@ function visit_link() end end + function back() - if session.history.loc > 1 and #session.history.items > 1 then - session.history.loc = session.history.loc - 1 - go_to_url(session.history.items[session.history.loc]) + local th = session.tabs[session.current_tab].history + if th.loc > 1 and #th.items > 1 then + th.loc = th.loc - 1 + go_to_url(th.items[th.loc]) end end function forward() - if session.history.loc < #session.history.items then - session.history.loc = session.history.loc + 1 - go_to_url(session.history.items[session.history.loc]) + local th = session.tabs[session.current_tab].history + if th.loc < #th.items then + th.loc = th.loc + 1 + go_to_url(th.items[th.loc]) end end + function print_help() print(separator) print('\nStubb ' .. version) @@ -287,25 +346,43 @@ function print_help() 0 (text) and 1 (gopher map). The application is named for the character Stubb from Moby Dick known for his imaginative patter and good humor. + The command bar lists the current tab number, out of how many total tabs. It also lists the current place in history (for the tab you are currently on). + Commands: (B)ack (F)orward (G)o to url + - (B)ookmark + - (L)ink + - (T)ab + - (U)rl (R)eload current address - (V)iew link/bookmark - (A)dd bookmark - (D)elete bookmark - (L)ist bookmarks + (A)dd + - (B)ookmark + - (T)ab + (D)elete + - (B)ookmark + - (T)ab + (L)ist + - (B)ookmarks + - (T)abs (U)pdate bookmark title (H)elp (Q)uit Stubb (S)ave file to disk + + For more information, source code, or to get a copy: + + http://tildegit.org/sloum/stubb + + Stubb is free, as in freedom, software. Modify it as you see fit, give it to whoever you want. It would be great if you referenced the original or cross linked, but it is not required. + ]] print(helpstring) end @@ -318,7 +395,8 @@ end function save_file() - if not session.filedata then return print('No file available. Save canceled.') end + local t = session.tabs[session.current_tab] + if not t.filedata then return print('No file available. Save canceled.') end local homedir = assert(os.getenv('HOME'), 'Unable to find home directory') io.write('Enter the filename to save as (! to cancel): ' .. homedir .. '/') local filename = io.read() @@ -327,7 +405,7 @@ function save_file() elseif string.match(filename, '^[%.%w_-]+$') then local filepath = homedir .. '/' .. filename local f = io.open(filepath, 'w+') - f:write(table.concat(session.filedata,'\n')) + f:write(table.concat(t.filedata,'\n')) f:close() print(string.format('File saved as: %s', filepath)) else @@ -337,21 +415,22 @@ end function go_to_url(u, add2history) - url = parse_url(u) + local url = parse_url(u) + local t = session.tabs[session.current_tab] if not url.host or url.scheme ~= 'gopher' then print(separator) print(' I n v a l i d u r l') elseif table.has(valid_types, url.gophertype) then - session.filedata = request_gopher(url.host, url.port, url.path) + t.filedata = request_gopher(url.host, url.port, url.path) print(separator) - handle_response(session.filedata, url.gophertype) + handle_response(t.filedata, url.gophertype) local o = url.scheme .. '://' .. url.host .. ':' .. url.port .. '/' .. url.gophertype .. url.path print('\n\n') print(o) if add2history then - table.truncate(session.history.items, session.history.loc + 1) - table.insert(session.history.items, o) - session.history.loc = session.history.loc + 1 + table.truncate(t.history.items, t.history.loc + 1) + table.insert(t.history.items, o) + t.history.loc = t.history.loc + 1 end end end @@ -362,7 +441,7 @@ function handle_response(res_table, gtype) local o = table.concat(res_table, '\n') print(o) else - session.current_links = {} + session.tabs[session.current_tab].current_links = {} for i, v in ipairs(res_table) do display_gophermap_row(v) end @@ -405,6 +484,18 @@ function show_favorites() end +function show_tabs() + print(separator) + print('\n [ T A B S ]\n\n') + for i, v in ipairs(session.tabs) do + local u = v.history.items[v.history.loc] or 'Empty tab' + local t = session.current_tab == i and ' --> ' or ' ' + print(string.format('%s(%d) %s', t, i, u)) + end + print('\n' .. separator) +end + + function save_favorites() local file = io.open(save_file_location, 'w+') file:write("session.favorites = {\n") @@ -422,11 +513,30 @@ function file_exists(name) end +function new_tab() + local t = { + history = { + loc=0, + items={} + }, + current_links = {}, + favorites={} + } + if #session.tabs < 4 then + table.insert(session.tabs, t) + session.current_tab = #session.tabs + end +end + + +function init() + new_tab() + if file_exists(save_file_location) then + dofile(save_file_location) + end + mainloop() +end -- run the program -if file_exists(save_file_location) then - dofile(save_file_location) -end -mainloop() --- end of program +init() diff --git a/stubb-lite b/stubb-lite new file mode 100755 index 0000000..60858c4 --- /dev/null +++ b/stubb-lite @@ -0,0 +1,432 @@ +#!/usr/bin/env lua + +local version = 'v0.6.0' +local socket = require("socket") +local urlparser = require("socket.url") +local separator = '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n' +local valid_types = {'0', '1', 'i'} +session = { history={ loc=0, items={} }, current_links = {}, favorites={} } +local save_file_location = string.format('%s/.stubbfaves', assert(os.getenv('HOME'), 'Unable to get home directory')) + + +function string.split(str, sep) + assert(str and sep, 'sting.split requires both an input string and a field separator string') + local start = 1 + local t = {} + while true do + local out + local point = string.find(str, sep, start) + if point == start then + out = '' + elseif not point then + out = string.sub(str, start) + else + out = string.sub(str, start, point - 1) + end + table.insert(t, out) + start = point and point + 1 or nil + if not start then + break + end + end + return t +end + + +function table.out(t) + for i, v in ipairs(t) do + print(string.format('%q', v)) + end +end + + +table.has = function(t, item) + for i, v in pairs(t) do + if v == item then + return true + end + end + + return false +end + + +table.truncate = function(t, i) + while t[i] do + table.remove(t, i) + end +end + + +--based on: +--https://stackoverflow.com/questions/9013290/lua-socket-client/9014261#9014261 +function request_gopher(host, port, query) + local tcp = assert(socket.tcp()) + local response = {} + + tcp:settimeout(4, 't') + local connection = tcp:connect(host, port); + local request + if connection then + request = tcp:send(query.."\n"); + end + + if request then + while true do + local s, status, partial = tcp:receive() + if status == "closed" then break end + if s ~= '.' then + table.insert(response, (s or partial)) + end + end + tcp:close() + elseif connection then + response[1] = 'iConnection error. Bad request.' + tcp:close() + else + response[1] = 'iConnection error. Bad host.' + end + + return response +end + + +function display_gophermap_row(row) + local val = string.split(row, '\t') + local gophertype = string.sub(val[1], 1, 1) + local text = string.sub(val[1], 2) + local spacer = ' ' + if gophertype == 'i' then + print(spacer..text) + elseif table.has(valid_types, gophertype) then + local leader = gophertype == '0' and 'TXT' or 'MAP' + local url = string.format('%s:%s/%s%s', val[3] or '', val[4] or '', gophertype, val[2] or '/') + table.insert(session.current_links, url) + local linknum = string.format('(%d)', #session.current_links) + print(string.format(' %s %5s %s', leader, linknum, text)) + end +end + + +function getch() + -- taken from: http://lua.2524044.n2.nabble.com/How-to-get-one-keystroke-without-hitting-Enter-td5858614.html + os.execute("stty cbreak /dev/tty 2>&1") + local key = io.read(1) + os.execute("stty -cbreak /dev/tty 2>&1"); + print('\27[1D ') + return(key); +end + + +function mainloop() + print('\n') + while true do + local bar = string.format('\27[7m o--[ S t u b b ]--> (H)elp, (Q)uit %d/%d \27[0m', session.history.loc, #session.history.items) + print(bar) + -- print('\27[7m (G)o to url, (B)ack, (V)isit link, (Q)uit \27[0m') + local key = string.lower(getch()) + if key == 'q' then + save_favorites() + os.exit(0) + elseif key == 'h' then + print_help() + elseif key == 'b' then + back() + elseif key == 'f' then + forward() + elseif key == 'a' then + add_favorite() + elseif key == 'l' then + show_favorites() + elseif key == 'u' then + update_favorite() + elseif key == 'd' then + remove_favorite() + elseif key == 's' then + save_file() + elseif key == 'r' then + refresh() + elseif key == 'g' then + io.write('Enter gopher url > ') + local url = io.read() + if url ~= '0' and url ~= '' then + go_to_url(url, true) + else + print('\n') + end + elseif key == 'v' then + visit_link() + end + end +end + + +function refresh() + if session.history.loc > 0 then + go_to_url(session.history.items[session.history.loc]) + end +end + + +function add_favorite() + if #session.history.items == 0 then return false end + local title + repeat + io.write('Enter title for bookmark (! to cancel): ') + title = io.read() + until title + if title == '!' then return print('Cancelled') end + table.insert(session.favorites,{display = title, link = session.history.items[session.history.loc]}) + print('Bookmark added') + save_favorites() +end + + +function update_favorite() + local title + io.write('Enter the bookmark id (! to cancel) > ') + local favid = io.read() + if favid == '!' then return print('Cancelled') end + local id = tonumber(favid) or tonumber(string.sub(favid,2)) + if id then + local item = session.favorites[id] + if item then + print(string.format('Old title: %s', item.display)) + while not title or title == '' do + io.write('Enter new title (! to cancel) > ') + title = io.read() + if title == '!' then return print('Cancelled') end + end + item.display = title + print('Bookmark has been updated') + save_favorites() + else + print('Invalid bookmark id') + end + else + print('Invalid bookmark id') + end +end + + +function remove_favorite() + io.write('Enter the bookmark id (! to cancel) > ') + local favid = io.read() + if favid == '!' then return print('Cancelled') end + local id = tonumber(favid) or tonumber(string.sub(favid,2)) + if id then + local item = session.favorites[id] + if item then + table.remove(session.favorites, id) + print('Bookmark has been removed') + save_favorites() + else + print('Invalid bookmark id') + end + else + print('Invalid bookmark id') + end +end + + +function visit_link() + local linkid + local favorite + repeat + favorite = false + io.write('Enter link id (! to cancel) > ') + local inp = io.read() + if inp == '!' then return print('Cancelled') end + linkid = tonumber(inp) + if not linkid and string.sub(inp, 1, 1) == 'f' then + linkid = tonumber(string.sub(inp, 2)) + favorite = true + end + until linkid + local linkurl + if favorite then + selection = session.favorites[linkid] + if selection then + linkurl = session.favorites[linkid].link + else + return print('Invalid bookmark id') + end + else + linkurl = session.current_links[linkid] + end + if linkurl then + table.truncate(session.history.items, session.history.loc + 1) + go_to_url(linkurl, true) + else + local out = string.format('Link id %d does not exist...', linkid) + print_gopher_error(out) + end +end + +function back() + if session.history.loc > 1 and #session.history.items > 1 then + session.history.loc = session.history.loc - 1 + go_to_url(session.history.items[session.history.loc]) + end +end + + +function forward() + if session.history.loc < #session.history.items then + session.history.loc = session.history.loc + 1 + go_to_url(session.history.items[session.history.loc]) + end +end + +function print_help() + print(separator) + print('\nStubb ' .. version) + local helpstring = [[ + + Stubb is a text based gopher client that only displays and supports gophertypes: + 0 (text) and 1 (gopher map). The application is named for the character Stubb + from Moby Dick known for his imaginative patter and good humor. + + Commands: + + + (B)ack + (F)orward + (G)o to url + (R)eload current address + (V)iew link/bookmark + + + (A)dd bookmark + (D)elete bookmark + (L)ist bookmarks + (U)pdate bookmark title + + + (H)elp + (Q)uit Stubb + (S)ave file to disk + ]] + print(helpstring) +end + + +function print_gopher_error(text) + print(separator) + print(text) +end + + +function save_file() + if not session.filedata then return print('No file available. Save canceled.') end + local homedir = assert(os.getenv('HOME'), 'Unable to find home directory') + io.write('Enter the filename to save as (! to cancel): ' .. homedir .. '/') + local filename = io.read() + if filename == '!' or filename == '' then + return print('Cancelled') + elseif string.match(filename, '^[%.%w_-]+$') then + local filepath = homedir .. '/' .. filename + local f = io.open(filepath, 'w+') + f:write(table.concat(session.filedata,'\n')) + f:close() + print(string.format('File saved as: %s', filepath)) + else + print('Invalid filename') + end +end + + +function go_to_url(u, add2history) + url = parse_url(u) + if not url.host or url.scheme ~= 'gopher' then + print(separator) + print(' I n v a l i d u r l') + elseif table.has(valid_types, url.gophertype) then + session.filedata = request_gopher(url.host, url.port, url.path) + print(separator) + handle_response(session.filedata, url.gophertype) + local o = url.scheme .. '://' .. url.host .. ':' .. url.port .. '/' .. url.gophertype .. url.path + print('\n\n') + print(o) + if add2history then + table.truncate(session.history.items, session.history.loc + 1) + table.insert(session.history.items, o) + session.history.loc = session.history.loc + 1 + end + end +end + + +function handle_response(res_table, gtype) + if gtype == '0' or gtype == 0 then + local o = table.concat(res_table, '\n') + print(o) + else + session.current_links = {} + for i, v in ipairs(res_table) do + display_gophermap_row(v) + end + end +end + + +-- Modified version of socket.url.parse +-- Sets up defaults and adds parsing of gophertype +function parse_url(u) + if not string.find(u, '://', 1, true) then + u = 'gopher://'..u + end + + local default = {port=70, scheme='gopher'} + local parsed = urlparser.parse(u, default) + if not parsed.path then + parsed.path = '/' + end + if string.find(parsed.path, '^/[%d%a]') then + parsed.gophertype = string.sub(parsed.path, 2, 2) + parsed.path = string.sub(parsed.path, 3) or '/' + elseif string.sub(parsed.path, -1) == '/' then + parsed.gophertype = '1' + else + parsed.gophertype = '0' + end + + return parsed +end + + +function show_favorites() + print(separator) + print('\n >---B O O K M A R K S--->\n\n') + for i, v in ipairs(session.favorites) do + print(string.format(' (f%d) %s', i, v.display)) + end + print('\n' .. separator) +end + + +function save_favorites() + local file = io.open(save_file_location, 'w+') + file:write("session.favorites = {\n") + for _,v in ipairs(session.favorites) do + file:write(string.format(" {[%q] = %q, [%q] = %q},\n", 'display', v.display, 'link', v.link)) + end + file:write("}\n") + file:close() +end + + +function file_exists(name) + local f=io.open(name,"r") + if f~=nil then io.close(f) return true else return false end +end + + + + +-- run the program +if file_exists(save_file_location) then + dofile(save_file_location) +end +mainloop() +-- end of program