#!/usr/bin/ruby # -*- Ruby -*- # # Copyright (c) 2005, Free Geek. # # This is free software. You may use, modify, and/or redistribute this software # under the terms of the GNU General Public License, version 2 or later. (See # http://www.gnu.org/copyleft/gpl.html) # # = Synopsis # # RubyTUI is a set of functions that are useful for dealing with # the command line interface. # # = Authors # # * Michael Granger # * Martin Chase # # do some setup so we can be usably interactive. begin require 'readline' include Readline rescue LoadError #puts "faking readline.." def readline(prompt) $stderr.print prompt.chomp return $stdin.gets.chomp end end require 'timeout' # Set this to true for ANSI coloration. $COLOR = true # Set this to an integer to wrap user input in a timeout. $TIMEOUT = false module RubyTUI # Set some ANSI escape code constants (Shamelessly stolen from Perl's # Term::ANSIColor by Russ Allbery and Zenin AnsiAttributes = { 'clear' => 0, 'reset' => 0, 'bold' => 1, 'dark' => 2, 'underline' => 4, 'underscore' => 4, 'blink' => 5, 'reverse' => 7, 'concealed' => 8, 'black' => 30, 'on_black' => 40, 'red' => 31, 'on_red' => 41, 'green' => 32, 'on_green' => 42, 'yellow' => 33, 'on_yellow' => 43, 'blue' => 34, 'on_blue' => 44, 'magenta' => 35, 'on_magenta' => 45, 'cyan' => 36, 'on_cyan' => 46, 'white' => 37, 'on_white' => 47 } ErasePreviousLine = "\033[A\033[K" ############### module_function ############### DIST=(`uname`.strip == "Darwin" ? "mac" : `lsb_release -cs`.chomp) # Create a string that contains the ANSI codes specified and return it def ansiCode( *attributes ) return '' unless $COLOR return '' unless /(?:vt10[03]|screen|[aex]term(?:-color)?|linux|rxvt(?:-unicode))/i =~ ENV['TERM'] attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';') if attr.empty? return '' else str = "\001\033[%sm\002" if DIST == "lucid" str = "\e[%sm" end return str % attr end end ### Return the given +prompt+ with the specified +attributes+ turned on and ### a reset at the end. def colored( prompt, *attributes ) return ansiCode( *(attributes.flatten) ) + prompt + ansiCode( 'reset' ) end ### Output msg as a ANSI-colored program/section header (white on ### blue). HeaderColor = [ 'bold', 'white', 'on_blue' ] def header( msg ) msg.chomp! $stderr.puts ansiCode( *HeaderColor ) + msg + ansiCode( 'reset' ) $stderr.flush end ### Output msg as a ANSI-colored highlighted text. HighlightColor = [ 'red', 'bold' ] def highlight( msg ) $stderr.print ansiCode( *HighlightColor ) + msg + ansiCode( 'reset' ) $stderr.flush end ### Output msg to STDERR and flush it. MessageColor = [ 'cyan' ] def message( msg ) $stderr.print ansiCode( *MessageColor ) + msg + ansiCode( 'reset' ) $stderr.flush end ### Put a newline on the end of a message call. def echo( string ) message string.chomp + "\n" end ### Output the specified msg as an ANSI-colored error message ### (white on red). ErrorColor = [ 'bold', 'white', 'on_red' ] def errorMessage( msg ) message ansiCode( *ErrorColor ) + msg + ansiCode( 'reset' ) end ### Output the specified msg as an ANSI-colored debugging message ### (yellow on blue). DebugColor = [ 'bold', 'yellow', 'on_blue' ] def debugMsg( msg ) return unless $DEBUG msg.chomp! $stderr.puts ansiCode( *DebugColor ) + ">>> #{msg}" + ansiCode( 'reset' ) $stderr.flush end ### Output the specified msg without any colors def display( msg ) $stderr.print msg $stderr.flush end ### Erase the previous line (if supported by your terminal) and output the ### specified msg instead. def replaceMessage( msg ) print ErasePreviousLine message( msg ) end ### Output a divider made up of length hyphen characters. def divider( length=75 ) $stderr.puts( "-" * length ) $stderr.flush end alias :writeLine :divider ### Clear the screen. def clear $stderr.write `clear` $stderr.flush end ### Provide a pause and prompt to continue. def waitasec display "\n" divider(10) pausePrompt clear end ### Wait for input. def pausePrompt prompt "press ENTER to continue..." end ### Output the specified msg colored in ANSI red and exit with a ### status of 1. def abort( msg ) print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n" Kernel.exit!( 1 ) end ### Output the specified promptString as a prompt (in green) and ### return the user's input with leading and trailing spaces removed. PromptColor = ['bold', 'green'] def promptResponse(promptString) return readline( ansiCode(*PromptColor) + "#{promptString}: " + ansiCode('reset') ).strip end ### Output the specified promptString as a prompt and return the ### user's input. If a test is provided, the prompt will repeat until the ### test returns true. An optional failure message can also be passed in. def prompt( promptString, failure_msg="Try again.", &test ) promptString.chomp! response = "" if $TIMEOUT begin Timeout::timeout($TIMEOUT) { response = promptResponse(promptString) } rescue Timeout::Error errorMessage "\nTimed out!\n" end else response = promptResponse(promptString) end until test.call(response) errorMessage(failure_msg) message("\n") response = prompt( promptString ) end if test return response end ### Prompt the user with the given promptString via #prompt, ### substituting the given default if the user doesn't input ### anything. If a test is provided, the prompt will repeat until the test ### returns true. An optional failure message can also be passed in. def promptWithDefault( promptString, default, failure_msg="Try again.", &test ) response = prompt( "%s [%s]" % [ promptString, default ] ) response = default if response.empty? until test.call(response) errorMessage(failure_msg) message("\n") response = promptWithDefault( promptString, default ) end if test return response end ### Yes/No prompt with default of No. def yesNo( promptString ) yes_or_no(promptString, false) end ### Yes/No prompt with default of Yes. def noYes( promptString ) yes_or_no(promptString, true) end def yes_or_no(promptString, default = nil) answer = "" if !default.nil? answer = promptWithDefault( promptString, default ? "yes" : "no", "Please enter 'yes' or 'no'" ) {|response| response.match( '^[YyNn]' ) } else answer = prompt( promptString, "Please enter 'yes' or 'no'" ) {|response| response.match( '^[YyNn]' ) } end return answer.match( '^[Yy]' ) end ### Display a menu of numeric choices for the m_items passed in, ### with a title of head and a prompt of ques. def menu( head, ques, *m_items ) return m_items[0] if m_items.length == 1 choice = _displayMenu( head, ques, *m_items ) until choice and (1..(m_items.length)).include?( choice ) errorMessage "\nPlease enter a number between 1 and #{m_items.length}\n\n" choice = _displayMenu( head, ques, *m_items ) end return m_items[choice - 1] end ### Display a menu of numeric choices for the m_items ### passed in, with a title of head and a prompt of ### ques. Unlike menu, this respects the index ### of the m_items array. def numberedMenu( head, ques, m_items ) valid_choices = [] m_items.each_with_index{|x,i| valid_choices << i if !x.nil? } return m_items[valid_choices[0]] if valid_choices.length == 1 choice = _displayNumberedMenu( head, ques, m_items ) until valid_choices.include?( choice ) errorMessage "\nPlease enter a valid choice\n\n" choice = _displayNumberedMenu( head, ques, m_items ) end return m_items[choice] end ### Display a menu of numeric choices for the m_items passed in, ### with a title of head, a prompt of ques and a default ### value of default. def menuWithDefault( head, ques, default, *m_items ) if (m_items - [default, nil]).length == 0 return default end choice = _displayMenu( head, ques + " [#{default}]", *m_items ) return default unless choice until (1..(m_items.length)).include?( choice ) errorMessage "\nPlease enter a number between 1 and #{m_items.length}\n\n" choice = _displayMenu( head, ques + " [#{default}]", *m_items ) return default unless choice end return m_items[choice - 1] end ### Display a menu of numeric choices for the m_items ### passed in, with a title of head, a prompt of ### ques and a default value of default. Unlike ### menuWithDefault, this respects the index of the ### m_items array. def numberedMenuWithDefault( head, ques, default, m_items ) if (m_items - [default, nil]).length == 0 return default end choice = _displayNumberedMenu( head, ques + " [#{default}]", m_items ) return default unless choice valid_choices = [] m_items.each_with_index{|x,i| valid_choices << i if !x.nil? } until valid_choices.include?( choice ) errorMessage "\nPlease enter a valid choice\n\n" choice = _displayNumberedMenu( head, ques + " [#{default}]", m_items ) return default unless choice end return m_items[choice] end def _displayNumberedMenu( head, ques, m_items ) header head m_items.each_with_index {|item, i| if !item.nil? highlight "\t%d" % i.to_s display ": %s\n" % item end } choice = prompt( ques ) return choice.empty? ? nil : choice.to_i end def _displayMenu( head, ques, *m_items ) header head m_items.each_with_index {|item, i| highlight "\t%d" % (i+1).to_s display ": %s\n" % item } choice = prompt( ques ) return choice.empty? ? nil : choice.to_i end private :_displayMenu end # module RubyTUI #include RubyTUI # This is a library. It does stuff. require 'yaml' conffile = ENV["PRINTME_CONFIG"] || '/etc/printme.yml' defaults = {'server' => 'printme', 'port' => 80} yaml = defaults if File.exists?(conffile) f = File.open(conffile) yaml = YAML.load(f.read) f.close keys = ['server', 'port'] if !(yaml.keys - keys == [] && keys - yaml.keys == []) puts "Invalid configuration file" yaml = defaults.merge(yaml) end end $server = yaml['server'] + ':' + yaml['port'].to_s $PRINTME_VERSION=15 require 'fileutils' require 'soap/rpc/driver' include FileUtils include RubyTUI require 'tempfile' if RubyTUI::DIST == "lucid" $COLOR = false end trap( "SIGINT" ) { `reset -Q` errorMessage "\n\nUser interrupt caught. Exiting.\n\n" exit!( 1 ) } def add_method(*args) @driver.add_method(*args) end def setup_soap @driver = SOAP::RPC::Driver.new("http://#{$server}/", "urn:printme") # Connection Testing and Version Checking, required before automagic stuff can happen add_method("ping") add_method("version_compat", "client_version") add_method("version") add_method("bad_client_error") add_method("bad_mac_client_error") add_method("bad_server_error") add_method("soap_methods") end def color(blah) puts colored(blah) if $debug end def soap_list_methods @soap_methods.map{|x| x[0]} end def soap_has_method?(check) soap_list_methods.include?(check) end def soap_arguments(function) return @soap_methods.select{|x| x[0] == function}.map{|x| x.shift; x}.first end def automagic @soap_methods = @driver.soap_methods @soap_methods.each{|x| add_method(*x) } end def realmain check_for_people_who_dont_read_the_instructions color "Setting up connection to the server..." setup_soap check_version automagic mymain end def main begin realmain rescue SOAP::FaultError => e errorMessage "Server returned this error: #{e.message}\n\n" exit 1 # rescue NoMethodError, NameError errorMessage "There's a BUG in printme!\n\n" exit 1 end end def check_for_people_who_dont_read_the_instructions if ENV['USER'] == "root" and ENV['ALLOW_ROOT'] != 'true' puts "DO NOT RUN PRINTME AS ROOT. if you are typing 'sudo printme', then that is incorrect. Just type 'printme'." exit 1 end end def client_hash client_versions = Hash.new([]) client_versions[1] = [1] # dunno client_versions[2] = [2,3] # first one that makes it here. forced upgrade. client_versions[3] = [3] # forced upgrade client_versions[4] = [3,4] # forced upgrade client_versions[5] = [5] # forced. the server needs to clean the xml now since printme isn't. client_versions[6] = [6,7] # forced. add contracts support. client_versions[7] = [6,7,8] # forced. fix contracts support. (bad builder problem) client_versions[8] = [6,7,8] # forced. fix contracts support. (my bugs) client_versions[9] = [9] # soap if File.basename($0) == "list-printmes" || File.basename($0) == "printme-note" client_versions[10] = [10] # notes else client_versions[10] = [9,10] # soap end client_versions[11] = [11] # string change on both ends, that needs to go together (reworded contracts question) client_versions[12] = [12] # new info collected (covered), forced upgrade. automagic. client_versions[13] = [12,13] # all works. client_versions[14] = [14] # previous systems client_versions[15] = [15] # previous systems client_versions end def check_version begin retval = @driver.ping if retval != "pong" errorMessage "I could not connect to the server.\nMake sure you are connected to the network and try again.\n\n" exit false end rescue SOAP::RPCRoutingError, SOAP::ResponseFormatError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETDOWN, Errno::ENETUNREACH, Errno::ECONNRESET, Errno::ETIMEDOUT, NoMethodError, SocketError, NameError => e errorMessage "I could not connect to the server (#{e.message}).\nMake sure you are connected to the network and try again.\n\n" exit false end if !@driver.version_compat($PRINTME_VERSION) theirver = @driver.version if theirver > $PRINTME_VERSION # bug in older versions errorMessage (theirver >= 14 and DIST == "mac") ? @driver.bad_mac_client_error : @driver.bad_client_error + "\n" exit false end end if client_hash[$PRINTME_VERSION].class == Array && !client_hash[$PRINTME_VERSION].include?(@driver.version) errorMessage @driver.bad_server_error + "\n" exit false end end def runit(lshwname) return if !STDIN.tty? && File.exist?(lshwname) if File.exist?(lshwname) mv(lshwname, lshwname + '.old') end if DIST == "mac" system_check_ret("system_profiler -xml>#{lshwname}") else system_check_ret("sudo lshw -xml>#{lshwname}") end if File.readlines(lshwname).length == 0 errorMessage "ERROR: lshw outputted nothing. This may be a bug in lshw. Aborting.\n\n" exit 1 end end def system_check_ret(str) ret = system(str) if ! ret errorMessage "Failed to run command, aborting.\n\n" exit 1 end end def download_url(path, result) system("wget -q http://#{$server}#{path} -O #{result}") end def look_at_url(path) url="http://#{$server}#{path}" if ! system "#{DIST == "mac" ? "open" : "firefox"} #{url}" puts url end end #!/usr/bin/ruby def run_lshw_and_test_stuff lshwname = DIST == "mac" ? '/tmp/lshw.xml' : '/var/lib/freegeek-extras/printme/lshw.xml' runit(lshwname) lshw = File.open(lshwname, "rb") @lshw_output = lshw.read lshw.close end def make_dirs return if DIST == "mac" if ! File.exist?('/var/lib/freegeek-extras/') system_check_ret 'sudo mkdir /var/lib/freegeek-extras/' end if ! File.writable?('/var/lib/freegeek-extras/') system_check_ret "sudo chown -R #{ENV['USER']} /var/lib/freegeek-extras/" end if ! File.exist?('/var/lib/freegeek-extras/printme') mkdir('/var/lib/freegeek-extras/printme/') end end def get_id_and_stuff unless DIST == "mac" if File.exist?('/var/lib/freegeek-extras/printme/system_id') @old_system_id ||= File.read('/var/lib/freegeek-extras/printme/system_id').strip.to_i elsif File.exist?('/var/lib/freegeek-extras/printme/system.xml') # old new printme has been ran require 'rexml/document' @old_system_id ||= REXML::XPath.match(REXML::Document.new(File.read("/var/lib/freegeek-extras/printme/system.xml")), '/system/id').first.get_text.to_s.to_i end end @actions = @driver.actions @contracts = @driver.contracts @types = @driver.types @coveredness_enabled = @driver.coveredness_enabled @old_system_id ||= prompt('If the system had an id on it already, enter that here', 'Please supply a number or nothing at all') {|num| num.empty? or (num.to_i.to_s == num)} end def r_parse_hash(arr) h = {} arr.each{|x,y| h[x] = y } h end def get_current_info @detected_system_id = @driver.get_system_id(@lshw_output) if @detected_system_id and !@driver.is_system_gone(@detected_system_id) @question_defaults = r_parse_hash(@driver.question_defaults(@detected_system_id)) @default_contract = @driver.contract_label_for_system(@detected_system_id) @default_type = @driver.type_description_for_system(@detected_system_id) covered = @driver.covered_for_system(@detected_system_id) if covered.nil? @default_uncovered = nil else @default_uncovered = !covered end else @question_defaults = {} @default_type = @driver.default_type_description @default_contract = nil @default_uncovered = nil @detected_system_id = nil end @default_action = @driver.default_action_description end def show_a_message(first) second = "Are you sure that you entered it correctly (HINT: look for the sticker)?" puts puts colored(first, 'bold', 'green') out = prompt second, "Please type yes or no" do |x| x.match(/yes/i) || x.match(/no/i) end return out.match(/yes/i) end def show_message return show_a_message("You are are changing the contract.") end def show_covered_message return show_a_message("You are are changing the uncovered electronic device value.") end class Array def to_my_array(thing) arr = [] self.each{|x| arr[x.thing_id] = eval("x.#{thing}") } arr end end def ask_contract # FIXME: all of this logic needs to be cleaned up first_time = @detected_system_id.nil? contract = nil loop do contract = nil checkable_contracts = @contracts.to_my_array("label").select{|x| !x.nil?}.select{|x| x != "No Sticker"} # TODO: sort so that the current contract is the first asked question. or the last. not sure how we want to do it. for this_contract in checkable_contracts if yes_or_no("Does the system have a #{this_contract} sticker?#{(!@default_contract.nil?) ? " [The previous technician said #{@default_contract == this_contract ? "yes" : "no"}, but you should check as well]" : ""}") contract = this_contract break end end if contract.nil? contract = "No Sticker" end if contract != @default_contract && !first_time if show_message break end else break end end return contract end def ask_covered covered = nil if @coveredness_enabled return false if @contract.to_i != 1 # FIXME: this logic needs to be elsewhere loop do covered = !yes_or_no("Does the system have an uncovered electronic device sticker?#{(!@default_uncovered.nil?) ? " [The previous technician said #{@default_uncovered ? "yes" : "no"}, but you should check as well]" : ""}") if covered != (!@default_uncovered) && !@default_uncovered.nil? if show_covered_message break end else break end end end return covered end class Hash def ryan52_to_xml(name) if self.length == 0 return "<#{name} />" else s = "<#{name}>\n" self.each{|k,v| ks = k.to_s s += "<#{ks}>#{v}\n" } s += "" return s end end end def ask_and_send_stuff @asked_data = {} @asked_questions = {} tmp_action = numberedMenuWithDefault('Work Tasks', 'Which job did you perform?', @default_action, @actions.to_my_array("description")) tmp_type = numberedMenuWithDefault('System Type', 'What kind of system is this intended to be?', @default_type, @types.to_my_array("description")) @asked_data['action_id'] = @actions.select{|x| x.description == tmp_action}.first.thing_id @asked_data['type_id'] = @types.select{|x| x.description == tmp_type}.first.thing_id @open_questions = @driver.get_extra_questions @reject_questions = [] @asked_data["questions"] = [] def ask_this_question(x) if @question_defaults.keys.include?(x.name) @asked_questions[x.name] = promptWithDefault(x.question, @question_defaults[x.name]) else @asked_questions[x.name] = prompt(x.question, 'Please enter something.') {|num| num.strip.length > 0} end @asked_data["questions"] << [x.id_name, @asked_questions[x.name]] end def does_satisfy_conditions(x) lookfor = x.conditions.map{|y| y.field_name}.uniq lookfor.select{|y| !(@reject_questions.include?(y) || @asked_data.keys.include?(y) || @asked_questions.keys.include?(y))}.length == 0 end def conditions_are_true(x) lookfor = x.conditions.map{|y| y.field_name}.uniq lookfor.each{|y| if @reject_questions.include?(y) return false end } x.conditions.each{|y| value = @asked_questions.keys.include?(y.field_name) ? @asked_questions[y.field_name] : @asked_data[y.field_name] case y.operator when '=' return false unless value.to_s == y.expected_value when '=~' return false unless value.to_s =~ /#{y.expected_value}/ when '>' return false unless value.to_i > y.expected_value.to_i end } return true end def handle_questions # check if all of the field_name it's looking for has been asked good = @open_questions.select{|x| does_satisfy_conditions(x) } @open_questions = @open_questions - good # if so, check values for relevance, skip if not relevant good.each{|x| if conditions_are_true(x) ask_this_question(x) else @reject_questions << x.name end # ask and remove, then check for ones which are now available handle_questions # recursive so that followup questions are asked immediately } end oldlength = -1 while @open_questions.length > 0 # shouldn't have to loop much handle_questions if oldlength == @open_questions.length raise "Logic Error, remaining questions could not be asked or skipped, for unknown reasons." end oldlength = @open_questions.length end contract = ask_contract @asked_data["contract_id"] = @contract = @contracts.select{|x| x.label == contract}.first.thing_id @asked_data["covered"] = ask_covered @asked_data["contact_id"] = prompt('What is your volunteer id?', 'Please enter a number.') {|num| num.to_i.to_s == num} @asked_data["notes"] = prompt('What, if anything, is notable about this system?') @asked_data["os"] = (DIST == "mac" ? "Mac" : `lsb_release --description --short`) unless (@driver.version < 13) battery_results = "" if File.exists?("/usr/bin/batterytest") and File.exists?("#{ENV["HOME"]}/bat_mon") and tmp_type.match(/laptop/i) battmp = `batterytest`.chomp if noYes("Do you want to include the results of the last battery test (#{battmp})?") battery_results = battmp end end fgdb_printme_hash = {} if !battery_results.empty? fgdb_printme_hash[:batterytest] = battery_results end unless not File.exist?('/usr/sbin/dmidecode') max_capacity = `sudo LANG=C dmidecode -t 16 | grep "Maximum Capacity" | cut -d : -f 2`.strip unless max_capacity.empty? fgdb_printme_hash[:max_capacity] = max_capacity end end lshw_lines = @lshw_output.split("\n") first = lshw_lines.shift lshw_lines.shift if lshw_lines.first.match(/DOCTYPE/) @lshw_output = "#{first}\n\n#{fgdb_printme_hash.ryan52_to_xml("fgdb_printme")}\n#{lshw_lines.join("\n")}" end @asked_data["old_id"] = @old_system_id @asked_data["lshw_output"] = @lshw_output data_struct = @driver.empty_struct @asked_data.each{|k,v| eval("data_struct.#{k} = v") } @report_id = @driver.submit(RubyTUI::DIST == "mac" ? @asked_data : data_struct) system_id=@driver.get_system_for_report(@report_id) if system_id != @old_system_id @old_system_id = system_id end return if DIST == "mac" system=File.open('/var/lib/freegeek-extras/printme/system_id', 'w') system.puts(@old_system_id) system.close Dir.glob("/var/lib/freegeek-extras/printme/{system,report.*,spec_sheet.*}.xml").each{|x| rm x} # remove teh cruft end def printer_there?(printer) `lpstat -p`.split("\n").map{|x| x.split(" ")[1]}.include?(printer) end def printer_setup printer = ARGV[1] system_check_ret('sudo su - -c "echo \"Browsing Yes #freegeek-extras\" >> /etc/cups/cupsd.conf"') if File.exists?("/etc/init.d/cups") system_check_ret('sudo /etc/init.d/cups restart >/dev/null') elsif File.exists?("/etc/init.d/cupsys") system_check_ret('sudo /etc/init.d/cupsys restart >/dev/null') else errorMessage "ERROR: Can't figure out how to restart cups\n\n" exit 1 end num = 0 found=false if printer.nil? || printer.empty? errorMessage "the #{ARGV[0]} option required an option (the printer's name)" + "\n" exit 1 end # in my tests it took about 27 seconds for cups to figure it out while num < 10 num += 1 printf "looking for printer #{printer}..." if printer_there?(printer) found=true puts "found it" break else found=false printf "not found " if num < 10 sleep 5 puts "(will try again in a few seconds)" else puts "(giving up)" end end end puts if !found errorMessage "Please specify a valid printer." + "\n\n" exit 1 end system_check_ret("sudo su - -c \"echo \\\"Default #{printer} #freegeek-extras\\\" >> /etc/cups/lpoptions\"") end def mymain if ARGV[0] == "-p" || ARGV[0] == "--printer" printer_setup end if ARGV[0] == "-h" || ARGV[0] == "--help" system("man 1 printme | cat -") exit 0 end color "Making directories" make_dirs color "Running lshw..." run_lshw_and_test_stuff color "Getting information about this system's past..." get_current_info color "Asking questions and submitting to server..." get_id_and_stuff ask_and_send_stuff url = @driver.spec_sheet_url(@report_id) unless DIST == "mac" download_url("/stylesheets/fgss.css", "/var/lib/freegeek-extras/printme/fgss.css") download_url(url, "/var/lib/freegeek-extras/printme/index.html") system(%q{sed -i -e '/link/ s/\/stylesheets\///' -e '/id="hidden"/,/<\/div>/ d' /var/lib/freegeek-extras/printme/index.html}) end look_at_url url end main