Added tabs and created a forked lite version
This commit is contained in:
parent
29e5f07ae4
commit
94436701a8
60
README.md
60
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.
|
||||
|
||||
|
|
226
stubb
226
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:
|
||||
|
||||
<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()
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue