cheat 1.1.0 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/lib/cheat.rb +61 -20
  2. data/lib/diffr.rb +49 -0
  3. data/lib/responder.rb +42 -0
  4. data/lib/site.rb +95 -45
  5. data/lib/wrap.rb +1 -1
  6. metadata +4 -2
@@ -4,13 +4,41 @@ $:.unshift File.dirname(__FILE__)
4
4
  module Cheat
5
5
  extend self
6
6
 
7
- HOST = 'cheat.errtheblog.com'
8
- PORT = 80
7
+ HOST = ARGV.include?('debug') ? 'localhost' : 'cheat.errtheblog.com'
8
+ PORT = ARGV.include?('debug') ? 3001 : 80
9
9
  SUFFIX = ''
10
10
 
11
11
  def sheets(args)
12
12
  args = args.dup
13
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
+ uri = uri.sub('/y/', sheet == 'recent' ? '/yr/' : '/ya/')
22
+ return open(uri) { |body| show(body.read) }
23
+ end
24
+
25
+ return show(File.read(cache_file)) if File.exists?(cache_file) rescue clear_cache if cache_file
26
+
27
+ fetch_sheet(uri, @sheet) if @sheet
28
+ end
29
+
30
+ def fetch_sheet(uri, try_to_cache = true)
31
+ open(uri, headers) do |body|
32
+ sheet = body.read
33
+ File.open(cache_file, 'w') { |f| f.write(sheet) } if try_to_cache && cache_file && !@edit
34
+ @edit ? edit(sheet) : show(sheet)
35
+ end
36
+ exit
37
+ rescue OpenURI::HTTPError => e
38
+ puts "Whoa, some kind of Internets error!", "=> #{e} from #{uri}"
39
+ end
40
+
41
+ def parse_args(args)
14
42
  puts "Looking for help? Try http://cheat.errtheblog.com or `$ cheat cheat'" and return if args.empty?
15
43
 
16
44
  if args.delete('--clear-cache') || args.delete('--new')
@@ -18,31 +46,42 @@ module Cheat
18
46
  return if args.empty?
19
47
  end
20
48
 
49
+ if i = args.index('--diff')
50
+ diff_sheets(args.first, args[i+1])
51
+ end
52
+
53
+ show_versions(args.first) if args.delete('--versions')
54
+
21
55
  add(args.shift) and return if args.delete('--add')
56
+ clear_cache if @edit = args.delete('--edit')
22
57
 
23
- if edit = args.delete('--edit')
24
- clear_cache
25
- end
58
+ @sheet = args.shift
26
59
 
27
- sheet = args.shift
60
+ true
61
+ end
28
62
 
29
- cache_file = "#{cache_dir}/#{sheet}.yml" if cache_dir
30
- FileUtils.mkdir(cache_dir) unless File.exists?(cache_dir) if cache_dir
63
+ # $ cheat greader --versions
64
+ def show_versions(sheet)
65
+ fetch_sheet("http://#{cheat_uri}/h/#{sheet}/")
66
+ end
31
67
 
32
- uri = "http://#{cheat_uri}/y/"
68
+ # $ cheat greader --diff 1[:3]
69
+ def diff_sheets(sheet, version)
70
+ return unless version =~ /^(\d+)(:(\d+))?$/
71
+ old_version, new_version = $1, $3
33
72
 
34
- if %w[sheets all recent].include? sheet
35
- uri = uri.sub('/y/', sheet == 'recent' ? '/yr/' : '/ya/')
36
- return open(uri) { |body| show(body.read) }
37
- end
73
+ uri = "http://#{cheat_uri}/d/#{sheet}/#{old_version}"
74
+ uri += "/#{new_version}" if new_version
38
75
 
39
- return show(File.read(cache_file)) if File.exists?(cache_file) rescue clear_cache if cache_file
76
+ fetch_sheet(uri, false)
77
+ end
40
78
 
41
- open(uri += sheet) do |body|
42
- sheet = body.read
43
- File.open(cache_file, 'w') { |f| f.write(sheet) } if cache_file && !edit
44
- edit ? edit(sheet) : show(sheet)
45
- end if sheet
79
+ def cache_file
80
+ "#{cache_dir}/#{@sheet}.yml" if cache_dir
81
+ end
82
+
83
+ def headers
84
+ { 'User-Agent' => 'cheat!', 'Accept' => 'text/yaml' }
46
85
  end
47
86
 
48
87
  def cheat_uri
@@ -53,7 +92,9 @@ module Cheat
53
92
  sheet = YAML.load(sheet_yaml).to_a.first
54
93
  sheet[-1] = sheet.last.join("\n") if sheet[-1].is_a?(Array)
55
94
  puts sheet.first + ':'
56
- puts ' ' + sheet.last.gsub("\r",'').gsub("\n", "\n ").wrap(80)
95
+ puts ' ' + sheet.last.gsub("\r",'').gsub("\n", "\n ").wrap
96
+ rescue
97
+ puts "That didn't work. Try something else?"
57
98
  end
58
99
 
59
100
  def edit(sheet_yaml)
@@ -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
@@ -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
@@ -21,12 +21,12 @@
21
21
  # Cheat Lake, a nearby resevoir
22
22
  # Cheat Mountain, one of the highest mountains in the Alleghenies
23
23
  #
24
- %w[rubygems camping camping/db erb open-uri acts_as_versioned wrap].each { |f| require f }
24
+ %w[rubygems camping camping/db erb open-uri acts_as_versioned wrap diffr responder].each { |f| require f }
25
25
  require_gem 'camping', '>=1.4.152'
26
26
 
27
27
  Camping.goes :Cheat
28
28
 
29
- URL = 'http://cheat.errtheblog.com' # need this to get around exposing :port in redirects
29
+ URL = ARGV.include?('debug') ? 'http://localhost:3001' : 'http://cheat.errtheblog.com'
30
30
  FEED = 'http://feeds.feedburner.com/cheatsheets' # rss feed
31
31
 
32
32
  module Cheat::Models
@@ -108,14 +108,11 @@ module Cheat::Controllers
108
108
  class Edit < R '/e/(\w+)/(\d+)', '/e/(\w+)'
109
109
  def get(title, version = nil)
110
110
  @sheet = Sheet.find_by_title(title)
111
- unless @sheet
112
- @error = "Cheat sheet not found."
113
- return render(:error)
114
- end
111
+ @error = "Cheat sheet not found." unless @sheet
115
112
  unless version.nil? || version == @sheet.version.to_s
116
113
  @sheet = @sheet.find_version(version)
117
114
  end
118
- render :edit
115
+ render @error ? :error : :edit
119
116
  end
120
117
  end
121
118
 
@@ -125,12 +122,9 @@ module Cheat::Controllers
125
122
 
126
123
  @error = input.sheet_body =~ /<a\s*href=/
127
124
 
128
- unless input.from_gem
129
- @error = true unless captcha_pass?(input.chunky, input.bacon)
130
- end
125
+ check_captcha! unless input.from_gem
131
126
 
132
- if !@error && @sheet.update_attributes(:title => input.sheet_title,
133
- :body => input.sheet_body)
127
+ if !@error && @sheet.update_attributes(:title => input.sheet_title, :body => input.sheet_body)
134
128
  redirect "#{URL}/s/#{@sheet.title}"
135
129
  else
136
130
  @error = true
@@ -138,6 +132,10 @@ module Cheat::Controllers
138
132
  end
139
133
  end
140
134
 
135
+ def check_captcha!
136
+ @error ||= !(@cookies[:passed] ||= captcha_pass?(input.chunky, input.bacon))
137
+ end
138
+
141
139
  def captcha_pass?(session, answer)
142
140
  open("http://captchator.ruby-forum.com/captcha/check_answer/#{session}/#{answer}").read.to_i.nonzero? rescue false
143
141
  end
@@ -150,31 +148,46 @@ module Cheat::Controllers
150
148
  end
151
149
  end
152
150
 
153
- class Show < R '/s/(\w+)', '/s/(\w+)/(\d+)', '/s/(\w+\.txt)'
151
+ class Show < R '/s/(\w+)', '/s/(\w+)/(\d+)'
154
152
  def get(title, version = nil)
155
- # fake respond_to
156
- if title =~ /\.txt/ && title.sub!('.txt', '').size
157
- text = true
158
- @headers['Content-Type'] = 'text/plain'
159
- end
160
-
161
153
  @sheet = Sheet.find_by_title(title)
162
154
  @sheet = @sheet.find_version(version) if version && @sheet
163
155
 
164
- if @sheet && text
165
- return @sheet.body
166
- elsif @sheet
167
- render :show
168
- else
169
- redirect "#{URL}/b/"
156
+ @sheet ? render(:show) : redirect("#{URL}/b/")
157
+ end
158
+ end
159
+
160
+ # we are going to start consolidating classes with respond_to and what not.
161
+ # diff is the first, as the api and the site will use the same code
162
+ class Diff < R '/d/(\w+)/(\d+)', '/d/(\w+)/(\d+)/(\d+)'
163
+ include Responder
164
+
165
+ def get(title, old_version, new_version = nil)
166
+ redirect "#{URL}/b/" and return unless old_version.to_i.nonzero?
167
+
168
+ @sheet = Sheet.find_by_title(title)
169
+ @old_sheet = @sheet.find_version(old_version)
170
+ @new_sheet = (new_version ? @sheet.find_version(new_version) : @sheet)
171
+
172
+ @diffed = Diffr.diff(@old_sheet, @new_sheet) rescue nil
173
+
174
+ respond_to do |wants|
175
+ wants.html { render :diff }
176
+ wants.yaml { { @sheet.title => @diffed }.to_yaml }
170
177
  end
171
178
  end
172
179
  end
173
180
 
174
181
  class History < R '/h/(\w+)'
182
+ include Responder
183
+
175
184
  def get(title)
176
185
  @sheets = Sheet.find_by_title(title).find_versions(:order => 'version DESC')
177
- render :history
186
+
187
+ respond_to do |wants|
188
+ wants.html { render :history }
189
+ wants.yaml { { @sheets.first.title => @sheets.map { |s| s.version } }.to_yaml }
190
+ end
178
191
  end
179
192
  end
180
193
  end
@@ -185,15 +198,13 @@ module Cheat::Views
185
198
  head {
186
199
  _style
187
200
  link :href => FEED, :rel => "alternate", :title => "Recently Updated Cheat Sheets", :type => "application/atom+xml"
188
- title defined?(@page_title) ? "$ cheat #{@page_title}" : \
189
- "$ command line ruby cheat sheets"
201
+ title @page_title ? "$ cheat #{@page_title}" : "$ command line ruby cheat sheets"
190
202
  }
191
203
  body {
192
204
  div.main {
193
205
  div.header {
194
206
  h1 { logo_link 'cheat sheets.' }
195
- code.header defined?(@sheet_title) ? "$ cheat #{@sheet_title}" : \
196
- "$ command line ruby cheat sheets"
207
+ code.header @sheet_title ? "$ cheat #{@sheet_title}" : "$ command line ruby cheat sheets"
197
208
  }
198
209
  div.content { self << yield }
199
210
  div.side { _side }
@@ -214,7 +225,7 @@ module Cheat::Views
214
225
  def show
215
226
  @page_title = @sheet.title
216
227
  @sheet_title = @sheet.title
217
- pre.sheet { text h(sheet.body.wrap(80)) }
228
+ pre.sheet { text h(@sheet.body.wrap) }
218
229
  div.version {
219
230
  text "Version "
220
231
  strong sheet.version
@@ -223,26 +234,49 @@ module Cheat::Views
223
234
  text " ago. "
224
235
  br
225
236
  text ". o 0 ( "
226
- a "edit", :href => R(Edit, @sheet.title)
227
- if @sheet.version > 1
237
+ if @sheet.version == current_sheet.version
238
+ a "edit", :href => R(Edit, @sheet.title)
228
239
  text " | "
240
+ end
241
+ if @sheet.version > 1
229
242
  a "previous", :href => R(Show, @sheet.title, @sheet.version - 1)
230
243
  text " | "
231
- a "history", :href => R(History, @sheet.title)
232
244
  end
233
- if @sheet.version != (current = current_sheet).version
234
- text " | "
245
+ a "history", :href => R(History, @sheet.title)
246
+ text " | "
247
+ unless @sheet.version == current_sheet.version
235
248
  a "revert to", :href => R(Edit, @sheet.title, @sheet.version)
236
249
  text " | "
237
250
  a "current", :href => R(Show, @sheet.title)
238
- end
239
- if @sheet.version == current_sheet.version
240
251
  text " | "
241
- a "text", :href => R(Show, @sheet.title + '.txt')
242
252
  end
253
+ diff_version =
254
+ if @sheet.version == current_sheet.version
255
+ @sheet.version == 1 ? nil : @sheet.version - 1
256
+ else
257
+ @sheet.version
258
+ end
259
+ a "diff", :href => R(Diff, @sheet.title, diff_version) if diff_version
243
260
  text " )"
244
261
  }
245
262
  end
263
+
264
+ def diff
265
+ @page_title = @sheet.title
266
+ @sheet_title = @sheet.title
267
+ pre.sheet { color_diff(@diffed) if @diffed }
268
+ div.version {
269
+ text ". o 0 ("
270
+ if @old_sheet.version > 1
271
+ a "diff previous", :href => R(Diff, @sheet.title, @old_sheet.version - 1)
272
+ text " | "
273
+ end
274
+ a "history", :href => R(History, @sheet.title)
275
+ text " | "
276
+ a "current", :href => R(Show, @sheet.title)
277
+ text " )"
278
+ }
279
+ end
246
280
 
247
281
  def browse
248
282
  @page_title = "browse"
@@ -269,7 +303,9 @@ module Cheat::Views
269
303
  text " - created "
270
304
  text last_updated(sheet)
271
305
  text " ago"
272
- strong " (current)" if i == 0
306
+ strong " (current)" if i.zero?
307
+ text " "
308
+ a "(diff to current)", :href => R(Diff, sheet.title, sheet.version) if i.nonzero?
273
309
  }
274
310
  end
275
311
  }
@@ -312,11 +348,13 @@ module Cheat::Views
312
348
  text 'Cheat Sheet:'
313
349
  br
314
350
  textarea @sheet.body, :name => 'sheet_body', :cols => 80, :rows => 30
315
- random = rand(10_000)
316
- br
317
- img :src => "http://captchator.ruby-forum.com/captcha/image/#{random}"
318
- input :name => 'chunky', :type => 'hidden', :value => random
319
- input :name => 'bacon', :size => 10, :type => 'text'
351
+ unless @cookies[:passed]
352
+ random = rand(10_000)
353
+ br
354
+ img :src => "http://captchator.ruby-forum.com/captcha/image/#{random}"
355
+ input :name => 'chunky', :type => 'hidden', :value => random
356
+ input :name => 'bacon', :size => 10, :type => 'text'
357
+ end
320
358
  }
321
359
  }
322
360
  p "Your cheat sheet will be editable (fixable) by anyone. Each cheat
@@ -459,6 +497,8 @@ module Cheat::Views
459
497
  div.clear_10 { clear: both; font-size: 10px; line-height: 10px; }
460
498
  textarea { font-family: courier; }
461
499
  code { background-color: #{version} }
500
+ span.diff_cut { color: #f65077; }
501
+ span.diff_add { color: #009933; }
462
502
  @media print {
463
503
  .side, .version, .footer { display: none; }
464
504
  div.content { width: 100%; }
@@ -516,6 +556,16 @@ module Cheat::Helpers
516
556
  def h(text)
517
557
  ::Cheat::Helpers.h(text)
518
558
  end
559
+
560
+ def color_diff(diff)
561
+ diff.split("\n").map do |line|
562
+ action = case line
563
+ when /^-/ then 'cut'
564
+ when /^\+/ then 'add'
565
+ end
566
+ action ? span.send("diff_#{action}", line) : line
567
+ end * "\n"
568
+ end
519
569
  end
520
570
 
521
571
  def Cheat.create
@@ -1,7 +1,7 @@
1
1
  # >> Evan Weaver
2
2
  # => http://blog.evanweaver.com/articles/2006/09/03/smart-plaintext-wrapping
3
3
  class String
4
- def wrap(width, hanging_indent = 0, magic_lists = false)
4
+ def wrap(width = 80, hanging_indent = 0, magic_lists = false)
5
5
  lines = self.split(/\n/)
6
6
 
7
7
  lines.collect! do |line|
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: cheat
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.1.0
7
- date: 2006-10-15 00:00:00 -07:00
6
+ version: "1.2"
7
+ date: 2006-11-09 00:00:00 -08:00
8
8
  summary: Cheat is a simple command line utility reference program.
9
9
  require_paths:
10
10
  - lib
@@ -33,6 +33,8 @@ files:
33
33
  - LICENSE
34
34
  - bin/cheat
35
35
  - lib/cheat.rb
36
+ - lib/diffr.rb
37
+ - lib/responder.rb
36
38
  - lib/site.rb
37
39
  - lib/wrap.rb
38
40
  - test/fixtures