gem.awk

A gemini client in POSIX awk
git clone git://git.vgx.fr/gem.awk
Log | Files | Refs

gem.awk (7596B)


      1 #!/usr/bin/awk -f
      2 
      3 # ad hoc, probably very slow URL encoder
      4 function url_encode(url) {
      5     gsub("%", "%25", url)
      6     for (i=0 ; i<256 ; i++) {
      7         char = sprintf("%c",i)
      8         if (char !~ /[%0-9A-Za-z._~-]/ && index(url, char)) {
      9             char_re = char ~ /[.^[$()|*+?{\\]/ ? "\\" char : char
     10             gsub(char_re, sprintf("%%%02X", i), url)
     11         }
     12     }
     13     return url
     14 }
     15 
     16 function parse_gemini(connexion_cmd) {
     17     PAGE_URL=CURRENT_URL
     18     PAGE_LINE_NUM=0
     19     PAGE_LINK_NUM=0
     20     PAGE_TITLE_NUM=0
     21     pre=0
     22     for (i in PAGE_LINES) delete PAGE_LINES[i]
     23     for (i in PAGE_LINKS) delete PAGE_LINKS[i]
     24     for (i in PAGE_TITLES) delete PAGE_TITLES[i]
     25 
     26     if (!HISTORY_NUM || HISTORY[HISTORY_NUM] != PAGE_URL)
     27         HISTORY[++HISTORY_NUM] = PAGE_URL
     28 
     29     while (connexion_cmd | getline) {
     30         sub(/\r$/, "")
     31         if (!pre) {
     32             if (/^=>/) {
     33                 PAGE_LINKS[++PAGE_LINK_NUM]=length($1) == 2 ? $2 : substr($1, 2)
     34                 match($0, /^=>[ \t]*[^ \t]+[ \t]*/)
     35                 $0="=> [" PAGE_LINK_NUM "] \033[4m" PAGE_LINKS[PAGE_LINK_NUM] "\033[0m " (RSTART ? substr($0, RSTART + RLENGTH) : "")
     36             } else if (/^#/) {
     37                 PAGE_TITLES[++PAGE_TITLE_NUM]=PAGE_LINE_NUM + 1
     38                 $0="\033[1;4m" $0 "\033[0m"
     39             }
     40         }
     41 
     42         if (/^```/)
     43             pre = pre ? 0 : 1
     44         else
     45             PAGE_LINES[++PAGE_LINE_NUM]=$0
     46     }
     47     close(connexion_cmd)
     48 
     49     for (i in PAGE_LINES)
     50         print PAGE_LINES[i]
     51 }
     52 
     53 function print_text(connexion_cmd) {
     54     while (connexion_cmd | getline)
     55         print
     56     close(connexion_cmd)
     57 }
     58 
     59 function connexion_open(url, domain, port) {
     60     if (!port)
     61         port = 1965
     62     connexion_cmd="echo '" url "' | openssl s_client -crlf -quiet -verify_quiet -connect '" domain ":" port "' 2>/dev/null"
     63     connexion_cmd | getline
     64 }
     65 
     66 function plumb_out(connexion_cmd, out) {
     67 	system(connexion_cmd " | tail +2 " out )
     68 }
     69 
     70 function gemini_url_open(url) {
     71     split(url, path_elt, "/")
     72     domain=path_elt[3]
     73     port=1965
     74     if (match(domain, /:[[:digit:]]+$/)) {
     75         port=substr(domain, RSTART + 1)
     76         domain=substr(domain, 1, RSTART - 1)
     77     }
     78 
     79     connexion_open(url, domain, port)
     80 
     81     if (! $0) {
     82         close(connexion_cmd)
     83         sub(/null$/, "stdout", connexion_cmd)
     84         sub(/-quiet -verify_quiet/, "", connexion_cmd)
     85         print "\033[1mOpenSSL connexion error:\033[0m"
     86         system(connexion_cmd)
     87     } else if (/^1[0-9]/) { # INPUT
     88         close(connexion_cmd)
     89         print "Input requested: (blank to ignore)"
     90         prompt(substr($2, 4, length($2) -4))
     91         getline
     92         if ($0)
     93             gemini_url_open(url "?" url_encode($0))
     94     } else if (/^2[0-9]/) { # SUCCESS
     95         CURRENT_URL=url
     96         if (!$2 || $2 ~ /text\/gemini/)
     97             parse_gemini(connexion_cmd)
     98         else if ($2 ~ /^text/)
     99             print_text(connexion_cmd)
    100         else {
    101             close(connexion_cmd)
    102             print "Binary filetype: " $2
    103             print "Blank to ignore, '| cmd' or '> file' to redirect"
    104             prompt("Redirection ")
    105             getline
    106             if (/^[|>]/)
    107                 plumb_out(connexion_cmd, $0)
    108             else
    109                 print "Ignored."
    110         }
    111     } else if (/^3[0-9]/) { # REDIRECT
    112         close(connexion_cmd)
    113         redirect_url = substr($2, 1, length($2) -1)
    114         print "Follow redirection ? => \033[4m" redirect_url "\033[0m"
    115         prompt("Y/n")
    116         getline
    117         if (! /^[nN]/)
    118             any_url_open(redirect_url, url)
    119     } else {
    120         close(connexion_cmd)
    121         print "Error: " $0
    122     }
    123 
    124     # $0 has been completely changed at this point:
    125     prompt()
    126     next
    127 }
    128 
    129 function any_url_open(url, base_url) {
    130     if (!base_url)
    131         base_url = PAGE_URL
    132     if (!url) {
    133         print "Empty URL."
    134     } else if (url ~ /^\/\//) { # scheme-less remote link
    135         gemini_url_open("gemini:" url)
    136     } else if (url ~ /^\//) {
    137         # local absolute link
    138         match(PAGE_URL, /^gemini:\/\/[^\/]+/)
    139         gemini_url_open(substr(PAGE_URL, 1, RLENGTH) url)
    140     } else if (url ~ /^[^:]+(\/.*)?$/) {
    141         # relative link
    142         if (base_url ~ /\/$/)
    143             gemini_url_open(base_url url)
    144         else {
    145             parent_url=parent(base_url)
    146             if (parent_url == "gemini://")
    147                 gemini_url_open(base_url "/" url)
    148             else
    149                 gemini_url_open(parent_url url)
    150         }
    151     } else if (url ~ /^gemini:\/\//) {
    152         gemini_url_open(url)
    153     } else {
    154         print "Not a gemini URL, open with (blank to ignore):"
    155         prompt("System command")
    156         getline
    157         if($0)
    158             system($0 " '" url "'")
    159     }
    160 
    161     # $0 has been completely changed at this point:
    162     prompt()
    163     next
    164 }
    165 
    166 function parent(url) {
    167     sub(/\/[^\/]*\/?$/, "/", url)
    168     return url
    169 }
    170 
    171 function prompt(str) {
    172     printf("%s%s", (str ? str : PAGE_URL), "\033[1m>\033[0m ")
    173 }
    174 
    175 function help() {
    176     print "The following commands are available:"
    177     print "  gemini://\033[3;4m.*\033[0m : open a gemini URL"
    178     print "  \033[3;4mn\033[0m           : follow link \033[3;4mn\033[0m of current text/gemini page"
    179     print "  .           : reload current page"
    180     print "  ..          : go to parent"
    181     print "  | \033[3;4mcmd\033[0m       : redirect current page to \033[3;4mcmd\033[0m (new connexion)"
    182     print "  > \033[3;4mfile\033[0m      : redirect current page to \033[3;4mfile\033[0m (new connexion)"
    183     print "  toc         : list titles in a text/gemini page"
    184     print "  links       : list URLs linked in a text/gemini page"
    185     print "  history [\033[3;4mN\033[0m] : list URLs of visited pages, or open \033[3;4mN\033[0mth visited page"
    186     print "  back        : go back to previous page in history (swapping the 2 last elements of history)"
    187     print "  help        : show this help"
    188 }
    189 
    190 BEGIN {
    191     help()
    192     prompt()
    193 }
    194 
    195 /^gemini:\/\// {
    196     gemini_url_open($1)
    197 }
    198 
    199 $1 ~ /^[[:digit:]]+$/ {
    200     if ($1 == 0 || $1 > PAGE_LINK_NUM)
    201         print "No link with id " $1
    202     else
    203         any_url_open(PAGE_LINKS[$1])
    204 }
    205 
    206 /^\.$/ {
    207     if (CURRENT_URL)
    208         gemini_url_open(CURRENT_URL)
    209     else
    210         print "No current page to reload."
    211 }
    212 
    213 /^\.\.$/ {
    214     if (CURRENT_URL) {
    215         parent_url=parent(CURRENT_URL)
    216         if (parent_url == "gemini://")
    217             print "Already at site root."
    218         else
    219             gemini_url_open(parent_url)
    220         }
    221     else
    222         print "No current page."
    223 }
    224 
    225 /^[|>]/ {
    226     if (CURRENT_URL) {
    227         plumb_out(connexion_cmd, $0)
    228     }
    229     else
    230         print "No current page."
    231 }
    232 
    233 $1 == "toc" {
    234     if (!PAGE_TITLE_NUM) {
    235         print "No title found."
    236     }
    237     for(i in PAGE_TITLES)
    238         print PAGE_LINES[PAGE_TITLES[i]] ": " PAGE_TITLES[i]
    239 }
    240 
    241 $1 == "links" {
    242     if (!PAGE_LINK_NUM) {
    243         print "No link found."
    244     }
    245     for(i in PAGE_LINKS)
    246         print "=> [" i "] \033[4m" PAGE_LINKS[i] "\033[0m"
    247 }
    248 
    249 $1 == "history" {
    250     if (!HISTORY_NUM) {
    251         print "No page visited in this session."
    252     } else if ($2) {
    253         if ($2 >= 1 && $2 <= HISTORY_NUM)
    254             gemini_url_open(HISTORY[$2])
    255         else
    256             print "Bad history ID."
    257     } else
    258         for(i in HISTORY)
    259             print "=> [" i "] \033[4m" HISTORY[i] "\033[0m"
    260 }
    261 
    262 $1 == "back" {
    263     if (HISTORY_NUM <= 1) {
    264         print "Nowhere to go back."
    265     } else {
    266         url = HISTORY[HISTORY_NUM - 1]
    267         HISTORY[HISTORY_NUM - 1] = HISTORY[HISTORY_NUM]
    268         HISTORY[HISTORY_NUM] = url
    269         gemini_url_open(url)
    270     }
    271 }
    272 
    273 $1 == "help" {
    274     help()
    275 }
    276 
    277 { prompt() }
    278 
    279 END {
    280     print "Bye!"
    281 }