#!/usr/bin/ruby ############### # Rupher # # Version 0.3 # ############### # 249 SLOC, including config stuff # # Created by Jacob # gopher://gopher.silentmessengers.org for contact info # #------------------------------------------------------ # # This is NOT pure ruby. Much of this is just glorified # shell scripting. Likewise, this is NOT cross-platform. # It will probably work on anything but windows, but I # run linux, and I wrote this with that in mind. # # We need: # * fold # * curl # * cat # * echo # * md5sum # # This may change in the future, but these should be # already installed on any reasonable system. # # Optionally, if you have pmenu installed, you will # get a nicer functionality with the bookmarks. It's # like dmenu, but for the shell. You will also need # zsh for this to work at the moment. # # https://github.com/sgtpep/pmenu # # And lastly, if the symbols do not display correctly, # you can install nerd fonts below, or customize the # variables so that they're useful to you. Chances are # there is a package available in your distros repos. # # http://nerdfonts.com # # CONFIGURATION HOME = "gopher.silentmessengers.org" MARKS = "#{ENV["HOME"]}/.config/rupherholes" TMP = "/tmp/rupher" # Make sure it does NOT end with / PROMPT = "> " PAGER = ENV["PAGER"] BROWSER = ENV["BROWSER"] VIEWER = "sxiv -a" FOLD_W = 65 # Link symbols # Make sure these strings are the same length # for best results. Colors are defined at the # end of the script, fyi. FILE = WHITE + "" DIR = BLUE + "" IMAGE = CYAN + "" WWW = GREEN + "爵" TELNET = MAGENTA + "" BIN = RED + "" SCRIPT = YELLOW + "" ERROR = RED + "" # If no uri is specified at the command line, the home # page will be opened up. All uris are pre-prefaced # with gopher:// in the curl command, so please don't # use that prefix. I'll fix that later so you won't # have to bother with doing less work if you so choose. # # Commands are as follows: # # > help # > h # # Displays a short list of commands without any # explanation. Just for a reminder. Read here # for more information. # # > go [args] # # Will go to the provided uri. # # > fold [line number] # # Will fold a link (document) to the width specified # for the FOLD_W constant # # > cat [line number] # # Prints a document to the stdout. # If no line number is specified, it re-prints the # current directory to the screen. # # > less [line number] # # Opens a document in the PAGER. Doesn't yet handle # directories. # # > url [line number] # # If no line number is specified, will print the url # of the current directory. Otherwise, it will print # the url of the document at the line number provided. # # > dl [line number] # # Downloads the document or file specified. Prompts # for a filename. Downloads to TMP by default. # # > up # # Goes up one level. # i.e. somewhere.org/1/some/dir => somewhere.org/1/some # # > back # > b # # Goes to the previous uri. # # > tour [number] [number...] # # Adds documents to a sort of queue. Used in conjunction # with the next command to browse lots of links. Takes # the following arguments # # * A single link number # > tour 15 # * A range # > tour 15-20 # * Multiple links # > tour 1 3 5 7 # * list, to list links # > tour list # # > next # > n # # Goes to the next link in the tour list. # # > history # > hist # # Shows a list of places you've been # # > home # # Takes you to whatever your HOME is. # # > add # # Adds a uri to your bookmarks file. Prompts # for a small description of your liking. # # > bookmarks # > marks # # Displays your bookmarks. If you have pmenu # installed, a list will pop up, and you can # fuzzy select a bookmark to load. # # > search [arguments...] # # Searches using Veronica II # # > quit # > q # > exit # # Erases the TMP cache directory and exits # cleanly. # # > abort # # This is sort of a debugging thing that lets # you quit without erasing the cache directory. # # I have not taken any special efforts to sanitize # this or make it so that you can't break it. It # it made to work with intelligent user input. If # you find any serious issues, let me know. If you # do something strange, I am not responsible. # # Enjoy! # #------------------------------------------------------ #------------------------------------------------------ # Preamble require 'fileutils' ARGV[0] == nil ? base_uri = HOME : base_uri = ARGV[0] FileUtils.touch(MARKS) if File.exists?(MARKS) == false FileUtils.mkdir(TMP) if File.exists?(TMP) == false $history, $tour = Array.new, Array.new $lasturi = [base_uri] #------------------------------------------------------ # Useful for quickly returning a URL from the raw line def makeuri(l, lines) line = l.to_i - 1 newuri = "#{lines[line][3]}/#{lines[line][0]}#{lines[line][2]}" newuri = lines[line][2].gsub(/^URL:/, '') if lines[line][0] == "h" return newuri end #------------------------------------------------------ # Caches the raw document and returns the filename def cacheit(uri, geturi) md5 = `echo #{uri} | md5sum`.split[0] f = "#{TMP}/#{md5}" (File.exists?(f) == false) ? ( $history.push(uri) if geturi == 1; system("curl -s 'gopher://#{uri}' > #{f}") ) : 0 return f end #------------------------------------------------------ # This handles all of the user input def getinput(lines, uri) print PROMPT raw_line = STDIN.gets.chomp if raw_line.to_i >= 1 return getinput(lines, uri) if lines == nil newuri = makeuri(raw_line, lines) line = raw_line.to_i - 1 case lines[line][0] when "0" f = cacheit(newuri, 0) (File.read(f).lines.length > `tput lines`.to_i) ? system("#{PAGER} #{f}") : (puts File.read(f)) return getinput(lines, uri) when "1" $lasturi.push(uri) return geturi(newuri) when "h" system("#{BROWSER} #{newuri}") return getinput(lines, uri) when "7" $lasturi.push(uri) print "\nEnter your query> " answer = gets.chomp (newuri.match?(/\?$/) == true) ? (return geturi("#{newuri}#{answer}")) : (return geturi("#{newuri}?#{answer}")) when "g", "I" system("curl -s gopher://#{newuri} > #{TMP}/#{newuri.split("/")[-1]}") system("#{VIEWER} #{TMP}/#{newuri.split("/")[-1]}") return getinput(lines, uri) when "9" system("curl -s gopher://#{newuri} > #{TMP}/#{newuri.split("/")[-1]}") puts "saved to #{TMP}/#{newuri.split("/")[-1]}" return getinput(lines, uri) when "8" host, port = lines[line][3], lines[line][4] print host, " ", port, "\n" return getinput(lines, uri) else return getinput(lines, uri) end elsif raw_line.match?(/^go /) == true $lasturi.push(uri) return geturi(raw_line.gsub(/^go /, '')) elsif raw_line.match?(/^tour/) == true if raw_line.split[1] == "list" $tour.each { |i| puts i } return getinput(lines, uri) elsif raw_line.split[1] == "clear" $tour = Array.new return getinput(lines, uri) elsif raw_line.split.length == 2 and raw_line.split[1].match?("-") == true x, y = raw_line.split first, last = y.split("-") (first.to_i..last.to_i).each { |item| $tour.push(makeuri(item, lines)) } return getinput(lines, uri) elsif raw_line.split.length == 2 x, y = raw_line.split $tour.push(makeuri(y, lines)) return getinput(lines, uri) elsif raw_line.split.length > 2 raw_line.split[1..-1].each { |item| $tour.push(makeuri(item, lines)) } return getinput(lines, uri) else return getinput(lines, uri) end elsif ["next", "n"].include? raw_line item = $tour.shift if $tour != [] (item == nil?) ? (return getinput(lines, uri)) : (return geturi(item)) elsif ["hist", "history"].include? raw_line $history.each { |i| puts i } return getinput(lines, uri) elsif raw_line.match?(/^cat/) == true return geturi(uri) if raw_line.split[1] == nil newuri = makeuri(raw_line.split[1], lines) f = cacheit(newuri, 0) puts File.read(f) return getinput(lines, uri) elsif raw_line.match?(/^fold /) == true return getinput(lines, uri) if raw_line.split[1] == nil newuri = makeuri(raw_line.split[1], lines) f = cacheit(newuri, 0) (File.read(f).lines.length > `tput lines`.to_i) ? system("fold -s -w #{FOLD_W} #{f} | #{PAGER}") : (system("fold -s -w #{FOLD_W} #{f}"); puts) return getinput(lines, uri) elsif raw_line.match?(/^less/) == true return getinput(lines, uri) if raw_line.split[1] == nil newuri = makeuri(raw_line.split[1], lines) f = cacheit(newuri, 0) system("#{PAGER} #{f}") return getinput(lines, uri) elsif raw_line.match?(/^url/) == true puts uri if raw_line.split[1] == nil puts makeuri(raw_line.split[1], lines) if raw_line.split[1] != nil return getinput(lines, uri) elsif raw_line.match?(/^dl /) == true x, line = raw_line.split newuri = makeuri(line, lines) print "Please enter a filename => " filename = STDIN.gets.chomp (filename != "") ? (system("curl -s 'gopher://#{newuri}' > #{TMP}/#{filename}"); puts "Saved to #{TMP}/#{filename}") : (puts "Cancelled.") return getinput(lines, uri) elsif raw_line.match?(/^search/) == true return getinput(lines, uri) if raw_line.split[1] == nil qry = raw_line.split[1..-1].join(" ") return geturi("gopher.floodgap.com/7/v2/vs?#{qry}") elsif raw_line == "home" return geturi(HOME) elsif ["back", "b"].include? raw_line ($lasturi.length > 0) ? (return geturi($lasturi.pop)) : (return geturi(uri)) elsif raw_line == "up" $lasturi.push(uri) new_uri = uri.split("/")[0..-2].join("/") (new_uri != "") ? (return geturi(new_uri)) : (return getinput(lines, uri)) elsif raw_line == "add" print "Short description => " note = STDIN.gets.chomp File.open(MARKS, "a") { |f| f.print uri, " :: ", note, "\n" } return getinput(lines, uri) elsif ["marks", "bookmarks"].include? raw_line # Gonna make this nicer soon if system("which pmenu") == true if system("which zsh") == true choice = `zsh -c "{ echo '#{uri} :: Current Page' ; cat #{MARKS} } | pmenu"` choice = choice.split(" :: ")[0] return geturi(choice) end else puts File.read(MARKS) return getinput(lines, url) end elsif ["q", "quit", "exit"].include? raw_line FileUtils.remove_dir(TMP) abort elsif raw_line == "abort" abort elsif ["help", "h"].include? raw_line puts "\033[1;34mEnter a line number to navigate there.\n\n" puts "\033[1;33mOther commands: " puts "\033[1;37mgo, fold, less, cat, url, dl, back, up, tour, next" puts "\033[1;37mhistory, home, add, bookmarks, search\n\n" puts "\033[1;31mq to quit\033[0m" return getinput(lines, uri) else return getinput(lines, uri) end end #------------------------------------------------------ # This presents us with a nicely formatted document def geturi(uri) file = cacheit(uri, 1) raw = File.read(file) (raw == "") ? ( puts "Timeout, bad request, or empty response."; $history.delete(uri); return getinput(nil, uri) ) : 0 puts "\n\033[1;33m#{uri}", "-"*uri.length, "\n\033[0m" lines = raw.encode("UTF-8", :invalid => :replace).split("\n") lines.length.times { lines.push( lines.shift.split("\t") ) } lines.pop x = 1 lines.each do |i| first = i.shift i.reverse! i.push(first[1..-1]) i.push(first[0]) i.reverse! case i[0] when "0" print "#{x} #{FILE} " # print "#{x}\033[1;37m  " when "1" print "#{x} #{DIR} " # print "#{x}\033[1;34m  " when "h" print "#{x} #{WWW} " # print "#{x}\033[1;32m 爵 " when "7" print "#{x} #{SCRIPT} " # print "#{x}\033[1;33m  " when "g" print "#{x} #{IMAGE} " # print "#{x}\033[1;36m  " when "I" print "#{x} #{IMAGE} " # print "#{x}\033[1;36m  " when "3" print "#{x} #{ERROR} " # print "#{x}\033[1;31m  " when "9" print "#{x} #{BIN} " # print "#{x}\033[1;31m  " when "8" print "#{x} #{TELNET} " # print "#{x}\033[1;35m  " else print " " end puts i[1]; print "\033[0m" x += 1 end getinput(lines, uri) end #------------------------------------------------------ # These are our color definitions BEGIN { RED = "\033[1;31m" GREEN = "\033[1;32m" YELLOW = "\033[1;33m" BLUE = "\033[1;34m" MAGENTA = "\033[1;35m" CYAN = "\033[1;36m" WHITE = "\033[1;37m" } #------------------------------------------------------ # This starts up the whole show by loading whatever # uri was provided, or else loading HOME geturi(base_uri) if __FILE__ == $0 #------------------------------------------------------ # EOF