public
Description: Cheating is fun!
Homepage: http://cheat.errtheblog.com
Clone URL: git://github.com/defunkt/cheat.git
cheat / lib / cheat.rb
100644 200 lines (158 sloc) 5.232 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
$:.unshift File.dirname(__FILE__)
%w[rubygems tempfile fileutils net/http yaml open-uri wrap].each { |f| require f }
 
module Cheat
  extend self
 
  HOST = ARGV.include?('debug') ? 'localhost' : 'cheat.errtheblog.com'
  PORT = ARGV.include?('debug') ? 3001 : 80
  SUFFIX = ''
 
  def sheets(args)
    args = args.dup
 
    return unless parse_args(args)
 
    FileUtils.mkdir(cache_dir) unless File.exists?(cache_dir) if cache_dir
 
    uri = "http://#{cheat_uri}/y/"
 
    if %w[sheets all recent].include? @sheet
      uri = uri.sub('/y/', @sheet == 'recent' ? '/yr/' : '/ya/')
      return open(uri) { |body| show(body.read) }
    end
 
    return show(File.read(cache_file)) if File.exists?(cache_file) rescue clear_cache if cache_file
 
    fetch_sheet(uri + @sheet) if @sheet
  end
 
  def fetch_sheet(uri, try_to_cache = true)
    open(uri, headers) do |body|
      sheet = body.read
      File.open(cache_file, 'w') { |f| f.write(sheet) } if try_to_cache && cache_file && !@edit
      @edit ? edit(sheet) : show(sheet)
    end
    exit
  rescue OpenURI::HTTPError => e
    puts "Whoa, some kind of Internets error!", "=> #{e} from #{uri}"
  end
 
  def parse_args(args)
    puts "Looking for help? Try http://cheat.errtheblog.com or `$ cheat cheat'" and return if args.empty?
 
    if args.delete('--clear-cache') || args.delete('--new')
      clear_cache
      return if args.empty?
    end
 
    if i = args.index('--diff')
      diff_sheets(args.first, args[i+1])
    end
 
    show_versions(args.first) if args.delete('--versions')
 
    add(args.shift) and return if args.delete('--add')
    clear_cache if @edit = args.delete('--edit')
 
    @sheet = args.shift
 
    true
  end
 
  # $ cheat greader --versions
  def show_versions(sheet)
    fetch_sheet("http://#{cheat_uri}/h/#{sheet}/", false)
  end
 
  # $ cheat greader --diff 1[:3]
  def diff_sheets(sheet, version)
    return unless version =~ /^(\d+)(:(\d+))?$/
    old_version, new_version = $1, $3
 
    uri = "http://#{cheat_uri}/d/#{sheet}/#{old_version}"
    uri += "/#{new_version}" if new_version
 
    fetch_sheet(uri, false)
  end
 
  def cache_file
    "#{cache_dir}/#{@sheet}.yml" if cache_dir
  end
 
  def headers
    { 'User-Agent' => 'cheat!', 'Accept' => 'text/yaml' }
  end
 
  def cheat_uri
    "#{HOST}:#{PORT}#{SUFFIX}"
  end
 
  def show(sheet_yaml)
    sheet = YAML.load(sheet_yaml).to_a.first
    sheet[-1] = sheet.last.join("\n") if sheet[-1].is_a?(Array)
    run_pager
    puts sheet.first + ':'
    puts ' ' + sheet.last.gsub("\r",'').gsub("\n", "\n ").wrap
  rescue Errno::EPIPE
    # do nothing
  rescue
    puts "That didn't work. Maybe try `$ cheat cheat' for help?" # Fix Emacs ruby-mode highlighting bug: `"
  end
 
  def edit(sheet_yaml)
    sheet = YAML.load(sheet_yaml).to_a.first
    sheet[-1] = sheet.last.gsub("\r", '')
    body, title = write_to_tempfile(*sheet), sheet.first
    return if body.strip == sheet.last.strip
    res = post_sheet(title, body)
    check_errors(res, title, body)
  end
 
  def add(title)
    body = write_to_tempfile(title)
    res = post_sheet(title, body, true)
    check_errors(res, title, body)
  end
 
  def post_sheet(title, body, new = false)
    uri = "http://#{cheat_uri}/w/"
    uri += title unless new
    Net::HTTP.post_form(URI.parse(uri), "sheet_title" => title, "sheet_body" => body.strip, "from_gem" => true)
  end
 
  def write_to_tempfile(title, body = nil)
    # god dammit i hate tempfile, this is so messy but i think it's
    # the only way.
    tempfile = Tempfile.new(title + '.cheat')
    tempfile.write(body) if body
    tempfile.close
    system "#{editor} #{tempfile.path}"
    tempfile.open
    body = tempfile.read
    tempfile.close
    body
  end
 
  def check_errors(result, title, text)
    if result.body =~ /<p class="error">(.+?)<\/p>/m
      puts $1.gsub(/\n/, '').gsub(/<.+?>/, '').squeeze(' ').wrap(80)
      puts
      puts "Here's what you wrote, so it isn't lost in the void:"
      puts text
    else
      puts "Success! Try it!", "$ cheat #{title} --new"
    end
  end
 
  def editor
    ENV['VISUAL'] || ENV['EDITOR'] || "vim"
  end
 
  def cache_dir
    PLATFORM =~ /win32/ ? win32_cache_dir : File.join(File.expand_path("~"), ".cheat")
  end
 
  def win32_cache_dir
    unless File.exists?(home = ENV['HOMEDRIVE'] + ENV['HOMEPATH'])
      puts "No HOMEDRIVE or HOMEPATH environment variable. Set one to save a" +
           "local cache of cheat sheets."
      return false
    else
      return File.join(home, 'Cheat')
    end
  end
 
  def clear_cache
    FileUtils.rm_rf(cache_dir) if cache_dir
  end
 
  def run_pager
    return if PLATFORM =~ /win32/
    return unless STDOUT.tty?
 
    read, write = IO.pipe
 
    unless Kernel.fork # Child process
      STDOUT.reopen(write)
      STDERR.reopen(write) if STDERR.tty?
      read.close
      write.close
      return
    end
 
    # Parent process, become pager
    STDIN.reopen(read)
    read.close
    write.close
 
    ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
 
    # wait until we have input before we start the pager
    Kernel.select [STDIN]
    pager = ENV['PAGER'] || 'less'
    exec pager rescue exec "/bin/sh", "-c", pager
  rescue
  end
end
 
Cheat.sheets(ARGV) if __FILE__ == $0