# # According to Wikipedia, Cheat can refer to: # Cheating, to take advantage of a situation by the breaking of accepted rules # or standards # Cheating (casino) # Cheating in poker # Cheating in online games # In relationships, to have an affair # A cheat code, a hidden means of gaining an advantage in a video game # Cheating, parasitic abuse of symbiotic relationships # The Cheat, a character in the cartoon series Homestar Runner # Cheat!, a television show on the G4 network # The Cheat, a 1915 Cecil B. DeMille movie about a wealthy and domineering # Asian gentleman taking advantage of an American female # Cheats, a 2002 comedy, starring Matthew Lawrence and Mary Tyler Moore # Cheat, a song by The Clash from the UK version of their album The Clash # Bullshit, sometimes known as "Cheat," a card game # An alternate term for defection in the prisoner's dilemma in game theory # Cheat River, a tributary of the Monongahela River in Appalachia; the Cheat # starts in West Virginia, and flows westward # Cheat Lake, a nearby resevoir # Cheat Mountain, one of the highest mountains in the Alleghenies # %w[rubygems camping camping/db erb rubygems/open-uri acts_as_versioned wrap diffr responder ambition].each { |f| require f } gem 'camping', '>=1.4.152' Camping.goes :Cheat # for defunkt. campistrano. if ARGV.include? '--update' ssh = 'ssh deploy@errtheblog.com' puts `#{ssh} 'cd /var/www/cheat; svn up'` system "#{ssh} 'sudo /etc/init.d/rv restart'" exit end URL = ARGV.include?('debug') ? 'http://localhost:8020' : 'http://cheat.errtheblog.com' FEED = 'http://feeds.feedburner.com/cheatsheets' # rss feed module Cheat::Models class Sheet < Base validates_uniqueness_of :title validates_format_of :title, :with => /^[a-z]+[a-z0-9_]*$/i validates_presence_of :title, :body before_save { |r| r.title = r.title.gsub(' ', '_').underscore.downcase } acts_as_versioned end class SetUpUsTheCheat < V 1.0 def self.up create_table :cheat_sheets, :force => true do |t| t.column :id, :integer, :null => false t.column :title, :string, :null => false t.column :body, :text t.column :created_at, :datetime, :null => false t.column :updated_at, :datetime, :null => false end Sheet.create_versioned_table Sheet.reset_column_information end def self.down drop_table :cheat_sheets Sheet.drop_versioned_table end end end module Cheat::Controllers class APIShow < R '/y/(\w+)' def get(title) @headers['Content-Type'] = 'text/plain' sheet = Sheet.detect { |s| s.title == title } return { 'Error!' => "Cheat sheet `#{title}' not found." }.to_yaml unless sheet return { sheet.title => sheet.body }.to_yaml end end class APIRecent < R '/yr' def get @headers['Content-Type'] = 'text/plain' sheets = Sheet.sort_by { |s| -s.created_at }.first(15).map(&:title) return { 'Recent Cheat Sheets' => sheets }.to_yaml end end class APIAll < R '/ya' def get @headers['Content-Type'] = 'text/plain' sheets = Sheet.sort_by(&:title).map(&:title) return { 'All Cheat Sheets' => sheets }.to_yaml end end class Feed < R '/f' def get @headers['Content-Type'] = 'application/xml' return Cheat::Views.feed end end class Index < R '/' def get render :index end end class Add < R '/a' def get @sheet = Sheet.new render :add end end class Edit < R '/e/(\w+)/(\d+)', '/e/(\w+)' def get(title, version = nil) @sheet = Sheet.detect { |s| s.title == title } @error = "Cheat sheet not found." unless @sheet unless version.nil? || version == @sheet.version.to_s @sheet = @sheet.find_version(version) end render @error ? :error : :edit end end class Write < R '/w', '/w/(\w+)' def post(title = nil) @sheet = title ? Sheet.find_by_title(title) : Sheet.new @sheet = title ? Sheet.detect { |s| s.title == title } : Sheet.new check_captcha! unless input.from_gem if !@error && @sheet.update_attributes(:title => input.sheet_title, :body => input.sheet_body) redirect "#{URL}/s/#{@sheet.title}" else @error = true render title ? :edit : :add end end def check_captcha! @error ||= !(@cookies[:passed] ||= captcha_pass?(input.chunky, input.bacon)) end def captcha_pass?(session, answer) open("http://captchator.com/captcha/check_answer/#{session}/#{answer}").read.to_i.nonzero? rescue false end end class Browse < R '/b' def get @sheets = Sheet.sort_by(&:title) render :browse end end class Show < R '/s/(\w+)', '/s/(\w+)/(\d+)' def get(title, version = nil) @sheet = Sheet.detect { |s| s.title == title } @sheet = @sheet.find_version(version) if version && @sheet @sheet ? render(:show) : redirect("#{URL}/b/") end end # we are going to start consolidating classes with respond_to and what not. # diff is the first, as the api and the site will use the same code class Diff < R '/d/(\w+)/(\d+)', '/d/(\w+)/(\d+)/(\d+)' include Responder def get(title, old_version, new_version = nil) redirect "#{URL}/b/" and return unless old_version.to_i.nonzero? @sheet = Sheet.detect { |s| s.title == title } @old_sheet = @sheet.find_version(old_version) @new_sheet = (new_version ? @sheet.find_version(new_version) : @sheet) @diffed = Diffr.diff(@old_sheet, @new_sheet) rescue nil respond_to do |wants| wants.html { render :diff } wants.yaml { { @sheet.title => @diffed }.to_yaml } end end end class History < R '/h/(\w+)' include Responder def get(title) if sheets = Sheet.detect { |s| s.title == title } @sheets = sheets.find_versions(:order => 'version DESC') end respond_to do |wants| wants.html { render :history } wants.yaml { { @sheets.first.title => @sheets.map(&:version) }.to_yaml } end end end end module Cheat::Views def layout html { head { _style link :href => FEED, :rel => "alternate", :title => "Recently Updated Cheat Sheets", :type => "application/atom+xml" title @page_title ? "$ cheat #{@page_title}" : "$ command line ruby cheat sheets" } body { div.main { div.header { h1 { logo_link 'cheat sheets.' } code.header @sheet_title ? "$ cheat #{@sheet_title}" : "$ command line ruby cheat sheets" } div.content { self << yield } div.side { _side } div.clear { '' } div.footer { _footer } } _clicky } } end def _clicky text '' end def error @page_title = "error" p "An error:" code.version @error p ":(" end def show @page_title = @sheet.title @sheet_title = @sheet.title pre.sheet { text h(@sheet.body.wrap) } div.version { text "Version " strong sheet.version text ", updated " text last_updated(@sheet) text " ago. " br text ". o 0 ( " if @sheet.version == current_sheet.version a "edit", :href => R(Edit, @sheet.title) end if @sheet.version > 1 text " | " a "previous", :href => R(Show, @sheet.title, @sheet.version - 1) end text " | " a "history", :href => R(History, @sheet.title) unless @sheet.version == current_sheet.version text " | " a "revert to", :href => R(Edit, @sheet.title, @sheet.version) text " | " a "current", :href => R(Show, @sheet.title) end diff_version = if @sheet.version == current_sheet.version @sheet.version == 1 ? nil : @sheet.version - 1 else @sheet.version end if diff_version text " | " a "diff", :href => R(Diff, @sheet.title, diff_version) end text " )" } end def diff @page_title = @sheet.title @sheet_title = @sheet.title pre.sheet { color_diff(h(@diffed)) if @diffed } div.version { text ". o 0 (" if @old_sheet.version > 1 a "diff previous", :href => R(Diff, @sheet.title, @old_sheet.version - 1) text " | " end a "history", :href => R(History, @sheet.title) text " | " a "current", :href => R(Show, @sheet.title) text " )" } end def browse @page_title = "browse" p { "Wowzers, we've got #{@sheets.size} cheat sheets hereabouts." } ul { @sheets.each do |sheet| li { sheet_link sheet.title } end } p { text "Are we missing a cheat sheet? Why don't you do the whole world a favor and " a "add it", :href => R(Add) text " yourself!" } end def history @page_title = "history" @sheet_title = @sheets.first.title h2 @sheets.first.title ul { @sheets.each_with_index do |sheet, i| li { a "version #{sheet.version}", :href => R(Show, sheet.title, sheet.version) text " - created " text last_updated(sheet) text " ago" strong " (current)" if i.zero? text " " a "(diff to current)", :href => R(Diff, sheet.title, sheet.version) if i.nonzero? } end } end def add @page_title = "add" p { text "Thanks for wanting to add a cheat sheet. If you need an example of the standard cheat sheet format, check out the " a "cheat", :href => R(Show, 'cheat') text " cheat sheet. (There's really no standard format, though)." } _form end def edit @page_title = "edit" _form end def _form if @error p.error { strong "HEY! " text "Something is wrong! You can't give your cheat sheet the same name as another, alphanumeric titles only, and you need to make sure you filled in all (two) of the fields. Okay?" } end form :method => 'post', :action => R(Write, @sheet.title) do p { p { text 'Cheat Sheet Title: ' input :value => @sheet.title, :name => 'sheet_title', :size => 30, :type => 'text' small " [ no_spaces_alphanumeric_only ]" } p { text 'Cheat Sheet:' br textarea @sheet.body, :name => 'sheet_body', :cols => 80, :rows => 30 unless @cookies[:passed] random = rand(10_000) br img :src => "http://captchator.com/captcha/image/#{random}" input :name => 'chunky', :type => 'hidden', :value => random input :name => 'bacon', :size => 10, :type => 'text' end } } p "Your cheat sheet will be editable (fixable) by anyone. Each cheat sheet is essentially a wiki page. It may also be used by millions of people for reference purposes from the comfort of their command line. If this is okay with you, please save." input :value => "Save the Damn Thing!", :name => "save", :type => 'submit' end end def index p { text "Welcome. You've reached the central repository for " strong "cheat" text ", the RubyGem which puts Ruby-centric cheat sheets right into your terminal. The inaugural blog entry " a "is here", :href => "http://errtheblog.com/post/23" text "." } p "Get started:" code "$ sudo gem install cheat" br code "$ cheat strftime" p "A magnificent cheat sheet for Ruby's strftime method will be printed to your terminal." p "To get some help on cheat itself:" code "$ cheat cheat" p "How meta." p { text "Cheat sheets are basically wiki pages accessible from the command line. You can " a 'browse', :href => R(Browse) text ', ' a 'add', :href => R(Add) text ', or ' a 'edit', :href => R(Edit, 'cheat') text ' cheat sheets. Try to keep them concise. For a style guide, check out the ' a 'cheat', :href => R(Edit, 'cheat') text ' cheat sheet.' } p "To access a cheat sheet, simply pass the program the desired sheet's name:" code "$ cheat " p end def self.feed xml = Builder::XmlMarkup.new(:indent => 2) xml.instruct! xml.feed "xmlns"=>"http://www.w3.org/2005/Atom" do xml.title "Recently Updated Cheat Sheets" xml.id URL + '/' xml.link "rel" => "self", "href" => FEED sheets = Cheat::Models::Sheet.sort_by { |s| -s.updated_at }.first(20) xml.updated sheets.first.updated_at.xmlschema sheets.each do |sheet| xml.entry do xml.id URL + '/s/' + sheet.title xml.title sheet.title xml.author { xml.name "An Anonymous Cheater" } xml.updated sheet.updated_at.xmlschema xml.link "rel" => "alternate", "href" => URL + '/s/' + sheet.title xml.summary "A cheat sheet about #{sheet.title}. Run it: `$ cheat #{sheet.title}'" xml.content 'type' => 'html' do xml.text! sheet.body.gsub("\n", '
').gsub("\r", '') end end end end end def _side text '( ' a 'add new', :href => R(Add) text ' | ' a 'see all', :href => R(Browse) text ' )' ul { li { strong "updated sheets" } li do a :href => FEED do img(:border => 0, :alt => "Recently Updated Cheat Sheets Feed", :src => "http://errtheblog.com/images/feed.png") end end recent_sheets.each do |sheet| li { sheet_link sheet.title } end } end def _footer text "Powered by " a 'Camping', :href => "http://code.whytheluckystiff.net/camping" text ", " a 'Mongrel', :href => "http://mongrel.rubyforge.org/" text " and, to a lesser extent, " a 'Err the Blog', :href => "http://errtheblog.com/" text "." end def _style bg = "#fff" h1 = "#4fa3da" link = h1 hover = "#f65077" dash = hover version = "#fcf095" style :type => "text/css" do text %[ body { font-family: verdana, sans-serif; background-color: #{bg}; line-height: 20px; } a:link, a:visited { color: #{link}; } a:hover { text-decoration: none; color: #{hover}; } div.header { border-bottom: 1px dashed #{dash}; } code.header { margin-left: 30px; font-weight: bold; background-color: #{version}; } h1 { font-size: 5em; margin: 0; padding-left: 30px; color: #{h1}; clear: both; font-weight: bold; letter-spacing: -5px; } h1 a { text-decoration: none; } div.main { float: left; width: 100%; } div.content { float: left; width: 70%; padding: 15px 0 15px 30px; line-height: 20px; } div.side { float: left; padding: 10px; text-align: right; width: 20%; } div.footer { text-align: center; border-top: 1px dashed #{dash}; padding-top: 10px; font-size: small; } div.sheet { font-size: .8em; line-height: 17px; padding: 5px; font-family: courier, fixed-width; background-color: #e8e8e8; } pre.sheet { line-height: 15px; } li { list-style: none; } div.version { background-color: #{version}; padding: 5px; width: 450px; margin-top: 50px; } p.error { background-color: #{version}; padding: 5px; } div.clear { clear: both; } div.clear_10 { clear: both; font-size: 10px; line-height: 10px; } textarea { font-family: courier; } code { background-color: #{version} } span.diff_cut { color: #f65077; } span.diff_add { color: #009933; } @media print { .side, .version, .footer { display: none; } div.content { width: 100%; } h1 a:link, h1 a:visited { color: #eee;} .header code { font-size: 18px; background: none; } div.header { border-bottom: none; } } ].gsub(/(\s{2,})/, '').gsub("\n", '') end end end module Cheat::Helpers def logo_link(title) ctr = Cheat::Controllers if @sheet && !@sheet.new_record? && @sheet.version != current_sheet.version a title, :href => R(ctr::Show, @sheet.title) else a title, :href => R(ctr::Index) end end def current_sheet title = @sheet.title @current_sheet ||= Cheat::Models::Sheet.detect { |s| s.title == title } end def recent_sheets Cheat::Models::Sheet.sort_by { |s| -s.updated_at }.first(15) end def sheet_link(title, version = nil) a title, :href => R(Cheat::Controllers::Show, title, version) end def last_updated(sheet) from = sheet.updated_at.to_i to = Time.now.to_i from = from.to_time if from.respond_to?(:to_time) to = to.to_time if to.respond_to?(:to_time) distance = (((to - from).abs)/60).round case distance when 0..1 then return (distance==0) ? 'less than a minute' : '1 minute' when 2..45 then "#{distance} minutes" when 46..90 then 'about 1 hour' when 90..1440 then "about #{(distance.to_f / 60.0).round} hours" when 1441..2880 then '1 day' else "#{(distance / 1440).round} days" end end def self.h(text) ERB::Util::h(text) end def h(text) ::Cheat::Helpers.h(text) end def color_diff(diff) diff.split("\n").map do |line| action = case line when /^-/ then 'cut' when /^\+/ then 'add' end action ? span.send("diff_#{action}", line) : line end * "\n" end end def Cheat.create Cheat::Models.create_schema end if __FILE__ == $0 begin require 'mongrel/camping' rescue LoadError => e abort "** Try running `camping #$0' instead." end Cheat::Models::Base.establish_connection :adapter => 'mysql', :user => 'root', :database => 'camping', :host => 'localhost' Cheat::Models::Base.logger = nil Cheat::Models::Base.threaded_connections = false Cheat.create server = Mongrel::Camping.start("0.0.0.0", 8020, "/", Cheat) puts "** Cheat is running at http://0.0.0.0:8020/" server.run.join end