├── bin └── .gitkeep ├── .gitignore ├── lib ├── weechat │ ├── blankslate.rb │ ├── rubyext │ │ ├── float.rb │ │ ├── integer.rb │ │ ├── hash.rb │ │ ├── boolean.rb │ │ ├── array.rb │ │ └── string.rb │ ├── terminal.rb │ ├── hooks.rb │ ├── hooks │ │ ├── config.rb │ │ ├── command_run.rb │ │ ├── print.rb │ │ └── signal.rb │ ├── irc │ │ ├── identifier.rb │ │ ├── ctcp.rb │ │ ├── host.rb │ │ ├── user.rb │ │ ├── whois.rb │ │ ├── message.rb │ │ ├── channel.rb │ │ └── server.rb │ ├── info.rb │ ├── callback.rb │ ├── callbacks.rb │ ├── custom_free_content_buffer.rb │ ├── custom_buffer.rb │ ├── process.rb │ ├── color.rb │ ├── pointer.rb │ ├── option.rb │ ├── input.rb │ ├── property.rb │ ├── timer.rb │ ├── exceptions.rb │ ├── infolist.rb │ ├── utilities.rb │ ├── line.rb │ ├── hook.rb │ ├── script.rb │ ├── plugin.rb │ ├── window.rb │ ├── command.rb │ ├── script │ │ └── config.rb │ ├── modifier.rb │ ├── bar.rb │ ├── properties.rb │ └── buffer.rb └── weechat.rb ├── AUTHORS.md ├── Rakefile ├── TODO ├── gitlog2changelog.py ├── README.md └── COPYING /bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/ 2 | pkg/ 3 | .yardoc 4 | \#* 5 | .#* 6 | ChangeLog 7 | .idea 8 | -------------------------------------------------------------------------------- /lib/weechat/blankslate.rb: -------------------------------------------------------------------------------- 1 | class Blankslate 2 | instance_methods.each { |m| undef_method m unless m =~ /^__/ || m.to_s == 'object_id' } 3 | end 4 | -------------------------------------------------------------------------------- /lib/weechat/rubyext/float.rb: -------------------------------------------------------------------------------- 1 | class Float 2 | def self.from_weechat_config(v) 3 | Float(v) 4 | end 5 | 6 | def to_weechat_config 7 | to_s 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/weechat/rubyext/integer.rb: -------------------------------------------------------------------------------- 1 | class Integer 2 | def self.from_weechat_config(v) 3 | Integer(v) 4 | end 5 | 6 | def to_weechat_config 7 | to_s 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/weechat/rubyext/hash.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | class Hash 3 | def to_weechat_config 4 | to_json 5 | end 6 | 7 | def self.from_weechat_config(v) 8 | JSON.load(v) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/weechat/terminal.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | class Terminal 3 | # Sets the terminal's title 4 | def self.title=(value) 5 | Weechat.window_set_title(value.to_s) 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/weechat/hooks.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module Hooks 3 | end 4 | end 5 | 6 | require 'weechat/hooks/command_run.rb' 7 | require 'weechat/hooks/print.rb' 8 | require 'weechat/hooks/config.rb' 9 | require 'weechat/hooks/signal.rb' 10 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | main developers 2 | =============== 3 | 4 | * Dominik Honnef 5 | 6 | 7 | code pieces used 8 | ================ 9 | 10 | * lib/weechat/rubyext/string.rb :: {String#shell_split} -- Loren Segal (http://github.com/lsegal/yard/commit/d19f90615e7384f8c6c07837e5c2520a2dfbda6e#L0R20) 11 | 12 | -------------------------------------------------------------------------------- /lib/weechat/hooks/config.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module Hooks 3 | class Config < Hook 4 | def initialize(option, &callback) 5 | super 6 | @callback = EvaluatedCallback.new(callback) 7 | @ptr = Weechat.hook_config(option, "config_callback", id.to_s) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/weechat/rubyext/boolean.rb: -------------------------------------------------------------------------------- 1 | class Boolean 2 | def self.from_weechat_config(v) 3 | Weechat.integer_to_bool(Weechat.config_string_to_boolean(v)) 4 | end 5 | end 6 | 7 | class TrueClass 8 | def to_weechat_config 9 | "on" 10 | end 11 | end 12 | 13 | class FalseClass 14 | def to_weechat_config 15 | "off" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/weechat/irc/identifier.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module IRC 3 | class Identifier 4 | attr_reader :user 5 | attr_reader :host 6 | def initialize(string) 7 | @user, @host = string.split("@") 8 | end 9 | 10 | def identd? 11 | @user[0..0] != "~" 12 | end 13 | alias_method :ident?, :identd? 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/weechat/info.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | class Info < Hook 3 | attr_reader :name 4 | attr_reader :description 5 | def initialize(name, description, &callback) 6 | super 7 | @name, @description = name, description 8 | @callback = callback 9 | @ptr = Weechat.hook_info(name, description, "info_callback", id.to_s) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/weechat/callback.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | class Callback 3 | def initialize(callback) 4 | @callback = callback 5 | end 6 | 7 | def call(*args) 8 | return Weechat::Utilities.safe_call {@callback.call(*args)} 9 | end 10 | end 11 | 12 | class EvaluatedCallback < Callback 13 | def call(*args) 14 | return Weechat::Utilities.evaluate_call {@callback.call(*args)} 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/weechat/rubyext/array.rb: -------------------------------------------------------------------------------- 1 | class Array 2 | def self.from_weechat_config(v) 3 | v.escaped_split(",") 4 | end 5 | 6 | def to_weechat_config 7 | map {|entry| 8 | case entry 9 | when String 10 | entry.gsub(/(\\+)?,/) {|m| 11 | if $1.nil? 12 | "\\," 13 | else 14 | $1 + $1 + "\\," 15 | end 16 | } 17 | else 18 | entry.to_weechat_config 19 | end 20 | }.join(",") 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/weechat/irc/ctcp.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module IRC 3 | class CTCP < Weechat::Modifier 4 | def initialize(command, &callback) 5 | super("irc_in_privmsg") do |server, line| 6 | ret = line 7 | m = Weechat::IRC::Message.new(line.message) 8 | 9 | if m.ctcp? && (ctcp = m.to_ctcp).ctcp_command == command 10 | callback.call(server, ctcp) 11 | ret = nil 12 | end 13 | 14 | ret 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/weechat/callbacks.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module Callbacks 3 | @unique_id = 0 4 | @callbacks = {} 5 | 6 | class << self 7 | attr_reader :callbacks 8 | attr_reader :unique_id 9 | 10 | def compute_free_id 11 | @unique_id += 1 12 | end 13 | end 14 | 15 | def call_callback(id, type, *args) 16 | return callbacks[id.to_i][type].call(*args) 17 | end 18 | 19 | def register_callback(args = {}) 20 | callbacks[unique_id] = args 21 | end 22 | 23 | def compute_free_id 24 | Callbacks.compute_free_id 25 | end 26 | 27 | def callbacks 28 | Callbacks.callbacks 29 | end 30 | 31 | def unique_id 32 | Callbacks.unique_id 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/weechat/custom_free_content_buffer.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module Weechat 4 | # Override to create a custom buffer with free content. These buffers 5 | # don't work on a line basis (so methods such as puts won't work). They 6 | # work by setting specific lines, which can be done with the 7 | # print_line method 8 | class CustomFreeContentBuffer < CustomBuffer 9 | def initialize(name) 10 | super 11 | self.type = :free 12 | end 13 | 14 | 15 | # Sets a given line in the buffer 16 | # @param [Integer] line_number The line number to print to 17 | # @param [String] line The line to print 18 | # @return [void] 19 | def print_line(line_number, line) 20 | Weechat.print_y(self.ptr, line_number, line) 21 | end 22 | 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/weechat/custom_buffer.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'weechat/blankslate' 3 | require 'delegate' 4 | 5 | module Weechat 6 | # Subclass to create a custom buffer 7 | class CustomBuffer < Delegator 8 | @@tracked_buffers = {} 9 | 10 | def __getobj__ 11 | @base_buffer 12 | end 13 | 14 | # Override to determine what should be 15 | # done when the user enters input 16 | def handle_input 17 | 18 | end 19 | 20 | # Override to add a buffer closed action 21 | def buffer_closed 22 | 23 | end 24 | 25 | # Creates a new instance of the custom buffer using 26 | # the given name 27 | def initialize(name) 28 | buffer = Buffer.new(name, 29 | lambda {|b, input| @@tracked_buffers[b.ptr].handle_input(input)}, 30 | lambda {|b| @@tracked_buffers[b.ptr].buffer_closed} 31 | ) 32 | super(buffer) 33 | @@tracked_buffers[buffer.ptr] = self 34 | @base_buffer = buffer 35 | 36 | end 37 | 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/weechat/process.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | class Process < Hook 3 | # Returns a new instance of Process 4 | # 5 | # @param [String] command Command to execute 6 | # @param [Integer] timeout Timeout after which to terminate the process 7 | # @param [Boolean] collect If true, buffer output until process ends 8 | 9 | attr_reader :collect 10 | def initialize(command, timeout = 0, collect = false, &callback) 11 | super 12 | @command = command 13 | @collect = collect 14 | @stdout, @stderr = [], [] 15 | @callback = EvaluatedCallback.new(callback) 16 | @ptr = Weechat.hook_process(command, timeout, "process_callback", id.to_s) 17 | end 18 | alias_method :collect?, :collect 19 | 20 | def stdout 21 | @stdout.join("") 22 | end 23 | 24 | def stderr 25 | @stderr.join("") 26 | end 27 | 28 | def buffer(stdout, stderr) 29 | @stdout << stdout 30 | @stderr << stderr 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/weechat/irc/host.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module IRC 3 | class Host 4 | attr_reader :nick 5 | attr_reader :user 6 | attr_reader :host 7 | 8 | def initialize(string) 9 | parts = string.split("@") 10 | @host = parts.last 11 | @nick, @user = parts.first.split("!") 12 | end 13 | 14 | def to_s 15 | "#{@nick}!#{@user}@#{@host}" 16 | end 17 | end 18 | 19 | class Prefix 20 | attr_reader :nick 21 | attr_reader :user 22 | attr_reader :host 23 | 24 | def initialize(string) 25 | @host, @nick, @user = nil, nil, nil 26 | parts = string.split("@") 27 | if parts.size == 1 28 | @nick = parts.first 29 | else 30 | @host = parts.last 31 | @nick, @user = parts.first.split("!") 32 | end 33 | end 34 | 35 | def to_s 36 | if @host.empty? 37 | @nick 38 | else 39 | "#{@nick}!#{@user}@#{@host}" 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/weechat/color.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | # This class represents a Weechat color. 3 | # 4 | # It is created from a color name and saves that name and the color 5 | # representation. 6 | class Color 7 | def self.from_weechat_config(v) 8 | new(v) 9 | end 10 | 11 | def self.const_missing(c) 12 | self.new(c.to_s.downcase) 13 | end 14 | 15 | # @param [String] name Name of the color 16 | attr_reader :name 17 | attr_reader :color 18 | def initialize(name) 19 | @name = name 20 | end 21 | 22 | def color 23 | Weechat.color(name) 24 | end 25 | alias_method :to_s, :color 26 | alias_method :to_str, :color 27 | 28 | def to_weechat_config 29 | @name 30 | end 31 | 32 | def ==(other) 33 | @name == other.name 34 | end 35 | end 36 | 37 | class << self 38 | alias_method :old_color, :color 39 | def color(name) 40 | color = Weechat.old_color(name) 41 | color.empty? ? nil : color 42 | end 43 | alias_method :get_color, :color 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/weechat/pointer.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | # mixin for objects with a underlying implementation as a weechat c pointer. Gives 3 | # the class access to the from_ptr class method, which will create new instance of the 4 | # class using a pointer. Also defines the equality methods to compare pointer values 5 | module Pointer 6 | module ClassMethods 7 | def from_ptr(ptr) 8 | o = allocate 9 | o.instance_variable_set(:@ptr, ptr) 10 | o 11 | end 12 | end 13 | 14 | def self.included(by) 15 | by.extend Weechat::Pointer::ClassMethods 16 | end 17 | 18 | attr_reader :ptr 19 | alias_method :pointer, :ptr 20 | 21 | def to_s 22 | @ptr 23 | end 24 | 25 | def ==(other) 26 | other.respond_to?(:ptr) and @ptr == other.ptr 27 | end 28 | alias_method :eql?, "==" 29 | alias_method :equal?, "==" 30 | 31 | def hash 32 | @ptr.hash 33 | end 34 | 35 | def inspect 36 | sprintf "#<%s:0x%x @ptr=%p>", self.class, object_id << 1, @ptr 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/weechat/hooks/command_run.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module Hooks 3 | class CommandRun < Hook 4 | # Returns a new instance of CommandRunHook 5 | # 6 | # @param [Boolean] also_arguments If true, two hooks will be 7 | # made, one for "$command" and one for "$command *", matching 8 | # both calls with and without arguments 9 | def initialize(command, also_arguments = false, &callback) 10 | super 11 | @command = if command.is_a? Command 12 | command.command 13 | else 14 | command.to_s 15 | end 16 | 17 | @callback = EvaluatedCallback.new(callback) 18 | @ptr = Weechat.hook_command_run(@command, "command_run_callback", id.to_s) 19 | if also_arguments 20 | @ptr2 = Weechat.hook_command_run("#@command *", "command_run_callback", id.to_s) 21 | end 22 | end 23 | 24 | def unhook(*args) 25 | super 26 | self.class.unhook(@ptr2) if @ptr2 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/weechat/option.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | class Option < Blankslate 3 | @options = [] # we can't use a Hash because hashing 4 | # blankslate... breaks things 5 | 6 | 7 | def initialize(config, option) 8 | @old_obj = config.__get(option) 9 | @config = config 10 | @option = option 11 | @frozen = false 12 | end 13 | 14 | def __config__ 15 | @config 16 | end 17 | 18 | def __option__ 19 | @option 20 | end 21 | 22 | def __freeze__ 23 | @frozen = true 24 | end 25 | 26 | def method_missing(m, *args, &block) 27 | if @frozen 28 | obj = @old_obj 29 | else 30 | obj = @config.__get(@option) 31 | end 32 | ret = obj.__send__(m, *args, &block) 33 | 34 | if (@old_obj != obj) && !@frozen 35 | @config.set!(@option, obj) 36 | end 37 | 38 | unless @frozen 39 | begin 40 | @old_obj = obj.dup 41 | rescue TypeError 42 | @old_obj = obj 43 | end 44 | end 45 | 46 | ret 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/weechat/input.rb: -------------------------------------------------------------------------------- 1 | # :input=>1, 2 | # :input_buffer_alloc=>256, 3 | # :input_buffer_length=>0, 4 | # :input_buffer_1st_display=>0, 5 | 6 | module Weechat 7 | class Input 8 | def initialize(buffer) 9 | @buffer = buffer 10 | end 11 | 12 | # Returns the current content of the input line. 13 | def to_s 14 | @buffer.get_property("input") 15 | end 16 | alias_method :text, :to_s 17 | alias_method :content, :to_s 18 | 19 | # Sets the content of the input line. 20 | def text=(val) 21 | @buffer.set_property("input", val) 22 | end 23 | alias_method :content=, "text=" 24 | 25 | def size 26 | @buffer.input_buffer_size 27 | end 28 | alias_method :length, :size 29 | 30 | def pos 31 | @buffer.input_buffer_pos 32 | end 33 | 34 | def get_unknown_commands=(val) 35 | @buffer.set_property("input_get_unknown_commands", val) 36 | end 37 | 38 | # Returns true if unknown commands are being sent as plain text to the buffer. 39 | # 40 | # @return [Boolean] 41 | def get_unknown_commands? 42 | @buffer.get_property("input_get_unknown_commands") 43 | end 44 | alias_method :get_unknown_commands, :get_unknown_commands? 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/weechat/property.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | class Property < Blankslate 3 | @properties = [] # we can't use a Hash because hashing 4 | # blankslate... breaks things 5 | 6 | 7 | def initialize(weechat_obj, property) 8 | @old_obj = weechat_obj.__get_property(property) 9 | @weechat_obj = weechat_obj 10 | @property = property 11 | @settable = weechat_obj.settable_property?(property) 12 | @frozen = false 13 | end 14 | 15 | def __weechat_obj__ 16 | @weechat_obj 17 | end 18 | 19 | def __property__ 20 | @property 21 | end 22 | 23 | def __freeze__ 24 | @frozen = true 25 | end 26 | 27 | def method_missing(m, *args, &block) 28 | if @frozen 29 | obj = @old_obj 30 | else 31 | obj = @weechat_obj.__get_property(@property) 32 | end 33 | ret = obj.__send__(m, *args, &block) 34 | 35 | if (@old_obj != obj) && @settable && !@frozen 36 | @weechat_obj.set_property(@property, obj) 37 | end 38 | 39 | unless @frozen 40 | begin 41 | @old_obj = obj.dup 42 | rescue TypeError 43 | @old_obj = obj 44 | end 45 | end 46 | 47 | ret 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/weechat/timer.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | class Timer < Hook 3 | attr_reader :interval 4 | attr_reader :align 5 | attr_reader :max 6 | def initialize(interval, max=0, align=0, &block) 7 | super 8 | @remaining= nil 9 | @callback = EvaluatedCallback.new(block) 10 | @interval = interval 11 | @align = align 12 | @max = max 13 | @ptr = _init(interval, align, max) 14 | self.class.register(self) 15 | end 16 | 17 | def _init(interval, align, max) 18 | Weechat.hook_timer(interval, align, max, "timer_callback", @id.to_s) 19 | end 20 | 21 | def call(remaining) 22 | @remaining = remaining.to_i 23 | ret = super 24 | 25 | if @remaining == 0 26 | self.unhook 27 | end 28 | 29 | return ret 30 | end 31 | 32 | def stop 33 | unhook 34 | end 35 | 36 | def start 37 | unless @hooked 38 | if @remaining == 0 || @remaining.nil? 39 | # the timer never ran or finished already. restart it 40 | max = @max 41 | else 42 | # continue running hook 43 | max = @remaining 44 | end 45 | 46 | @ptr = _init(@interval, @align, max) 47 | end 48 | end 49 | 50 | def restart 51 | stop 52 | @remaining = nil 53 | start 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/weechat/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module Exception 3 | # This exception gets raised whenever one tries to read a property 4 | # that doesn't exist. 5 | # 6 | # @see Buffer#get_property 7 | # @see Window#get_property 8 | class UnknownProperty < RuntimeError; end 9 | 10 | # This exception gets raised whenever one tries to set a property 11 | # that cannot be set. 12 | # 13 | # @see Buffer#set_property 14 | # @see Window#set_property 15 | class UnsettableProperty < RuntimeError; end 16 | 17 | # This exception gets raised whenever one tries to set a property, 18 | # supplying a value not suiting it. 19 | class InvalidPropertyValue < RuntimeError; end 20 | 21 | # This exception gets raised when one tries to create a buffer 22 | # with the name of an already existing one. 23 | # 24 | # @see Buffer.create 25 | class DuplicateBufferName < RuntimeError; end 26 | 27 | class WEECHAT_RC_OK < ::Exception; end 28 | class WEECHAT_RC_ERROR < ::Exception; end 29 | class WEECHAT_RC_OK_EAT < ::Exception; end 30 | 31 | # This exception gets raised when one tries to receive the channel 32 | # of a buffer which does not represent one. 33 | class NotAChannel < RuntimeError; end 34 | 35 | class UnknownServer < RuntimeError; end 36 | 37 | class NotJoined < RuntimeError; end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/weechat/hooks/print.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module Hooks 3 | # Hooks for adding behaviour when a line is printed out on a buffer 4 | class Print < Hook 5 | # Creates a new Print hook. By default it will be called for every printed 6 | # line in every buffer. Set the :buffer, :tags and :message options 7 | # to only respond to a subset of messages 8 | # @param [Hash] opts Options to determine when and how the Print hook is called 9 | # @option opts [Buffer] :buffer If supplied, only printed lines from this Buffer 10 | # will be printed. Default nil 11 | # @option opts [Array] :tags Tags for the message TODO how does this filter 12 | # @option opts [String] :message TODO how does this filter 13 | # @option opts [Boolean] :strip_colors whether color chars should be filtered 14 | # before being sent to the hook 15 | # 16 | # @yield (line) The callback that should handle the line 17 | # @yieldparam [PrintedLine] line 18 | def initialize(opts = {}, &callback) 19 | super 20 | buffer = opts[:buffer] || "*" 21 | tags = opts[:tags] || [] 22 | message = opts[:message] || '' 23 | strip_colors = opts[:strip_colors] || false 24 | 25 | buffer = buffer.ptr if buffer.respond_to?(:ptr) 26 | tags = tags.join(",") 27 | strip_colors = Weechat.bool_to_integer(strip_colors) 28 | @callback = EvaluatedCallback.new(callback) 29 | @ptr = Weechat.hook_print(buffer, tags, message, strip_colors, "print_callback", id.to_s) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/weechat/infolist.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | class Infolist 3 | def self.parse(type, ptr="", arguments="", requirements = {}, *fields) 4 | infolist_ptr = Weechat.infolist_get(type.to_s, ptr.to_s, arguments.to_s) 5 | ret = [] 6 | while Weechat.infolist_next(infolist_ptr) > 0 7 | h = {} 8 | not_matching = requirements.any? {|option, value| 9 | type, value = value 10 | Weechat.__send__("infolist_#{type}", infolist_ptr, option.to_s) != value 11 | } 12 | 13 | if not_matching 14 | next 15 | else 16 | str = Weechat.infolist_fields(infolist_ptr) 17 | str.split(/,/).each do |item| 18 | type, name = item.split(/:/) 19 | sname = name.to_sym 20 | next if !fields.empty? && !fields.include?(sname) 21 | h[sname] = case type 22 | when 'p' 23 | Weechat.infolist_pointer(infolist_ptr, name) 24 | when 'i' 25 | Weechat.infolist_integer(infolist_ptr, name) 26 | when 's' 27 | Weechat.infolist_string(infolist_ptr, name) 28 | when 'b' 29 | # FIXME: not exposed to script API yet. 30 | # Weechat.infolist_buffer(infolist_ptr, name) 31 | when 't' 32 | Weechat.infolist_time(infolist_ptr, name) 33 | end 34 | end 35 | 36 | ret << h 37 | end 38 | end 39 | Weechat.infolist_free(infolist_ptr) 40 | return ret 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/weechat/utilities.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module Utilities 3 | def self.format_exception(e) 4 | prefix = Weechat.prefix("error") 5 | 6 | line1 = e.backtrace[0] + ": " + e.message + " (" + e.class.name + ")" 7 | backtrace = " " + e.backtrace[1..-1].join("\n ") 8 | 9 | Weechat.puts("#{prefix}error in evaluated call: #{line1}") 10 | Weechat.puts("#{prefix}#{backtrace}") 11 | end 12 | 13 | def self.safe_call 14 | begin 15 | ret = yield 16 | rescue => e 17 | format_exception(e) 18 | return Weechat::WEECHAT_RC_ERROR 19 | end 20 | ret 21 | end 22 | 23 | def self.evaluate_call 24 | begin 25 | yield 26 | rescue Weechat::Exception::WEECHAT_RC_OK 27 | return Weechat::WEECHAT_RC_OK 28 | rescue Weechat::Exception::WEECHAT_RC_OK_EAT 29 | return Weechat::WEECHAT_RC_OK_EAT 30 | rescue Weechat::Exception::WEECHAT_RC_ERROR 31 | return Weechat::WEECHAT_RC_ERROR 32 | rescue => e 33 | format_exception(e) 34 | return Weechat::WEECHAT_RC_ERROR 35 | end 36 | 37 | return Weechat::WEECHAT_RC_OK 38 | end 39 | 40 | def self.apply_transformation(property, value, transformations) 41 | transformation = transformations.find {|properties, transformations| 42 | properties.any? {|prop| 43 | case prop 44 | when Regexp 45 | prop =~ property.to_s 46 | when String, Symbol 47 | prop.to_sym == property.to_sym 48 | else 49 | false 50 | end 51 | } 52 | } 53 | 54 | if transformation 55 | transformation[1].call(value) 56 | else 57 | value 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake/gempackagetask' 3 | 4 | VERSION = "0.1.0" 5 | 6 | spec = Gem::Specification.new do |s| 7 | s.name = "weechat" 8 | s.summary = "An abstraction layer on top of the WeeChat API." 9 | s.description = "An abstraction layer on top of the WeeChat API, allowing a cleaner and more intuitive way of writing Ruby scripts for WeeChat." 10 | s.version = VERSION 11 | s.author = "Dominik Honnef" 12 | s.email = "dominikho@gmx.net" 13 | s.date = Time.now.strftime "%Y-%m-%d" 14 | s.require_path = "lib" 15 | s.homepage = "http://dominikh.fork-bomb.de" 16 | s.rubyforge_project = "" 17 | 18 | s.has_rdoc = 'yard' 19 | 20 | # s.required_ruby_version = '>= 1.9.1' 21 | 22 | # s.add_dependency "keyword_arguments" 23 | s.add_dependency "json" 24 | # s.add_development_dependency "baretest" 25 | # s.add_development_dependency "mocha" 26 | 27 | s.files = FileList["bin/*", "lib/**/*.rb", "[A-Z]*", "examples/**/*"].to_a 28 | s.executables = [""] 29 | end 30 | 31 | Rake::GemPackageTask.new(spec)do |pkg| 32 | end 33 | 34 | begin 35 | require 'yard' 36 | YARD::Rake::YardocTask.new do |t| 37 | end 38 | rescue LoadError 39 | end 40 | 41 | task :test do 42 | begin 43 | require "baretest" 44 | rescue LoadError => e 45 | puts "Could not run tests: #{e}" 46 | end 47 | 48 | BareTest.load_standard_test_files( 49 | :verbose => false, 50 | :setup_file => 'test/setup.rb', 51 | :chdir => File.absolute_path("#{__FILE__}/../") 52 | ) 53 | 54 | BareTest.run(:format => "cli", :interactive => false) 55 | end 56 | -------------------------------------------------------------------------------- /lib/weechat/line.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | # This class encapsulates lines like they're printed in WeeChat. 3 | # 4 | # A line usually consists of a prefix (doesn't have to) and a text. 5 | # One can access both parts individually, or call methods on both 6 | # combined, which means that the method will be first called on the 7 | # prefix and then on the text part. 8 | class Line 9 | class << self 10 | def parse(line) 11 | parts = line.split("\t") 12 | case parts.size 13 | when 0 14 | parts = ["", ""] 15 | when 1 16 | if line =~ /\t(\t+)\Z/ 17 | parts << $1 18 | else 19 | parts.unshift "" 20 | end 21 | when 2 22 | else 23 | parts = [parts[0], parts[1..-1].join("\t")] 24 | end 25 | 26 | new(*parts) 27 | end 28 | 29 | def from_hash(h) 30 | prefix = h.delete(:prefix) 31 | message = h.delete(:message) 32 | new(prefix, message, h) 33 | end 34 | end 35 | 36 | %w(y date date_printed str_time tags_count tags displayed highlight last_read_line).each do |prop| 37 | define_method(prop) { details[prop] } 38 | define_method("#{prop}=") {|v| details[prop] = v } 39 | end 40 | 41 | attr_accessor :prefix 42 | attr_accessor :message 43 | attr_reader :details 44 | def initialize(prefix, message, details = {}) 45 | @prefix, @message, @details = prefix, message, details 46 | end 47 | 48 | def to_s 49 | join("\t") 50 | end 51 | 52 | def join(delimiter) 53 | [prefix.empty? ? nil : prefix, message].compact.join(delimiter) 54 | end 55 | 56 | def method_missing(m, *args) 57 | rets = [] 58 | [prefix, message].each do |var| 59 | rets << var.__send__(m, *args) rescue var 60 | end 61 | [rets[0].empty? ? nil : rets[0], rets[1]].compact.join("\t") 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/weechat/irc/user.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module IRC 3 | class User 4 | attr_reader :name 5 | attr_reader :host 6 | attr_reader :flags 7 | attr_reader :color 8 | attr_reader :channel 9 | attr_reader :server 10 | 11 | 12 | # FIXME make users server agnostic 13 | def initialize(args = {}) 14 | @name = args[:name] 15 | @identifier = IRC::Identifier.new(args[:host]) 16 | @flags, @color, @channel = args.values_at(:flags, :color, :channel) 17 | # @flags, @color = args.values_at(:flags, :color) 18 | @server = @channel.server 19 | end 20 | 21 | %w(chanowner? chanadmin? chanadmin2? op? 22 | halfop? voice? away? chanuser?).each_with_index do |method, bit| 23 | define_method(method) {(@flags & (2 ** bit)) > 0} 24 | end 25 | alias_method :opped?, :op? 26 | alias_method :voiced?, :voice? 27 | 28 | def ==(other) 29 | @name == other.name && @host == other.host #&& @channel == other.channel 30 | end 31 | alias_method :eql?, "==" 32 | alias_method :equal?, :eql? 33 | 34 | def whois 35 | Weechat::IRC::Whois.new(self) { |w| yield(w) if block_given? } 36 | end 37 | 38 | def real_name 39 | whois { |w| yield(w.real_name) if block_given? } 40 | end 41 | 42 | def channels 43 | whois { |w| yield(w.channels) if block_given? } 44 | end 45 | 46 | def op 47 | @channel.exec("/op #@name") 48 | end 49 | 50 | def halfop 51 | @channel.exec("/halfop #@name") 52 | end 53 | 54 | def voice 55 | @channel.exec("/voice #@name") 56 | end 57 | 58 | def deop 59 | @channel.exec("/deop #@name") 60 | end 61 | 62 | def dehalfop 63 | @channel.exec("/dehalfop #@name") 64 | end 65 | 66 | def devoice 67 | @channel.exec("/devoice #@name") 68 | end 69 | 70 | def kick(reason="") 71 | @channel.exec("/kick #@name #{reason}") 72 | end 73 | 74 | def ban 75 | @channel.exec("/ban #@name") 76 | end 77 | 78 | def unban 79 | @channel.exec("/unban #@name") 80 | end 81 | 82 | def kickban(reason="") 83 | kick(reason) 84 | ban 85 | end 86 | end # User 87 | end # IRC 88 | end # Weechat 89 | -------------------------------------------------------------------------------- /lib/weechat/hook.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | # Class that adds a hook to a weechat event. Has a callback that is called when the 3 | # event occurs. Overridden by subclasses for specific hooks 4 | # Each hook as an unique ID, which is passed to the middle-man 5 | # 6 | # callback, which then calls the appropriate callback. 7 | class Hook 8 | include Weechat::Pointer 9 | @@unique_id = 0 10 | 11 | @hook_classes = [self] 12 | class << self 13 | attr_reader :hook_classes 14 | 15 | def inherited(by) 16 | by.init 17 | @hook_classes << by 18 | end 19 | 20 | # returns all active hooks 21 | def all; @hooks; end 22 | 23 | def init 24 | @hooks = {} 25 | end 26 | end 27 | 28 | init 29 | 30 | attr_reader :id 31 | attr_reader :callback 32 | def initialize(*args) 33 | @id = self.class.compute_free_id 34 | @ptr = nil 35 | @callback = nil 36 | @hooked = true 37 | self.class.register(self) 38 | end 39 | 40 | def callback=(callback) 41 | @callback = EvaluatedCallback.new(callback) 42 | end 43 | 44 | class << self 45 | alias_method :hook, :new 46 | end 47 | 48 | # def to_s 49 | # @ptr 50 | # end 51 | 52 | def hooked? 53 | @hooked 54 | end 55 | 56 | # low level unhooking, no checks whatsoever. Basically used for 57 | # unhooking foreign hooks. 58 | def self.unhook(ptr) 59 | Weechat.unhook(ptr) 60 | end 61 | 62 | # Note: this also unhooks all hooks that were made using the API 63 | def self.unhook_all 64 | @hook_classes.each do |hook_class| 65 | hook_class.hooks.values.each {|hook| hook.unhook} 66 | end 67 | Weechat.unhook_all 68 | end 69 | 70 | def unhook(_raise = true) 71 | if _raise and !hooked? 72 | raise "not hooked" 73 | end 74 | 75 | self.class.unhook(@ptr) 76 | self.class.unregister(self) 77 | @callback = nil 78 | @hooked = false 79 | true 80 | end 81 | 82 | def call(*args) 83 | return @callback.call(*args) 84 | end 85 | 86 | # finds the hook with the given id 87 | def self.find_by_id(id) 88 | @hooks[id.to_i] 89 | end 90 | 91 | # Returns a new, unique ID. 92 | def self.compute_free_id 93 | @@unique_id += 1 94 | end 95 | 96 | # registers the hook so it will be called when the event the hook 97 | # is hooked to occurs 98 | def self.register(hook) 99 | @hooks[hook.id] = hook 100 | end 101 | 102 | # unregisters the hook 103 | def self.unregister(hook) 104 | @hooks.delete hook.id 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/weechat/script.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | class Script 3 | module Skeleton 4 | def self.included(other) 5 | other.__send__ :include, InstanceMethods 6 | other.__send__ :extend, InstanceMethods 7 | other.__send__ :extend, ClassMethods 8 | end 9 | 10 | module ClassMethods 11 | def script 12 | { 13 | :license => 'unlicensed', 14 | :version => '0.0.1', 15 | :author => 'Anonymous', 16 | :description => 'Empty script description', 17 | :charset => '', 18 | :gem_version => '0.0.1', 19 | }.merge(@script) 20 | end 21 | 22 | def config 23 | @config || Weechat::Script::Config.new({}) 24 | end 25 | end 26 | 27 | module InstanceMethods 28 | def weechat_init 29 | if (self.script[:gem_version].split('.').map{|i| i.to_i} <=> Weechat::VERSION.split('.').map{|i| i.to_i}) > 0 30 | Weechat.puts "This script ('#{self.script[:name]}') "\ 31 | "requires a version of the weechat ruby gem of at least #{self.script[:gem_version]}. "\ 32 | "You are currently using the version #{Weechat::VERSION}" 33 | return Weechat::WEECHAT_RC_ERROR 34 | end 35 | 36 | ret = Weechat.register(self.script[:name], 37 | self.script[:author], 38 | self.script[:version], 39 | self.script[:license], 40 | self.script[:description], 41 | 'weechat_script_unload', 42 | self.script[:charset]) 43 | if Weechat.integer_to_bool(ret) 44 | self.config.set_script_name!(self.script[:name]) 45 | self.config.init! 46 | if respond_to?(:setup) 47 | return Weechat::Utilities.evaluate_call { setup } 48 | end 49 | 50 | return Weechat::WEECHAT_RC_OK 51 | end 52 | end 53 | 54 | def weechat_script_unload 55 | if respond_to?(:teardown) 56 | return Weechat::Utilities.evaluate_call { teardown } 57 | end 58 | 59 | return Weechat::WEECHAT_RC_OK 60 | end 61 | end 62 | end 63 | 64 | include Weechat::Pointer 65 | extend Weechat::Properties 66 | 67 | init_properties 68 | 69 | class << self 70 | def all(plugin = nil) 71 | Plugin.all.map {|plugin| plugin.scripts}.flatten 72 | end 73 | end 74 | 75 | def initialize(ptr, plugin) 76 | super(ptr) 77 | @plugin = plugin 78 | end 79 | 80 | def get_infolist 81 | Weechat::Infolist.parse("#{@plugin.name}_script", @ptr) 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/weechat/irc/whois.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | module IRC 3 | class Whois 4 | attr_reader :data 5 | 6 | def populated? 7 | @populated 8 | end 9 | 10 | def process_reply(line) 11 | ret = line 12 | m = Weechat::IRC::Message.new(line.message) 13 | m.params.shift 14 | 15 | nick = m.params.first 16 | if nick == @user.name 17 | ret = nil 18 | 19 | case m.command 20 | when "401", "318" # ERR_NOSUCHNICK, RPL_ENDOFWHOIS 21 | # NOTE: 401 (no such nick) may not result in a 318 22 | @hooks.each do |hook| 23 | hook.unhook 24 | end 25 | @hooks = [] 26 | @populated = true 27 | 28 | if @block 29 | @block.call(self) 30 | end 31 | 32 | when "311" # RPL_WHOISUSER 33 | @data[:nick] = m.params[0] 34 | @data[:user] = m.params[1] 35 | @data[:host] = m.params[2] 36 | @data[:real_name] = m.params[4] 37 | 38 | when "312" # RPL_WHOISSERVER 39 | 40 | when "313" # RPL_WHOISOPERATOR 41 | @data[:operator] = true 42 | 43 | when "317" # RPL_WHOISIDLE 44 | @data[:idle] = m.params.first.to_i 45 | 46 | when "301" # RPL_AWAY 47 | @data[:away_reason] = m.params.last 48 | 49 | when "319" # RPL_WHOISCHANNELS 50 | m.params[1].split(" ").each do |channel| 51 | if channel !~ /^([&#!.~]|\+{2}).+$/ 52 | channel[0..0] = "" 53 | end 54 | 55 | @data[:channels] << Weechat::IRC::Channel.new(@user.server, channel) 56 | end 57 | end 58 | end 59 | 60 | ret 61 | end 62 | 63 | def initialize(user, &block) 64 | @data = { 65 | :nick => "", 66 | :user => "", 67 | :host => "", 68 | :real_name => "", 69 | :operator => false, 70 | :idle => 0, 71 | :away_reason => "", 72 | :channels => [], 73 | } 74 | @hooks = [] 75 | @populated = false 76 | @block = block #async callback 77 | @user = user 78 | 79 | # DONE: 301, 311, 313, 317, 318, 319, 401 80 | # CONSIDERED: 312 81 | # TODO: 307, 310, 320, 338, 330, 378, 379, 671 82 | [301,307,310,311,312,313,317,318,319,320,338,330,378,379,401,671].each do |numeric| 83 | @hooks << Weechat::Modifier.new("irc_in_#{numeric}") {|modifier, line| process_reply(line) } 84 | end 85 | 86 | user.server.exec("/whois #{user.name}") 87 | end 88 | 89 | def method_missing(m, *args) 90 | if @data.has_key?(m.to_sym) 91 | @data[m.to_sym] 92 | else 93 | super 94 | end 95 | end 96 | end # Whois 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/weechat/plugin.rb: -------------------------------------------------------------------------------- 1 | module Weechat 2 | # == Gettable properties 3 | # 4 | # [filename] Filename of the plugin 5 | # [handle] ? 6 | # [name] Name of the plugin 7 | # [description] Description of the plugin 8 | # [author] Author of the plugin 9 | # [version] Version of the plugin 10 | # [license] Licence of the plugin 11 | # [charset] ? 12 | # [debug?] ? 13 | class Plugin 14 | include Weechat::Pointer 15 | extend Weechat::Properties 16 | 17 | @mappings = { 18 | :licence => :license, 19 | :debug? => :debug, 20 | } 21 | 22 | @transformations = { 23 | [:debug] => lambda {|v| Weechat.integer_to_bool(v) }, 24 | } 25 | 26 | init_properties 27 | 28 | class << self 29 | def find(name) 30 | if name.nil? or name.empty? or name == "core" 31 | return Plugin.from_ptr("") 32 | end 33 | all.find {|plugin| plugin.name == name} 34 | end 35 | alias_method :from_name, :find 36 | 37 | def all 38 | items = super 39 | items[0,0] = Plugin.find("") 40 | items 41 | end 42 | 43 | # Loads a plugin. 44 | # 45 | # @return [void] 46 | def load(name) 47 | Weechat.exec("/plugin load #{name}") 48 | end 49 | 50 | # Reloads all plugins. 51 | # 52 | # Note: This will not reload the ruby plugin. 53 | # 54 | # @return [Array] All plugins that have been reloaded. 55 | def reload_all 56 | plugins = all.select{|plugin| plugin.name != "ruby"} 57 | plugins.each {|plugin| plugin.reload} 58 | end 59 | end 60 | 61 | def name 62 | Weechat.plugin_get_name(@ptr) 63 | end 64 | 65 | # Unloads the plugin. 66 | # 67 | # @param [Boolean] force If the plugin to be unloaded is "ruby", 68 | # +force+ has to be true. 69 | # @return [Boolean] true if we attempted to unload the plugin 70 | def unload(force = false) 71 | if name == "ruby" and !force 72 | Weechat.puts "Won't unload the ruby plugin unless you force it." 73 | false 74 | else 75 | Weechat.exec("/plugin unload #{name}") 76 | true 77 | end 78 | end 79 | 80 | # Reload the plugin. 81 | # 82 | # @param [Boolean] force If the plugin to be reloaded is "ruby", +force+ has to be true. 83 | # @return [Boolean] true if we attempted to reload the plugin 84 | def reload(force = false) 85 | if name == "ruby" and !force 86 | Weechat.puts "Won't reload the ruby plugin unless you force it." 87 | else 88 | Weechat.exec("/plugin reload #{name}") 89 | end 90 | end 91 | 92 | # Returns an array of all scripts loaded by this plugin. 93 | # 94 | # @return [Array