#!/usr/bin/env ruby # # Copyright (c) 2006 Aron Schlesinger # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # NoPaste 1.0 10.09.2006 # --------------------------------------------------------------------- # Posten und Lesen von Text-Eintaegen auf: # # - http://nopaste.paefchen.net # - http://nopaste.bsdforen.de # # Fehler, Anregung, Kritik immer herdamit per Mail oder IRC # - irc.freenode.net/paefchen # # Fuer Hilfe einfach diesen Skript mit --help aufrufen. # ---------------------------------------------------------------------- # http://www.paefchen.net as@paefchen.net # Anpassen: # --------------------------------------------------------------------- # Default NoPaste-Server URL: NOPASTE_URL = 'http://nopaste.paefchen.net/' # Default NoPaste Tmp-Dir: NOPASTE_TMP = ENV.key?('NOPASTE_TMP') ? ENV['NOPASTE_TMP'] : '/tmp' # --------------------------------------------------------------------- NOPASTE_VERSION = 1.0 require 'getoptlong' require 'timeout' require 'uri' require 'net/http' # Suffix => Syntax Tabelle. # Kann angepasst durch Syntaxas die der Server unterstuetzt. @syntax_table = { 'rb' => 'ruby', 'sh' => 'bash' } # NoPaste Fehler KLassen class NoPasteError < StandardError; end class NoPasteIOError < StandardError; end class NoPasteInputError < StandardError; end # NoPaste Input/Output Modul # sorgt fuer den Transfer zwischen Client und Server. # # TODO: hier koenten noch weitere Module fuer # andere Paste Dienste geschrieben werden. module NoPasteIOPaefchenNopaste private # Verfolstaendigt die URL und stellt # ein URI Objekt zu verfuegung. # # @var string Paste-Server URL # @return self def initialize_io url url = 'http://' + url if url[0, 7] != 'http://' url += '/' if url[-1, 1] != '/' @http_url = URI.parse url self end attr_reader :http_url # Schickt eine Anfrage an einen HTTP-Server # und liefert den Body an http_parse_body() # # @var Net::HTTP::* # @return http_parse_body() def http_request req Net::HTTP.new(http_url.host, http_url.port).start do |http| result = http.request req if Net::HTTPSuccess === result http_parse_body result.body else raise NoPasteIOError.new("IOError - #{result.code} => #{result.class}") end end end # schickt ein neuen NoPaste-Eintrag zum Server # # @return http_request() def write post_val = @values.merge( { 'submit' => nil, 'method' => 'plain', 'client_v' => NOPASTE_VERSION } ) (req = Net::HTTP::Post.new http_url.path).set_form_data(post_val) http_request req end # liesst ein NoPaste-Eintrag # # @var int NoPaste-ID # @return http_request() def read id get_val = http_url.path + id.to_s + '/method/plain/download' http_request Net::HTTP::Get.new(get_val) end # schickt eine Anfrage ab um alle moeglichen # Syntaxas zurueck zu bekommen. # # @return http_request() def read_syntaxas get_val = http_url.path + 'method/plain/syntaxas' http_request Net::HTTP::Get.new(get_val) end # Teilt den Body in Zeilen auf, liesst den Status aus, # im Fehlerfall wird die Message ausgelesen ansonsten # die restlichen Zeilen an die richtige Methode zur # weiterverarbeitung ueberegeben. # # @var string HTML-Body # @return self (write/read) # array (read_syntaxas) def http_parse_body body body.strip! # kein Body raise NoPasteIOError.new('get emty result from Server') if body.empty? status = (lines = body.split("\n")).shift # Status abfangen key, value = status.split(':', 2) raise NoPasteIOError.new('get no status from Server') if key != 'status' # wenn status nicht ok if value.to_i != 1 # eventuelle Server nachricht parsen type, message = lines.shift.split(':', 2) if type == 'message' error = "server error - #{message}" else error = 'unknow server error' end raise NoPasteError.new(error) end # Die restlichen Zeilen als array # weiter zur verarbeitung geben. case @what_do when 1 http_parse_write_lines lines when 2 http_parse_read_lines lines else lines end end # Teil die HTML-Body-Lines von der Bestaetigung eines # neuen NoPaste Eintrags in Infos auf und generiert # die "url". Das ganze wird in @values gespeichert. # # @var array HTML-Body-Lines # @return self def http_parse_write_lines lines lines.each do |line| key, value = line.split(':', 2) @values[key] = value.nil? ? nil : value.strip end @values['url'] = http_url.to_s + @values['id'] self end # Teild die HTML-Body-Lines von einem NoPaste Eintrag # in Infos und Source auf und speichert sie in @values. # # @var array HTML-Body-Lines # @return self def http_parse_read_lines lines source = nil lines.each do |line| unless source if line.empty? source = true else key, value = line.split(':', 2) @values[key] = value.nil? ? nil : value.strip end else @values['source'] << line + "\n" end end self end end # NoPaste Klasse class NoPaste include NoPasteIOPaefchenNopaste # Setzt Default-Werte und # inizialisiert das IO Modul # # @val string NoPaste-Server URL # @return self def initialize url # Type der Aktionen: # 1 => write # 2 => read @what_do = nil; # Werte die geschrieben bzw. gelesen werden. @values = { 'syntax' => '', 'nickname' => '', 'description' => '', 'source' => '', 'linenumber' => '', 'remoteaddr' => '' } # Array mit moeglichen Syntaxas @syntaxas = nil # initialize vom IO Modul initialize_io url end # NoPaste-Eintrag abschicken # # @return self def puts raise NoPasteError.new('no source to write') if @values['source'].empty? raise NoPasteError.new('unknow syntax - ' + @values['syntax']) if ! syntaxas.include? @values['syntax'] @what_do = 1 write end # NoPaste-Eintrag einlesen # # @val id int Id der NoPaste # @return self def gets id @what_do = 2 read id end # liefert alle moeglichen Syntaxas vom Server. # # @return array def syntaxas @syntaxas = read_syntaxas unless @syntaxas @syntaxas end # liefert ein Block mit allen Infos ausser source # # @block key, value # @return self def infos case @what_do when 1, 2 @values.each_key do |k| next if k == 'source' yield k, get(k) end else raise NoPasteError.new('natthing to each') end self end # method_missing() nutzen um get_* und set_* zu setzen/lesen. # # @return get_*() || set_*() def method_missing method, *args type, what = method.to_s.split '_' raise NoMethodError.new("missing method - #{method}") if ! @values.key? what case type when 'get' get what when 'set' raise ArgumentError.new("method needs a value - #{method}") if args.length == 0 set what, args.shift else raise NoMethodError.new("missing method - #{method}") if ! @values.key? what end end private # setzt einen Wert def set key, value @values[key] = value.to_s end # liesst einen Wert def get key case key when 'timestamp' Time.at @values[key].to_i when 'linenumber' @values[key] == '1' ? 'yes' : 'no' else @values[key] end end end # ENDE NoPaste Klasse # ============== # MAIN Methoden # -------------- # Ersetzt das defaul gets. # Liesst STDIN nur dann ein, wenn auch was »reinkommt« # ansonsten wird nil zurückgegeben. # # @return string || nil def gets begin source = '' Timeout.timeout(0.1) { $stdin.each { |line| source << line }} source rescue Timeout::Error nil end end # probiert anhand einer Endung den Syntax rauszufinden. # # @val string # @return string def suffix2syntax suffix suffix = @syntax_table[suffix] if @syntax_table.key?(suffix) suffix end # Datei in eine Temporaere Datei kopieren # # @val string File-Name # @return string def edit_file file tmp_file = File.join NOPASTE_TMP, 'nopaste_' + File.basename(file) File.new(tmp_file, 'w') << File.new(file).read edit tmp_file end # String in eine Temporaere Datei # kopieren und bearbeiten. # # @val string # @val_opt string File-Name # @return string def edit_string str, name=nil # Name der Datei tmp_file = 'nopaste' tmp_file << '_' + name if name tmp_file << '.txt' tmp_file = File.join NOPASTE_TMP, tmp_file fh = File.new(tmp_file, 'w') fh.sync = true fh << str fh.close edit tmp_file end # Datei bearbeiten, auslesen und loeschen. # # @val string File-Path # @return string def edit file raise NoPasteError.new('$EDITOR not set') if ! ENV.key? 'EDITOR' system "#{ENV['EDITOR']} #{file}" string = File.new(file).read File.unlink file string end # Paste posten def nopaste_post nopaste = NoPaste.new @opts['url'] # STDIN lesen, ist keiner so wird File gelesen. unless (source = gets) raise NoPasteInputError.new('no source file and input') if ARGV.empty? raise NoPasteInputError.new('onley one source file') if ARGV.length > 1 file = ARGV.shift raise NoPasteInputError.new('no regular file - ' + file) if ! File.file? file # File-Name als Beschreibung setzen wenn keine Angegeben wurde. @opts['description'] = File.basename file unless @opts['description'] # Syntax probieren zu definieren anhand des Sufix wenn keiner angegeb wurde. unless @opts['syntax'] syntax = suffix2syntax File.extname(file)[1..-1] @opts['syntax'] = syntax if nopaste.syntaxas.include? syntax end # Source im Editor oefnen wenn -e gesetzt woden ist. source = @opts['editor'] ? edit_file(file) : File.new(file).read else # Source im Editor oefnen wenn -e gesetzt woden ist. source = edit_string source if @opts['editor'] end # Alle gesetzen Werte und Souce an das NoPase Objekt uebergeben. nopaste.set_source source nopaste.set_syntax @opts['syntax'] if @opts['syntax'] nopaste.set_nickname @opts['nickname'] if @opts['nickname'] nopaste.set_description @opts['description'] if @opts['description'] nopaste.set_linenumber @opts['linenumber'] # und alles abschicken nopaste.puts # Nur die ID ausgeben wenn -q gesetzt ist. if @opts['quiet'] $stdout.puts nopaste.get_id # ansonsten alle vorhandenen Infos ausgeben. else nopaste.infos do |key, value| $stdout.puts sprintf('%-13s: %s', key, value) if ! value.to_s.empty? end end end # Paste lesen def nopaste_gets raise NoPasteInputError.new('no id given') if ARGV.empty? raise NoPasteInputError.new('onley one id') if ARGV.length > 1 id = ARGV.shift raise NoPasteInputError.new('id have incorect format') if ! id.to_i == 0 nopaste = NoPaste.new @opts['url'] # NoPaste lesen nopaste.gets id # Im Editor oeffnen if @opts['editor'] source = '' if ! @opts['quiet'] nopaste.infos do |key, value| source << sprintf("# %-13s: %s\n", key, value) if ! value.to_s.empty? end source << "\n" end edit_string source + nopaste.get_source, id # Infos auf STDERR ausgeben und Source auf STDOUT else if ! @opts['quiet'] nopaste.infos do |key, value| $stderr << sprintf("%-13s: %s\n", key, value) if ! value.to_s.empty? end $stderr.puts end $stdout.puts nopaste.get_source end end # Alle Syntaxas ausgeben def nopaste_languages line = {} num = 5 0.upto((syntaxas = NoPaste.new(@opts['url']).syntaxas).length) do |i| line[i % num] = syntaxas[i] line = nopaste_languages_line line if (i += 1) % num == 0 end nopaste_languages_line line true end # von nopaste_languages() die Zeilen ausgeben def nopaste_languages_line line line.each_value do |value| $stdout << sprintf('%-16s ', value) end $stdout.puts if line.length > 0 {} end # NoPaste Version ausgeben def nopaste_version puts NOPASTE_VERSION end # NoPaste def nopaste_help $stderr << < [optionen] [file|id] Funktionen: --post -p : Text an Server schicken --gets -g : NoPaste Eintrag vom Server anzeigen --languages -l : verfuegbare Syntaxas anzeigen --help -h : Diese Ausgabe anzeigen --version -v : Version anzeigen Optionen: --url -u : NoPaste URL --description -d : Beschreibung, bis 250 Zeichen. (wird eine Datei angegeben und keine Beschreibung, wird der Dateiname verwendet.) --nickname -n : Nickname (Default: $NOPASTE_USER oder $USER) --syntax -s : Sprachanzeige-Modus (Defailt: text. Wird eine Datei angegeben und kein Syntax, wird die Sprache anhand des Suffix probiert zu definieren.) --editor -e : Aktiviert in-/out-put in Editor --quiet -q : »Stiller« Modus Beim Post nur die neue ID. Beim Get nur der Source selber. Ist beim Get -e gesetzt so werden die Infos auf STDERR ausgegeben, der Source auf STDOUT. --linenumber -z : Zeigt im Web Zeilennummern mit an. --trace -t : Im Fehlerfall wird ein Fehler-Trace ausgegeben. Umgebungs-Variablen: $NOPASTE_NICKNAME: Default Nickname mit dem gesendet werden soll. Ist dieser nicht gesetzt wird $USER verwendet. $NOPASTE_QUIET : Setzt den »Stillen« Modus immer aktive. $NOPASTE_TRACE : Gibt immer im Fehlerfall ein Trace aus. $NOPASTE_TMP : Wo files abgelegt werden soll wenn --edit verwendet wird. $NOPASTE_URL : Hostname Beispiele: Posten: nopaste -p /etc/rc.conf postet /etc/rc.conf netstat -r | nopaste -pd "Routing Tabelle" postet die 'netstat' ausgabe mit einer Beschreibung. Lesen: nopaste -gu nopaste.bsdforen.de 123 zeigt Eintrag Nr. 123 vom Server nopaste.bsdforen.de an nopaste -g 123 > ~/nopaste.txt speichert den Eintrag 123 in ~/nopaste.txt nopaste -ge 123 oeffnet den Eintrag 123 im Editor EOU end # Wird aufgerufen wenn keine Funktion angegeben wurde. def nopaste_quit msg = "no function given, use --help for more Infos" raise NoPasteInputError.new(msg) end # Lets Go :-) begin # Methode die aufgerufen werden soll func = 'quit' # Erst mal Default Optionen setzen @opts = { 'trace' => ENV.key?('NOPASTE_TRACE') ? true : false, 'syntax' => nil, 'nickname' => ENV.key?('NOPASTE_NICK') ? ENV['NOPASTE_NICK'] : ENV['USER'], 'description' => nil, 'quiet' => ENV.key?('NOPASTE_QUIET') ? true : false, 'editor' => false, 'url' => ENV.key?('NOPASTE_URL') ? ENV['NOPASTE_URL'] : NOPASTE_URL, 'linenumber' => '' } (opts = GetoptLong.new( [ '--help', '-h', GetoptLong::NO_ARGUMENT ], [ '--post', '-p', GetoptLong::NO_ARGUMENT ], [ '--gets', '-g', GetoptLong::NO_ARGUMENT ], [ '--languages', '-l', GetoptLong::NO_ARGUMENT ], [ '--version', '-v', GetoptLong::NO_ARGUMENT ], [ '--trace', '-t', GetoptLong::NO_ARGUMENT ], [ '--quiet', '-q', GetoptLong::NO_ARGUMENT ], [ '--editor', '-e', GetoptLong::NO_ARGUMENT ], [ '--syntax', '-s', GetoptLong::REQUIRED_ARGUMENT ], [ '--nickname', '-n', GetoptLong::REQUIRED_ARGUMENT ], [ '--description', '-d', GetoptLong::REQUIRED_ARGUMENT ], [ '--linenumber', '-z', GetoptLong::NO_ARGUMENT], [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ] )).quiet = true opts.each do |opt, arg| opt = opt[2..-1] case opt # Funktion setzen when 'post', 'gets', 'languages', 'version', 'help' func = opt # Options setzen when 'linenumber' @opts[opt] = 1 else @opts[opt] = arg end end # und los gehts ;-) method('nopaste_' + func).call exit 1 # Fehler abfangen und formatiert ausgeben. rescue => e $stderr.puts sprintf('%s: %s => %s', File.basename($0), e.class, e.message) $stderr.puts e.backtrace.flatten if @opts['trace'] exit 0 end ## EOF