tarsius-cheat 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +18 -0
- data/README +38 -0
- data/bin/cheat +4 -0
- data/lib/cheat.rb +240 -0
- data/lib/diffr.rb +49 -0
- data/lib/responder.rb +42 -0
- data/lib/site.rb +611 -0
- data/lib/wrap.rb +42 -0
- 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
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
|
+
|