kirby 4.1 → 4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +2 -0
  3. data/README +13 -7
  4. data/bin/kirby +35 -10
  5. data/kirby.gemspec +4 -4
  6. data/lib/kirby.rb +102 -49
  7. metadata +3 -3
  8. metadata.gz.sig +2 -2
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG CHANGED
@@ -1,4 +1,6 @@
1
1
 
2
+ v4.2. Add channel logging; clean up options handling; fix docs.
3
+
2
4
  v4.1. Watch atom feeds for git commits [lifo].
3
5
 
4
6
  v4. Documentation; Rakefile. Shouldn't have bumped the version, but all right.
data/README CHANGED
@@ -25,20 +25,26 @@ Kirby is considered feature-locked.
25
25
 
26
26
  == Usage
27
27
 
28
- Run <tt>kirby [nick] [channel] [server] [delicious_name] [delicious_password]</tt>.
28
+ To start a basic Kirby from the command line:
29
29
 
30
- Optional parameters:
31
- <tt>-d</tt>:: Daemonize.
32
- <tt>-no-d</tt>:: Don't daemonize.
30
+ cd working/directory
31
+ kirby mynick mychannel myserver.org [delicious_user] [delicious_pass] [--options]
32
+
33
+ Logs and repository histories get saved to the working directory.
34
+
35
+ Available post-parameters are:
36
+ <tt>--daemon</tt>:: Daemonize.
33
37
  <tt>--silent</tt>:: Never speak, even for errors.
38
+ <tt>--log</tt>:: Log channel chatter to a file.
39
+ <tt>--debug</tt>:: Debug mode.
34
40
 
35
- See the Kirby class for commands.
41
+ See the Kirby class for in-channel commands.
36
42
 
37
43
  == Crontab example
38
44
 
39
- You can use a crontask to keep Kirby alive at all times.
45
+ You can use a crontask to keep Kirby alive at all times. For example:
40
46
 
41
- * * * * * bash -c 'cd /working/directory; kirby -d [options...] &> /dev/null'
47
+ * * * * * bash -c 'cd /working/directory; kirby mynick mychannel irc.freenode.net --daemon --log --silent &> /dev/null'
42
48
 
43
49
  == Reporting problems
44
50
 
data/bin/kirby CHANGED
@@ -1,22 +1,47 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  =begin rdoc
4
- Run <tt>kirby [nick] [channel] [server] [delicious_name] [delicious_password]</tt>.
4
+ Run <tt>kirby [nick] [channel] [server] [optional del.icio.us name] [optional del.icio.us password]</tt>.
5
5
 
6
- Optional parameters:
7
- <tt>-d</tt>:: Daemonize.
8
- <tt>-no-d</tt>:: Don't daemonize.
6
+ Optional post-parameters:
7
+ <tt>--daemon</tt>:: Daemonize.
9
8
  <tt>--silent</tt>:: Never speak, even for errors.
9
+ <tt>--log</tt>:: Log channel chatter to a file.
10
+ <tt>--debug</tt>:: Debug mode.
10
11
  =end
11
12
 
12
13
  require 'rubygems'
13
- require 'kirby'
14
+ require 'daemons'
14
15
 
15
- pid = open(Kirby::PIDFILE).gets.chomp rescue nil
16
+ begin
17
+ require "#{File.dirname(__FILE__)}/../lib/kirby"
18
+ rescue LoadError
19
+ require 'kirby'
20
+ end
21
+
22
+ def option?(opt)
23
+ ARGV.include?("--#{opt}")
24
+ end
25
+
26
+ def param(arg)
27
+ arg unless arg =~ /^--/
28
+ end
29
+
30
+ @kirby = Kirby.new(
31
+ :nick => param(ARGV[0]),
32
+ :channel => param(ARGV[1]),
33
+ :server => param(ARGV[2]),
34
+ :delicious_user => param(ARGV[3]),
35
+ :delicious_pass => param(ARGV[4]),
36
+ :silent => option?("silent"),
37
+ :debug => option?("debug"),
38
+ :log => option?("log")
39
+ )
40
+
41
+ pid = open(@kirby.config[:pidfile]).gets.chomp rescue nil
16
42
 
17
43
  if !pid or `ps #{pid}`.split("\n").size < 2
18
- puts "Starting"
19
- Daemons.daemonize if ARGV[0] == '-d' #:ontop => true
20
- open(Kirby::PIDFILE, 'w') {|f| f.puts $$}
21
- Kirby.instance.restart
44
+ Daemons.daemonize if option? "daemon"
45
+ open(@kirby.config[:pidfile], 'w') {|f| f.puts $$}
46
+ @kirby.restart
22
47
  end
@@ -1,16 +1,16 @@
1
1
 
2
- # Gem::Specification for Kirby-4.1
2
+ # Gem::Specification for Kirby-4.2
3
3
  # Originally generated by Echoe
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = %q{kirby}
7
- s.version = "4.1"
7
+ s.version = "4.2"
8
8
 
9
9
  s.specification_version = 2 if s.respond_to? :specification_version=
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.authors = [""]
13
- s.date = %q{2007-12-15}
13
+ s.date = %q{2007-12-31}
14
14
  s.default_executable = %q{kirby}
15
15
  s.description = %q{A super-clean IRC bot with sandboxed Ruby evaluation, svn watching, and link-logging to del.icio.us.}
16
16
  s.email = %q{}
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
20
20
  s.homepage = %q{http://blog.evanweaver.com/files/doc/fauna/kirby/}
21
21
  s.require_paths = ["lib"]
22
22
  s.rubyforge_project = %q{fauna}
23
- s.rubygems_version = %q{0.9.5}
23
+ s.rubygems_version = %q{1.0.1}
24
24
  s.summary = %q{A super-clean IRC bot with sandboxed Ruby evaluation, svn watching, and link-logging to del.icio.us.}
25
25
 
26
26
  s.add_dependency(%q<hpricot>, [">= 0"])
@@ -2,60 +2,94 @@
2
2
 
3
3
  =begin rdoc
4
4
  In-channel commands:
5
- <tt>>> [string of code]</tt>:: Evaluate some Ruby code.
6
- <tt>reset_irb</tt>:: Reset the <tt>irb</tt> session.
7
- <tt>add_svn [repository_url]</tt>:: Watch an svn repository for changes.
5
+ <tt>>> CODE</tt>:: evaluate code in IRB.
6
+ <tt>reset_irb</tt>:: get a clean IRB session.
7
+ <tt>add_svn [repository_url]</tt>:: watch an SVN repository.
8
+ <tt>add_atom [atom_feed_url]</tt>:: watch an atom feed, such as a Git repository
9
+
10
+ To remove a repository, manually kill the bot and delete the line from <tt>nick.svns</tt> or <tt>nick.atoms</tt> in the bot's working directory. Then restart the bot.
8
11
  =end
9
12
 
10
13
  class Kirby
11
- include Singleton
12
14
 
13
- PATH = Pathname.new(".").dirname.realpath.to_s
14
- STORE = PATH + '/kirby.repositories'
15
- ATOM = PATH + '/kirby.atoms'
16
- PIDFILE = PATH + '/kirby.pid'
15
+ attr_reader :config
17
16
 
18
- NICK = (ARGV[1] or "kirby-dev")
19
- CHANNEL = ("#" + (ARGV[2] or "kirby-dev"))
20
- SERVER = (ARGV[3] or "irc.freenode.org")
21
- DELICIOUS_USER, DELICIOUS_PASS = ARGV[4], ARGV[5]
22
- SILENT = ARGV[6] == "--silent"
17
+ # Make a new Kirby. Will not connect to the server until you call connect().
18
+ def initialize(opts = {})
19
+
20
+ # Defaults
21
+ path = File.expand_path(".").to_s
22
+ nick = opts[:nick] || config[:nick] || "kirby-dev"
23
+
24
+ @config ||= {
25
+ :svns => "#{path}/#{nick}.svns",
26
+ :atoms => "#{path}/#{nick}.atoms",
27
+ :pidfile => "#{path}/#{nick}.pid",
28
+ :nick => nick,
29
+ :channel => 'kirby-dev',
30
+ :server => "irc.freenode.org",
31
+ :delicious_user => nil,
32
+ :delicious_pass => nil,
33
+ :silent => false,
34
+ :log => false,
35
+ :logfile => "#{path}/#{nick}.log",
36
+ :time_format => '%Y/%m/%d %H:%M:%S',
37
+ :debug => false
38
+ }
39
+
40
+ # Nicely merge current options
41
+ opts.each do |key, value|
42
+ config[key] = value if value
43
+ end
44
+ end
23
45
 
24
46
  # Connect and reconnect to the server
25
- def restart
26
- $store = (YAML.load_file STORE rescue {})
27
- $atom = (YAML.load_file ATOM rescue {})
47
+ def restart
48
+ log "Restarting"
49
+ puts config.inspect if config[:debug]
50
+
51
+ @svns = (YAML.load_file config[:svns] rescue {})
52
+ @atoms = (YAML.load_file config[:atoms] rescue {})
53
+
28
54
  @socket.close if @socket
29
55
  connect
30
56
  listen
31
57
  end
32
58
 
33
59
  # Connect to the IRC server.
34
- def connect
35
- @socket = TCPSocket.new(SERVER, 6667)
36
- write "USER #{[NICK]*3*" "} :#{NICK}"
37
- write "NICK #{NICK}"
38
- write "JOIN #{CHANNEL}"
60
+ def connect
61
+ log "Connecting"
62
+ @socket = TCPSocket.new(config[:server], 6667)
63
+ write "USER #{config[:nick]} #{config[:nick]} #{config[:nick]} :#{config[:nick]}"
64
+ write "NICK #{config[:nick]}"
65
+ write "JOIN ##{config[:channel]}"
39
66
  end
40
67
 
41
68
  # The event loop. Waits for socket traffic, and then responds to it. The server sends <tt>PING</tt> every 3 minutes, which means we don't need a separate thread to check for svn updates. All we do is wake on ping (or channel talking).
42
69
  def listen
43
70
  @socket.each do |line|
44
- # puts "GOT: #{line.inspect}"
45
- poll unless SILENT
71
+ puts "GOT: #{line.inspect}" if config[:debug]
72
+ poll if !config[:silent]
46
73
  case line.strip
47
- when /^PING/ then write line.sub("PING", "PONG")[0..-3]
48
- when /^ERROR/, /KICK #{CHANNEL} #{NICK} / then restart unless line =~ /PRIVMSG/
49
- else
50
- if msg = line[/ PRIVMSG #{CHANNEL} \:(.+)/, 1]
74
+ when /^PING/
75
+ write line.sub("PING", "PONG")[0..-3]
76
+ when /^ERROR/, /KICK ##{config[:channel]} #{config[:nick]} /
77
+ restart unless line =~ /PRIVMSG/
78
+ when /:(.+?)!.* PRIVMSG ##{config[:channel]} \:\001ACTION (.+)\001/
79
+ log "* #{$1} #{$2}"
80
+ when /:(.+?)!.* PRIVMSG ##{config[:channel]} \:(.+)/
81
+ nick, msg = $1, $2
82
+ log "<#{nick}> #{msg}"
83
+ if !config[:silent]
51
84
  case msg
52
85
  when /^>>\s*(.+)/ then try $1.chop
53
- when /^#{NICK}/ then say "Usage: '>> CODE'. Say 'reset_irb' for a clean session. Say 'add_svn [repository_url]' to watch an svn repository and add_atom [atom_feed_url] to watch an atom feed"
86
+ when /^#{config[:nick]}:/
87
+ ["Usage:", " '>> CODE': evaluate code in IRB", " 'reset_irb': get a clean IRB session", " 'add_svn [repository_url]': watch an SVN repository", " 'add_atom [atom_feed_url]': watch an atom feed, such as a Git repository"].each {|s| say s}
54
88
  when /^reset_irb/ then reset_irb
55
- when /^add_svn (.+?)(\s|\r|\n|$)/ then $store[$1] = 0 and say $store.inspect
56
- when /^add_atom (.+?)(\s|\r|\n|$)/ then $atom[$1] = '' and say $atom.inspect
57
- end unless SILENT
58
- post($1) if DELICIOUS_PASS and msg =~ /(http:\/\/.*?)(\s|\r|\n|$)/
89
+ when /^add_svn (.+?)(\s|\r|\n|$)/ then @svns[$1] = 0 and say @svns.inspect
90
+ when /^add_atom (.+?)(\s|\r|\n|$)/ then @atoms[$1] = '' and say @atoms.inspect
91
+ when /(http:\/\/.*?)(\s|\r|\n|$)/ then post($1) if config[:delicious_pass]
92
+ end
59
93
  end
60
94
  end
61
95
  end
@@ -65,60 +99,78 @@ class Kirby
65
99
  def write s
66
100
  raise RuntimeError, "No socket" unless @socket
67
101
  @socket.puts s += "\r\n"
68
- # puts "WROTE: #{s.inspect}"
102
+ puts "WROTE: #{s.inspect}" if config[:debug]
103
+ end
104
+
105
+ # Write a string to the log, if the logfile is open.
106
+ def log s
107
+ # Open log, if necessary
108
+ if config[:log]
109
+ puts "LOG: #{s}" if config[:debug]
110
+ File.open(config[:logfile], 'a') do |f|
111
+ f.puts "#{Time.now.strftime(config[:time_format])} #{s}"
112
+ end
113
+ end
69
114
  end
70
115
 
71
116
  # Eval a piece of code in the <tt>irb</tt> environment.
72
117
  def try s
73
- reset_irb unless $session
118
+ reset_irb unless @session
74
119
  try_eval(s).select{|e| e !~ /^\s+from .+\:\d+(\:|$)/}.each {|e| say e} rescue say "session error"
75
120
  end
76
121
 
77
122
  # Say something in the channel.
78
123
  def say s
79
- write "PRIVMSG #{CHANNEL} :#{s[0..450]}"
124
+ write "PRIVMSG ##{config[:channel]} :#{s[0..450]}"
125
+ log "<#{config[:nick]}> #{s}"
80
126
  sleep 1
81
127
  end
82
128
 
83
129
  # Get a new <tt>irb</tt> session.
84
130
  def reset_irb
85
131
  say "Began new irb session"
86
- $session = try_eval("!INIT!IRB!")
132
+ @session = try_eval("!INIT!IRB!")
87
133
  end
88
134
 
89
135
  # Inner loop of the try method.
90
136
  def try_eval s
91
137
  reset_irb and return [] if s.strip == "exit"
92
138
  result = open("http://tryruby.hobix.com/irb?cmd=#{CGI.escape(s)}",
93
- {'Cookie' => "_session_id=#{$session}"}).read
139
+ {'Cookie' => "_session_id=#{@session}"}).read
94
140
  result[/^Your session has been closed/] ? (reset_irb and try_eval s) : result.split("\n")
95
141
  end
96
142
 
97
- # Look for svn changes.
143
+ # Look for SVN changes. Note that Rubyforge polls much better if you use the http:// protocol instead of the svn:// protocol for your repository.
98
144
  def poll
99
- return unless (Time.now - $last_poll > 15 rescue true)
145
+ return unless (Time.now - $last_poll > 60 rescue true)
100
146
  $last_poll = Time.now
101
- $store.each do |repo, last|
147
+ @svns.each do |repo, last|
148
+ puts "POLL: #{repo}" if config[:debug]
102
149
  (Hpricot(`svn log #{repo} -rHEAD:#{last} --limit 10 --xml`)/:logentry).reverse[1..-1].each do |ci|
103
- $store[repo] = rev = ci.attributes['revision'].to_i
104
- say "Commit #{rev} to #{repo.split("/").last} by #{(ci/:author).text}: #{(ci/:msg).text}"
150
+ @svns[repo] = rev = ci.attributes['revision'].to_i
151
+ project = repo.split(/\.|\//).reject do |path|
152
+ ['trunk', 'rubyforge', 'svn', 'org', 'com', 'net', 'http:', nil].include? path
153
+ end.last
154
+ say "Commit #{rev} to #{project || repo} by #{(ci/:author).text}: #{(ci/:msg).text}"
105
155
  end rescue nil
106
156
  end
107
- File.open(STORE, 'w') {|f| f.puts YAML.dump($store)}
157
+ File.open(config[:svns], 'w') {|f| f.puts YAML.dump(@svns)}
108
158
 
109
- $atom.each do |feed, last|
159
+ @atoms.each do |feed, last|
160
+ puts "POLL: #{feed}" if config[:debug]
110
161
  begin
111
162
  e = (Hpricot(open(feed))/:entry).first
112
- $atom[feed] = link = e.at("link")['href']
113
- say "#{(e/:title).text} by #{((e/:author)/:name).text} : #{link}" unless link == last
163
+ @atoms[feed] = link = e.at("link")['href']
164
+ say "Commit #{link} by #{((e/:author)/:name).text}: #{(e/:title).text}" unless link == last
114
165
  rescue
115
166
  end
116
167
  end
117
- File.open(ATOM, 'w') {|f| f.puts YAML.dump($atom)}
168
+ File.open(config[:atoms], 'w') {|f| f.puts YAML.dump(@atoms)}
118
169
  end
119
170
 
120
171
  # Post a url to the del.icio.us account.
121
172
  def post url
173
+ puts "POST: #{url}" if config[:debug]
122
174
  query = {:url => url,
123
175
  :description => (((Hpricot(open(url))/:title).first.innerHTML or url) rescue url),
124
176
  :tags => (Hpricot(open("http://del.icio.us/url/check?url=#{CGI.escape(url)}"))/'.alphacloud'/:a).map{|s| s.innerHTML}.join(" "),
@@ -126,11 +178,12 @@ class Kirby
126
178
  begin
127
179
  http = Net::HTTP.new('api.del.icio.us', 443)
128
180
  http.use_ssl = true
129
- http.start do |http|
181
+ response = http.start do |http|
130
182
  req = Net::HTTP::Get.new('/v1/posts/add?' + query.map{|k,v| "#{k}=#{CGI.escape(v)}"}.join('&'))
131
- req.basic_auth DELICIOUS_USER, DELICIOUS_PASS
183
+ req.basic_auth config[:delicious_user], config[:delicious_pass]
132
184
  http.request(req)
133
185
  end.body
186
+ puts "POST: #{response.inspect}" if config[:debug]
134
187
  end
135
188
  end
136
189
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kirby
3
3
  version: !ruby/object:Gem::Version
4
- version: "4.1"
4
+ version: "4.2"
5
5
  platform: ruby
6
6
  authors:
7
7
  - ""
@@ -30,7 +30,7 @@ cert_chain:
30
30
  yZ0=
31
31
  -----END CERTIFICATE-----
32
32
 
33
- date: 2007-12-15 00:00:00 -05:00
33
+ date: 2007-12-31 00:00:00 -05:00
34
34
  default_executable:
35
35
  dependencies:
36
36
  - !ruby/object:Gem::Dependency
@@ -88,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
88
  requirements: []
89
89
 
90
90
  rubyforge_project: fauna
91
- rubygems_version: 0.9.5
91
+ rubygems_version: 1.0.1
92
92
  signing_key:
93
93
  specification_version: 2
94
94
  summary: A super-clean IRC bot with sandboxed Ruby evaluation, svn watching, and link-logging to del.icio.us.
metadata.gz.sig CHANGED
@@ -1,2 +1,2 @@
1
- �Ӝ`g~xs���08��#��
2
- B����T&����Eeɋ���y?XW��`#�N��}7�e6��wQ��ޑ�^�3(��u������ �b�L_S4K����q����9`�;��g����U�ߡ��J5#��0;q�BU6Gc��m��ic ��(9� �<m�(�/ �`�R���b��> 2fp��jH��?\�M1-
1
+ R�{p^"���p�$|Rx�(���J�� %Ȗ���ia�
2
+