0
-# < subtlety : a remote subversion excursion'
0
-# > copyrite 2007 chris wanstrath
0
+# < subtlety 2 : a remote subversion (and hAtom) excursion'
0
+# > copyrite 2008 chris wanstrath
0
# < chris[at]ozmm[dot]org
0
-%w(rubygems erb camping camping/db open3).each { |f| require f }
0
-$debug = $0 =~ /camping/
0
-# for defunkt. campistrano.
0
-# ruby subtle.rb --update yourname@yourserver.com
0
-if at = ARGV.index('--update')
0
- ssh = 'ssh ' << (ARGV[at+1] || 'chris@errtheblog.com')
0
- puts `#{ssh} 'cd sites/subtlety; svn up; rm feeds/*'`
0
- exec "#{ssh} 'sudo /etc/init.d/rv restart'"
0
+%w( rubygems erb timeout sinatra sequel open3 ).each { |f| require f }
0
class String; def compact; gsub(/(\s{2,})/, ' ').gsub("\n", '') end end
0
module Kernel; def `(string) Open3.popen3(*string.split(' '))[1].read end end
0
+class Integer; def minutes; self * 60 end; def ago; Time.now - self end end
0
- class GetReadyToParty < V 1.0
0
- create_table :subtle_repositories do |t|
0
- t.column :id, :integer
0
- t.column :url, :string
0
- t.column :created_at, :datetime
0
+ Timeout.timeout(5, &block)
0
- drop_table :repositories
0
+DB = Sequel('sqlite:/db/subtle.db')
0
+class Item < Sequel::Model
0
+ boolean :atom, :default => false
0
- class Repository < Base
0
- errors.add("wrong format") unless url =~ /^(svn|http):\/\/(\w|\.|\/|-)+$/
0
- errors.add("invalid repos") unless `/usr/bin/env svn info #{url}` =~ /Path:/
0
-module Subtle::Controllers
0
+ "http://subtlety.errtheblog.com/O_o/#{key}.xml"
0
- url = input.feed.gsub(/\/$/,'')
0
- @repository = Repository.find_or_create_by_url(url)
0
- @rss = R(Feed, @repository.key) if @repository.errors.blank?
0
+ def self.find_or_create_by_url(url, atom = false)
0
+ if model = self[:url => url, :atom => atom]
0
+ return unless url =~ /^(svn|http):\/\/(\w|\.|\/|-)+$/ && timeout { `/usr/bin/env svn info #{url}` =~ /Path:/ }
0
+ create(:url => url, :created_at => Time.now, :atom => atom)
0
- class Feed < R '/O_o/(\w+).xml'
0
- @headers['Content-Type'] = 'application/xml'
0
- if File.exists?(@file = "feeds/#{key.gsub(/\W/,'')}.xml") && File.mtime(@file) > 15.minutes.ago
0
- @repository = Repository.find_by_id(key.to_i(16))
0
+Item.set_dataset DB[:items]
0
+Item.create_table unless Item.table_exists?
0
- tmp_file = "/tmp/tmp-#{@repository.key}.xml"
0
- dir = File.expand_path(File.dirname(__FILE__))
0
- erb_file = "#{dir}/svnlog.erb"
0
- xslt_file = "#{dir}/svnlog-#{@repository.key}.xslt"
0
+ Welcome. Here's what we do: we take a remote, public subversion repository (http:// or svn://) and give you an rss feed of
0
+ the changes. That's it. Have an <strong>svn:external</strong> or <strong><a href="http://piston.rubyforge.org/">pistonized</a></strong>
0
+ repository in your app you need to monitor? Look no further: just plug in the repository's location and start reveling in the
0
+ sweet, sweet changesets, rss-style.
0
+ The inaugural blog entry <a href="http://errtheblog.com/post/701">is here</a>.
0
+ One thing: please add the repository's root path. So, svn://errtheblog.com/svn/mofo, <strong>not</strong>
0
+ svn://errtheblog.com/svn/mofo/trunk. Thanks, and enjoy.
0
- File.open(xslt_file, 'w') do |file|
0
- file.puts ERB.new(File.read(erb_file)).result(binding)
0
- File.open(tmp_file, 'w') do |f|
0
- f.puts `/usr/bin/env svn log #{@repository.url.gsub(/ |\\|;/,'')} --limit 15 -v --xml`
0
+ url = params[:feed].chomp('/')
0
+ @item = Item.find_or_create_by_url(url)
0
+ @rss = "/O_o/#{@item.key}" if @item
0
+ Here it is, your very own RSS feed of the changes committed to <strong>#{@item.url}</strong>:
0
+ <a href="#{@item.full_url}">#{@item.full_url}</a>
0
+ « <a href="/">back home.</a>
0
+ Sorry, there was some kind of error. Are you sure your repository url's valid? Does it start with svn:// or http://?
0
- File.open(@file, 'w') do |f|
0
- f.puts `/usr/bin/env xsltproc #{xslt_file} #{tmp_file}`
0
- `rm #{tmp_file} #{xslt_file}`
0
+ @headers['Content-Type'] = 'application/xml'
0
+ if File.exists?(@file = "feeds/#{key.gsub(/\W/,'')}.xml") && File.mtime(@file) > 15.minutes.ago
0
+ render_feed Item[:id => key.to_i(16)]
0
- return if file.include? '..'
0
+ tmp_file = "/tmp/tmp-#{item.key}.xml"
0
+ dir = File.expand_path(File.dirname(__FILE__))
0
+ erb_file = "#{dir}/templates/svnlog.erb"
0
+ xslt_file = "#{dir}/tmp/svnlog-#{item.key}.xslt"
0
- return File.read(file)
0
+ File.open(xslt_file, 'w') do |file|
0
+ file.puts ERB.new(File.read(erb_file)).result(binding)
0
- # TODO: cant get this working
0
- # not implemented or tested, rly
0
- @headers['X-Sendfile'] = Pathname.new(__FILE__).dirname.realpath.to_s + file
0
- @headers['X-Accel-Redirect'] = "/static/#{file}"
0
+ File.open(tmp_file, 'w') do |f|
0
+ f.puts `/usr/bin/env svn log #{item.url.gsub(/ |\\|;/,'')} --limit 15 -v --xml`
0
- class Image < R '/images/diag.gif'
0
- @headers['Content-Type'] = 'image/gif'
0
- File.read('images/diag.gif')
0
+ File.open(@file, 'w') do |f|
0
+ f.puts `/usr/bin/env xsltproc #{xslt_file} #{tmp_file}`
0
+ `rm #{tmp_file} #{xslt_file}`
0
- link :href => @rss, :rel => "alternate", :type => "application/rss+xml"
0
- title 'subtlety : a remote subversion excursion'
0
- div.header { h1 'subtlety.' }
0
- div.main { self << yield.compact }
0
- div.footer { _footer }
0
+config_for :development do
0
+ return if file.include? '..'
0
- text '<script src="http://getclicky.com/1149.js"> </script> <noscript><img height=0 width=0 src="http://getclicky.com/1149ns.gif"></noscript>'
0
+ get '/images/diag.gif' do
0
+ @headers['Content-Type'] = 'image/gif'
0
+ File.read('images/diag.gif')
0
- Welcome. Here's what we do: we take a remote, public subversion repository (http:// or svn://) and give you an rss feed of
0
- the changes. That's it. Have an <strong>svn:external</strong> or <strong><a href="http://piston.rubyforge.org/">pistonized</a></strong>
0
- repository in your app you need to monitor? Look no further: just plug in the repository's location and start reveling in the
0
- sweet, sweet changesets, rss-style.
0
- p { %[The inaugural blog entry <a href="http://errtheblog.com/post/701">is here</a>.] }
0
- One thing: please add the repository's root path. So, svn://errtheblog.com/svn/mofo, <strong>not</strong>
0
- svn://errtheblog.com/svn/mofo/trunk. Thanks, and enjoy.
0
+config_for :production do
0
+ return if file.include? '..'
0
+ @headers['X-Accel-Redirect'] = "/static/#{file}"
0
- p.first text("Here it is, your very own RSS feed of the changes committed to <strong>#{@repository.url}</strong>:")
0
- url = R(Feed, @repository.key)
0
- h3 { a("http://subtlety.errtheblog.com#{url}", :href => url) }
0
- p text("« " + a("back home.", :href => '/'))
0
- p.highlight "Sorry, there was some kind of error. Are you sure your repository url's valid? Does it start with svn:// or http://?"
0
+ '<script src="http://getclicky.com/1149.js"> </script> <noscript><img height=0 width=0 src="http://getclicky.com/1149ns.gif"></noscript>'
0
- h3 "create an rss feed from a public subversion repository:"
0
- form :id => 'feed-me', :method => 'post', :action => R(Save) do
0
- p { input.normal :name => 'feed', :size => 61, :type => 'text', :value => @repository ? @repository.url : '' }
0
- p { input.submit :type => 'submit', :value => "feed me." }
0
+ <h3>create an rss feed from a public subversion repository:</h3>
0
+ <form id="feed-me" method="post" action="/s">
0
+ <p><input class="normal" type="text" name="feed" size="61" value="<%= @repository ? @repository.url : '' %>" /></p>
0
+ <p><input type="submit" value="feed me."/></p>
0
- style :type => "text/css" do
0
- body { background-color: #333; margin: 10px; font-family: arial, sans-serif; font-size: 13px; color: #333;
0
- background-image: url(/images/diag.gif); background-position: 760px; text-align: center; }
0
- div.wrap { width: 760px; text-align: left; margin: 0 auto; }
0
- a { text-decoration: none; color: #333; border-bottom: 1px solid #333; font-weight: bold; }
0
- a:hover { border-bottom: none; }
0
- div.header { background-color: #a7bc66; padding: 10px 10px 6px 10px; margin-bottom: 10px; text-align:right; }
0
- div.main { line-height: 150%; background-color: white; padding: 10px; margin-bottom: 10px; }
0
- div.footer { text-align: center; line-height: 150%; background-color: #999; padding: 10px;
0
- margin-bottom: 10px; }
0
- div.main p { margin: 10px 0 0 0; }
0
- div.main p.first { margin-top: 0; }
0
- div.main p.title { font-size: 16px; font-weight: bold; margin-top: 0; }
0
- p.highlight { font-size: 2em; background: #999; padding: 10px; line-height: 120%; }
0
- .caps { font-size: 93%; text-transform: uppercase; }
0
- .desc { margin-left: 10px; padding: 10px; background-color: #ddd; line-height: 150%; }
0
- .desc .title { font-weight: bold; }
0
- .bar { margin-top: 10px; border-top: 1px solid #ccc; padding-top: 10px; }
0
- table { margin-bottom: 10px; }
0
- input.normal { font-size: 18px; font-weight: bold; padding: 5px; color: #a7bc66; border-color: #999999;
0
- border-width: 1px; border-style: solid; }
0
+ body { background-color: #333; margin: 10px; font-family: arial, sans-serif; font-size: 13px; color: #333;
0
+ background-image: url(/images/diag.gif); background-position: 760px; text-align: center; }
0
+ div.wrap { width: 760px; text-align: left; margin: 0 auto; }
0
+ a { text-decoration: none; color: #333; border-bottom: 1px solid #333; font-weight: bold; }
0
+ a:hover { border-bottom: none; }
0
+ div.header { background-color: #a7bc66; padding: 10px 10px 6px 10px; margin-bottom: 10px; text-align:right; }
0
+ div.main { line-height: 150%; background-color: white; padding: 10px; margin-bottom: 10px; }
0
+ div.footer { text-align: center; line-height: 150%; background-color: #999; padding: 10px;
0
+ margin-bottom: 10px; }
0
+ div.main p { margin: 10px 0 0 0; }
0
+ div.main p.first { margin-top: 0; }
0
+ div.main p.title { font-size: 16px; font-weight: bold; margin-top: 0; }
0
+ p.highlight { font-size: 2em; background: #999; padding: 10px; line-height: 120%; }
0
+ .caps { font-size: 93%; text-transform: uppercase; }
0
+ .desc { margin-left: 10px; padding: 10px; background-color: #ddd; line-height: 150%; }
0
+ .desc .title { font-weight: bold; }
0
+ .bar { margin-top: 10px; border-top: 1px solid #ccc; padding-top: 10px; }
0
+ table { margin-bottom: 10px; }
0
+ input.normal { font-size: 18px; font-weight: bold; padding: 5px; color: #a7bc66; border-color: #999999;
0
+ border-width: 1px; border-style: solid; }
0
+ "<style type='text/css'>#{style}</style>"
0
- a 'Camping', :href => "http://code.whytheluckystiff.net/camping/"
0
- a 'Mongrel', :href => "http://mongrel.rubyforge.org/"
0
- text " and, to a lesser extent, "
0
- a 'Err the Blog', :href => "http://errtheblog.com/"
0
+ [ 'Sinatra', "http://sinatra.rubyforge.org/" ],
0
+ [ 'Sequel', "http://sequel.rubyforge.org/" ],
0
+ " and, to a lesser extent",
0
+ [ 'Err the Blog', "http://errtheblog.com/" ]
0
+ links.map { |link| link.is_a?(Array) ? "<a href='#{link.last}'>#{link.first}</a>" : link }.join(", ") +
0
- Subtle::Models.create_schema
0
+ <link href="<%= @rss %>" rel="alternate" type="application/rss+xml"/>
0
+ <title>subtlety : a remote subversion and hAtom excursion</title>
Comments
No one has commented yet.