websnap 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (C) 2010 Francis Chong
2
+
3
+ GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
4
+ This is free software: you are free to change and redistribute it. There is NO
5
+ WARRANTY, to the extent permitted by law.
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ Manifest
3
+ README.md
4
+ Rakefile
5
+ bin/wkhtmltoimage-linux-i386-0.10.0
6
+ bin/wkhtmltoimage-proxy
7
+ lib/websnap.rb
8
+ lib/websnap/source.rb
9
+ lib/websnap/websnap.rb
10
+ spec/fixtures/google.html
11
+ spec/source_spec.rb
12
+ spec/spec_helper.rb
13
+ spec/websnap_spec.rb
14
+ websnap.gemspec
@@ -0,0 +1,45 @@
1
+ # WebSnap
2
+
3
+ Create snapshot of webpage. Uses [wkhtmltoimage](http://github.com/antialize/wkhtmltopdf) on the backend which renders HTML using Webkit.
4
+
5
+ ## Installation
6
+
7
+ 1. Install wkhtmltoimage (optional)
8
+ ** WebSnap comes bundled with wkhtmltoimage binaries for Linux i386 (OSX i386 will be bundled, when it is available from [wkhtmltopdf] site)
9
+ ** WebSnap defaults to user installed versions of wkhtmltoimage if found
10
+ ** Installing wkhtmltoimage binary
11
+ *** Download the latest binary from http://code.google.com/p/wkhtmltopdf/downloads/list
12
+ *** Place the binary somewhere on your path (e.g /usr/local/bin)
13
+ 2. Install WebSnap
14
+
15
+ $ gem install websnap
16
+
17
+ ## Usage
18
+
19
+ # WebSnap.new takes the HTML and any options for wkhtmltoimage
20
+ # run `wkhtmltoimage -H` for a full list of options
21
+ snap = WebSnap.new('http://google.com', :format => 'png')
22
+
23
+ # Get the binary image data
24
+ png = snap.bytes
25
+
26
+ # Save the PDF to a file
27
+ file = snap.to_file('/path/to/save/png')
28
+
29
+ # WebSnap.new can optionally accept a URL or a File.
30
+ kit = WebSnap.new('http://google.com')
31
+ kit = WebSnap.new(File.new('/path/to/html'))
32
+
33
+ ## Note on Patches/Pull Requests
34
+
35
+ * Fork the project.
36
+ * Make your feature addition or bug fix.
37
+ * Add tests for it. This is important so I don't break it in a
38
+ future version unintentionally.
39
+ * Commit, do not mess with rakefile, version, or history.
40
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
41
+ * Send me a pull request. Bonus points for topic branches.
42
+
43
+ ## Copyright
44
+
45
+ Copyright (c) 2010 Francis Chong. See LICENSE for details.
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'echoe'
3
+
4
+ require 'rake'
5
+ require 'spec/rake/spectask'
6
+
7
+ Echoe.new("websnap", "0.1.1") do |p|
8
+ p.author = "Francis Chong"
9
+ p.description = "Create snapshot of webpage"
10
+ p.url = "http://github.com/siuying/websnap"
11
+ p.ignore_pattern = []
12
+ p.development_dependencies = ["rspec", "echoe", "mocha"]
13
+ p.runtime_dependencies = []
14
+ end
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ executable = `which wkhtmltoimage`.chomp
4
+
5
+ if executable.empty?
6
+
7
+ binary = case RUBY_PLATFORM
8
+ when /linux/
9
+ 'wkhtmltoimage-linux-i386-0.10.0'
10
+ when /darwin/
11
+ 'wkhtmltoimage-osx-i386-0.10.0'
12
+ else
13
+ raise "No bundled binaries found for your system. Please install wkhtmltopdf."
14
+ end
15
+ executable = File.join(File.dirname(__FILE__), binary)
16
+
17
+ end
18
+
19
+ arguments = $*.map { |arg| arg.include?(' ') ? %Q("#{arg}") : arg }
20
+
21
+ system(executable + " " + arguments.join(" "))
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'websnap/websnap'
3
+ require 'websnap/source'
@@ -0,0 +1,27 @@
1
+ class WebSnap
2
+
3
+ class Source
4
+
5
+ def initialize(url_file_or_html)
6
+ @source = url_file_or_html
7
+ end
8
+
9
+ def url?
10
+ @source.is_a?(String) && @source.match(/^http/)
11
+ end
12
+
13
+ def file?
14
+ @source.kind_of?(File)
15
+ end
16
+
17
+ def html?
18
+ !(url? || file?)
19
+ end
20
+
21
+ def to_s
22
+ file? ? @source.path : @source
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,95 @@
1
+ class WebSnap
2
+
3
+ class NoExecutableError < StandardError
4
+ def initialize
5
+ super('Could not locate wkhtmltoimage-proxy executable')
6
+ end
7
+ end
8
+
9
+ class ImproperSourceError < StandardError
10
+ def initialize(msg)
11
+ super("Improper Source: #{msg}")
12
+ end
13
+ end
14
+
15
+ attr_accessor :source, :stylesheets
16
+ attr_reader :options
17
+
18
+ def initialize(url_file_or_html, options={})
19
+ @source = Source.new(url_file_or_html)
20
+
21
+ @stylesheets = []
22
+
23
+ default_options = {
24
+ :'scale-h' => '90',
25
+ :'scale-w' => '120',
26
+ :'crop-h' => '768',
27
+ :'crop-w' => '1024',
28
+ :'crop-x' => '0',
29
+ :'crop-y' => '0',
30
+ :'format' => 'png'
31
+ }
32
+ @options = normalize_options(default_options.merge(options))
33
+
34
+ raise NoExecutableError.new if wkhtmltoimage.nil? || wkhtmltoimage == ''
35
+ end
36
+
37
+ def command
38
+ args = [wkhtmltoimage]
39
+ args += @options.to_a.flatten.compact
40
+
41
+ if @source.html?
42
+ args << '-' # Get HTML from stdin
43
+ else
44
+ args << @source.to_s
45
+ end
46
+
47
+ args << '-' # Read PDF from stdout
48
+ args.join(' ')
49
+ end
50
+
51
+ def to_bytes
52
+ img = IO.popen(command, "w+")
53
+ img.puts(@source.to_s) if @source.html?
54
+ img.close_write
55
+ result = img.gets(nil)
56
+ img.close_read
57
+ return result
58
+ end
59
+
60
+ def to_file(path)
61
+ File.open(path,'w') {|file| file << self.to_bytes}
62
+ end
63
+
64
+ protected
65
+
66
+ def wkhtmltoimage
67
+ @wkhtmltoimage ||= `which wkhtmltoimage-proxy`.chomp
68
+ end
69
+
70
+ def normalize_options(options)
71
+ normalized_options = {}
72
+ options.each do |key, value|
73
+ next if !value
74
+ normalized_key = "--#{normalize_arg key}"
75
+ normalized_options[normalized_key] = normalize_value(value)
76
+ end
77
+ normalized_options
78
+ end
79
+
80
+ def normalize_arg(arg)
81
+ arg.to_s.downcase.gsub(/[^a-z0-9]/,'-')
82
+ end
83
+
84
+ def normalize_value(value)
85
+ case value
86
+ when TrueClass
87
+ nil
88
+ when String
89
+ value.match(/\s/) ? "\"#{value}\"" : value
90
+ else
91
+ value
92
+ end
93
+ end
94
+
95
+ end
@@ -0,0 +1,265 @@
1
+ <!doctype html><html><head><meta content="text/html; charset=Big5" http-equiv="content-type" /><title>Google</title><script>window.google={kEI:"RVsnTITaHIqgkQXPkNyGAw",kEXPI:"24483,25013,25164,25233,25282,25299,25335,25368",kCSI:{e:"24483,25013,25164,25233,25282,25299,25335,25368",ei:"RVsnTITaHIqgkQXPkNyGAw",expi:"24483,25013,25164,25233,25282,25299,25335,25368"},ml:function(){},kHL:"zh-TW",time:function(){return(new Date).getTime()},log:function(b,d,c){var a=new Image,e=google,g=e.lc,f=e.li;a.onerror=(a.onload=(a.onabort=function(){delete g[f]}));g[f]=a;c=c||"/gen_204?atyp=i&ct="+b+"&cad="+d+"&zx="+google.time();a.src=c;e.li=f+1},lc:[],li:0,Toolbelt:{}};
2
+ window.google.sn="webhp";window.google.timers={load:{t:{start:(new Date).getTime()}}};try{}catch(u){}window.google.jsrt_kill=1;
3
+ var _gjwl=location;function _gjuc(){var e=_gjwl.href.indexOf("#");if(e>=0){var a=_gjwl.href.substring(e);if(a.indexOf("&q=")>0||a.indexOf("#q=")>=0){a=a.substring(1);if(a.indexOf("#")==-1){for(var c=0;c<a.length;){var d=c;if(a.charAt(d)=="&")++d;var b=a.indexOf("&",d);if(b==-1)b=a.length;var f=a.substring(d,b);if(f.indexOf("fp=")==0){a=a.substring(0,c)+a.substring(b,a.length);b=c}else if(f=="cad=h")return 0;c=b}_gjwl.href="/search?"+a+"&cad=h";return 1}}}return 0}function _gjp(){!(window._gjwl.hash&&
4
+ window._gjuc())&&setTimeout(_gjp,500)};
5
+ window._gjp && _gjp()</script><style>body{margin:0}#gog{padding:3px 8px 0}td{line-height:.8em;}.gac_m td{line-height:17px;}form{margin-bottom:20px;}body,td,a,p,.h{font-family:arial,sans-serif}.h{color:#36c;font-size:20px}.q{color:#00c}.ts td{padding:0}.ts{border-collapse:collapse}em{color:#c03;font-style:normal;font-weight:normal}a em{text-decoration:underline}.lst{width:496px}.tiah{width:458px}input{font-family:inherit}a.gb1,a.gb2,a.gb3,a.gb4{color:#11c !important}#gbar,#guser{font-size:13px;padding-top:1px !important}#gbar{height:22px}#guser{padding-bottom:7px !important;text-align:right}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}@media all{.gb1{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb4{color:#00c !important}body{background:#fff;color:black}input{-moz-box-sizing:content-box}a{color:#11c;text-decoration:none}a:hover,a:active{text-decoration:underline}.fl a{color:#4272db}a:visited{color:#551a8b}a.gb1,a.gb4{text-decoration:underline}a.gb3:hover{text-decoration:none}#ghead a.gb2:hover{color:#fff!important}.ds{display:-moz-inline-box}.ds{border-bottom:solid 1px #e7e7e7;border-right:solid 1px #e7e7e7;display:inline-block;margin:3px 0 4px;margin-left:4px;margin-right:3px:height:32px}.sblc{padding-top:5px}.sblc a{display:block;margin:2px 0;margin-left:13px;font-size:11px;}.lsbb{background:#eee;border:solid 1px;border-color:#ccc #999 #999 #ccc;height:30px;display:block}.lsb{background:url(/images/srpr/nav_logo13.png) bottom;font:15px arial,sans-serif;border:none;color:#000;cursor:pointer;height:30px;margin:0;outline:0;vertical-align:top}.lsb:active{background:#ccc}.lst:focus{outline:none}.ftl,#fll a{margin:0 12px}#addlang a{padding:0 3px}.gac_v div{display:none}.gac_v .gac_v2,.gac_bt{display:block!important}</style><script>google.y={};google.x=function(e,g){google.y[e.id]=[e,g];return false};</script></head><body text="#000000" vlink="#551a8b" onload="document.f.q.focus();if(document.images)new Image().src='/images/srpr/nav_logo13.png'" alink="#ff0000" link="#0000cc" bgcolor="#ffffff"><textarea id="csi" style="display:none"></textarea><div id="ghead"><div id="gbar"><nobr /><b class="gb1">�Ҧ�����</b> <a href="http://www.google.com.hk/imghp?hl=zh-TW&tab=wi" class="gb1" onclick="gbar.qs(this)">�Ϥ�</a> <a href="http://video.google.com.hk/?hl=zh-TW&tab=wv" class="gb1" onclick="gbar.qs(this)">�v��</a> <a href="http://maps.google.com.hk/maps?hl=zh-TW&tab=wl" class="gb1" onclick="gbar.qs(this)">�a��</a> <a href="http://news.google.com.hk/nwshp?hl=zh-TW&tab=wn" class="gb1" onclick="gbar.qs(this)">�s�D</a> <a href="http://www.google.com.hk/finance?hl=zh-TW&tab=we" class="gb1" onclick="gbar.qs(this)">�]�g</a> <a href="http://mail.google.com/mail/?hl=zh-TW&tab=wm" class="gb1">Gmail</a> <a href="http://www.google.com.hk/intl/zh-TW/options/" class="gb1" style="text-decoration:none"><u>��h</u> &raquo;</a></div><div id="guser" width="100%"><nobr /><span class="gbi" id="gbn"></span><span class="gbf" id="gbf"></span><span id="gbe"><a href="/url?sa=p&pref=ig&pval=3&q=http://www.google.com.hk/ig%3Fhl%3Dzh-TW%26source%3Diglk&usg=AFQjCNHPiYicHSGMua-uVhGTjPT-hWpWXA" class="gb4">iGoogle</a> | </span><a href="/preferences?hl=zh-TW" class="gb4">�]�w</a> | <a href="https://www.google.com/accounts/Login?hl=zh-TW&continue=http://www.google.com.hk/" class="gb4">�n�J</a></div><div class="gbh" style="left:0"></div><div class="gbh" style="right:0"></div></div> <center><br clear="all" id="lgpd" /><div id="lga"><table cellspacing="0" border="0" cellpadding="0"><tr><td align="right" valign="bottom"><img src="data:image/gif;base64,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
100
+ ADs=
101
+ " height="78" alt="Google" width="158" /></td><td valign="bottom"><img src="data:image/gif;base64,R0lGODdhMgBOAHcAACwAAAAAMgBOAIf///+9473v9++c06U5ukIxtjmMy4z3
102
+ 9/f38+/v6+/38/fv8/fW4//G1/+tx/elvu+EqveEmtZzjs6lttb39/9aiu9K
103
+ fedjlu85cd4xZdYhWdYYTcYYSbUIPKUQNIQQPJSUnrXv7+//+//n6/9zou8Y
104
+ Ra17hq3n4+f3+/9Sba05YbVjeaXGx8737+/n5+fe5//Gy9bv5+fn497W09al
105
+ rr3e29bOz87Oy87e297Gx8a9vr0QRbW1srW9tr29ur3W19be3+fv6+fGw8bG
106
+ vr21trWEhpTe394pUbWtrq2lpq1KYZTOw6UYUc6ljjEIJGOMkpS1usaUeQCc
107
+ mpyloqVagtYpQWuchkKUkoytqq2Urt6lqq2EaQDO19a1pozO0+dzeYylhnvO
108
+ 09b/9/eEdTn///daaXtKVXO1sq21w95jUQClpqW9z+/W2+ecnpw5WZStpq17
109
+ ZVLW186EkrWEfXMpSZStqqVraWvn5+/Ox8be19bW2961tr3e797O687W197O
110
+ z9b39+8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
111
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
112
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
113
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
114
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
115
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
116
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
117
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
118
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI/wABCBxIsKDBgwgTKlzIsKHD
119
+ hxAjSpxIsaLFixgzatzIsaPHjyArBgjZUMAAAgVCNgxgwMCAkCFBHkAQ8uGB
120
+ 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
166
+ OHDgmGFDSI82R0pw4MChxAcPdGzcOSBCYEAAOw==
167
+ " height="78" alt="" width="50" /></td><td valign="bottom"><img src="data:image/gif;base64,R0lGODdhRABOAPcAAP///73jvXvbhGvTc1rLWlqqY+fr72PTY0LDSiGqKRCW
168
+ GAB9CABlAEKOQtbf1v/3//f39//7/5zTpYzLjDG2OQhRCMbDxu/r7+fj5/fz
169
+ 7/fr9zm6QhiiISldMc7Lzs7Pzt7b3ufn5/fz9+fb3rW2tb22vcbHxtbX1t7f
170
+ 562urbWytVp1Wq2mrd7f3u/v79bT1oR9c8a+vYSGlIySlJSSjO/375yanPfn
171
+ 1ve6rfemnPeWhPeGc2tpa97v3vfXzu9ZQuc8Id4wGOdJMdZlWu/n5/91Y9Yk
172
+ CMYYALUkEMaSjL2+vaWmpZyenP/r57VFMc6upda2tbVZSr0sGL26vf/Lxvdp
173
+ Us48KZRBMe/z99bb3s7T1rWqpd7LxqWipc7rzq0UAJxlWt7j57W6xvf3/9bX
174
+ zowQAIwgEHtNQsbHzmsMAKWGe+/v962qrWssId7X1ntlUq2qpf/z76WmrcZ1
175
+ a97XxqWqrd7b1q2WjM7X1ufn73uWexh5IYyujM7fzs7P1rW2vbWyrff7/wAA
176
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
177
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
178
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
179
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
180
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
181
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
182
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
183
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
184
+ AAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAABEAE4AQAj/AAEIHEiwoMGD
185
+ CBMqXMiwocOHEFGMGIBgA4cFDCpUWMFChYUWLiJAHEkSQIYBBxBczFhhhgoP
186
+ GCCUnPmwxwACFjFqtEHCQwiZDH0UAWLEyBEkSI0EASKkKdEgSSCINMhDCQAM
187
+ S2BEiMEDw0AvBwhQUKCzApMpL35OFUmlSBEhUtLAEMNGRYy0GSAAHQjhAgoh
188
+ RpCUKZOmcBsQF0RMVRgg7NiyNtBekCpQh1u4X8qYqUMiSZQjoL84SQICi4EX
189
+ H2KoVjLFgocTLUJMXsywsViyLCO/mDzwwg8gRI98yVzmDooQImhCtP2YAYMO
190
+ TErs3iuwLxQkoLNrPzKECEQIVOI0/+wLwoQKFl2WpO8Cx64F1yCQ04YgIsQL
191
+ CyVYLJHDhAmN/zQwwUIKJKhgoApTKOGBFvEpptyDEC4kgQQTCHBTcxVYcAEG
192
+ F+RFW4TKPSBASgmUBYNdIOQF4oo1oKQSZD3F9OGKM0XgxU0b4KbRWWkldxAO
193
+ VfxgxRdghAGCByaYEMN7MSihRAkqfFaUUcM5oeJDzOloFlo/DRSHW1VYgUQa
194
+ bSyRggUghFQQFTsQZURmV6hhoA102sCCEj3W5piWNkg3W2VVVAEYcWeI4QEI
195
+ LbRwZJIefPACCh06CGGWkElGHQRJHGFUaMOVAYYBWMxIYwAc4MZSBc4xsMAe
196
+ DTRQQB8Ekf/nxx1mDFbGcLh+kd2m2XV6hRsmZeBhQxHoVWxeeUGQLHULKZsB
197
+ BiFggEILKIBgbaKQZiACZTR26+234AqE0gDkkkgWRg04EC5NIYwwwQYbJKCA
198
+ cxV0QEIJJpwg37oQRSDCiAiUyBJHJOgrKr8LiUhiWTKoYAIKkiL8EAQurqQR
199
+ DTFyK7FDLR6QU249IUfSDTjkoMMOO+iQAw43iBTBwTzEfAIKPEjVQs0D2YQT
200
+ n1wyu2YVQIh5xROgGlBtFlps4YSmQRQFmhRcULUEACZc0AUTAJz4FY5a8ihy
201
+ QU0UEaQUZXTxwRT3xuDBC4leEC0IL3iQRBBNCzeYGSBEvBBYt5X/5TV1bRXx
202
+ A9ltMNHeR2oeFEcOQtht2Bs0pIBnlwtRmlvPA0FRheBSZFZmCR+EIFATOgAm
203
+ nGik+eVBDOfVhe8HDR5MkOU7+bmXDoFiNtiJackEgQEneCC8Bao1+gK028pe
204
+ +Z6Q2Z75D7+9SZwaFrxgRwjRyiYVt8ovx/zl060FhVKbdlpcHhlITPuWvRO0
205
+ babb5RqVz6NuQMFKqephQnwz9pWFGrkaznaOYIUkcEFvAIgDFXQgJigAoHsC
206
+ mQAf+KAHGKzgghhcgR70wIcQPEAxtKnPff5ggzPUKg22SmEK26CGD+QBbi+A
207
+ zU/oF6v6wK1RH8ihDnN4AsS4QGOx2hAI//xggT8YSD9sYEES64IgE7wGMUDc
208
+ mBSnSMUqWnGKARCAFi10EwIU4IsFMMAVE4KAA2yRIvJC1wPWGMUxAuABGpAA
209
+ AXBisQ4ID3b7cuNAqjUA+1msXinwCGLSp8eBuOACAiBAwMpCsDwVkiAKWyRL
210
+ ToQ4CFIRAgATmEZOFAOQuMySEvvXwljikv0hsJAnSckfMXaoNuqxYy8CGUxo
211
+ 6MaOfax2ofPRI3OGoz/yxCeuPIgPdPAbICyFKU1pyhB04J2DZMAFPChBBkDA
212
+ gxhQrSu8pKPfQPe1hDRhB0IIAlJG8wQoPMEzUlAKcOrmhBEcpJpXoaYKLsCD
213
+ D2xtZ9tsH0Jw8P8DIQypSMATngeItyQxgEFTThOOFNxZEO4JZAwXuGeO/IY5
214
+ g1Ahd0i4wgnIsJrWGO8DSFKCCqwwJbuVYX5r2VtYJgo+tQhkKpYJUxnOgAYD
215
+ teZRHdILfQ4JAguE802EKYwa1nDKg/ANQxXQDeUGsgOxWcEMaYicCiZHSwC4
216
+ IQiOK0wa3gCTKynkqHySzlIBkIPLCKYNclhCRcFWOqMQpg1ngEGduqCC0FVV
217
+ IOvTDW8E4oPNCclzcnCYjKoDpLp9wQxzgEK1SsAGJqQHDvhCTFFn970drbWs
218
+ nAOsElCABTq0iUpISKwBAmoC1kzBox9oAfIsmVdLDSSmujPDG66ABCf/RGEO
219
+ osUCCuI20OI9MQQuSNZIWqtPAOBOUJ0bDBiqBykMZCFuSVpSo0DAoWHRhLhL
220
+ zQH0BnU3FpjAejA8AWw4JBVQNoS4ewWAD5xCpcFc4S4YCK5igjmpyibVUmuZ
221
+ A93KByc8DJZf64vOdAgSApLyyld4+CHCAmy7lNqBV5wazvwAbN+/FUQEKFja
222
+ AHEVlXUFQJFpTJWA02tIDCQhgAOUQoebhYMhONCSAyjVAjCSqj3wIQYgeMBB
223
+ XFAtAAZQgNoxAhCGMAeUCQ6rX0BC1HTavYuURSNQThUD9iAB6hQrBCdAAxzA
224
+ UCsV/hhXd1PDTzAArQ5BsC8fyE9/1iy5Q1lLa3u0edYJilgHJsDgDG3Is57z
225
+ fAYw3KF6A33P2mJD3+pc4EhKAMSBDGRaCzgqPhmYkQg29AH86EcOV1vzmvdj
226
+ 0+rFziHOKrNssOc2UgOXvvTB3g1NwCTVTMGJa6PuDB8ZCL0kywW4FpZOaRIQ
227
+ ADs=
228
+ " height="78" alt="" width="68" /></td></tr><tr><td class="h" align="right" valign="top"><b></b></td><td valign="top"><img src="data:image/gif;base64,R0lGODdhMgAgAGYAACwAAAAAMgAgAIb///+Eqvdjlu9aiu85YbUQPJQIJGOl
229
+ pq3Gvr21srWtqq29ur3Oy87W19bW297e397e3+fv6+/v7+/n4+fW09a9vr0p
230
+ UbUQRbUYTcYYRa0QNISlrr338+//+//G1/9zou85cd4pSZRaaXutpq3Gx8b3
231
+ 9/f38/eUnrUYSbWMkpTn5+dKfeecnpytrq3e297Ox8Z7hq0YUc6EhpTO0+cp
232
+ QWucmpzOz873+/8xZdZzeYzGw8alvu+loqXe29ZagtYhWdb/9/+Urt6Emta1
233
+ trX/9/dKVXOlpqXv7/e9tr29z+9jeaWlttb39+8IPKWEkrXW4/+1w95zjs7e
234
+ 5/////etx/fe4+elqq3W1845WZRKYZS1usbv6+fGx86tqqVSba2Ukoy1sq0A
235
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
236
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
237
+ AAAH/4AAgoIBAgIDBAUGBwgJCgkJCwwNDg8PEBEREhISERMUDBUHFhcYFxkF
238
+ GhobEBwdg4MeAh8CICEGIgsjIwkLJBQPExElJiUdgwAlJREPDAgnGBgoGQUa
239
+ BikNKiUdg4IBHx8rFhoGKSMsLRUMLhESJR2Dg4MAJRIqFC8wMRgoBRqABjIM
240
+ LhIlHQAAADMfAQIgGRo0NTUtCzYuEiUdgACCg4SDNyUqLhU4MRgFGgY5OhQq
241
+ JYQ7Hx8DOAUGNDU8CQwuEh2FhYWCHBE9Pj8YBRoGMhUMKkCEQR8fKxYFBjQ1
242
+ CjoNKiaFhYWDNyURQj8YBRoGKUMMEyUdgiUBHwIgFgUaNP8sCQwTJh0dhB2F
243
+ hYMlREE4GAUGNCwJDBMlHYOAQgIDOBgFGho1CzYqJSWAAIKDhIWGQTgWGgZF
244
+ IwkMEyWEQisgOBgZBRo5OhQqJYaGhoRCOBkaBjJGCQwTJYIdAEE4OD8YGQUa
245
+ IRUNKiUdhoaGgkcrPwUGRSwsSAwqQIIdAEk/MTEYFxkFBUo9KiWGhoaDSzgW
246
+ GgY1PEYVDSolHYNMPhgYKCgoGRlNThEdgACCg4SFhU8gFgUGMgojCiQQHB2F
247
+ UCgoKBcoGRkZGVERHYaGhlIrGAUGOQsjI0gUKiYdhCYcTk2AGYKCKCgoBFBT
248
+ gACCg4SFAFQgGAUaMAwtPAkMLhL/JR2FEg9KTU1NGRkZGSgXGCBBVUyGhCZU
249
+ Az8YBSFWV0gJLRUUKiYdhiYqFDCABYKCGRkoKBgxPyBRQUtJHh5JOwEDIDEY
250
+ TQVKFC4ICQkLNhAcHYAAgoMAJREuLyxYGhoaBQUZGSgYgDGCg4QYGChNBVla
251
+ Ww0ICwsIDC4SHYAAgoODJSoUOgkyWQUaBQUZGSiAGIKDKCgoGU0FITBcKhMM
252
+ FQhIOgwuEh2AAIKDhAAcEw06SF0pOVkhIRkZKCgoKE0ZGRYWXk5aDSo9NjoI
253
+ Cws6Ng8qJR2FhYIcEw06SF1GBzVfKTIwMDBOTicbWiQ2DRQMDAgLCwgVOg0q
254
+ EiUdhYWE5yYqFAwVYApGPDxGPF0tQwlICwsVFUgLCxUVSDoMFC4qJSWFhYWC
255
+ JYAqExQMCAsJIwojCiMjViMJCQkLQwsLOjoMFD0TEiUdgACCg4SFhCURERMQ
256
+ DQwMCBVIYENDQ0gLCxU6DBQULi4qESUdhoaGhSUlERETEw0NgBSCg4INLi4P
257
+ KhElJYAAgoOEhYaHACUcEhISEhETEREqEREREhwcJSUlgx2IiIiGJR0lJSWI
258
+ iIiIiIiIiIiIhB0AHYIdAB2IiIiIiIiIiIiIAB0AHYiIiIiIiIiIiIiCHQAd
259
+ gh0AHYiIiIiIiIiEgQA7
260
+ " height="32" alt="" width="50" /></td><td class="h" valign="top"><font color="#666666" style="font-size:16px"><b>����</b></font></td></tr></table><br /></div><form name="f" action="/search"><table cellspacing="0" cellpadding="0"><tr valign="top"><td width="25%">&nbsp;</td><td nowrap align="center"><input name="hl" value="zh-TW" type="hidden" /><input name="source" value="hp" type="hidden" /><input name="ie" value="Big5" type="hidden" /><div class="ds" style="height:32px;margin:4px 0"><input name="q" size="57" maxlength="2048" title="Google �j�M" class="lst" value="" style="background:#fff;border:1px solid #ccc;border-bottom-color:#999;border-right-color:#999;color:#000;font:18px arial,sans-serif bold;height:25px;margin:0;padding:5px 8px 0 6px;vertical-align:top" autocomplete="off" /></div><br style="line-height:0" /><span class="ds"><span class="lsbb"><input name="btnG" class="lsb" value="Google �j�M" type="submit" /></span></span><span class="ds"><span class="lsbb"><input name="btnI" class="lsb" value="�n���" type="submit" /></span></span></td><td class="sblc" nowrap align="left" width="25%"><a href="/advanced_search?hl=zh-TW">�i���j�M</a><a href="/language_tools?hl=zh-TW">�y���ﶵ</a></td></tr></table></form><div style="font-size:83%;min-height:3.5em"><br /><div id="als"><font size="-1" id="addlang">Google.com.hk �ٴ��ѡG <a href="http://www.google.com.hk/setprefs?sig=0_UkHZK4ufPhWK5HQLOFa-oO1q8bU=&amp;hl=zh-CN">����(&#31616;�^)</a> <a href="http://www.google.com.hk/setprefs?sig=0_UkHZK4ufPhWK5HQLOFa-oO1q8bU=&amp;hl=en">English</a></font><br /><br /></div></div><div id="res"></div><span id="footer" /><center id="fctr"><div style="font-size:10pt"><div id="fll" style="margin:19px auto 19px auto;text-align:center"><a href="/intl/zh-TW/ads/">�s�i�A��</a><a href="/intl/zh-TW/about.html">Google ������U</a><a href="http://www.google.com/ncr">Google.com in English</a></div></div><p style="color:#767676;font-size:8pt">&copy; 2010 - <a href="/intl/zh-TW/privacy.html">���p�v�F��</a></p></center> <div id="xjsd"></div><div id="xjsi"><script>if(google.y)google.y.first=[];if(google.y)google.y.first=[];google.dstr=[];google.rein=[];window.setTimeout(function(){var a=document.createElement("script");a.src="/extern_js/f/CgV6aC1UVxICaGsgDSswRTgBLCswWjgBLCswDjgRLCswFzgHLCswJzgELCswPDgDLCswCjhtQAIsKzAlOMqIASwrMEA4EiwrMEE4BSwrMBg4BSwrMCY4CyyAAhM/ct5vAm6hZds.js";(document.getElementById("xjsd")||document.body).appendChild(a);if(google.timers&&google.timers.load.t)google.timers.load.t.xjsls=(new Date).getTime();},0);
261
+ ;google.neegg=1;google.y.first.push(function(){google.ac.i(document.f,document.f.q,'','','',{o:1,sw:1});google.History&&google.History.initialize('/')});if(google.j&&google.j.en&&google.j.xi){window.setTimeout(google.j.xi,0);google.fade=null;}</script></div><script>(function(){
262
+ var b,d,e,f;function g(a,c){if(a.removeEventListener){a.removeEventListener("load",c,false);a.removeEventListener("error",c,false)}else{a.detachEvent("onload",c);a.detachEvent("onerror",c)}}function h(a){f=(new Date).getTime();++d;a=a||window.event;var c=a.target||a.srcElement;g(c,h)}var i=document.getElementsByTagName("img");b=i.length;d=0;for(var j=0,k;j<b;++j){k=i[j];if(k.complete||typeof k.src!="string"||!k.src)++d;else if(k.addEventListener){k.addEventListener("load",h,false);k.addEventListener("error",
263
+ h,false)}else{k.attachEvent("onload",h);k.attachEvent("onerror",h)}}e=b-d;function l(){google.timers.load.t.ol=(new Date).getTime();google.timers.load.t.iml=f;google.kCSI.imc=d;google.kCSI.imn=b;google.kCSI.imp=e;google.report&&google.report(google.timers.load,google.kCSI)}if(window.addEventListener)window.addEventListener("load",l,false);else if(window.attachEvent)window.attachEvent("onload",l);google.timers.load.t.prt=(f=(new Date).getTime());
264
+ })();
265
+ </script></center></body></html>
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe WebSnap::Source do
4
+
5
+ describe "#url?" do
6
+ it "should return true if passed a url like string" do
7
+ source = WebSnap::Source.new('http://google.com')
8
+ source.should be_url
9
+ end
10
+
11
+ it "should return false if passed a file" do
12
+ source = WebSnap::Source.new(File.new(__FILE__))
13
+ source.should_not be_url
14
+ end
15
+
16
+ it "should return false if passed HTML" do
17
+ source = WebSnap::Source.new('<blink>Oh Hai!</blink>')
18
+ source.should_not be_url
19
+ end
20
+ end
21
+
22
+ describe "#file?" do
23
+ it "should return true if passed a file" do
24
+ source = WebSnap::Source.new(File.new(__FILE__))
25
+ source.should be_file
26
+ end
27
+
28
+ it "should return false if passed a url like string" do
29
+ source = WebSnap::Source.new('http://google.com')
30
+ source.should_not be_file
31
+ end
32
+
33
+ it "should return false if passed HTML" do
34
+ source = WebSnap::Source.new('<blink>Oh Hai!</blink>')
35
+ source.should_not be_file
36
+ end
37
+ end
38
+
39
+ describe "#html?" do
40
+ it "should return true if passed HTML" do
41
+ source = WebSnap::Source.new('<blink>Oh Hai!</blink>')
42
+ source.should be_html
43
+ end
44
+
45
+ it "should return false if passed a file" do
46
+ source = WebSnap::Source.new(File.new(__FILE__))
47
+ source.should_not be_html
48
+ end
49
+
50
+ it "should return false if passed a url like string" do
51
+ source = WebSnap::Source.new('http://google.com')
52
+ source.should_not be_html
53
+ end
54
+ end
55
+
56
+ describe "#to_s" do
57
+ it "should return the HTML if passed HTML" do
58
+ source = WebSnap::Source.new('<blink>Oh Hai!</blink>')
59
+ source.to_s.should == '<blink>Oh Hai!</blink>'
60
+ end
61
+
62
+ it "should return a path if passed a file" do
63
+ source = WebSnap::Source.new(File.new(__FILE__))
64
+ source.to_s.should == __FILE__
65
+ end
66
+
67
+ it "should return the url if passed a url like string" do
68
+ source = WebSnap::Source.new('http://google.com')
69
+ source.to_s.should == 'http://google.com'
70
+ end
71
+ end
72
+
73
+ end
@@ -0,0 +1,8 @@
1
+ SPEC_ROOT = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(SPEC_ROOT)
3
+ $LOAD_PATH.unshift(File.join(SPEC_ROOT, '..', 'lib'))
4
+
5
+ require 'websnap'
6
+ require 'rubygems'
7
+ require 'spec/rake/spectask'
8
+ require 'mocha'
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe WebSnap do
4
+
5
+ context "initialization" do
6
+ it "should accept HTML as the source" do
7
+ websnap = WebSnap.new('<h1>Oh Hai</h1>')
8
+ websnap.source.should be_html
9
+ websnap.source.to_s.should == '<h1>Oh Hai</h1>'
10
+ end
11
+
12
+ it "should accept a URL as the source" do
13
+ websnap = WebSnap.new('http://google.com')
14
+ websnap.source.should be_url
15
+ websnap.source.to_s.should == 'http://google.com'
16
+ end
17
+
18
+ it "should accept a File as the source" do
19
+ file_path = File.join(SPEC_ROOT,'fixtures','google.html')
20
+ websnap = WebSnap.new(File.new(file_path))
21
+ websnap.source.should be_file
22
+ websnap.source.to_s.should == file_path
23
+ end
24
+
25
+ it "should parse the options into a cmd line friedly format" do
26
+ websnap = WebSnap.new('html', :'crop-w' => '800', :'crop-h' => '600')
27
+ websnap.options.should have_key('--crop-w')
28
+ end
29
+
30
+ it "should provide default options" do
31
+ websnap = WebSnap.new('<h1>Oh Hai</h1>')
32
+ ['--crop-w', '--crop-h', '--scale-w', '--scale-h', '--format'].each do |option|
33
+ websnap.options.should have_key(option)
34
+ end
35
+ end
36
+ end
37
+
38
+ context "command" do
39
+ it "should contstruct the correct command" do
40
+ websnap = WebSnap.new('html', :'encoding' => 'Big5')
41
+ websnap.command.should include('--encoding Big5')
42
+ end
43
+
44
+ it "read the source from stdin if it is html" do
45
+ websnap = WebSnap.new('html')
46
+ websnap.command.should match(/ - -$/)
47
+ end
48
+
49
+ it "specify the URL to the source if it is a url" do
50
+ websnap = WebSnap.new('http://google.com')
51
+ websnap.command.should match(/ http:\/\/google\.com -$/)
52
+ end
53
+
54
+ it "should specify the path to the source if it is a file" do
55
+ file_path = File.join(SPEC_ROOT,'fixtures','google.html')
56
+ websnap = WebSnap.new(File.new(file_path))
57
+ websnap.command.should match(/ #{file_path} -$/)
58
+ end
59
+ end
60
+
61
+ context "#to_bytes" do
62
+ it "should generate a PDF of the HTML" do
63
+ websnap = WebSnap.new('html')
64
+ websnap.expects(:to_bytes).returns('PNG')
65
+ png = websnap.to_bytes
66
+ png.should match(/PNG/)
67
+ end
68
+ end
69
+
70
+ context "#to_file" do
71
+ before do
72
+ @file_path = File.join(SPEC_ROOT,'fixtures','test.png')
73
+ File.delete(@file_path) if File.exist?(@file_path)
74
+ end
75
+
76
+ after do
77
+ File.delete(@file_path)
78
+ end
79
+
80
+ it "should create a file with the PNG as content" do
81
+ websnap = WebSnap.new('html')
82
+ websnap.expects(:to_bytes).returns('PNG')
83
+
84
+ file = websnap.to_file(@file_path)
85
+ file.should be_instance_of(File)
86
+ File.read(file.path).should == 'PNG'
87
+ end
88
+ end
89
+
90
+ end
@@ -0,0 +1,40 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{websnap}
5
+ s.version = "0.1.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Francis Chong"]
9
+ s.date = %q{2010-06-27}
10
+ s.description = %q{Create snapshot of webpage}
11
+ s.email = %q{}
12
+ s.executables = ["wkhtmltoimage-linux-i386-0.10.0", "wkhtmltoimage-proxy"]
13
+ s.extra_rdoc_files = ["LICENSE", "README.md", "bin/wkhtmltoimage-linux-i386-0.10.0", "bin/wkhtmltoimage-proxy", "lib/websnap.rb", "lib/websnap/source.rb", "lib/websnap/websnap.rb"]
14
+ s.files = ["LICENSE", "Manifest", "README.md", "Rakefile", "bin/wkhtmltoimage-linux-i386-0.10.0", "bin/wkhtmltoimage-proxy", "lib/websnap.rb", "lib/websnap/source.rb", "lib/websnap/websnap.rb", "spec/fixtures/google.html", "spec/source_spec.rb", "spec/spec_helper.rb", "spec/websnap_spec.rb", "websnap.gemspec"]
15
+ s.homepage = %q{http://github.com/siuying/websnap}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Websnap", "--main", "README.md"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{websnap}
19
+ s.rubygems_version = %q{1.3.6}
20
+ s.summary = %q{Create snapshot of webpage}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ s.add_development_dependency(%q<rspec>, [">= 0"])
28
+ s.add_development_dependency(%q<echoe>, [">= 0"])
29
+ s.add_development_dependency(%q<mocha>, [">= 0"])
30
+ else
31
+ s.add_dependency(%q<rspec>, [">= 0"])
32
+ s.add_dependency(%q<echoe>, [">= 0"])
33
+ s.add_dependency(%q<mocha>, [">= 0"])
34
+ end
35
+ else
36
+ s.add_dependency(%q<rspec>, [">= 0"])
37
+ s.add_dependency(%q<echoe>, [">= 0"])
38
+ s.add_dependency(%q<mocha>, [">= 0"])
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: websnap
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
+ platform: ruby
11
+ authors:
12
+ - Francis Chong
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-06-27 00:00:00 +08:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: echoe
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :development
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: mocha
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :development
55
+ version_requirements: *id003
56
+ description: Create snapshot of webpage
57
+ email: ""
58
+ executables:
59
+ - wkhtmltoimage-linux-i386-0.10.0
60
+ - wkhtmltoimage-proxy
61
+ extensions: []
62
+
63
+ extra_rdoc_files:
64
+ - LICENSE
65
+ - README.md
66
+ - bin/wkhtmltoimage-linux-i386-0.10.0
67
+ - bin/wkhtmltoimage-proxy
68
+ - lib/websnap.rb
69
+ - lib/websnap/source.rb
70
+ - lib/websnap/websnap.rb
71
+ files:
72
+ - LICENSE
73
+ - Manifest
74
+ - README.md
75
+ - Rakefile
76
+ - bin/wkhtmltoimage-linux-i386-0.10.0
77
+ - bin/wkhtmltoimage-proxy
78
+ - lib/websnap.rb
79
+ - lib/websnap/source.rb
80
+ - lib/websnap/websnap.rb
81
+ - spec/fixtures/google.html
82
+ - spec/source_spec.rb
83
+ - spec/spec_helper.rb
84
+ - spec/websnap_spec.rb
85
+ - websnap.gemspec
86
+ has_rdoc: true
87
+ homepage: http://github.com/siuying/websnap
88
+ licenses: []
89
+
90
+ post_install_message:
91
+ rdoc_options:
92
+ - --line-numbers
93
+ - --inline-source
94
+ - --title
95
+ - Websnap
96
+ - --main
97
+ - README.md
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ segments:
112
+ - 1
113
+ - 2
114
+ version: "1.2"
115
+ requirements: []
116
+
117
+ rubyforge_project: websnap
118
+ rubygems_version: 1.3.6
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: Create snapshot of webpage
122
+ test_files: []
123
+