blur 1.8.6 → 2.1.6

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
- SHA1:
3
- metadata.gz: 37ce3e4e8a98062906f53c98b5baa1b798fe089b
4
- data.tar.gz: 4406a27f69812c4b181949a2f2106f55e663c0a9
2
+ SHA256:
3
+ metadata.gz: 6c4add65cc3b256a41629f69bb62065d523f60a5cd934d8b4c1045b326113ce6
4
+ data.tar.gz: f55fbec4a5863465d61f110ceeb1fe3b7f89d6a6d9fc659ac30907ef7921e510
5
5
  SHA512:
6
- metadata.gz: f3f6a46334910c4be692cfa1c56587613bc981dcaeb8be10b5a931951a10e8a1e20607a66c8afea0837f066648591d3b1e64675388633352f5834e11d7f292ec
7
- data.tar.gz: 1aac96349671e30b299bc4fdb92b8a6df71d57e93b36361ee036780ca37b713a41d6ecdf0b831cad43dbb9ee92128d6c9bf3e14aa8bad3aaa7b1a3bacf6e966e
6
+ metadata.gz: 05bc91b4a941bfe212e42d9a0c56d6e7969aa01e9cde2fa86946dc0f24244b462de299683d06875e2ea3bdad7e32280afd869cac6874d0fd7ef1f3d9bfa10d63
7
+ data.tar.gz: 9ca64c3952d10ccb6c9dce8a51723da4b81643b46d2090aa72b72886965d8973da540a6b9da477254493baf99d1ad2174b7c5129557f963fb08db892ce42a632
data/README.md CHANGED
@@ -1,35 +1,21 @@
1
- Blur
2
- ====
1
+ # Blur
3
2
  Blur is an event-driven IRC-framework written in and for Ruby.
4
3
 
5
4
  There are a bunch of other well-written, well-running IRC libraries made for
6
5
  Ruby, but for me, they don't quite cut it as **the** library I wanted to use for
7
6
  my IRC services. That's how Blur came to be.
8
7
 
9
- Blur scales. A lot.
8
+ [![Build Status](https://travis-ci.org/mkroman/blur.svg?branch=isupport)](https://travis-ci.org/mkroman/blur)
10
9
 
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.
10
+ ## Getting started
13
11
 
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
12
 
17
- [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/mkroman/blur/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
18
- [![Build Status](https://travis-ci.org/mkroman/blur.svg?branch=isupport)](https://travis-ci.org/mkroman/blur)
19
- [![Dependency Status](https://gemnasium.com/mkroman/blur.svg)](https://gemnasium.com/mkroman/blur)
13
+ ## Documentation
20
14
 
21
- Features
22
- --------
23
- * SSL/TLS encryption
24
- * Connect to multiple networks
25
- * FiSH (channel-wide) encryptions
26
- * Non-blocking connections (no threading)
27
- * Extensible with scripts, (re)loadable during runtime
28
- * Modular, makes it a piece of cake to extend its IRC-capability
15
+ Documentation is available [here](https://www.rubydoc.info/github/mkroman/blur)
29
16
 
30
- Future Plans
31
- ------------
32
- * DCC File-transfers
33
- * DH1080 Key-Exchange
34
- * ISupport implementation
35
- * Better event-handling in scripts
17
+ ## Features
18
+ * SSL/TLS connections
19
+ * Connect to multiple networks in a single process
20
+ * Non-blocking connections (no threading just for networking)
21
+ * Extensible with scripts that are (re)loadable during runtime
data/executables/blur ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../library/blur'
5
+
6
+ require 'optparse'
7
+
8
+ options = {
9
+ verbose: false,
10
+ config_path: 'config.yml'
11
+ }
12
+
13
+ OptionParser.new do |opts|
14
+ opts.banner = "Usage: #{$PROGRAM_NAME} [-c <config>] [-e <env>]"
15
+
16
+ opts.separator ''
17
+ opts.separator 'Specific options:'
18
+
19
+ opts.on '-v', '--[no-]verbose', 'Enable verbose logging' do |verbose|
20
+ options[:verbose] = verbose
21
+ end
22
+
23
+ opts.on '-c', '--config=PATH', 'Set the configuration file' do |config_path|
24
+ options[:config_path] = config_path
25
+ end
26
+
27
+ opts.on '-eENV', '--environment=ENV', 'Environment to run in' do |environment|
28
+ options[:environment] = environment
29
+ end
30
+
31
+ opts.on '-r', '--require LIBRARY', 'Require the LIBRARY before running' do |lib|
32
+ require lib
33
+ end
34
+
35
+ opts.on_tail '-h', '--help', 'Show this message' do
36
+ puts opts
37
+ exit
38
+ end
39
+ end.parse!
40
+
41
+ begin
42
+ require 'blur'
43
+ rescue LoadError => e
44
+ puts 'Ruby was unable to load the blur library!'
45
+ puts
46
+ puts "Please ensure that you've installed it using the following command:"
47
+ puts 'gem install blur'
48
+ raise e
49
+ end
50
+
51
+ puts "Blur #{Blur.version}"
52
+
53
+ config_path = File.expand_path options[:config_path]
54
+ puts "Loading configuration file `#{config_path}' .."
55
+
56
+ raise "Configuration file `#{config_path}' is not readable" unless File.readable? config_path
57
+
58
+ EM.run do
59
+ @client = Blur::Client.new options
60
+ @client.connect
61
+ end
62
+
63
+ # vim: syntax=ruby
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blur
4
+ module Callbacks
5
+ # Get a list of callbacks registered.
6
+ #
7
+ # @returns [Array] the list of callbacks
8
+ def callbacks
9
+ @callbacks ||= {}
10
+ end
11
+
12
+ # Emit a new event with given arguments.
13
+ #
14
+ # @param name [Symbol] The event name.
15
+ # @param args [optional, Array] The list of arguments to pass.
16
+ # @return [true, false] True if any callbacks were invoked, nil otherwise
17
+ def emit name, *args
18
+ # Trigger callbacks in scripts before triggering events in the client.
19
+ notify_scripts name, *args
20
+
21
+ matching_callbacks = callbacks[name]
22
+ return false unless matching_callbacks&.any?
23
+
24
+ matching_callbacks.each { |callback| callback.call *args }
25
+ end
26
+
27
+ # Add a new event callback.
28
+ #
29
+ # @param name [Symbol] The event name.
30
+ # @yield [args, ...] The arguments passed from #emit.
31
+ def on name, &block
32
+ (callbacks[name] ||= []) << block
33
+ end
34
+
35
+ protected
36
+
37
+ def notify_scripts name, *args
38
+ scripts = @scripts.values.select { |script| script.class.events.key? name }
39
+ scripts.each do |script|
40
+ script.class.events[name].each do |method|
41
+ if method.is_a? Proc
42
+ method.call script, *args
43
+ else
44
+ script.__send__ method, *args
45
+ end
46
+ end
47
+ rescue StandardError => e
48
+ warn "#{e.class}: #{e.message}"
49
+ warn nil, 'Backtrace:', '---', e.backtrace
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
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 = ''
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#{object_id.to_s 16} " \
62
+ "@name=#{@name.inspect} " \
63
+ "@topic=#{@topic.inspect} " \
64
+ "@users=#{@users.inspect}>"
65
+ end
66
+
67
+ # Called when YAML attempts to save the object, which happens when a
68
+ # scripts cache contains this user and the script is unloaded.
69
+ def to_yaml options = {}
70
+ @name.to_yaml options
71
+ end
72
+
73
+ # Get the channels name.
74
+ def to_s
75
+ @name
76
+ end
77
+ end
78
+ end
@@ -1,6 +1,6 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'blur/handling'
3
+ require_relative './handling'
4
4
 
5
5
  module Blur
6
6
  # The +Client+ class is the controller of the low-level access.
@@ -8,150 +8,208 @@ 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 Handling, Logging
12
-
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
11
+ include Callbacks
12
+ include Handling
13
+
14
+ # The default environment.
15
+ ENVIRONMENT = ENV['BLUR_ENV'] || 'development'
16
+
17
+ # The default configuration.
18
+ DEFAULT_CONFIG = {
19
+ 'blur' => {
20
+ 'cache_dir' => 'cache/',
21
+ 'scripts_dir' => 'scripts/',
22
+ 'networks' => []
23
+ },
24
+ 'scripts' => {}
25
+ }.freeze
26
+
17
27
  # @return [Array] a list of instantiated networks.
18
28
  attr_accessor :networks
19
-
29
+ # @return [Hash] client configuration.
30
+ attr_accessor :config
31
+ # @return [Hash] initialized scripts.
32
+ attr_accessor :scripts
33
+ # @return [Boolean] whether verbose logging is enabled.
34
+ attr_accessor :verbose
35
+ # @return [String] the path to the currently used config file.
36
+ attr_accessor :config_path
37
+
20
38
  # Instantiates the client, stores the options, instantiates the networks
21
39
  # and then loads available scripts.
22
40
  #
23
41
  # @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
42
+ # @option options [String] :config_path path to a configuration file.
43
+ # @option options [String] :environment the client environment.
44
+ def initialize options = {}
45
+ @scripts = {}
46
+ @networks = []
47
+ @config_path = options[:config_path]
48
+ @environment = options[:environment] || ENVIRONMENT
49
+ @verbose = options[:verbose] == true
50
+
51
+ raise ConfigError, 'missing config file path in :config_path option' unless @config_path
52
+
53
+ load_config!
54
+
55
+ networks = @config['blur']['networks']
56
+
57
+ if networks&.any?
58
+ networks.each do |network_options|
59
+ @networks.<< Network.new network_options, self
60
+ end
61
+ end
62
+
35
63
  trap 2, &method(:quit)
36
64
  end
37
-
65
+
38
66
  # Connect to each network available that is not already connected, then
39
67
  # proceed to start the run-loop.
40
68
  def connect
41
- networks = @networks.select {|network| not network.connected? }
42
-
43
- EventMachine.run do
44
- EventMachine.error_handler{|e| p e }
45
-
46
- networks.each do |network|
47
- network.delegate = self
48
- network.connect
49
- end
69
+ networks = @networks.reject &:connected?
70
+
71
+ load_scripts!
72
+ networks.each &:connect
73
+
74
+ EventMachine.error_handler do |exception|
75
+ message_pattern = /^.*?:(\d+):/
76
+ backtrace = exception.backtrace.first
77
+ error_line = backtrace.match(message_pattern)[1].to_i + 1
78
+
79
+ puts "#{exception.message} on line #{error_line.to_s}"
80
+ puts exception.backtrace.join "\n"
50
81
  end
51
82
  end
52
-
83
+
53
84
  # Is called when a command have been received and parsed, this distributes
54
85
  # the command to the loader, which then further distributes it to events
55
86
  # and scripts.
56
87
  #
57
88
  # @param [Network] network the network that received the command.
58
89
  # @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
65
- 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!
72
-
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
81
- end
82
- end
83
-
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
+ def got_message network, message
91
+ puts "← #{message.command.to_s.ljust(8, ' ')} #{message.parameters.map(&:inspect).join ' '}" if @verbose
90
92
 
91
- @scripts.each do |script|
92
- script.unload!
93
- end.clear
93
+ name = :"got_#{message.command.downcase}"
94
+
95
+ __send__ name, network, message if respond_to? name
94
96
  end
95
97
 
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
99
101
  end
100
-
102
+
101
103
  # Try to gracefully disconnect from each network, unload all scripts and
102
104
  # exit properly.
103
105
  #
104
106
  # @param [optional, Symbol] signal The signal received by the system, if any.
105
- def quit signal = :SIGINT
106
- unload_scripts
107
-
107
+ def quit _signal = :SIGINT
108
108
  @networks.each do |network|
109
- network.transmit :QUIT, "Got SIGINT?"
109
+ network.transmit :QUIT, 'Got SIGINT?'
110
110
  network.disconnect
111
111
  end
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.
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
+ script_file_paths = Dir.glob File.join scripts_dir, '*.rb'
131
+
132
+ # Sort the script file paths by file name so they load by alphabetical
133
+ # order.
134
+ #
135
+ # This will make it possible to create a script called '10_database.rb'
136
+ # which will be loaded before '20_settings.rb' and non-numeric prefixes
137
+ # will be loaded after that.
138
+ script_file_paths = script_file_paths.sort do |a, b|
139
+ File.basename(a) <=> File.basename(b)
140
+ end
141
+
142
+ script_file_paths.each { |script_path| load_script_file script_path }
143
+
144
+ initialize_superscripts
145
+
146
+ emit :scripts_loaded
147
+ end
148
+
149
+ # Loads the given +file_path+ as a Ruby script, wrapping it in an anonymous
150
+ # module to protect our global namespace.
120
151
  #
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
143
- end
152
+ # @param [String] file_path the path to the ruby script.
153
+ #
154
+ # @raise [Exception] if there was any problems loading the file
155
+ def load_script_file file_path
156
+ load file_path, true
157
+ rescue Exception => e
158
+ warn "The script `#{file_path}' failed to load"
159
+ warn "#{e.class}: #{e.message}"
160
+ warn ''
161
+ warn 'Backtrace:', '---', e.backtrace
162
+ end
163
+
164
+ # Instantiates each +SuperScript+ in the +Blur.scripts+ list by manually
165
+ # allocating an instance and calling #initialize on it, then the instance is
166
+ # stored in +Client#scripts+.
167
+ #
168
+ # @raise [Exception] any exception that might occur in any scripts'
169
+ # #initialize method.
170
+ def initialize_superscripts
171
+ scripts_config = @config['scripts']
172
+ scripts_cache_dir = File.expand_path @config['blur']['cache_dir']
173
+
174
+ Blur.scripts.each do |name, superscript|
175
+ script = superscript.allocate
176
+ script.cache = ScriptCache.load name, scripts_cache_dir
177
+ script.config = scripts_config.fetch name, {}
178
+ script._client_ref = self
179
+ script.send :initialize
180
+
181
+ @scripts[name] = script
144
182
  end
145
183
  end
146
-
147
- # Stores the block as an event-handler with name `name`.
184
+
185
+ # Unloads initialized scripts and superscripts.
186
+ #
187
+ # This method will call #unloaded on the instance of each loaded script to
188
+ # give it a chance to clean up any resources.
189
+ def unload_scripts!
190
+ @scripts.each do |_name, script|
191
+ script.__send__ :unloaded if script.respond_to? :unloaded
192
+ end.clear
193
+
194
+ Blur.reset_scripts!
195
+ end
196
+
197
+ private
198
+
199
+ # Load the user-specified configuration file.
148
200
  #
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
201
+ # @returns true on success, false otherwise.
202
+ def load_config!
203
+ config = YAML.load_file @config_path
204
+
205
+ unless config.key? @environment
206
+ raise ClientError, "No configuration found for specified environment `#{@environment}'"
207
+ end
208
+
209
+ @config = config[@environment]
210
+ @config.deeper_merge! DEFAULT_CONFIG
211
+
212
+ emit :config_load
154
213
  end
155
-
156
214
  end
157
215
  end