tarsius-cheat 1.2.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.
- 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
|
+
|