tarsius-cheat 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (9) hide show
  1. data/LICENSE +18 -0
  2. data/README +38 -0
  3. data/bin/cheat +4 -0
  4. data/lib/cheat.rb +240 -0
  5. data/lib/diffr.rb +49 -0
  6. data/lib/responder.rb +42 -0
  7. data/lib/site.rb +611 -0
  8. data/lib/wrap.rb +42 -0
  9. metadata +64 -0
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2006 Chris Wanstrath
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,38 @@
1
+ == Cheat
2
+
3
+ Cheat is a simple command line reference utility. Use it to retrieve handy
4
+ information from the cheat sheet repository.
5
+
6
+ $ cheat sheets
7
+ $ cheat cheat
8
+ $ cheat recent
9
+
10
+ To freshen your local cache, supply the --new edit.
11
+
12
+ $ cheat sheets --new
13
+
14
+ To edit a cheat sheet, use the --edit switch.
15
+
16
+ $ cheat markaby --edit
17
+
18
+ To add a cheat sheet, use the --add switch.
19
+
20
+ $ cheat readme --add
21
+
22
+ To execute a sheet, use the --execute or -x switch.
23
+
24
+ $ cheat rspec_rails_install_edge --execute
25
+ $ cheat rspec_rails_install_edge --x
26
+
27
+ Special Thanks To:
28
+ - Evan Weaver
29
+ - Kevin Marsh
30
+ - Jeremy Apthorp
31
+
32
+ The Cheat Sheet Repository:
33
+ - http://cheat.errtheblog.com/
34
+
35
+ Enjoy.
36
+
37
+ >> Chris Wanstrath
38
+ => chris[at]ozmm[dot]org
data/bin/cheat ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'cheat'
4
+ Cheat.sheets(ARGV)
data/lib/cheat.rb ADDED
@@ -0,0 +1,240 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ %w[rubygems tempfile fileutils net/http yaml open-uri wrap].each { |f| require f }
3
+
4
+ module Cheat
5
+ extend self
6
+
7
+ HOST = ARGV.include?('debug') ? 'localhost' : 'cheat.errtheblog.com'
8
+ PORT = ARGV.include?('debug') ? 3001 : 80
9
+ SUFFIX = ''
10
+
11
+ def sheets(args)
12
+ args = args.dup
13
+
14
+ return unless parse_args(args)
15
+
16
+ FileUtils.mkdir(cache_dir) unless File.exists?(cache_dir) if cache_dir
17
+
18
+ uri = "http://#{cheat_uri}/y/"
19
+
20
+ if %w[sheets all recent].include? @sheet
21
+ options = headers.update(proxy_options)
22
+ uri = uri.sub('/y/', @sheet == 'recent' ? '/yr/' : '/ya/')
23
+ return open(uri,options) { |body| process(body.read) }
24
+ end
25
+
26
+ return process(File.read(cache_file)) if File.exists?(cache_file) rescue clear_cache if cache_file
27
+
28
+ fetch_sheet(uri + @sheet) if @sheet
29
+ end
30
+
31
+ def fetch_sheet(uri, try_to_cache = true)
32
+ options = headers.update(proxy_options)
33
+ open(uri, options) do |body|
34
+ sheet = body.read
35
+ FileUtils.mkdir_p(cache_dir) unless File.exists?(cache_dir)
36
+ File.open(cache_file, 'w') { |f| f.write(sheet) } if try_to_cache && cache_file && !@edit
37
+ @edit ? edit(sheet) : show(sheet)
38
+ end
39
+ exit
40
+ rescue OpenURI::HTTPError => e
41
+ puts "Whoa, some kind of Internets error!", "=> #{e} from #{uri}"
42
+ end
43
+
44
+ def parse_args(args)
45
+ puts "Looking for help? Try http://cheat.errtheblog.com or `$ cheat cheat'" and return if args.empty?
46
+
47
+ if args.delete('--clear-cache') || args.delete('--new')
48
+ clear_cache
49
+ return if args.empty?
50
+ end
51
+
52
+ if i = args.index('--diff')
53
+ diff_sheets(args.first, args[i+1])
54
+ end
55
+
56
+ show_versions(args.first) if args.delete('--versions')
57
+
58
+ add(args.shift) and return if args.delete('--add')
59
+ clear_cache if @edit = args.delete('--edit')
60
+
61
+ @execute = true if args.delete("--execute") || args.delete("-x")
62
+ @sheet = args.shift
63
+
64
+ true
65
+ end
66
+
67
+ # $ cheat greader --versions
68
+ def show_versions(sheet)
69
+ fetch_sheet("http://#{cheat_uri}/h/#{sheet}/", false)
70
+ end
71
+
72
+ # $ cheat greader --diff 1[:3]
73
+ def diff_sheets(sheet, version)
74
+ return unless version =~ /^(\d+)(:(\d+))?$/
75
+ old_version, new_version = $1, $3
76
+
77
+ uri = "http://#{cheat_uri}/d/#{sheet}/#{old_version}"
78
+ uri += "/#{new_version}" if new_version
79
+
80
+ fetch_sheet(uri, false)
81
+ end
82
+
83
+ def cache_file
84
+ "#{cache_dir}/#{@sheet}.yml" if cache_dir
85
+ end
86
+
87
+ def headers
88
+ { 'User-Agent' => 'cheat!', 'Accept' => 'text/yaml' }
89
+ end
90
+
91
+ def proxy_options
92
+ if ENV['HTTP_PROXY'] || ENV['http_proxy']
93
+ proxy_array = []
94
+ proxy_array << (ENV['HTTP_PROXY'] || ENV['http_proxy'])
95
+ proxy_array << (ENV['HTTP_PROXY_USER'] || ENV['http_proxy_user'] || '')
96
+ proxy_array << (ENV['HTTP_PROXY_PASS'] || ENV['http_proxy_pass'] || '')
97
+ {:proxy_http_basic_authentication => proxy_array}
98
+ else
99
+ {}
100
+ end
101
+ end
102
+
103
+ def cheat_uri
104
+ "#{HOST}:#{PORT}#{SUFFIX}"
105
+ end
106
+
107
+ def execute(sheet_yaml)
108
+ sheet_body = YAML.load(sheet_yaml).to_a.flatten.last
109
+ puts "\n " + sheet_body.gsub("\r",'').gsub("\n", "\n ").wrap
110
+ puts "\nWould you like to execute the above sheet? (Y/N)"
111
+ answer = STDIN.gets
112
+ case answer.chomp
113
+ when "Y" then system YAML.load(sheet_yaml).to_a.flatten.last
114
+ when "N" then puts "Not executing sheet."
115
+ else
116
+ puts "Must be Y or N!"
117
+ end
118
+ rescue Errno::EPIPE
119
+ # do nothing
120
+ rescue
121
+ puts "That didn't work. Maybe try `$ cheat cheat' for help?" # Fix Emacs ruby-mode highlighting bug: `"
122
+ end
123
+
124
+ def process(sheet_yaml)
125
+ if @execute
126
+ execute(sheet_yaml)
127
+ else
128
+ show(sheet_yaml)
129
+ end
130
+ end
131
+
132
+ def show(sheet_yaml)
133
+ sheet = YAML.load(sheet_yaml).to_a.first
134
+ sheet[-1] = sheet.last.join("\n") if sheet[-1].is_a?(Array)
135
+ run_pager
136
+ puts sheet.first + ':'
137
+ puts ' ' + sheet.last.gsub("\r",'').gsub("\n", "\n ").wrap
138
+ rescue Errno::EPIPE
139
+ # do nothing
140
+ rescue
141
+ puts "That didn't work. Maybe try `$ cheat cheat' for help?" # Fix Emacs ruby-mode highlighting bug: `"
142
+ end
143
+
144
+ def edit(sheet_yaml)
145
+ sheet = YAML.load(sheet_yaml).to_a.first
146
+ sheet[-1] = sheet.last.gsub("\r", '')
147
+ body, title = write_to_tempfile(*sheet), sheet.first
148
+ return if body.strip == sheet.last.strip
149
+ res = post_sheet(title, body)
150
+ check_errors(res, title, body)
151
+ end
152
+
153
+ def add(title)
154
+ body = write_to_tempfile(title)
155
+ res = post_sheet(title, body, true)
156
+ check_errors(res, title, body)
157
+ end
158
+
159
+ def post_sheet(title, body, new = false)
160
+ uri = "http://#{cheat_uri}/w/"
161
+ uri += title unless new
162
+ Net::HTTP.post_form(URI.parse(uri), "sheet_title" => title, "sheet_body" => body.strip, "from_gem" => true)
163
+ end
164
+
165
+ def write_to_tempfile(title, body = nil)
166
+ # god dammit i hate tempfile, this is so messy but i think it's
167
+ # the only way.
168
+ tempfile = Tempfile.new(title + '.cheat')
169
+ tempfile.write(body) if body
170
+ tempfile.close
171
+ system "#{editor} #{tempfile.path}"
172
+ tempfile.open
173
+ body = tempfile.read
174
+ tempfile.close
175
+ body
176
+ end
177
+
178
+ def check_errors(result, title, text)
179
+ if result.body =~ /<p class="error">(.+?)<\/p>/m
180
+ puts $1.gsub(/\n/, '').gsub(/<.+?>/, '').squeeze(' ').wrap(80)
181
+ puts
182
+ puts "Here's what you wrote, so it isn't lost in the void:"
183
+ puts text
184
+ else
185
+ puts "Success! Try it!", "$ cheat #{title} --new"
186
+ end
187
+ end
188
+
189
+ def editor
190
+ ENV['VISUAL'] || ENV['EDITOR'] || "vim"
191
+ end
192
+
193
+ def cache_dir
194
+ PLATFORM =~ /win32/ ? win32_cache_dir : File.join(File.expand_path("~"), ".cheat")
195
+ end
196
+
197
+ def win32_cache_dir
198
+ unless File.exists?(home = ENV['HOMEDRIVE'] + ENV['HOMEPATH'])
199
+ puts "No HOMEDRIVE or HOMEPATH environment variable. Set one to save a" +
200
+ "local cache of cheat sheets."
201
+ return false
202
+ else
203
+ return File.join(home, 'Cheat')
204
+ end
205
+ end
206
+
207
+ def clear_cache
208
+ FileUtils.rm_rf(cache_dir) if cache_dir
209
+ end
210
+
211
+ def run_pager
212
+ return if PLATFORM =~ /win32/
213
+ return unless STDOUT.tty?
214
+
215
+ read, write = IO.pipe
216
+
217
+ unless Kernel.fork # Child process
218
+ STDOUT.reopen(write)
219
+ STDERR.reopen(write) if STDERR.tty?
220
+ read.close
221
+ write.close
222
+ return
223
+ end
224
+
225
+ # Parent process, become pager
226
+ STDIN.reopen(read)
227
+ read.close
228
+ write.close
229
+
230
+ ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
231
+
232
+ # wait until we have input before we start the pager
233
+ Kernel.select [STDIN]
234
+ pager = ENV['PAGER'] || 'less'
235
+ exec pager rescue exec "/bin/sh", "-c", pager
236
+ rescue
237
+ end
238
+ end
239
+
240
+ Cheat.sheets(ARGV) if __FILE__ == $0
data/lib/diffr.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'diff/lcs'
3
+ require 'diff/lcs/hunk'
4
+
5
+ module Cheat
6
+ class Diffr
7
+ def self.diff(sheet_old, sheet_new)
8
+ format, lines, output = :unified, 10000, ''
9
+ file_length_difference = 0
10
+
11
+ data_old = sheet_old.body.wrap.split(/\n/).map! { |e| e.chomp }
12
+ data_new = sheet_new.body.wrap.split(/\n/).map! { |e| e.chomp }
13
+
14
+ diffs = Diff::LCS.diff(data_old, data_new)
15
+ return if diffs.empty?
16
+
17
+ header = ''
18
+ ft = sheet_old.updated_at
19
+ header << "#{'-' * 3} #{sheet_new.title} version #{sheet_old.version}\t#{ft}\n"
20
+ ft = sheet_new.updated_at
21
+ header << "#{'+' * 3} #{sheet_new.title} version #{sheet_new.version}\t#{ft}\n"
22
+
23
+ oldhunk = hunk = nil
24
+
25
+ diffs.each do |piece|
26
+ begin
27
+ hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, lines, file_length_difference)
28
+ file_length_difference = hunk.file_length_difference
29
+
30
+ next unless oldhunk
31
+
32
+ if lines > 0 && hunk.overlaps?(oldhunk)
33
+ hunk.unshift(oldhunk)
34
+ else
35
+ output << oldhunk.diff(format)
36
+ end
37
+ ensure
38
+ oldhunk = hunk
39
+ output << "\n"
40
+ end
41
+ end
42
+
43
+ output << oldhunk.diff(format)
44
+ output << "\n"
45
+
46
+ return header + output.lstrip
47
+ end
48
+ end
49
+ end
data/lib/responder.rb ADDED
@@ -0,0 +1,42 @@
1
+ # class Something < R 'route'
2
+ # include Responder
3
+ #
4
+ # def get
5
+ # ... important code ...
6
+ #
7
+ # respond_to do |wants|
8
+ # wants.html { render :something }
9
+ # wants.text { "Just some text." }
10
+ # wants.yaml { "Something neat!".to_yaml }
11
+ # wants.xml { "Also, XML.".to_xml }
12
+ # end
13
+ # end
14
+ # end
15
+ module Cheat::Controllers
16
+ module Responder
17
+ def respond_to
18
+ yield response = Response.new(env.HTTP_ACCEPT)
19
+ @headers['Content-Type'] = response.content_type
20
+ response.body
21
+ end
22
+
23
+ class Response
24
+ attr_reader :body, :content_type
25
+ def initialize(accept) @accept = accept end
26
+
27
+ TYPES = {
28
+ :yaml => %w[application/yaml text/yaml],
29
+ :text => %w[text/plain],
30
+ :html => %w[text/html */* application/html],
31
+ :xml => %w[application/xml]
32
+ }
33
+
34
+ def method_missing(method, *args)
35
+ if TYPES[method] && @accept =~ Regexp.union(*TYPES[method])
36
+ @content_type = TYPES[method].first
37
+ @body = yield if block_given?
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/site.rb ADDED
@@ -0,0 +1,611 @@
1
+ #
2
+ # According to Wikipedia, Cheat can refer to:
3
+ # Cheating, to take advantage of a situation by the breaking of accepted rules
4
+ # or standards
5
+ # Cheating (casino)
6
+ # Cheating in poker
7
+ # Cheating in online games
8
+ # In relationships, to have an affair
9
+ # A cheat code, a hidden means of gaining an advantage in a video game
10
+ # Cheating, parasitic abuse of symbiotic relationships
11
+ # The Cheat, a character in the cartoon series Homestar Runner
12
+ # Cheat!, a television show on the G4 network
13
+ # The Cheat, a 1915 Cecil B. DeMille movie about a wealthy and domineering
14
+ # Asian gentleman taking advantage of an American female
15
+ # Cheats, a 2002 comedy, starring Matthew Lawrence and Mary Tyler Moore
16
+ # Cheat, a song by The Clash from the UK version of their album The Clash
17
+ # Bullshit, sometimes known as "Cheat," a card game
18
+ # An alternate term for defection in the prisoner's dilemma in game theory
19
+ # Cheat River, a tributary of the Monongahela River in Appalachia; the Cheat
20
+ # starts in West Virginia, and flows westward
21
+ # Cheat Lake, a nearby resevoir
22
+ # Cheat Mountain, one of the highest mountains in the Alleghenies
23
+ #
24
+ %w[rubygems camping camping/db erb rubygems/open-uri acts_as_versioned wrap diffr responder ambition].each { |f| require f }
25
+ gem 'camping', '>=1.4.152'
26
+
27
+ Camping.goes :Cheat
28
+
29
+ # for defunkt. campistrano.
30
+ if ARGV.include? '--update'
31
+ ssh = 'ssh deploy@errtheblog.com'
32
+ puts `#{ssh} 'cd /var/www/cheat; svn up'`
33
+ system "#{ssh} 'sudo /etc/init.d/rv restart'"
34
+ exit
35
+ end
36
+
37
+ URL = ARGV.include?('debug') ? 'http://localhost:8020' : 'http://cheat.errtheblog.com'
38
+ FEED = 'http://feeds.feedburner.com/cheatsheets' # rss feed
39
+
40
+ module Cheat::Models
41
+ class Sheet < Base
42
+ validates_uniqueness_of :title
43
+ validates_format_of :title, :with => /^[a-z]+[a-z0-9_]*$/i
44
+ validates_presence_of :title, :body
45
+ before_save { |r| r.title = r.title.gsub(' ', '_').underscore.downcase }
46
+ acts_as_versioned
47
+ end
48
+
49
+ class SetUpUsTheCheat < V 1.0
50
+ def self.up
51
+ create_table :cheat_sheets, :force => true do |t|
52
+ t.column :id, :integer, :null => false
53
+ t.column :title, :string, :null => false
54
+ t.column :body, :text
55
+ t.column :created_at, :datetime, :null => false
56
+ t.column :updated_at, :datetime, :null => false
57
+ end
58
+ Sheet.create_versioned_table
59
+ Sheet.reset_column_information
60
+ end
61
+ def self.down
62
+ drop_table :cheat_sheets
63
+ Sheet.drop_versioned_table
64
+ end
65
+ end
66
+ end
67
+
68
+ module Cheat::Controllers
69
+ class APIShow < R '/y/(\w+)'
70
+ def get(title)
71
+ @headers['Content-Type'] = 'text/plain'
72
+
73
+ sheet = Sheet.detect { |s| s.title == title }
74
+ return { 'Error!' => "Cheat sheet `#{title}' not found." }.to_yaml unless sheet
75
+
76
+ return { sheet.title => sheet.body }.to_yaml
77
+ end
78
+ end
79
+
80
+ class APIRecent < R '/yr'
81
+ def get
82
+ @headers['Content-Type'] = 'text/plain'
83
+
84
+ sheets = Sheet.sort_by { |s| -s.created_at }.first(15).map(&:title)
85
+ return { 'Recent Cheat Sheets' => sheets }.to_yaml
86
+ end
87
+ end
88
+
89
+ class APIAll < R '/ya'
90
+ def get
91
+ @headers['Content-Type'] = 'text/plain'
92
+
93
+ sheets = Sheet.sort_by(&:title).map(&:title)
94
+ return { 'All Cheat Sheets' => sheets }.to_yaml
95
+ end
96
+ end
97
+
98
+ class Feed < R '/f'
99
+ def get
100
+ @headers['Content-Type'] = 'application/xml'
101
+ return Cheat::Views.feed
102
+ end
103
+ end
104
+
105
+ class Index < R '/'
106
+ def get
107
+ render :index
108
+ end
109
+ end
110
+
111
+ class Add < R '/a'
112
+ def get
113
+ @sheet = Sheet.new
114
+ render :add
115
+ end
116
+ end
117
+
118
+ class Edit < R '/e/(\w+)/(\d+)', '/e/(\w+)'
119
+ def get(title, version = nil)
120
+ @sheet = Sheet.detect { |s| s.title == title }
121
+
122
+ @error = "Cheat sheet not found." unless @sheet
123
+ unless version.nil? || version == @sheet.version.to_s
124
+ @sheet = @sheet.find_version(version)
125
+ end
126
+ render @error ? :error : :edit
127
+ end
128
+ end
129
+
130
+ class Write < R '/w', '/w/(\w+)'
131
+ def post(title = nil)
132
+ @sheet = title ? Sheet.find_by_title(title) : Sheet.new
133
+ @sheet = title ? Sheet.detect { |s| s.title == title } : Sheet.new
134
+
135
+ check_captcha! unless input.from_gem
136
+
137
+ if !@error && @sheet.update_attributes(:title => input.sheet_title, :body => input.sheet_body)
138
+ redirect "#{URL}/s/#{@sheet.title}"
139
+ else
140
+ @error = true
141
+ render title ? :edit : :add
142
+ end
143
+ end
144
+
145
+ def check_captcha!
146
+ @error ||= !(@cookies[:passed] ||= captcha_pass?(input.chunky, input.bacon))
147
+ end
148
+
149
+ def captcha_pass?(session, answer)
150
+ open("http://captchator.com/captcha/check_answer/#{session}/#{answer}").read.to_i.nonzero? rescue false
151
+ end
152
+ end
153
+
154
+ class Browse < R '/b'
155
+ def get
156
+ @sheets = Sheet.sort_by(&:title)
157
+ render :browse
158
+ end
159
+ end
160
+
161
+ class Show < R '/s/(\w+)', '/s/(\w+)/(\d+)'
162
+ def get(title, version = nil)
163
+ @sheet = Sheet.detect { |s| s.title == title }
164
+ @sheet = @sheet.find_version(version) if version && @sheet
165
+
166
+ @sheet ? render(:show) : redirect("#{URL}/b/")
167
+ end
168
+ end
169
+
170
+ # we are going to start consolidating classes with respond_to and what not.
171
+ # diff is the first, as the api and the site will use the same code
172
+ class Diff < R '/d/(\w+)/(\d+)', '/d/(\w+)/(\d+)/(\d+)'
173
+ include Responder
174
+
175
+ def get(title, old_version, new_version = nil)
176
+ redirect "#{URL}/b/" and return unless old_version.to_i.nonzero?
177
+
178
+ @sheet = Sheet.detect { |s| s.title == title }
179
+ @old_sheet = @sheet.find_version(old_version)
180
+ @new_sheet = (new_version ? @sheet.find_version(new_version) : @sheet)
181
+
182
+ @diffed = Diffr.diff(@old_sheet, @new_sheet) rescue nil
183
+
184
+ respond_to do |wants|
185
+ wants.html { render :diff }
186
+ wants.yaml { { @sheet.title => @diffed }.to_yaml }
187
+ end
188
+ end
189
+ end
190
+
191
+ class History < R '/h/(\w+)'
192
+ include Responder
193
+
194
+ def get(title)
195
+ if sheets = Sheet.detect { |s| s.title == title }
196
+ @sheets = sheets.find_versions(:order => 'version DESC')
197
+ end
198
+
199
+ respond_to do |wants|
200
+ wants.html { render :history }
201
+ wants.yaml { { @sheets.first.title => @sheets.map(&:version) }.to_yaml }
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ module Cheat::Views
208
+ def layout
209
+ html {
210
+ head {
211
+ _style
212
+ link :href => FEED, :rel => "alternate", :title => "Recently Updated Cheat Sheets", :type => "application/atom+xml"
213
+ title @page_title ? "$ cheat #{@page_title}" : "$ command line ruby cheat sheets"
214
+ }
215
+ body {
216
+ div.main {
217
+ div.header {
218
+ h1 { logo_link 'cheat sheets.' }
219
+ code.header @sheet_title ? "$ cheat #{@sheet_title}" : "$ command line ruby cheat sheets"
220
+ }
221
+ div.content { self << yield }
222
+ div.side { _side }
223
+ div.clear { '' }
224
+ div.footer { _footer }
225
+ }
226
+ _clicky
227
+ }
228
+ }
229
+ end
230
+
231
+ def _clicky
232
+ text '<script src="http://getclicky.com/1070.js"> </script><noscript><img height=0 width=0 src="http://getclicky.com/1070ns.gif"></noscript>'
233
+ end
234
+
235
+ def error
236
+ @page_title = "error"
237
+ p "An error:"
238
+ code.version @error
239
+ p ":("
240
+ end
241
+
242
+ def show
243
+ @page_title = @sheet.title
244
+ @sheet_title = @sheet.title
245
+ pre.sheet { text h(@sheet.body.wrap) }
246
+ div.version {
247
+ text "Version "
248
+ strong sheet.version
249
+ text ", updated "
250
+ text last_updated(@sheet)
251
+ text " ago. "
252
+ br
253
+ text ". o 0 ( "
254
+ if @sheet.version == current_sheet.version
255
+ a "edit", :href => R(Edit, @sheet.title)
256
+ end
257
+ if @sheet.version > 1
258
+ text " | "
259
+ a "previous", :href => R(Show, @sheet.title, @sheet.version - 1)
260
+ end
261
+ text " | "
262
+ a "history", :href => R(History, @sheet.title)
263
+ unless @sheet.version == current_sheet.version
264
+ text " | "
265
+ a "revert to", :href => R(Edit, @sheet.title, @sheet.version)
266
+ text " | "
267
+ a "current", :href => R(Show, @sheet.title)
268
+ end
269
+ diff_version =
270
+ if @sheet.version == current_sheet.version
271
+ @sheet.version == 1 ? nil : @sheet.version - 1
272
+ else
273
+ @sheet.version
274
+ end
275
+ if diff_version
276
+ text " | "
277
+ a "diff", :href => R(Diff, @sheet.title, diff_version)
278
+ end
279
+ text " )"
280
+ }
281
+ end
282
+
283
+ def diff
284
+ @page_title = @sheet.title
285
+ @sheet_title = @sheet.title
286
+ pre.sheet { color_diff(h(@diffed)) if @diffed }
287
+ div.version {
288
+ text ". o 0 ("
289
+ if @old_sheet.version > 1
290
+ a "diff previous", :href => R(Diff, @sheet.title, @old_sheet.version - 1)
291
+ text " | "
292
+ end
293
+ a "history", :href => R(History, @sheet.title)
294
+ text " | "
295
+ a "current", :href => R(Show, @sheet.title)
296
+ text " )"
297
+ }
298
+ end
299
+
300
+ def browse
301
+ @page_title = "browse"
302
+ p { "Wowzers, we've got <strong>#{@sheets.size}</strong> cheat sheets hereabouts." }
303
+ ul {
304
+ @sheets.each do |sheet|
305
+ li { sheet_link sheet.title }
306
+ end
307
+ }
308
+ p {
309
+ text "Are we missing a cheat sheet? Why don't you do the whole world a favor and "
310
+ a "add it", :href => R(Add)
311
+ text " yourself!"
312
+ }
313
+ end
314
+
315
+ def history
316
+ @page_title = "history"
317
+ @sheet_title = @sheets.first.title
318
+ h2 @sheets.first.title
319
+ ul {
320
+ @sheets.each_with_index do |sheet, i|
321
+ li {
322
+ a "version #{sheet.version}", :href => R(Show, sheet.title, sheet.version)
323
+ text " - created "
324
+ text last_updated(sheet)
325
+ text " ago"
326
+ strong " (current)" if i.zero?
327
+ text " "
328
+ a "(diff to current)", :href => R(Diff, sheet.title, sheet.version) if i.nonzero?
329
+ }
330
+ end
331
+ }
332
+ end
333
+
334
+ def add
335
+ @page_title = "add"
336
+ p {
337
+ text "Thanks for wanting to add a cheat sheet. If you need an example of
338
+ the standard cheat sheet format, check out the "
339
+ a "cheat", :href => R(Show, 'cheat')
340
+ text " cheat sheet. (There's really no standard format, though)."
341
+ }
342
+ _form
343
+ end
344
+
345
+ def edit
346
+ @page_title = "edit"
347
+ _form
348
+ end
349
+
350
+ def _form
351
+ if @error
352
+ p.error {
353
+ strong "HEY! "
354
+ text "Something is wrong! You can't give your cheat sheet the same name
355
+ as another, alphanumeric titles only, and you need to make sure
356
+ you filled in all (two) of the fields. Okay?"
357
+ }
358
+ end
359
+ form :method => 'post', :action => R(Write, @sheet.title) do
360
+ p {
361
+ p {
362
+ text 'Cheat Sheet Title: '
363
+ input :value => @sheet.title, :name => 'sheet_title', :size => 30,
364
+ :type => 'text'
365
+ small " [ no_spaces_alphanumeric_only ]"
366
+ }
367
+ p {
368
+ text 'Cheat Sheet:'
369
+ br
370
+ textarea @sheet.body, :name => 'sheet_body', :cols => 80, :rows => 30
371
+ unless @cookies[:passed]
372
+ random = rand(10_000)
373
+ br
374
+ img :src => "http://captchator.com/captcha/image/#{random}"
375
+ input :name => 'chunky', :type => 'hidden', :value => random
376
+ input :name => 'bacon', :size => 10, :type => 'text'
377
+ end
378
+ }
379
+ }
380
+ p "Your cheat sheet will be editable (fixable) by anyone. Each cheat
381
+ sheet is essentially a wiki page. It may also be used by millions of
382
+ people for reference purposes from the comfort of their command line.
383
+ If this is okay with you, please save."
384
+ input :value => "Save the Damn Thing!", :name => "save", :type => 'submit'
385
+ end
386
+ end
387
+
388
+ def index
389
+ p {
390
+ text "Welcome. You've reached the central repository for "
391
+ strong "cheat"
392
+ text ", the RubyGem which puts Ruby-centric cheat sheets right into your
393
+ terminal. The inaugural blog entry "
394
+ a "is here", :href => "http://errtheblog.com/post/23"
395
+ text "."
396
+ }
397
+ p "Get started:"
398
+ code "$ sudo gem install cheat"
399
+ br
400
+ code "$ cheat strftime"
401
+ p "A magnificent cheat sheet for Ruby's strftime method will be printed to
402
+ your terminal."
403
+ p "To get some help on cheat itself:"
404
+ code "$ cheat cheat"
405
+ p "How meta."
406
+ p {
407
+ text "Cheat sheets are basically wiki pages accessible from the command
408
+ line. You can "
409
+ a 'browse', :href => R(Browse)
410
+ text ', '
411
+ a 'add', :href => R(Add)
412
+ text ', or '
413
+ a 'edit', :href => R(Edit, 'cheat')
414
+ text ' cheat sheets. Try to keep them concise. For a style guide, check
415
+ out the '
416
+ a 'cheat', :href => R(Edit, 'cheat')
417
+ text ' cheat sheet.'
418
+ }
419
+ p "To access a cheat sheet, simply pass the program the desired sheet's
420
+ name:"
421
+ code "$ cheat <sheet name>"
422
+ p
423
+ end
424
+
425
+ def self.feed
426
+ xml = Builder::XmlMarkup.new(:indent => 2)
427
+
428
+ xml.instruct!
429
+ xml.feed "xmlns"=>"http://www.w3.org/2005/Atom" do
430
+
431
+ xml.title "Recently Updated Cheat Sheets"
432
+ xml.id URL + '/'
433
+ xml.link "rel" => "self", "href" => FEED
434
+
435
+ sheets = Cheat::Models::Sheet.sort_by { |s| -s.updated_at }.first(20)
436
+ xml.updated sheets.first.updated_at.xmlschema
437
+
438
+ sheets.each do |sheet|
439
+ xml.entry do
440
+ xml.id URL + '/s/' + sheet.title
441
+ xml.title sheet.title
442
+ xml.author { xml.name "An Anonymous Cheater" }
443
+ xml.updated sheet.updated_at.xmlschema
444
+ xml.link "rel" => "alternate", "href" => URL + '/s/' + sheet.title
445
+ xml.summary "A cheat sheet about #{sheet.title}. Run it: `$ cheat #{sheet.title}'"
446
+ xml.content 'type' => 'html' do
447
+ xml.text! sheet.body.gsub("\n", '<br/>').gsub("\r", '')
448
+ end
449
+ end
450
+ end
451
+ end
452
+ end
453
+
454
+ def _side
455
+ text '( '
456
+ a 'add new', :href => R(Add)
457
+ text ' | '
458
+ a 'see all', :href => R(Browse)
459
+ text ' )'
460
+ ul {
461
+ li { strong "updated sheets" }
462
+ li do
463
+ a :href => FEED do
464
+ img(:border => 0, :alt => "Recently Updated Cheat Sheets Feed", :src => "http://errtheblog.com/images/feed.png")
465
+ end
466
+ end
467
+ recent_sheets.each do |sheet|
468
+ li { sheet_link sheet.title }
469
+ end
470
+ }
471
+ end
472
+
473
+ def _footer
474
+ text "Powered by "
475
+ a 'Camping', :href => "http://code.whytheluckystiff.net/camping"
476
+ text ", "
477
+ a 'Mongrel', :href => "http://mongrel.rubyforge.org/"
478
+ text " and, to a lesser extent, "
479
+ a 'Err the Blog', :href => "http://errtheblog.com/"
480
+ text "."
481
+ end
482
+
483
+ def _style
484
+ bg = "#fff"
485
+ h1 = "#4fa3da"
486
+ link = h1
487
+ hover = "#f65077"
488
+ dash = hover
489
+ version = "#fcf095"
490
+ style :type => "text/css" do
491
+ text %[
492
+ body { font-family: verdana, sans-serif; background-color: #{bg};
493
+ line-height: 20px; }
494
+ a:link, a:visited { color: #{link}; }
495
+ a:hover { text-decoration: none; color: #{hover}; }
496
+ div.header { border-bottom: 1px dashed #{dash}; }
497
+ code.header { margin-left: 30px; font-weight: bold;
498
+ background-color: #{version}; }
499
+ h1 { font-size: 5em; margin: 0; padding-left: 30px; color: #{h1};
500
+ clear: both; font-weight: bold; letter-spacing: -5px; }
501
+ h1 a { text-decoration: none; }
502
+ div.main { float: left; width: 100%; }
503
+ div.content { float: left; width: 70%; padding: 15px 0 15px 30px;
504
+ line-height: 20px; }
505
+ div.side { float: left; padding: 10px; text-align: right;
506
+ width: 20%; }
507
+ div.footer { text-align: center; border-top: 1px dashed #{dash};
508
+ padding-top: 10px; font-size: small; }
509
+ div.sheet { font-size: .8em; line-height: 17px; padding: 5px;
510
+ font-family: courier, fixed-width; background-color: #e8e8e8; }
511
+ pre.sheet { line-height: 15px; }
512
+ li { list-style: none; }
513
+ div.version { background-color: #{version}; padding: 5px;
514
+ width: 450px; margin-top: 50px; }
515
+ p.error { background-color: #{version}; padding: 5px; }
516
+ div.clear { clear: both; }
517
+ div.clear_10 { clear: both; font-size: 10px; line-height: 10px; }
518
+ textarea { font-family: courier; }
519
+ code { background-color: #{version} }
520
+ span.diff_cut { color: #f65077; }
521
+ span.diff_add { color: #009933; }
522
+ @media print {
523
+ .side, .version, .footer { display: none; }
524
+ div.content { width: 100%; }
525
+ h1 a:link, h1 a:visited { color: #eee;}
526
+ .header code { font-size: 18px; background: none; }
527
+ div.header { border-bottom: none; }
528
+ }
529
+ ].gsub(/(\s{2,})/, '').gsub("\n", '')
530
+ end
531
+ end
532
+ end
533
+
534
+ module Cheat::Helpers
535
+ def logo_link(title)
536
+ ctr = Cheat::Controllers
537
+ if @sheet && !@sheet.new_record? && @sheet.version != current_sheet.version
538
+ a title, :href => R(ctr::Show, @sheet.title)
539
+ else
540
+ a title, :href => R(ctr::Index)
541
+ end
542
+ end
543
+
544
+ def current_sheet
545
+ title = @sheet.title
546
+ @current_sheet ||= Cheat::Models::Sheet.detect { |s| s.title == title }
547
+ end
548
+
549
+ def recent_sheets
550
+ Cheat::Models::Sheet.sort_by { |s| -s.updated_at }.first(15)
551
+ end
552
+
553
+ def sheet_link(title, version = nil)
554
+ a title, :href => R(Cheat::Controllers::Show, title, version)
555
+ end
556
+
557
+ def last_updated(sheet)
558
+ from = sheet.updated_at.to_i
559
+ to = Time.now.to_i
560
+ from = from.to_time if from.respond_to?(:to_time)
561
+ to = to.to_time if to.respond_to?(:to_time)
562
+ distance = (((to - from).abs)/60).round
563
+ case distance
564
+ when 0..1 then return (distance==0) ? 'less than a minute' : '1 minute'
565
+ when 2..45 then "#{distance} minutes"
566
+ when 46..90 then 'about 1 hour'
567
+ when 90..1440 then "about #{(distance.to_f / 60.0).round} hours"
568
+ when 1441..2880 then '1 day'
569
+ else "#{(distance / 1440).round} days"
570
+ end
571
+ end
572
+
573
+ def self.h(text)
574
+ ERB::Util::h(text)
575
+ end
576
+
577
+ def h(text)
578
+ ::Cheat::Helpers.h(text)
579
+ end
580
+
581
+ def color_diff(diff)
582
+ diff.split("\n").map do |line|
583
+ action = case line
584
+ when /^-/ then 'cut'
585
+ when /^\+/ then 'add'
586
+ end
587
+ action ? span.send("diff_#{action}", line) : line
588
+ end * "\n"
589
+ end
590
+ end
591
+
592
+ def Cheat.create
593
+ Cheat::Models.create_schema
594
+ end
595
+
596
+ if __FILE__ == $0
597
+ begin
598
+ require 'mongrel/camping'
599
+ rescue LoadError => e
600
+ abort "** Try running `camping #$0' instead."
601
+ end
602
+
603
+ Cheat::Models::Base.establish_connection :adapter => 'mysql', :user => 'root', :database => 'camping', :host => 'localhost'
604
+ Cheat::Models::Base.logger = nil
605
+ Cheat::Models::Base.threaded_connections = false
606
+ Cheat.create
607
+
608
+ server = Mongrel::Camping.start("0.0.0.0", 8020, "/", Cheat)
609
+ puts "** Cheat is running at http://0.0.0.0:8020/"
610
+ server.run.join
611
+ end
data/lib/wrap.rb ADDED
@@ -0,0 +1,42 @@
1
+ # >> Evan Weaver
2
+ # => http://blog.evanweaver.com/articles/2006/09/03/smart-plaintext-wrapping
3
+ class String
4
+ def wrap(width = 80, hanging_indent = 0, magic_lists = false)
5
+ lines = self.split(/\n/)
6
+
7
+ lines.collect! do |line|
8
+
9
+ if magic_lists
10
+ line =~ /^([\s\-\d\.\:]*\s)/
11
+ else
12
+ line =~ /^([\s]*\s)/
13
+ end
14
+
15
+ indent = $1.length + hanging_indent rescue hanging_indent
16
+
17
+ buffer = ""
18
+ first = true
19
+
20
+ while line.length > 0
21
+ first ? (i, first = 0, false) : i = indent
22
+ pos = width - i
23
+
24
+ if line.length > pos and line[0..pos] =~ /^(.+)\s/
25
+ subline = $1
26
+ else
27
+ subline = line[0..pos]
28
+ end
29
+ buffer += " " * i + subline + "\n"
30
+ line.tail!(subline.length)
31
+ end
32
+ buffer[0..-2]
33
+ end
34
+
35
+ lines.join("\n")
36
+ end
37
+
38
+ def tail!(pos)
39
+ self[0..pos] = ""
40
+ strip!
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tarsius-cheat
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Chris Wanstrath
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ date: 2006-11-10 00:00:00 -08:00
12
+ default_executable: cheat
13
+ dependencies: []
14
+
15
+ description: Cheat is a simple command line utility reference program. Use it to, well, cheat.
16
+ email: chris@ozmm.org
17
+ executables:
18
+ - cheat
19
+ extensions: []
20
+
21
+ extra_rdoc_files: []
22
+
23
+ files:
24
+ - README
25
+ - LICENSE
26
+ - bin/cheat
27
+ - lib/cheat
28
+ - lib/cheat.rb
29
+ - lib/diffr.rb
30
+ - lib/new_cheat.rb
31
+ - lib/responder.rb
32
+ - lib/site.rb
33
+ - lib/wrap.rb
34
+ - lib/cheat/commands.rb
35
+ - test/fixtures
36
+ - test/test_cheat.rb
37
+ has_rdoc: false
38
+ homepage: http://cheat.errtheblog.com/
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">"
47
+ - !ruby/object:Gem::Version
48
+ version: 0.0.0
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.2.0
60
+ signing_key:
61
+ specification_version: 1
62
+ summary: Cheat is a simple command line utility reference program.
63
+ test_files: []
64
+