blur 1.8.6 → 2.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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