Added tabs and created a forked lite version

This commit is contained in:
sloumdrone 2019-01-05 22:17:15 -08:00
parent 29e5f07ae4
commit 94436701a8
3 changed files with 643 additions and 75 deletions

View File

@ -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.

226
stubb
View File

@ -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:
<Navigation>
(B)ack
(F)orward
(G)o to url
- (B)ookmark
- (L)ink
- (T)ab
- (U)rl
(R)eload current address
(V)iew link/bookmark
<Bookmarks>
(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
<System>
(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()

432
stubb-lite Executable file
View File

@ -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 >/dev/tty 2>&1")
local key = io.read(1)
os.execute("stty -cbreak </dev/tty >/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:
<Navigation>
(B)ack
(F)orward
(G)o to url
(R)eload current address
(V)iew link/bookmark
<Bookmarks>
(A)dd bookmark
(D)elete bookmark
(L)ist bookmarks
(U)pdate bookmark title
<System>
(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