cheat 1.1.0 → 1.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 (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