cool.io 1.2.0-x86-mingw32

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.
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