kirby 4.1 → 4.2

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 (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
+