shin-irc_cat 0.4.0

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.
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # `cat` for irccat :)
4
+ #
5
+ # Created on 2008-2-17.
6
+ # Copyright (c) 2008. All rights reserved.
7
+
8
+ require 'optparse'
9
+ require 'yaml'
10
+
11
+ OPTIONS = {
12
+ :profile => 'default'
13
+ }
14
+ MANDATORY_OPTIONS = %w( )
15
+
16
+ parser = OptionParser.new do |opts|
17
+ opts.banner = <<BANNER
18
+ icat - just like cat but for irccat
19
+
20
+ Usage: #{File.basename($0)} [options] <files or STDIN>
21
+
22
+ Options are:
23
+ BANNER
24
+ opts.separator ""
25
+ opts.on("-p", "--profile=PROFILE", String,
26
+ "Profile to use (in ~/.irccat.yml).",
27
+ "Default: 'default'") { |OPTIONS[:profile]| }
28
+ opts.on("-h", "--help",
29
+ "Show this help message.") { puts opts; exit }
30
+ opts.parse!(ARGV)
31
+
32
+ if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
33
+ puts opts; exit
34
+ end
35
+ end
36
+
37
+ raise "Can't read configuration file (~/.irccat.yml)" unless File.exists?(File.expand_path("~/.irccat.yml"))
38
+ @config = YAML.load_file(File.expand_path("~/.irccat.yml"))
39
+
40
+ @profile = @config["#{OPTIONS[:profile]}"]
41
+ raise "Can't find profile #{OPTIONS[:profile]} in ~/.irccat.yml" if @profile.nil?
42
+
43
+ require File.dirname(__FILE__) + '/../lib/irc_cat/tcp_client'
44
+
45
+ if ARGV.empty?
46
+ puts "has stdin"
47
+ message = STDIN.read
48
+ IrcCat::TcpClient.notify(@profile['host'], @profile['port'], message)
49
+ else
50
+ ARGV.each do |file|
51
+ raise "Can't read #{file}" unless File.exists?(File.expand_path(file))
52
+ IrcCat::TcpClient.notify(@profile['host'], @profile['port'], File.read(File.expand_path(file)))
53
+ end
54
+ end
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # `echo` for irccat :)
4
+ #
5
+ # Created on 2008-2-17.
6
+ # Copyright (c) 2008. All rights reserved.
7
+
8
+ require 'optparse'
9
+ require 'yaml'
10
+
11
+ OPTIONS = {
12
+ :profile => 'default'
13
+ }
14
+ MANDATORY_OPTIONS = %w( )
15
+
16
+ parser = OptionParser.new do |opts|
17
+ opts.banner = <<BANNER
18
+ iecho - just like echo but for irccat
19
+
20
+ Usage: #{File.basename($0)} [options] text
21
+
22
+ Options are:
23
+ BANNER
24
+ opts.separator ""
25
+ opts.on("-p", "--profile=PROFILE", String,
26
+ "Profile to use (in ~/.irccat.yml).",
27
+ "Default: 'default'") { |OPTIONS[:profile]| }
28
+ opts.on("-h", "--help",
29
+ "Show this help message.") { puts opts; exit }
30
+ opts.parse!(ARGV)
31
+
32
+ if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
33
+ puts opts; exit
34
+ end
35
+ end
36
+
37
+ raise "Can't read configuration file (~/.irccat.yml)" unless File.exists?(File.expand_path("~/.irccat.yml"))
38
+ @config = YAML.load_file(File.expand_path("~/.irccat.yml"))
39
+
40
+ @profile = @config["#{OPTIONS[:profile]}"]
41
+ raise "Can't find profile #{OPTIONS[:profile]} in ~/.irccat.yml" if @profile.nil?
42
+
43
+ raise "Nothing to send" if ARGV.empty?
44
+
45
+ require File.dirname(__FILE__) + '/../lib/irc_cat/tcp_client'
46
+
47
+ message = ARGV.join(' ')
48
+
49
+ IrcCat::TcpClient.notify(@profile['host'], @profile['port'], message)
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created on 2008-2-17.
4
+ # Copyright (c) 2008. All rights reserved.
5
+
6
+ require 'optparse'
7
+ require 'yaml'
8
+
9
+ options = {
10
+ :config_filename => './config.yml'
11
+ }
12
+
13
+ parser = OptionParser.new do |opts|
14
+ opts.banner = <<BANNER
15
+ irc_cat - stdout for irc
16
+
17
+ Usage: #{File.basename($0)} [options]
18
+
19
+ Options are:
20
+ BANNER
21
+
22
+ opts.separator ""
23
+
24
+ opts.on("-c", "--config=CONFIG", String, "Path to the config file.", "Default: ./config.yml") do |filename|
25
+ options[:config_filename] = filename
26
+ end
27
+
28
+ opts.on("-h", "--help", "Show this help message.") {
29
+ puts opts
30
+ exit
31
+ }
32
+ end
33
+
34
+ parser.parse!(ARGV)
35
+
36
+ raise "Can't read configuration file (#{options[:config_filename]})" unless File.exists?(options[:config_filename])
37
+ config = YAML.load_file(options[:config_filename])
38
+
39
+ lib_dir = File.dirname(__FILE__) + '/../lib/'
40
+ require lib_dir + '/irc_cat'
41
+
42
+ threads = []
43
+ puts "irccat #{IrcCat::VERSION::STRING} (http://irccat.rubyforge.org/)"
44
+
45
+ Thread.abort_on_exception = true
46
+
47
+ irc_config = config['irc']
48
+ IrcCat::Bot.run(irc_config['host'], irc_config['port'], irc_config['nick'], irc_config['pass']) do |bot|
49
+ puts "Connecting..."
50
+ irc_config['channels'].each do |channel|
51
+ name, pass = channel.split(" ")
52
+ bot.join_channel(name, pass)
53
+ end
54
+
55
+ tcp_config = config['tcp']
56
+ if tcp_config['enabled']
57
+ require lib_dir + 'irc_cat/tcp_server'
58
+ IrcCat::TcpServer.run(bot, tcp_config)
59
+ end
60
+
61
+ http_config = config['http']
62
+ if http_config['enabled']
63
+ require lib_dir + 'irc_cat/http_server'
64
+ IrcCat::HttpServer.run(bot, http_config)
65
+ end
66
+ end
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Configuration generator for irccat
4
+ #
5
+ # Created on 2008-2-17.
6
+ # Copyright (c) 2008. All rights reserved.
7
+
8
+ require 'rubygems'
9
+ require 'optparse'
10
+ require 'yaml'
11
+ require 'highline/import'
12
+
13
+ def irccat_config
14
+ config = Hash.new
15
+ puts "== IRC Configuration"
16
+ config['irc'] = Hash.new
17
+ config['irc']['host'] = ask("Hostname: ") { |q| q.default = "irc.freenode.net" }
18
+ config['irc']['port'] = ask("Port: ") { |q| q.default = "6667" }
19
+ config['irc']['nick'] = ask("Nickname: ") { |q| q.default = "irc_cat" }
20
+ config['irc']['channel'] = ask("Channel: ") { |q| q.default = "#irc_cat" }
21
+ puts "\n== HTTP Configuration"
22
+ config['http'] = Hash.new
23
+ if agree("Enable HTTP? ", true); config['http']['enabled'] = true; else; config['http']['enabled'] = false; end
24
+ if config['http']['enabled']
25
+ config['http']['host'] = ask("Hostname: ") { |q| q.default = "0.0.0.0" }
26
+ config['http']['port'] = ask("Port: ") { |q| q.default = "3489" }
27
+ if agree("Enable Basic HTTP send? ", true); config['http']['send'] = true; else; config['http']['send'] = false; end
28
+ if agree("Enable Github? ", true); config['http']['github'] = true; else; config['http']['github'] = false; end
29
+ end
30
+ puts "\n== TCP Configuration"
31
+ config['tcp'] = Hash.new
32
+ if agree("Enable TCP? ", true); config['tcp']['enabled'] = true; else; config['tcp']['enabled'] = false; end
33
+ if config['tcp']['enabled']
34
+ config['tcp']['host'] = ask("Hostname: ") { |q| q.default = "0.0.0.0" }
35
+ config['tcp']['port'] = ask("Port: ") { |q| q.default = "5678" }
36
+ config['tcp']['size'] = ask("Socket size: ") { |q| q.default = "400" }
37
+ end
38
+ puts "\n== Final steps"
39
+ file = ask("Configuration filename: ") { |q| q.default = "config.yml" }
40
+ puts "Warning: File exists! If you choose yes now the file will be overwritten" if File.exists?(File.expand_path(file))
41
+ if agree("Write configuration file? ", true)
42
+ File.open(File.expand_path(file), "w") { |yfile| YAML.dump(config, yfile) }
43
+ else
44
+ exit
45
+ end
46
+ end
47
+
48
+ def help
49
+ puts "irccat-config - configuration generator for irccat\n"
50
+ puts "Usage: #{File.basename($0)} irccat|client > configfile.yml"
51
+ exit 0
52
+ end
53
+
54
+ if ARGV[0].nil?
55
+ help
56
+ else
57
+ case ARGV[0]
58
+ when 'irccat'
59
+ irccat_config
60
+ when 'client'
61
+ #client_config
62
+ # TODO: todo :D
63
+ help
64
+ else
65
+ help
66
+ end
67
+ end
@@ -0,0 +1,9 @@
1
+ require 'socket'
2
+ require 'rack'
3
+ require 'json'
4
+
5
+ current_dir = File.dirname(__FILE__)
6
+
7
+ require current_dir + '/irc_cat/version'
8
+ require current_dir + '/irc_cat/indifferent_access'
9
+ require current_dir + '/irc_cat/bot'
@@ -0,0 +1,148 @@
1
+ # This is the bot.
2
+ # Original code by Madx (yapok.org)
3
+
4
+ module IrcCat
5
+ class Bot
6
+ def self.run(host, port, nick, nick_pass = nil, &block)
7
+ new(host, port, nick, nick_pass, &block).run
8
+ end
9
+
10
+ def initialize(host, port, nick, nick_pass, &block)
11
+ @host, @port, @nick, @nick_pass = host, port, nick, nick_pass
12
+ @online, @connect_block = false, block
13
+
14
+ @realname = "irccat v#{VERSION::STRING}"
15
+ @refresh_rate = 10
16
+ @channels = {}
17
+
18
+ puts "Connecting to IRC #{@host}:#{@port}"
19
+ end
20
+
21
+ def online?
22
+ @online
23
+ end
24
+
25
+ def run(&block)
26
+ @socket = TCPSocket.open(@host, @port)
27
+ login
28
+
29
+ trap(:INT) {
30
+ puts "Bye bye."
31
+ sexit('God^WConsole killed me')
32
+ sleep 1
33
+ @socket.close
34
+ exit
35
+ }
36
+
37
+ threads = []
38
+
39
+ threads << Thread.new {
40
+ begin
41
+ while line = @socket.gets do
42
+ # Remove all formatting
43
+ line.gsub!(/[\x02\x1f\x16\x0f]/,'')
44
+ # Remove CTCP ASCII
45
+ line.gsub!(/\001/,'')
46
+ # Send to event handler
47
+ handle line
48
+ # Handle Pings from Server
49
+ sendln "PONG #{$1}" if /^PING\s(.*)/ =~ line
50
+ end
51
+ rescue EOFError
52
+ err 'Server Reset Connection'
53
+ rescue Exception => e
54
+ err e
55
+ end
56
+ }
57
+ threads << Thread.new {
58
+ }
59
+ threads.each { |th| th.join }
60
+ end
61
+
62
+ # Announces states
63
+ def announce(msg)
64
+ @channels.each do |channel,key|
65
+ say(channel, msg)
66
+ end
67
+ end
68
+
69
+ # Say something funkeh
70
+ def say(chan, msg)
71
+ sendln "PRIVMSG #{chan} :#{msg}"
72
+ end
73
+
74
+ def topic(chan, msg)
75
+ sendln "TOPIC #{chan} :#{msg}"
76
+ end
77
+
78
+ # Send EXIT
79
+ def sexit(message = 'quit')
80
+ sendln "QUIT :#{message}"
81
+ end
82
+
83
+ # Sends a message to the server
84
+
85
+ def sendln(cmd)
86
+ puts "Send: #{cmd}"
87
+ if cmd.size <= 510
88
+ @socket.write("#{cmd}\r\n")
89
+ STDOUT.flush
90
+ else
91
+ end
92
+ end
93
+
94
+ # Handle a received message
95
+
96
+ def handle(line)
97
+ puts "Got: #{line}"
98
+ case line
99
+ when /^:.+\s376/
100
+ puts "We're online"
101
+ @online = true
102
+ @connect_block.call(self) if @connect_block
103
+ when /^:.+KICK (#[^\s]+)/
104
+ auto_rejoin($1)
105
+ end
106
+ end
107
+
108
+ # Automatic events
109
+
110
+ def join_channel(channel, key)
111
+ @channels[channel] = key
112
+ $stdout.flush
113
+ if key
114
+ sendln "JOIN #{channel} #{key}"
115
+ else
116
+ sendln "JOIN #{channel}"
117
+ end
118
+ end
119
+
120
+ def auto_rejoin(channel)
121
+ join_channel(channel, @channels[channel])
122
+ end
123
+
124
+ def login
125
+ begin
126
+ sendln "NICK #{@nick}"
127
+ sendln "USER irc_cat . . :#{@realname}"
128
+ if @nick_pass
129
+ puts "logging in to NickServ"
130
+ sendln "PRIVMSG NICKSERV :identify #{@nick_pass}"
131
+ end
132
+ rescue Exception => e
133
+ err e
134
+ end
135
+ end
136
+
137
+ # Logging methods
138
+
139
+ def log(str)
140
+ $stdout.puts "DEBUG: #{str}"
141
+ end
142
+
143
+ def err(exception)
144
+ $stderr.puts "ERROR: #{exception}"
145
+ $stderr.puts "TRACE: #{exception.backtrace}"
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,125 @@
1
+ module IrcCat
2
+ class HttpServer
3
+ class Unauthorized < StandardError; end
4
+ class BadRequest < StandardError; end
5
+
6
+ def self.run(bot, config)
7
+ new(bot, config).run
8
+ end
9
+
10
+ def initialize(bot, config)
11
+ @bot, @config = bot, config
12
+ end
13
+
14
+ def run
15
+ host, port = @config['host'], @config['port']
16
+ if handler = Rack::Handler.get(handler_name)
17
+ Thread.new {
18
+ handler.run(app, :Host => host, :Port => port)
19
+ }
20
+ else
21
+ raise "Could not find a valid Rack handler for #{handler_name.inspect}"
22
+ end
23
+ end
24
+
25
+ def handler_name
26
+ @config["server"] || "mongrel"
27
+ end
28
+
29
+ def app
30
+ endpoint = self
31
+ builder = Rack::Builder.new
32
+ if requires_auth?
33
+ builder.use Rack::Auth::Basic, "irccat" do |user,pass|
34
+ auth_user == user && auth_pass == pass
35
+ end
36
+ end
37
+ if prefix = @config["prefix"]
38
+ builder.map prefix do
39
+ run endpoint
40
+ end
41
+ else
42
+ builder.run endpoint
43
+ end
44
+ builder
45
+ end
46
+
47
+ def auth_user
48
+ @config["user"]
49
+ end
50
+
51
+ def auth_pass
52
+ @config["pass"]
53
+ end
54
+
55
+ def requires_auth?
56
+ if auth_user
57
+ if auth_pass
58
+ if auth_pass.empty?
59
+ raise "Empty HTTP password in config"
60
+ end
61
+ true
62
+ end
63
+ end
64
+ end
65
+
66
+ def call(env)
67
+ request = Rack::Request.new(env)
68
+
69
+ case request.path_info
70
+ when "/"
71
+ [200, {"Content-Type" => "text/plain"}, "OK"]
72
+ when "/topic"
73
+ message = request.params["message"]
74
+ if channel = request.params["channel"]
75
+ @bot.join_channel("##{channel}", request.params["key"])
76
+ @bot.topic("##{channel}", message)
77
+ else
78
+ raise "Need a channel"
79
+ end
80
+
81
+ [200, {"Content-Type" => "text/plain"}, "OK"]
82
+ when "/send"
83
+ raise "Send support is not enabled" unless @config["send"]
84
+
85
+ message = request.params["message"]
86
+ if channel = request.params["channel"]
87
+ @bot.join_channel("##{channel}", request.params["key"])
88
+ @bot.say("##{channel}", message)
89
+ elsif nick = request.params["nick"]
90
+ @bot.say(nick, message)
91
+ else
92
+ raise "Unknown action"
93
+ end
94
+
95
+ [200, {"Content-Type" => "text/plain"}, "OK"]
96
+ when "/github"
97
+ raise "GitHub support is not enabled" unless @config["github"]
98
+
99
+ data = JSON.parse(request.POST['payload'])
100
+
101
+ repository = data['repository']['name']
102
+ data['commits'].each do |commit|
103
+ topic = commit['message'].split("\n").first
104
+ ref = commit['id'][0,7]
105
+ author = commit['author']['name']
106
+ @bot.announce "#{topic} - #{ref} - #{author}"
107
+ end
108
+
109
+ [200, {"Content-Type" => "text/plain"}, "OK"]
110
+ else
111
+ [404, {}, ""]
112
+ end
113
+ rescue Unauthorized
114
+ [401, {'WWW-Authenticate' => %(Basic realm="IrcCat")}, 'Authorization Required']
115
+ rescue BadRequest
116
+ [400, {}, 'Bad Request']
117
+ rescue Exception => e
118
+ puts "Got an error: #{e.class}, #{e.message}"
119
+ e.backtrace.each do |b|
120
+ puts "- #{b}"
121
+ end
122
+ [503, {}, "ERR"]
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,75 @@
1
+ # this class has dubious semantics and we only have it so that
2
+ # people can write params[:key] instead of params['key']
3
+
4
+ class HashWithIndifferentAccess < Hash
5
+ def initialize(constructor = {})
6
+ if constructor.is_a?(Hash)
7
+ super()
8
+ update(constructor)
9
+ else
10
+ super(constructor)
11
+ end
12
+ end
13
+
14
+ def default(key = nil)
15
+ if key.is_a?(Symbol) && include?(key = key.to_s)
16
+ self[key]
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
23
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
24
+
25
+ def []=(key, value)
26
+ regular_writer(convert_key(key), convert_value(value))
27
+ end
28
+
29
+ def update(other_hash)
30
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
31
+ self
32
+ end
33
+
34
+ alias_method :merge!, :update
35
+
36
+ def key?(key)
37
+ super(convert_key(key))
38
+ end
39
+
40
+ alias_method :include?, :key?
41
+ alias_method :has_key?, :key?
42
+ alias_method :member?, :key?
43
+
44
+ def fetch(key, *extras)
45
+ super(convert_key(key), *extras)
46
+ end
47
+
48
+ def values_at(*indices)
49
+ indices.collect {|key| self[convert_key(key)]}
50
+ end
51
+
52
+ def dup
53
+ HashWithIndifferentAccess.new(self)
54
+ end
55
+
56
+ def merge(hash)
57
+ self.dup.update(hash)
58
+ end
59
+
60
+ def delete(key)
61
+ super(convert_key(key))
62
+ end
63
+
64
+ def stringify_keys!; self end
65
+ def symbolize_keys!; self end
66
+
67
+ protected
68
+ def convert_key(key)
69
+ key.kind_of?(Symbol) ? key.to_s : key
70
+ end
71
+ def convert_value(value)
72
+ value.is_a?(Hash) ? value.with_indifferent_access : value
73
+ end
74
+ end
75
+
@@ -0,0 +1,9 @@
1
+ require 'socket'
2
+ module IrcCat
3
+ class TcpClient
4
+ def self.notify(ip,port,message)
5
+ socket = TCPSocket.open(ip,port)
6
+ socket.write(message + "\r\n")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,38 @@
1
+ # The TCP Server
2
+ module IrcCat
3
+ class TcpServer
4
+ def self.run(bot, config)
5
+ new(bot, config).run
6
+ end
7
+
8
+ def initialize(bot, config)
9
+ @bot, @config = bot, config
10
+ end
11
+
12
+ def run
13
+ Thread.new do
14
+ socket = TCPserver.new(ip, port)
15
+ puts "Starting TCP (#{ip}:#{port})"
16
+
17
+ loop do
18
+ Thread.start(socket.accept) do |s|
19
+ str = s.recv(@config['size'])
20
+ sstr = str.split(/\n/)
21
+ sstr.each do |l|
22
+ @bot.announce("#{l}")
23
+ end
24
+ s.close
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ def ip
31
+ @config["ip"] || '127.0.0.1'
32
+ end
33
+
34
+ def port
35
+ @config["port"] || '8080'
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ module IrcCat #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 4
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shin-irc_cat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Jordan Bracco
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-21 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rack
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: json
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: rake
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: irccat is like `cat`, but here, the STDOUT is an IRC channel.
46
+ email: jordan@bracco.name
47
+ executables:
48
+ - icat
49
+ - iecho
50
+ - irccat
51
+ - irccat-config
52
+ extensions: []
53
+
54
+ extra_rdoc_files: []
55
+
56
+ files:
57
+ - lib/irc_cat/bot.rb
58
+ - lib/irc_cat/http_server.rb
59
+ - lib/irc_cat/indifferent_access.rb
60
+ - lib/irc_cat/tcp_client.rb
61
+ - lib/irc_cat/tcp_server.rb
62
+ - lib/irc_cat/version.rb
63
+ - lib/irc_cat.rb
64
+ has_rdoc: true
65
+ homepage: http://github.com/webs/irccat
66
+ licenses: []
67
+
68
+ post_install_message:
69
+ rdoc_options: []
70
+
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ requirements: []
86
+
87
+ rubyforge_project:
88
+ rubygems_version: 1.3.5
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: irccat is like `cat`, but here, the STDOUT is an IRC channel.
92
+ test_files: []
93
+