cool.io 1.2.0-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +26 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +4 -0
  4. data/CHANGES.md +176 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +20 -0
  7. data/README.md +172 -0
  8. data/Rakefile +81 -0
  9. data/cool.io.gemspec +28 -0
  10. data/examples/dslified_echo_client.rb +34 -0
  11. data/examples/dslified_echo_server.rb +24 -0
  12. data/examples/echo_client.rb +38 -0
  13. data/examples/echo_server.rb +27 -0
  14. data/examples/google.rb +9 -0
  15. data/examples/httpclient.rb +38 -0
  16. data/ext/cool.io/.gitignore +5 -0
  17. data/ext/cool.io/cool.io.h +58 -0
  18. data/ext/cool.io/cool.io_ext.c +25 -0
  19. data/ext/cool.io/ev_wrap.h +8 -0
  20. data/ext/cool.io/extconf.rb +73 -0
  21. data/ext/cool.io/iowatcher.c +189 -0
  22. data/ext/cool.io/libev.c +8 -0
  23. data/ext/cool.io/loop.c +301 -0
  24. data/ext/cool.io/stat_watcher.c +269 -0
  25. data/ext/cool.io/timer_watcher.c +219 -0
  26. data/ext/cool.io/utils.c +122 -0
  27. data/ext/cool.io/watcher.c +264 -0
  28. data/ext/cool.io/watcher.h +71 -0
  29. data/ext/http11_client/.gitignore +5 -0
  30. data/ext/http11_client/LICENSE +31 -0
  31. data/ext/http11_client/ext_help.h +14 -0
  32. data/ext/http11_client/extconf.rb +6 -0
  33. data/ext/http11_client/http11_client.c +300 -0
  34. data/ext/http11_client/http11_parser.c +403 -0
  35. data/ext/http11_client/http11_parser.h +48 -0
  36. data/ext/http11_client/http11_parser.rl +173 -0
  37. data/ext/iobuffer/extconf.rb +9 -0
  38. data/ext/iobuffer/iobuffer.c +765 -0
  39. data/ext/libev/Changes +388 -0
  40. data/ext/libev/LICENSE +36 -0
  41. data/ext/libev/README +58 -0
  42. data/ext/libev/README.embed +3 -0
  43. data/ext/libev/ev.c +4803 -0
  44. data/ext/libev/ev.h +845 -0
  45. data/ext/libev/ev_epoll.c +279 -0
  46. data/ext/libev/ev_kqueue.c +214 -0
  47. data/ext/libev/ev_poll.c +148 -0
  48. data/ext/libev/ev_port.c +185 -0
  49. data/ext/libev/ev_select.c +314 -0
  50. data/ext/libev/ev_vars.h +203 -0
  51. data/ext/libev/ev_win32.c +163 -0
  52. data/ext/libev/ev_wrap.h +200 -0
  53. data/ext/libev/test_libev_win32.c +123 -0
  54. data/lib/.gitignore +2 -0
  55. data/lib/cool.io.rb +32 -0
  56. data/lib/cool.io/async_watcher.rb +43 -0
  57. data/lib/cool.io/custom_require.rb +9 -0
  58. data/lib/cool.io/dns_resolver.rb +225 -0
  59. data/lib/cool.io/dsl.rb +135 -0
  60. data/lib/cool.io/eventmachine.rb +234 -0
  61. data/lib/cool.io/http_client.rb +427 -0
  62. data/lib/cool.io/io.rb +174 -0
  63. data/lib/cool.io/iowatcher.rb +17 -0
  64. data/lib/cool.io/listener.rb +93 -0
  65. data/lib/cool.io/loop.rb +130 -0
  66. data/lib/cool.io/meta.rb +49 -0
  67. data/lib/cool.io/server.rb +74 -0
  68. data/lib/cool.io/socket.rb +230 -0
  69. data/lib/cool.io/timer_watcher.rb +17 -0
  70. data/lib/cool.io/version.rb +5 -0
  71. data/lib/coolio.rb +2 -0
  72. data/spec/async_watcher_spec.rb +57 -0
  73. data/spec/dns_spec.rb +39 -0
  74. data/spec/spec_helper.rb +12 -0
  75. data/spec/stat_watcher_spec.rb +77 -0
  76. data/spec/timer_watcher_spec.rb +55 -0
  77. data/spec/unix_listener_spec.rb +25 -0
  78. data/spec/unix_server_spec.rb +25 -0
  79. metadata +200 -0
@@ -0,0 +1,123 @@
1
+ // a single header file is required
2
+ #include <ev.h>
3
+ #include <stdio.h>
4
+ #include <io.h>
5
+
6
+ // every watcher type has its own typedef'd struct
7
+ // with the name ev_TYPE
8
+ ev_io stdin_watcher;
9
+ ev_timer timeout_watcher;
10
+
11
+ // all watcher callbacks have a similar signature
12
+ // this callback is called when data is readable on stdin
13
+ static void
14
+ stdin_cb (EV_P_ ev_io *w, int revents)
15
+ {
16
+ puts ("stdin ready or done or something");
17
+ // for one-shot events, one must manually stop the watcher
18
+ // with its corresponding stop function.
19
+ //ev_io_stop (EV_A_ w);
20
+
21
+ // this causes all nested ev_loop's to stop iterating
22
+ //ev_unloop (EV_A_ EVUNLOOP_ALL);
23
+ }
24
+
25
+ // another callback, this time for a time-out
26
+ static void
27
+ timeout_cb (EV_P_ ev_timer *w, int revents)
28
+ {
29
+ puts ("timeout");
30
+ // this causes the innermost ev_loop to stop iterating
31
+ ev_unloop (EV_A_ EVUNLOOP_ONE);
32
+ }
33
+
34
+
35
+
36
+ #include <winsock.h>
37
+
38
+ #include <stdlib.h>
39
+ #include <iostream>
40
+ int get_server_fd()
41
+ {
42
+
43
+ //----------------------
44
+ // Initialize Winsock.
45
+ WSADATA wsaData;
46
+ int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
47
+ if (iResult != NO_ERROR) {
48
+ printf("Error at WSAStartup()\n");
49
+ return 1;
50
+ }
51
+
52
+ //----------------------
53
+ // Create a SOCKET for listening for
54
+ // incoming connection requests.
55
+ SOCKET ListenSocket;
56
+ ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
57
+ if (ListenSocket == INVALID_SOCKET) {
58
+ printf("Error at socket(): %ld\n", WSAGetLastError());
59
+ WSACleanup();
60
+ return 1;
61
+ }
62
+ printf("socket returned %d\n", ListenSocket);
63
+
64
+ //----------------------
65
+ // The sockaddr_in structure specifies the address family,
66
+ // IP address, and port for the socket that is being bound.
67
+ sockaddr_in service;
68
+ service.sin_family = AF_INET;
69
+ service.sin_addr.s_addr = inet_addr("127.0.0.1");
70
+ service.sin_port = htons(4444);
71
+
72
+ if (bind( ListenSocket,
73
+ (SOCKADDR*) &service,
74
+ sizeof(service)) == SOCKET_ERROR) {
75
+ printf("bind() failed.\n");
76
+ closesocket(ListenSocket);
77
+ WSACleanup();
78
+ return 1;
79
+ }
80
+
81
+ //----------------------
82
+ // Listen for incoming connection requests.
83
+ // on the created socket
84
+ if (listen( ListenSocket, 1 ) == SOCKET_ERROR) {
85
+ printf("Error listening on socket.\n");
86
+ closesocket(ListenSocket);
87
+ WSACleanup();
88
+ return 1;
89
+ }
90
+
91
+
92
+ printf("sock and osf handle are %d %d, error is \n", ListenSocket, _get_osfhandle (ListenSocket)); // -1 is invalid file handle: http://msdn.microsoft.com/en-us/library/ks2530z6.aspx
93
+ printf("err was %d\n", WSAGetLastError());
94
+ //----------------------
95
+ return ListenSocket;
96
+ }
97
+
98
+
99
+ int
100
+ main (void)
101
+ {
102
+ struct ev_loop *loopy = ev_default_loop(0);
103
+ int fd = get_server_fd();
104
+ int fd_real = _open_osfhandle(fd, NULL);
105
+ int conv = _get_osfhandle(fd_real);
106
+ printf("got server fd %d, loop %d, fd_real %d, that converted %d\n", fd, loopy, fd_real, conv);
107
+ // accept(fd, NULL, NULL);
108
+ // initialise an io watcher, then start it
109
+ // this one will watch for stdin to become readable
110
+ ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ conv, EV_READ);
111
+ ev_io_start (loopy, &stdin_watcher);
112
+
113
+ // initialise a timer watcher, then start it
114
+ // simple non-repeating 5.5 second timeout
115
+ //ev_timer_init (&timeout_watcher, timeout_cb, 15.5, 0.);
116
+ //ev_timer_start (loopy, &timeout_watcher);
117
+ printf("starting loop\n");
118
+ // now wait for events to arrive
119
+ ev_loop (loopy, 0);
120
+
121
+ // unloop was called, so exit
122
+ return 0;
123
+ }
@@ -0,0 +1,2 @@
1
+ *.so
2
+ *.bundle
@@ -0,0 +1,32 @@
1
+ #--
2
+ # Copyright (C)2011 Tony Arcieri
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ require "cool.io/version"
8
+ require "cool.io/custom_require"
9
+ cool_require "iobuffer_ext"
10
+ cool_require "cool.io_ext"
11
+
12
+ require "cool.io/loop"
13
+ require "cool.io/meta"
14
+ require "cool.io/io"
15
+ require "cool.io/iowatcher"
16
+ require "cool.io/timer_watcher"
17
+ require "cool.io/async_watcher"
18
+ require "cool.io/listener"
19
+ require "cool.io/dns_resolver"
20
+ require "cool.io/socket"
21
+ require "cool.io/server"
22
+ require "cool.io/http_client"
23
+ require "cool.io/dsl"
24
+
25
+ module Coolio
26
+ def self.inspect; "Cool.io"; end
27
+ end
28
+
29
+ module Cool
30
+ # Allow Coolio module to be referenced as Cool.io
31
+ def self.io; Coolio; end
32
+ end
@@ -0,0 +1,43 @@
1
+ #--
2
+ # Copyright (C)2007-10 Tony Arcieri
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ module Coolio
8
+ # The AsyncWatcher lets you signal another thread to wake up. Its
9
+ # intended use is notifying another thread of events.
10
+ class AsyncWatcher < IOWatcher
11
+ def initialize
12
+ @reader, @writer = ::IO.pipe
13
+ super(@reader)
14
+ end
15
+
16
+ # Signal the async watcher. This call is thread safe.
17
+ def signal
18
+ # Write a byte to the pipe. What we write is meaningless, it
19
+ # merely signals an event has occurred for each byte written.
20
+ @writer.write "\0"
21
+ end
22
+
23
+ # Called whenever a signal is received
24
+ def on_signal; end
25
+ event_callback :on_signal
26
+
27
+ #########
28
+ protected
29
+ #########
30
+
31
+ def on_readable
32
+ # Read a byte from the pipe. This clears readability, unless
33
+ # another signal is pending
34
+ begin
35
+ @reader.read_nonblock 1
36
+ rescue Errno::EAGAIN
37
+ # in case there are spurious wakeups from forked processs
38
+ return
39
+ end
40
+ on_signal
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ def cool_require(gem)
2
+ begin
3
+ m = /(\d+.\d+)/.match(RUBY_VERSION)
4
+ ver = m[1]
5
+ require "#{ver}/#{gem}.so"
6
+ rescue LoadError
7
+ require gem
8
+ end
9
+ end
@@ -0,0 +1,225 @@
1
+ #--
2
+ # Copyright (C)2007-10 Tony Arcieri
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #
6
+ # Gimpy hacka asynchronous DNS resolver
7
+ #
8
+ # Word to the wise: I don't know what I'm doing here. This was cobbled together
9
+ # as best I could with extremely limited knowledge of the DNS format. There's
10
+ # obviously a ton of stuff it doesn't support (like IPv6 and TCP).
11
+ #
12
+ # If you do know what you're doing with DNS, feel free to improve this!
13
+ # A good starting point my be this EventMachine Net::DNS-based asynchronous
14
+ # resolver:
15
+ #
16
+ # http://gist.github.com/663299
17
+ #
18
+ #++
19
+
20
+ module Coolio
21
+ # A non-blocking DNS resolver. It provides interfaces for querying both
22
+ # /etc/hosts and nameserves listed in /etc/resolv.conf, or nameservers of
23
+ # your choosing.
24
+ #
25
+ # Presently the client only supports UDP requests against your nameservers
26
+ # and cannot resolve anything with records larger than 512-bytes. Also,
27
+ # IPv6 is not presently supported.
28
+ #
29
+ # DNSResolver objects are one-shot. Once they resolve a domain name they
30
+ # automatically detach themselves from the event loop and cannot be used
31
+ # again.
32
+ class DNSResolver < IOWatcher
33
+ #--
34
+ # TODO check if it's caching right
35
+ if RUBY_PLATFORM =~ /mingw|win32/
36
+ HOSTS = 'c:\WINDOWS\system32\drivers\etc\hosts'
37
+ else
38
+ HOSTS = '/etc/hosts'
39
+ end
40
+ DNS_PORT = 53
41
+ DATAGRAM_SIZE = 512
42
+ TIMEOUT = 3 # Retry timeout for each datagram sent
43
+ RETRIES = 4 # Number of retries to attempt
44
+ # so currently total is 12s before it will err due to timeouts
45
+ # if it errs due to inability to reach the DNS server [Errno::EHOSTUNREACH], same
46
+ # Query /etc/hosts (or the specified hostfile) for the given host
47
+ def self.hosts(host, hostfile = HOSTS)
48
+ hosts = {}
49
+ File.open(hostfile) do |f|
50
+ f.each_line do |host_entry|
51
+ entries = host_entry.gsub(/#.*$/, '').gsub(/\s+/, ' ').split(' ')
52
+ addr = entries.shift
53
+ entries.each { |e| hosts[e] ||= addr }
54
+ end
55
+ end
56
+
57
+ hosts[host]
58
+ end
59
+
60
+ # Create a new Coolio::Watcher descended object to resolve the
61
+ # given hostname. If you so desire you can also specify a
62
+ # list of nameservers to query. By default the resolver will
63
+ # use nameservers listed in /etc/resolv.conf
64
+ def initialize(hostname, *nameservers)
65
+ if nameservers.empty?
66
+ require 'resolv'
67
+ nameservers = Resolv::DNS::Config.default_config_hash[:nameserver]
68
+ raise RuntimeError, "no nameservers found" if nameservers.empty? # TODO just call resolve_failed, not raise [also handle Errno::ENOENT)]
69
+ end
70
+
71
+ @nameservers = nameservers
72
+ @question = request_question hostname
73
+
74
+ @socket = UDPSocket.new
75
+ @timer = Timeout.new(self)
76
+
77
+ super(@socket)
78
+ end
79
+
80
+ # Attach the DNSResolver to the given event loop
81
+ def attach(evloop)
82
+ send_request
83
+ @timer.attach(evloop)
84
+ super
85
+ end
86
+
87
+ # Detach the DNSResolver from the given event loop
88
+ def detach
89
+ @timer.detach if @timer.attached?
90
+ super
91
+ end
92
+
93
+ # Called when the name has successfully resolved to an address
94
+ def on_success(address); end
95
+ event_callback :on_success
96
+
97
+ # Called when we receive a response indicating the name didn't resolve
98
+ def on_failure; end
99
+ event_callback :on_failure
100
+
101
+ # Called if we don't receive a response, defaults to calling on_failure
102
+ def on_timeout
103
+ on_failure
104
+ end
105
+
106
+ #########
107
+ protected
108
+ #########
109
+
110
+ # Send a request to the DNS server
111
+ def send_request
112
+ nameserver = @nameservers.shift
113
+ @nameservers << nameserver # rotate them
114
+ @socket.connect @nameservers.first, DNS_PORT
115
+ begin
116
+ @socket.send request_message, 0
117
+ rescue Errno::EHOSTUNREACH # TODO figure out why it has to be wrapper here, when the other wrapper should be wrapping this one!
118
+ end
119
+ end
120
+
121
+ # Called by the subclass when the DNS response is available
122
+ def on_readable
123
+ datagram = nil
124
+ begin
125
+ datagram = @socket.recvfrom_nonblock(DATAGRAM_SIZE).first
126
+ rescue Errno::ECONNREFUSED
127
+ end
128
+
129
+ address = response_address datagram rescue nil
130
+ address ? on_success(address) : on_failure
131
+ detach
132
+ end
133
+
134
+ def request_question(hostname)
135
+ raise ArgumentError, "hostname cannot be nil" if hostname.nil?
136
+
137
+ # Query name
138
+ message = hostname.split('.').map { |s| [s.size].pack('C') << s }.join + "\0"
139
+
140
+ # Host address query
141
+ qtype = 1
142
+
143
+ # Internet query
144
+ qclass = 1
145
+
146
+ message << [qtype, qclass].pack('nn')
147
+ end
148
+
149
+ def request_message
150
+ # Standard query header
151
+ message = [2, 1, 0].pack('nCC')
152
+
153
+ # One entry
154
+ qdcount = 1
155
+
156
+ # No answer, authority, or additional records
157
+ ancount = nscount = arcount = 0
158
+
159
+ message << [qdcount, ancount, nscount, arcount].pack('nnnn')
160
+ message << @question
161
+ end
162
+
163
+ def response_address(message)
164
+ # Confirm the ID field
165
+ id = message[0..1].unpack('n').first.to_i
166
+ return unless id == 2
167
+
168
+ # Check the QR value and confirm this message is a response
169
+ qr = message[2..2].unpack('B1').first.to_i
170
+ return unless qr == 1
171
+
172
+ # Check the RCODE (lower nibble) and ensure there wasn't an error
173
+ rcode = message[3..3].unpack('B8').first[4..7].to_i(2)
174
+ return unless rcode == 0
175
+
176
+ # Extract the question and answer counts
177
+ qdcount, ancount = message[4..7].unpack('nn').map { |n| n.to_i }
178
+
179
+ # We only asked one question
180
+ return unless qdcount == 1
181
+ message.slice!(0, 12)
182
+
183
+ # Make sure it's the same question
184
+ return unless message[0..(@question.size-1)] == @question
185
+ message.slice!(0, @question.size)
186
+
187
+ # Extract the RDLENGTH
188
+ while not message.empty?
189
+ type = message[2..3].unpack('n').first.to_i
190
+ rdlength = message[10..11].unpack('n').first.to_i
191
+ rdata = message[12..(12 + rdlength - 1)]
192
+ message.slice!(0, 12 + rdlength)
193
+
194
+ # Only IPv4 supported
195
+ next unless rdlength == 4
196
+
197
+ # If we got an Internet address back, return it
198
+ return rdata.unpack('CCCC').join('.') if type == 1
199
+ end
200
+
201
+ nil
202
+ end
203
+
204
+ class Timeout < TimerWatcher
205
+ def initialize(resolver)
206
+ @resolver = resolver
207
+ @attempts = 0
208
+ super(TIMEOUT, true)
209
+ end
210
+
211
+ def on_timer
212
+ @attempts += 1
213
+ if @attempts <= RETRIES
214
+ begin
215
+ return @resolver.__send__(:send_request)
216
+ rescue Errno::EHOSTUNREACH # if the DNS is toast try again after the timeout occurs again
217
+ return nil
218
+ end
219
+ end
220
+ @resolver.__send__(:on_timeout)
221
+ @resolver.detach
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,135 @@
1
+ #--
2
+ # Copyright (C)2010 Tony Arcieri
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ module Coolio
8
+ # A module we stash all the connections defined by the DSL under
9
+ module Connections; end
10
+
11
+ # A DSL for defining Cool.io connection types and servers
12
+ module DSL
13
+ # Define all methods on the metaclass
14
+ module_function
15
+
16
+ # Run the default Cool.io event loop
17
+ def run
18
+ Cool.io::Loop.default.run
19
+ end
20
+
21
+ # Connect to the given host and port using the given connection class
22
+ def connect(host, port, connection_name = nil, *initializer_args, &block)
23
+ if block_given?
24
+ initializer_args.unshift connection_name if connection_name
25
+
26
+ klass = Class.new Cool.io::TCPSocket
27
+ connection_builder = ConnectionBuilder.new klass
28
+ connection_builder.instance_eval(&block)
29
+ else
30
+ raise ArgumentError, "no connection name or block given" unless connection_name
31
+ klass = self[connection_name]
32
+ end
33
+
34
+ client = klass.connect host, port, *initializer_args
35
+ client.attach Cool.io::Loop.default
36
+ client
37
+ end
38
+
39
+ # Create a new Cool.io::TCPServer
40
+ def server(host, port, connection_name = nil, *initializer_args, &block)
41
+ if block_given?
42
+ initializer_args.unshift connection_name if connection_name
43
+
44
+ klass = Class.new Cool.io::TCPSocket
45
+ connection_builder = ConnectionBuilder.new klass
46
+ connection_builder.instance_eval(&block)
47
+ else
48
+ raise ArgumentError, "no connection name or block given" unless connection_name
49
+ klass = self[connection_name]
50
+ end
51
+
52
+ server = Cool.io::TCPServer.new host, port, klass, *initializer_args
53
+ server.attach Cool.io::Loop.default
54
+ server
55
+ end
56
+
57
+ # Create a new Cool.io::TCPSocket class
58
+ def connection(name, &block)
59
+ # Camelize class name
60
+ class_name = name.to_s.split('_').map { |s| s.capitalize }.join
61
+
62
+ connection = Class.new Cool.io::TCPSocket
63
+ connection_builder = ConnectionBuilder.new connection
64
+ connection_builder.instance_eval(&block)
65
+
66
+ Coolio::Connections.const_set class_name, connection
67
+ end
68
+
69
+ # Look up a connection class by its name
70
+ def [](connection_name)
71
+ class_name = connection_name.to_s.split('_').map { |s| s.capitalize }.join
72
+
73
+ begin
74
+ Coolio::Connections.const_get class_name
75
+ rescue NameError
76
+ raise NameError, "No connection type registered for #{connection_name.inspect}"
77
+ end
78
+ end
79
+
80
+ # Builder for Cool.io::TCPSocket classes
81
+ class ConnectionBuilder
82
+ def initialize(klass)
83
+ @klass = klass
84
+ end
85
+
86
+ # Declare an initialize function
87
+ def initializer(&action)
88
+ @klass.send :define_method, :initialize, &action
89
+ end
90
+
91
+ # Declare the on_connect callback
92
+ def on_connect(&action)
93
+ @klass.send :define_method, :on_connect, &action
94
+ end
95
+
96
+ # Declare a callback fired if we failed to connect
97
+ def on_connect_failed(&action)
98
+ @klass.send :define_method, :on_connect_failed, &action
99
+ end
100
+
101
+ # Declare a callback fired if DNS resolution failed
102
+ def on_resolve_failed(&action)
103
+ @klass.send :define_method, :on_resolve_failed, &action
104
+ end
105
+
106
+ # Declare the on_close callback
107
+ def on_close(&action)
108
+ @klass.send :define_method, :on_close, &action
109
+ end
110
+
111
+ # Declare the on_read callback
112
+ def on_read(&action)
113
+ @klass.send :define_method, :on_read, &action
114
+ end
115
+
116
+ # Declare the on_write_complete callback
117
+ def on_write_complete(&action)
118
+ @klass.send :define_method, :on_write_complete, &action
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ # The Cool module containing all our coolness
125
+ module Cool
126
+ module Coolness
127
+ def cool; Cool::IOThunk; end
128
+ end
129
+
130
+ module IOThunk
131
+ def self.io; Coolio::DSL; end
132
+ end
133
+ end
134
+
135
+ extend Cool::Coolness