#!/usr/bin/env ruby # # Copyright (c) 2007 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. # BSDPaste 1.0.1 04.04.2007 # --------------------------------------------------------------------- # send/read text to/from server: # # - http://bsdpaste.bsdgroup.de # - http://nopaste.bsdforen.de # - http://nopaste.paefchen.net # # Helps: bsdpaste --help # ---------------------------------------------------------------------- # http://www.paefchen.net as@paefchen.net # CUSTOMIZE the next two entries! # --------------------------------------------------------------------- # Default BSDPaste-Server URL: BSDPASTE_URL = 'http://bsdpaste.bsdgroup.de/' # Default BSDPaste Tmp-Dir: BSDPASTE_TMP = ENV.key?('BSDPASTE_TMP') ? ENV['BSDPASTE_TMP'] : '/tmp' # --------------------------------------------------------------------- BSDPASTE_VERSION = "1.0.1" 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' } # BSDPaste Fehler KLassen class BSDPasteError < StandardError; end class BSDPasteIOError < StandardError; end class BSDPasteInputError < StandardError; end # BSDPaste Input/Output Modul # sorgt fuer den Transfer zwischen ent und Server. # # TODO: hier koenten noch weitere Module fuer # andere Paste Dienste geschrieben werden. module BSDPasteIOPaefchenNopaste 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 BSDPasteIOError.new("IOError - #{result.code} => #{result.class}") end end end # schickt ein neuen BSDPaste-Eintrag zum Server # # @return http_request() def write post_val = @values.merge( { 'submit' => nil, 'method' => 'plain', 'client_v' => BSDPASTE_VERSION } ) (req = Net::HTTP::Post.new http_url.path).set_form_data(post_val) http_request req end # liesst ein BSDPaste-Eintrag # # @var int BSDPaste-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 BSDPasteIOError.new('get emty result from Server') if body.empty? status = (lines = body.split("\n")).shift # Status abfangen key, value = status.split(':', 2) raise BSDPasteIOError.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 BSDPasteError.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 BSDPaste 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 BSDPaste 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 # BSDPaste Klasse class BSDPaste include BSDPasteIOPaefchenNopaste # Setzt Default-Werte und # inizialisiert das IO Modul # # @val string BSDPaste-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 # BSDPaste-Eintrag abschicken # # @return self def puts raise BSDPasteError.new('no source to write') if @values['source'].empty? raise BSDPasteError.new('unknow syntax - ' + @values['syntax']) if ! syntaxas.include? @values['syntax'] @what_do = 1 write end # BSDPaste-Eintrag einlesen # # @val id int Id der BSDPaste # @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 BSDPasteError.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 BSDPaste 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 BSDPASTE_TMP, 'bsdpaste_' + 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 = 'bsdpaste' tmp_file << '_' + name if name tmp_file << '.txt' tmp_file = File.join BSDPASTE_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 BSDPasteError.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 bsdpaste_post bsdpaste = BSDPaste.new @opts['url'] # STDIN lesen, ist keiner so wird File gelesen. unless (source = gets) raise BSDPasteInputError.new('no source file and input') if ARGV.empty? raise BSDPasteInputError.new('onley one source file') if ARGV.length > 1 file = ARGV.shift raise BSDPasteInputError.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 bsdpaste.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. bsdpaste.set_source source bsdpaste.set_syntax @opts['syntax'] if @opts['syntax'] bsdpaste.set_nickname @opts['nickname'] if @opts['nickname'] bsdpaste.set_description @opts['description'] if @opts['description'] bsdpaste.set_linenumber @opts['linenumber'] # und alles abschicken bsdpaste.puts # Nur die ID ausgeben wenn -q gesetzt ist. if @opts['quiet'] $stdout.puts bsdpaste.get_id # ansonsten alle vorhandenen Infos ausgeben. else bsdpaste.infos do |key, value| $stdout.puts sprintf('%-13s: %s', key, value) if ! value.to_s.empty? end end end # Paste lesen def bsdpaste_gets raise BSDPasteInputError.new('no id given') if ARGV.empty? raise BSDPasteInputError.new('onley one id') if ARGV.length > 1 id = ARGV.shift raise BSDPasteInputError.new('id have incorect format') if ! id.to_i == 0 bsdpaste = BSDPaste.new @opts['url'] # BSDPaste lesen bsdpaste.gets id # Im Editor oeffnen if @opts['editor'] source = '' if ! @opts['quiet'] bsdpaste.infos do |key, value| source << sprintf("# %-13s: %s\n", key, value) if ! value.to_s.empty? end source << "\n" end edit_string source + bsdpaste.get_source, id # Infos auf STDERR ausgeben und Source auf STDOUT else if ! @opts['quiet'] bsdpaste.infos do |key, value| $stderr << sprintf("%-13s: %s\n", key, value) if ! value.to_s.empty? end $stderr.puts end $stdout.puts bsdpaste.get_source end end # Alle Syntaxas ausgeben def bsdpaste_languages line = {} num = 5 0.upto((syntaxas = BSDPaste.new(@opts['url']).syntaxas).length) do |i| line[i % num] = syntaxas[i] line = bsdpaste_languages_line line if (i += 1) % num == 0 end bsdpaste_languages_line line true end # von bsdpaste_languages() die Zeilen ausgeben def bsdpaste_languages_line line line.each_value do |value| $stdout << sprintf('%-16s ', value) end $stdout.puts if line.length > 0 {} end # BSDPaste Version ausgeben def bsdpaste_version puts BSDPASTE_VERSION end # BSDPaste def bsdpaste_help $stderr << < [optionen] [file|id] Features: --post -p : send text to server --gets -g : show BSDPaste entry --languages -l : show available syntax --help -h : i bet you know --version -v : show version Options: --url -u : BSDPaste URL --description -d : description, up to 250 characters (if you specify a file and no description the filename will be used) --nickname -n : nickname (default: $BSDPASTE_USER or $USER) --syntax -s : syntax highlightning (default: text. A file without a syntax description will be (hopefullly) identified by the suffix) --editor -e : activate in-/output in editor --quiet -q : quiet-modus post only the new ID get display only the source using -e with Get, infos displayed at STDERR source will be displayed at STDOUT. --linenumber -z : shows the line number at the webinterface --trace -t : on error, an error trace will be the output environment variable: $BSDPASTE_NICKNAME: default nickname to send with not set: $USER will be used $BSDPASTE_QUIET : activates quiet modus $BSDPASTE_TRACE : on error: a trace will be the output $BSDPASTE_TMP : where to put the files, if --edit is used $BSDPASTE_URL : hostname Examples: Posten: bsdpaste -p /etc/rc.conf postet /etc/rc.conf netstat -r | bsdpaste -pd "routins" post the output of 'netstat -r' with a description read: bsdpaste -gu bsdpaste.bsdgroup.de 123 display entry no. 123 from server bsdpaste.bsdgroup.de bsdpaste -g 123 > ~/bsdpaste.txt saves the entry 123 in ~/bsdpaste.txt bsdpaste -ge 123 open entry 123 in an editor EOU end # Wird aufgerufen wenn keine Funktion angegeben wurde. def bsdpaste_quit msg = "no function given, use --help for more Infos" raise BSDPasteInputError.new(msg) end # Lets Go :-) begin # Methode die aufgerufen werden soll func = 'quit' # Erst mal Default Optionen setzen @opts = { 'trace' => ENV.key?('BSDPASTE_TRACE') ? true : false, 'syntax' => nil, 'nickname' => ENV.key?('BSDPASTE_NICK') ? ENV['BSDPASTE_NICK'] : ENV['USER'], 'description' => nil, 'quiet' => ENV.key?('BSDPASTE_QUIET') ? true : false, 'editor' => false, 'url' => ENV.key?('BSDPASTE_URL') ? ENV['BSDPASTE_URL'] : BSDPASTE_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('bsdpaste_' + 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