about summary refs log tree commit diff stats
path: root/gemini.tlv
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2021-12-20 12:19:05 -0800
committerKartik K. Agaram <vc@akkartik.com>2021-12-20 12:19:05 -0800
commit5c46538d2d44bdb7bbacfe649c6087dcc5eb2b3f (patch)
tree783ebec640ff711f335c1c8db71db6c36ad1cd49 /gemini.tlv
parentc4984c2ae5a630df02be330ea4b72b12115e37b7 (diff)
downloadteliva-5c46538d2d44bdb7bbacfe649c6087dcc5eb2b3f.tar.gz
start of a gemini browser
Diffstat (limited to 'gemini.tlv')
-rw-r--r--gemini.tlv357
1 files changed, 357 insertions, 0 deletions
diff --git a/gemini.tlv b/gemini.tlv
new file mode 100644
index 0000000..4f29a13
--- /dev/null
+++ b/gemini.tlv
@@ -0,0 +1,357 @@
+# .tlv file generated by https://github.com/akkartik/teliva
+# You may edit it if you are careful; however, you may see cryptic errors if you
+# violate Teliva's assumptions.
+#
+# .tlv files are representations of Teliva programs. Teliva programs consist of
+# sequences of definitions. Each definition is a table of key/value pairs. Keys
+# and values are both strings.
+#
+# Lines in .tlv files always follow exactly one of the following forms:
+# - comment lines at the top of the file starting with '#' at column 0
+# - beginnings of definitions starting with '- ' at column 0, followed by a
+#   key/value pair
+# - key/value pairs consisting of '  ' at column 0, containing either a
+#   spaceless value on the same line, or a multi-line value
+# - multiline values indented by more than 2 spaces, starting with a '>'
+#
+# If these constraints are violated, Teliva may unceremoniously crash. Please
+# report bugs at http://akkartik.name/contact
+- __teliva_timestamp: original
+  str_helpers:
+    >-- some string helpers from http://lua-users.org/wiki/StringIndexing
+    >
+    >-- index characters using []
+    >getmetatable('').__index = function(str,i)
+    >  if type(i) == 'number' then
+    >    return string.sub(str,i,i)
+    >  else
+    >    return string[i]
+    >  end
+    >end
+    >
+    >-- ranges using (), selected bytes using {}
+    >getmetatable('').__call = function(str,i,j)
+    >  if type(i)~='table' then
+    >    return string.sub(str,i,j)
+    >  else
+    >    local t={}
+    >    for k,v in ipairs(i) do
+    >      t[k]=string.sub(str,v,v)
+    >    end
+    >    return table.concat(t)
+    >  end
+    >end
+    >
+    >-- iterate over an ordered sequence
+    >function q(x)
+    >  if type(x) == 'string' then
+    >    return x:gmatch('.')
+    >  else
+    >    return ipairs(x)
+    >  end
+    >end
+    >
+    >-- insert within string
+    >function string.insert(str1, str2, pos)
+    >  return str1:sub(1,pos)..str2..str1:sub(pos+1)
+    >end
+    >
+    >function string.remove(s, pos)
+    >  return s:sub(1,pos-1)..s:sub(pos+1)
+    >end
+    >
+    >-- TODO: backport utf-8 support from Lua 5.3
+- __teliva_timestamp: original
+  debugy:
+    >debugy = 5
+- __teliva_timestamp: original
+  dbg:
+    >-- helper for debug by print; overlay debug information towards the right
+    >-- reset debugy within refresh when using this
+    >function dbg(window, s)
+    >  local oldy = 0
+    >  local oldx = 0
+    >  oldy, oldx = window:getyx()
+    >  window:mvaddstr(debugy, 60, s)
+    >  debugy = debugy+1
+    >  window:mvaddstr(oldy, oldx, '')
+    >end
+- __teliva_timestamp: original
+  check_eq:
+    >function check_eq(x, expected, msg)
+    >  if x == expected then
+    >    curses.addch('.')
+    >  else
+    >    print('F - '..msg)
+    >    print('  expected '..tostring(expected)..' but got '..x)
+    >    teliva_num_test_failures = teliva_num_test_failures + 1
+    >    -- overlay first test failure on editors
+    >    if teliva_first_failure == nil then
+    >      teliva_first_failure = msg
+    >    end
+    >  end
+    >end
+- __teliva_timestamp: original
+  map:
+    >-- only for arrays
+    >function map(l, f)
+    >  result = {}
+    >  for _, x in ipairs(l) do
+    >    table.insert(result, f(x))
+    >  end
+    >  return result
+    >end
+- __teliva_timestamp: original
+  reduce:
+    >-- only for arrays
+    >function reduce(l, f, init)
+    >  result = init
+    >  for _, x in ipairs(l) do
+    >    result = f(result, x)
+    >  end
+    >  return result
+    >end
+- __teliva_timestamp: original
+  filter:
+    >-- only for arrays
+    >function filter(l, f)
+    >  result = {}
+    >  for _, x in ipairs(l) do
+    >    if f(x) then
+    >      table.insert(result, x)
+    >    end
+    >  end
+    >  return result
+    >end
+- __teliva_timestamp: original
+  find_index:
+    >function find_index(arr, x)
+    >  for n, y in ipairs(arr) do
+    >    if x == y then
+    >      return n
+    >    end
+    >  end
+    >end
+- __teliva_timestamp: original
+  trim:
+    >function trim(s)
+    >  return s:gsub('^%s*', ''):gsub('%s*$', '')
+    >end
+- __teliva_timestamp: original
+  split:
+    >function split(s, d)
+    >  result = {}
+    >  for match in (s..d):gmatch("(.-)"..d) do
+    >    table.insert(result, match);
+    >  end
+    >  return result
+    >end
+- __teliva_timestamp: original
+  window:
+    >window = curses.stdscr()
+- __teliva_timestamp: original
+  render:
+    >function render(window)
+    >  window:clear()
+    >  -- draw stuff to screen here
+    >  window:attron(curses.A_BOLD)
+    >  window:mvaddstr(1, 5, "example app")
+    >  window:attrset(curses.A_NORMAL)
+    >  for i=0,15 do
+    >    window:attrset(curses.color_pair(i))
+    >    window:mvaddstr(3+i, 5, "========================")
+    >  end
+    >  curses.refresh()
+    >end
+- __teliva_timestamp: original
+  menu:
+    >menu = {}
+- __teliva_timestamp: original
+  update:
+    >function update(window)
+    >  local key = curses.getch()
+    >  -- process key here
+    >end
+- __teliva_timestamp: original
+  init_colors:
+    >function init_colors()
+    >  for i=0,7 do
+    >    curses.init_pair(i, i, -1)
+    >  end
+    >  curses.init_pair(8, 7, 0)
+    >  curses.init_pair(9, 7, 1)
+    >  curses.init_pair(10, 7, 2)
+    >  curses.init_pair(11, 7, 3)
+    >  curses.init_pair(12, 7, 4)
+    >  curses.init_pair(13, 7, 5)
+    >  curses.init_pair(14, 7, 6)
+    >  curses.init_pair(15, -1, 15)
+    >end
+- __teliva_timestamp: original
+  main:
+    >function main()
+    >  init_colors()
+    >
+    >  while true do
+    >    render(window)
+    >    update(window)
+    >  end
+    >end
+- __teliva_timestamp:
+    >Sun Dec 19 16:23:47 2021
+  http_get:
+    >function http_get(url)
+    >  -- https://stackoverflow.com/questions/42445423/luasocket-serveraccept-timeout-tcp
+    >  local parsed_url = socket.url.parse(url)
+    >  local tcp = socket.tcp()
+    >  tcp:connect(parsed_url.host, 80)
+    >  tcp:send('GET / HTTP/1.1\r\n')
+    >  -- http requires the Host header
+    >  tcp:send(string.format('Host: %s\r\n', parsed_url.host))
+    >  tcp:send('\r\n')
+    >  -- tcp:receive('*a') doesn't seem to detect when a request is done
+    >  -- so we have to manage the size of the expected response
+    >  headers = {}
+    >  while true do
+    >    local s, status = tcp:receive()
+    >    if s == nil then break end
+    >    if s == '' then break end
+    >    local header, value = string.match(s, '(.-): (.*)')
+    >    if header == nil then
+    >      print(s)
+    >    else
+    >      headers[string.lower(header)] = value
+    >      print(header, value)
+    >    end
+    >  end
+    >  local bytes_remaining = tonumber(headers['content-length'])
+    >  body = ''
+    >  while true do
+    >    local s, status = tcp:receive(bytes_remaining)
+    >    if s == nil then break end
+    >    body = body .. s
+    >    bytes_remaining = bytes_remaining - string.len(s)
+    >    if bytes_remaining <= 0 then break end
+    >  end
+    >  return body
+    >end
+- __teliva_timestamp:
+    >Mon Dec 20 07:40:17 2021
+  render_page:
+    >function render_page(window, s)
+    >  for i=1,string.len(s) do
+    >    window:addstr(s[i])
+    >  end
+    >end
+- https_get:
+    >-- http://notebook.kulchenko.com/programming/https-ssl-calls-with-lua-and-luasec
+    >function https_get(url)
+    >  local parsed_url = socket.url.parse(url)
+    >  local params = {
+    >    mode = 'client',
+    >    protocol = 'any',
+    >    verify = 'none',  -- I don't know what I'm doing
+    >    options = 'all',
+    >  }
+    >  local conn = socket.tcp()
+    >  conn:connect(parsed_url.host, parsed_url.port or 443)
+    >  conn, err = ssl.wrap(conn, params)
+    >  if conn == nil then
+    >      io.write(err)
+    >      os.exit(1)
+    >  end
+    >  conn:dohandshake()
+    >
+    >  conn:send(url .. "\r\n")
+    >  local line, err = conn:receive()
+    >  return line or err
+    >end
+  __teliva_timestamp:
+    >Mon Dec 20 07:40:46 2021
+- render:
+    >function render(window)
+    >  window:clear()
+    >  if #arg > 0 then
+    >    print(arg[1])
+    >    local text = gemini_get(arg[1])
+    >    render_page(window, text)
+    >  else
+    >    print('pass in a URL at the commandline')
+    >  end
+    >  window:refresh()
+    >end
+  __teliva_timestamp:
+    >Mon Dec 20 08:54:44 2021
+- __teliva_timestamp:
+    >Mon Dec 20 09:02:31 2021
+  parse_gemini_body:
+    >function parse_gemini_body(conn, type)
+    >  local result = ''
+    >  if type == 'text/gemini' then
+    >    while true do
+    >      local line, err = conn:receive()
+    >      if line == nil then break end
+    >      result = result..line..'\n'
+    >    end
+    >  elseif string.sub(type, 1, 5) == 'text/' then
+    >    while true do
+    >      local line, err = conn:receive()
+    >      if line == nil then break end
+    >      result = result..line
+    >    end
+    >  end
+    >  return result
+    >end
+- render:
+    >function render(window)
+    >  window:clear()
+    >  if #arg > 0 then
+    >    print(arg[1])
+    >    local text = gemini_get(arg[1])
+    >    render_page(window, text)
+    >  else
+    >    print('pass in a URL at the commandline')
+    >  end
+    >  window:refresh()
+    >end
+  __teliva_timestamp:
+    >Mon Dec 20 12:12:08 2021
+- __teliva_timestamp:
+    >Mon Dec 20 12:13:45 2021
+  gemini_get:
+    >-- http://notebook.kulchenko.com/programming/https-ssl-calls-with-lua-and-luasec
+    >-- https://tildegit.org/solderpunk/gemini-demo-2
+    >function gemini_get(url)
+    >  if string.find(url, "://") == nil then
+    >    url = "gemini://" .. url
+    >  end              
+    >  local parsed_url = socket.url.parse(url)
+    >  local params = {
+    >    mode = 'client',
+    >    protocol = 'any',
+    >    verify = 'none',  -- I don't know what I'm doing
+    >    options = 'all',
+    >  }
+    >  local conn = socket.tcp()
+    >  conn:connect(parsed_url.host, parsed_url.port or 1965)
+    >  conn, err = ssl.wrap(conn, params)
+    >  if conn == nil then
+    >      io.write(err)
+    >      os.exit(1)
+    >  end
+    >  conn:dohandshake()
+    >
+    >  conn:send(url .. "\r\n")
+    >  local line, err = conn:receive()
+    >  if line == nil then return err end
+    >  local status, meta = string.match(line, "(%S+) (%S+)")
+    >  if status[1] == '2' then
+    >    return parse_gemini_body(conn, meta)
+    >  elseif status[1] == '3' then
+    >    return gemini_get(socket.url.absolute(url, meta))
+    >  elseif status[1] == '4' or line[1] == '5' then
+    >    return 'Error: '..meta
+    >  else
+    >    return 'invalid response from server: '..line
+    >  end
+    >end