blur 1.8.6 → 2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37ce3e4e8a98062906f53c98b5baa1b798fe089b
4
- data.tar.gz: 4406a27f69812c4b181949a2f2106f55e663c0a9
3
+ metadata.gz: 976b042f31815f52310f120ccc9c2a3082fb0a93
4
+ data.tar.gz: 5b9a9266a92d19dd4b90494abe093dd26548b308
5
5
  SHA512:
6
- metadata.gz: f3f6a46334910c4be692cfa1c56587613bc981dcaeb8be10b5a931951a10e8a1e20607a66c8afea0837f066648591d3b1e64675388633352f5834e11d7f292ec
7
- data.tar.gz: 1aac96349671e30b299bc4fdb92b8a6df71d57e93b36361ee036780ca37b713a41d6ecdf0b831cad43dbb9ee92128d6c9bf3e14aa8bad3aaa7b1a3bacf6e966e
6
+ metadata.gz: 6630820a3dba98df19c1995971b0fab17187226356306f1354c19ea71819748bf6cb85299efddf556ffe101aec19d2a073ea8564e06655f774bc5e53d5a3e340
7
+ data.tar.gz: 7d7b87e2dc3952bcd6ce7b1360dc2f7e7cb88e795f61fcba3898d5713f42ebc144bc437dcf66daa4f1dedb93e87f0de01f409fadc26e54577a500f9e90105f8b
data/README.md CHANGED
@@ -6,15 +6,6 @@ There are a bunch of other well-written, well-running IRC libraries made for
6
6
  Ruby, but for me, they don't quite cut it as **the** library I wanted to use for
7
7
  my IRC services. That's how Blur came to be.
8
8
 
9
- Blur scales. A lot.
10
-
11
- When I stresstested the library on my network, I ended up throttling my VDSL
12
- connection before Blur even broke a sweat - albeit I only have 20/2.
13
-
14
- I managed to connect with 5000 clones before it couldn't resolve the hostname
15
- anymore, while this is an excellent feature, I would not suggest doing it.
16
-
17
- [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/mkroman/blur/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
18
9
  [![Build Status](https://travis-ci.org/mkroman/blur.svg?branch=isupport)](https://travis-ci.org/mkroman/blur)
19
10
  [![Dependency Status](https://gemnasium.com/mkroman/blur.svg)](https://gemnasium.com/mkroman/blur)
20
11
 
@@ -22,7 +13,6 @@ Features
22
13
  --------
23
14
  * SSL/TLS encryption
24
15
  * Connect to multiple networks
25
- * FiSH (channel-wide) encryptions
26
16
  * Non-blocking connections (no threading)
27
17
  * Extensible with scripts, (re)loadable during runtime
28
18
  * Modular, makes it a piece of cake to extend its IRC-capability
@@ -31,5 +21,4 @@ Future Plans
31
21
  ------------
32
22
  * DCC File-transfers
33
23
  * DH1080 Key-Exchange
34
- * ISupport implementation
35
24
  * Better event-handling in scripts
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ $:.unshift File.join __dir__, '../library'
5
+
6
+ require 'optparse'
7
+
8
+ options = {
9
+ verbose: false,
10
+ environment: 'development',
11
+ config_path: 'config.yml'
12
+ }
13
+
14
+ OptionParser.new do |opts|
15
+ opts.banner = "Usage: #$0 [-c <config>] [-e <env>]"
16
+
17
+ opts.separator ""
18
+ opts.separator "Specific options:"
19
+
20
+ opts.on '-v', '--[no-]verbose', 'Enable verbose logging' do |verbose|
21
+ options[:verbose] = verbose
22
+ end
23
+
24
+ opts.on '-c', '--config=PATH', 'Set the configuration file' do |config_path|
25
+ options[:config_path] = config_path
26
+ end
27
+
28
+ opts.on '-eENV', '--environment=ENV', 'Environment to run in' do |environment|
29
+ options[:environment] = environment
30
+ end
31
+
32
+ opts.on '-r', '--require LIBRARY', 'Require the LIBRARY before running' do |lib|
33
+ require lib
34
+ end
35
+
36
+ opts.on_tail '-h', '--help', 'Show this message' do
37
+ puts opts
38
+ exit
39
+ end
40
+ end.parse!
41
+
42
+ begin
43
+ require 'blur'
44
+ rescue LoadError => exception
45
+ puts 'Ruby was unable to load the blur library!'
46
+ puts
47
+ puts "Please ensure that you've installed it using the following command:"
48
+ puts 'gem install blur'
49
+ raise exception
50
+ exit 1
51
+ end
52
+
53
+ config_path = File.expand_path options[:config_path]
54
+ unless File.readable? config_path
55
+ fail "Configuration file `#{config_path}' is not readable"
56
+ exit 1
57
+ end
58
+
59
+ @client = Blur::Client.new options
60
+ @client.connect
61
+
62
+ # vim: syntax=ruby
@@ -1,36 +1,61 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'pp'
4
3
  require 'yaml'
5
- require 'majic'
6
4
  require 'socket'
7
5
  require 'ostruct'
8
6
  require 'openssl'
7
+
8
+ require 'deep_merge/rails_compat'
9
9
  require 'eventmachine'
10
+ require 'ircparser'
10
11
 
11
12
  # Require all library files.
13
+ require 'blur/logging'
12
14
  require 'blur/version'
13
- require 'blur/client'
14
- require 'blur/evaluable'
15
- require 'blur/script/dsl'
16
- require 'blur/extension'
15
+ require 'blur/callbacks'
17
16
  require 'blur/script'
17
+ require 'blur/script_cache'
18
18
  require 'blur/network'
19
- require 'blur/encryption'
19
+ require 'blur/client'
20
+ require 'blur/user'
21
+ require 'blur/channel'
20
22
  require 'blur/enhancements'
21
- require 'blur/script/cache'
22
- require 'blur/network/user'
23
- require 'blur/network/channel'
24
- require 'blur/network/command'
25
23
  require 'blur/network/isupport'
26
24
  require 'blur/network/connection'
27
- require 'blur/script/commands'
28
25
 
29
26
  # Blur is a very modular IRC-framework for ruby.
30
27
  #
31
28
  # It allows the developer to extend it in multiple ways.
32
29
  # It can be by handlers, scripts, communications, and what have you.
33
30
  module Blur
31
+ # Contains all superscript classes for scripts that may be used.
32
+ @@scripts = {}
33
+
34
+ # Creates a new superscript class and inserts it into the list of scripts.
35
+ def self.Script name, *args, &block
36
+ klass = Class.new SuperScript
37
+ klass.name = name
38
+ klass.events = {}
39
+ klass.class_exec &block
40
+ klass.init
41
+
42
+ @@scripts[name] = klass
43
+ end
44
+
45
+ # Gets all superscript classes.
46
+ def self.scripts
47
+ @@scripts
48
+ end
49
+
50
+ # Resets all scripts.
51
+ #
52
+ # This method will call `deinit` on each script class before removing them to
53
+ # give them a chance to clean up.
54
+ def self.reset_scripts!
55
+ @@scripts.each_value &:deinit
56
+ @@scripts.clear
57
+ end
58
+
34
59
  # Instantiates a client with given options and then makes the client instance
35
60
  # evaluate the given block to form a DSL.
36
61
  #
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ module Blur
4
+ module Callbacks
5
+ # Our list of callbacks.
6
+ @@callbacks = {}
7
+
8
+ # Get a list of callbacks registered.
9
+ #
10
+ # @returns [Array] the list of callbacks
11
+ def callbacks
12
+ @@callbacks
13
+ end
14
+
15
+ # Emit a new event with given arguments.
16
+ #
17
+ # @param name [Symbol] The event name.
18
+ # @param args [optional, Array] The list of arguments to pass.
19
+ def emit name, *args
20
+ EM.defer do
21
+ notify_scripts name, *args
22
+ end
23
+
24
+ if (callbacks = @@callbacks[name]) and callbacks.any?
25
+ EM.defer do
26
+ callbacks.each{|callback| callback.call *args }
27
+ end
28
+ end
29
+ end
30
+
31
+ # Add a new event callback.
32
+ #
33
+ # @param name [Symbol] The event name.
34
+ # @yield [args, ...] The arguments passed from #emit.
35
+ def on name, &block
36
+ (@@callbacks[name] ||= []) << block
37
+ end
38
+
39
+ protected
40
+
41
+ def notify_scripts name, *args
42
+ scripts = @scripts.values.select{|script| script.class.events.key? name }
43
+ scripts.each do |script|
44
+ begin
45
+ script.class.events[name].each do |method|
46
+ if method.is_a? Proc
47
+ method.call script, *args
48
+ else
49
+ script.__send__ method, *args
50
+ end
51
+ end
52
+ rescue => exception
53
+ STDERR.puts "#{exception.class}: #{exception.message}"
54
+ STDERR.puts nil, 'Backtrace:', '---', exception.backtrace
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+
3
+ module Blur
4
+ # The +Channel+ class is used for encapsulating a channel and its properties.
5
+ #
6
+ # Users inside the channel is stored in the {#channels} attribute.
7
+ #
8
+ # Modes can be set for a channel, but Blur is not
9
+ # {http://www.irc.org/tech_docs/005.html ISupport}-compliant yet.
10
+ #
11
+ # @todo make so that channels *and* users belongs to the network, and not
12
+ # like now where the user belongs to the channel, resulting in multiple
13
+ # user instances.
14
+ class Channel
15
+ # @return [String] the channels name.
16
+ attr_accessor :name
17
+ # @return [String] the channels topic.
18
+ attr_accessor :topic
19
+ # @return [Array] list of references to users in the channel.
20
+ attr_accessor :users
21
+ # @return [String] all the modes set on the channel.
22
+ attr_accessor :modes
23
+ # @return [Network] a reference to the network.
24
+ attr_accessor :network
25
+
26
+ # Instantiate a user with a nickname, a network and a user list.
27
+ def initialize name, network = nil
28
+ @name = name
29
+ @users = []
30
+ @modes = String.new
31
+ @network = network
32
+ end
33
+
34
+ # Merge the channels mode corresponding to the leading character (+ or -).
35
+ #
36
+ # @param [String] modes the modes to merge with.
37
+ def merge_modes modes
38
+ addition = true
39
+
40
+ modes.each_char do |char|
41
+ case char
42
+ when ?+
43
+ addition = true
44
+ when ?-
45
+ addition = false
46
+ else
47
+ addition ? @modes.concat(char) : @modes.delete!(char)
48
+ end
49
+ end
50
+ end
51
+
52
+ # Send a message to the channel.
53
+ #
54
+ # @param [String] message the message to send.
55
+ def say message
56
+ @network.say self, message
57
+ end
58
+
59
+ # Convert it to a debug-friendly format.
60
+ def inspect
61
+ %{#<#{self.class.name}:0x#{self.object_id.to_s 16} @name=#{@name.inspect} @topic=#{@topic.inspect} @users=#{@users.inspect}}
62
+ end
63
+
64
+ # Called when YAML attempts to save the object, which happens when a
65
+ # scripts cache contains this user and the script is unloaded.
66
+ def to_yaml options = {}
67
+ @name.to_yaml options
68
+ end
69
+
70
+ # Get the channels name.
71
+ def to_s
72
+ @name
73
+ end
74
+ end
75
+ end
@@ -8,44 +8,72 @@ module Blur
8
8
  # It stores networks, scripts and callbacks, and is also encharge of
9
9
  # distributing the incoming commands to the right networks and scripts.
10
10
  class Client
11
+ include Callbacks
11
12
  include Handling, Logging
13
+
14
+ # Client error.
15
+ Error = Class.new StandardError
16
+
17
+ # The default environment.
18
+ ENVIRONMENT = ENV['BLUR_ENV'] || 'development'
19
+
20
+ # The default configuration.
21
+ DEFAULT_CONFIG = {
22
+ 'blur' => {
23
+ 'cache_dir' => 'cache/',
24
+ 'scripts_dir' => 'scripts/',
25
+ 'networks' => []
26
+ },
27
+ 'scripts' => {},
28
+ }.freeze
12
29
 
13
- # @return [Array] the options that is passed upon initialization.
14
- attr_accessor :options
15
- # @return [Array] a list of scripts that is loaded during runtime.
16
- attr_accessor :scripts
17
30
  # @return [Array] a list of instantiated networks.
18
31
  attr_accessor :networks
19
-
32
+ # @return [Hash] client configuration.
33
+ attr_accessor :config
34
+ # @return [Hash] initialized scripts.
35
+ attr_accessor :scripts
36
+
37
+ attr_accessor :verbose
38
+
20
39
  # Instantiates the client, stores the options, instantiates the networks
21
40
  # and then loads available scripts.
22
41
  #
23
42
  # @param [Hash] options the options for the client.
24
- # @option options [Array] networks list of hashes that contain network
25
- # options.
26
- def initialize options
27
- @options = options
28
- @scripts = []
29
- @networks = []
30
- @callbacks = {}
31
-
32
- @networks = @options[:networks].map {|options| Network.new options }
33
-
34
- load_scripts
43
+ # @option options [String] :config_path path to a configuration file.
44
+ # @option options [String] :environment the client environment.
45
+ def initialize options = {}
46
+ @scripts = {}
47
+ @networks = []
48
+ @config_path = options[:config_path]
49
+ @environment = options[:environment]
50
+ @verbose = options[:verbose] == true
51
+
52
+ load_config!
53
+
54
+ networks = @config['blur']['networks']
55
+
56
+ if networks and networks.any?
57
+ networks.each do |network_options|
58
+ @networks.<< Network.new network_options, self
59
+ end
60
+ end
61
+
35
62
  trap 2, &method(:quit)
36
63
  end
37
64
 
38
65
  # Connect to each network available that is not already connected, then
39
66
  # proceed to start the run-loop.
40
67
  def connect
41
- networks = @networks.select {|network| not network.connected? }
68
+ networks = @networks.reject &:connected?
42
69
 
43
70
  EventMachine.run do
44
- EventMachine.error_handler{|e| p e }
71
+ load_scripts!
72
+ networks.each &:connect
45
73
 
46
- networks.each do |network|
47
- network.delegate = self
48
- network.connect
74
+ EventMachine.error_handler do |exception|
75
+ log.error "#{exception.message ^ :bold} on line #{exception.line.to_s ^ :bold}"
76
+ puts exception.backtrace.join "\n"
49
77
  end
50
78
  end
51
79
  end
@@ -56,43 +84,17 @@ module Blur
56
84
  #
57
85
  # @param [Network] network the network that received the command.
58
86
  # @param [Network::Command] command the received command.
59
- def got_command network, command
60
- log "#{'←' ^ :green} #{command.name.to_s.ljust(8, ' ') ^ :light_gray} #{command.params.map(&:inspect).join ' '}"
61
- name = :"got_#{command.name.downcase}"
62
-
63
- if respond_to? name
64
- __send__ name, network, command
87
+ def got_message network, message
88
+ if @verbose
89
+ log "#{'←' ^ :green} #{message.command.to_s.ljust(8, ' ') ^ :light_gray} #{message.parameters.map(&:inspect).join ' '}"
65
90
  end
66
- end
67
-
68
- # Searches for scripts in working_directory/scripts and then loads them.
69
- def load_scripts
70
- # Load script extensions.
71
- Script.load_extensions!
91
+ name = :"got_#{message.command.downcase}"
72
92
 
73
- # Load the scripts.
74
- script_path = File.dirname $0
75
-
76
- Dir.glob("#{script_path}/scripts/*.rb").each do |path|
77
- script = Script.new path
78
- script.__client = self
79
-
80
- @scripts << script
93
+ if respond_to? name
94
+ __send__ name, network, message
81
95
  end
82
96
  end
83
97
 
84
- # Unload all scripts gracefully that have been loaded into the client.
85
- #
86
- # @see Script#unload!
87
- def unload_scripts
88
- # Unload script extensions.
89
- Script.unload_extensions!
90
-
91
- @scripts.each do |script|
92
- script.unload!
93
- end.clear
94
- end
95
-
96
98
  # Called when a network connection is either closed, or terminated.
97
99
  def network_connection_closed network
98
100
  emit :connection_close, network
@@ -103,8 +105,6 @@ module Blur
103
105
  #
104
106
  # @param [optional, Symbol] signal The signal received by the system, if any.
105
107
  def quit signal = :SIGINT
106
- unload_scripts
107
-
108
108
  @networks.each do |network|
109
109
  network.transmit :QUIT, "Got SIGINT?"
110
110
  network.disconnect
@@ -112,46 +112,77 @@ module Blur
112
112
 
113
113
  EventMachine.stop
114
114
  end
115
-
116
- private
117
- # Finds all callbacks with name `name` and then calls them.
118
- # It also sends `name` to {Script} if the script responds to `name`, to all
119
- # available scripts.
120
- #
121
- # @param [Symbol] name the corresponding event-handlers name.
122
- # @param [...] args Arguments that is passed to the event-handler.
123
- # @private
124
- def emit name, *args
125
- EM.defer do
126
- @callbacks[name].each do |callback|
127
- begin
128
- callback.call *args
129
- rescue Exception => e
130
- log.error "Callback `#{name}' threw an exception - #{exception.message ^ :bold} on line #{exception.line.to_s ^ :bold}"
131
- puts exception.backtrace.join "\n"
132
- end
133
- end if @callbacks[name]
134
-
135
- scripts = @scripts.select{|script| script.__emissions.include? name }
136
- scripts.each do |script|
137
- begin
138
- script.__send__ name, *args
139
- rescue Exception => exception
140
- log.error "#{File.basename(script.__path) << " - " << exception.message ^ :bold} on line #{exception.line.to_s ^ :bold}"
141
- puts exception.backtrace.join "\n"
142
- end
115
+
116
+ # Reloads configuration file and scripts.
117
+ def reload!
118
+ EM.schedule do
119
+ unload_scripts!
120
+ load_config!
121
+ load_scripts!
122
+
123
+ yield if block_given?
124
+ end
125
+ end
126
+
127
+ # Loads all scripts in the script directory.
128
+ def load_scripts!
129
+ scripts_dir = File.expand_path @config['blur']['scripts_dir']
130
+ scripts_cache_dir = File.expand_path @config['blur']['cache_dir']
131
+
132
+ Dir.glob File.join(scripts_dir, '*.rb') do |file|
133
+ begin
134
+ load file, true
135
+ rescue Exception => e
136
+ STDERR.puts "The script `#{file}' failed to load"
137
+ STDERR.puts "#{e.class}: #{e.message}"
138
+ STDERR.puts
139
+ STDERR.puts 'Backtrace:', '---', e.backtrace
143
140
  end
144
141
  end
142
+
143
+ scripts_config = @config['scripts']
144
+
145
+ Blur.scripts.each do |name, superscript|
146
+ script = superscript.allocate
147
+ script.cache = ScriptCache.load name, scripts_cache_dir
148
+ script.config = scripts_config.fetch name, {}
149
+ script._client_ref = self
150
+ script.send :initialize
151
+
152
+ @scripts[name] = script
153
+ end
154
+
155
+ emit :scripts_loaded
145
156
  end
146
-
147
- # Stores the block as an event-handler with name `name`.
157
+
158
+ # Unloads initialized scripts and superscripts.
148
159
  #
149
- # @param [Symbol] name the corresponding event-handlers name.
150
- # @param [Block] block the event-handlers block that serves as a trigger.
151
- # @private
152
- def catch name, &block
153
- (@callbacks[name] ||= []) << block
160
+ # This method will call #unloaded on the instance of each loaded script to
161
+ # give it a chance to clean up any resources.
162
+ def unload_scripts!
163
+ @scripts.each do |name, script|
164
+ script.__send__ :unloaded if script.respond_to? :unloaded
165
+ end.clear
166
+
167
+ Blur.reset_scripts!
168
+ end
169
+
170
+ private
171
+
172
+ # Load the user-specified configuration file.
173
+ #
174
+ # @returns true on success, false otherwise.
175
+ def load_config!
176
+ config = YAML.load_file @config_path
177
+
178
+ if config.key? @environment
179
+ @config = config[@environment]
180
+ @config.deeper_merge! DEFAULT_CONFIG
181
+
182
+ emit :config_load
183
+ else
184
+ raise Error, "No configuration found for specified environment `#{@environment}'"
185
+ end
154
186
  end
155
-
156
187
  end
157
188
  end