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 }