butternut 0.2.2-java

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ .sass-cache
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jeremy Stephens
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,18 @@
1
+ = butternut
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but
13
+ bump version in a commit by itself I can ignore when I pull)
14
+ * Send me a pull request. Bonus points for topic branches.
15
+
16
+ == Copyright
17
+
18
+ Copyright (c) 2009 Jeremy Stephens. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,63 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "butternut"
8
+ gem.summary = %Q{A Cucumber formatter that uses Celerity to capture HTML sources (JRuby)}
9
+ gem.description = %Q{Based on Cucumber's HTML formatter, Butternut uses Celerity to capture page sources after each step.}
10
+ gem.email = "viking415@gmail.com"
11
+ gem.homepage = "http://github.com/viking/butternut"
12
+ gem.authors = ["Jeremy Stephens"]
13
+ gem.platform = "java"
14
+ gem.add_dependency "cucumber", ">= 0.6.0"
15
+ gem.add_dependency "celerity"
16
+ gem.add_dependency "nokogiri"
17
+ gem.add_development_dependency "rspec", ">= 1.2.9"
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
23
+ end
24
+
25
+ require 'spec/rake/spectask'
26
+ Spec::Rake::SpecTask.new(:spec) do |spec|
27
+ spec.ruby_opts = %w{-X+O}
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.spec_files = FileList['spec/**/*_spec.rb']
30
+ end
31
+
32
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
33
+ spec.libs << 'lib' << 'spec'
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+ task :spec => [:check_dependencies, "tmp:clear"]
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "butternut #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
51
+
52
+ desc 'Generate the css for the html formatter from sass'
53
+ task :sass do
54
+ sh 'sass -t expanded lib/butternut/cucumber.sass > lib/butternut/cucumber.css'
55
+ end
56
+
57
+ namespace :tmp do
58
+ desc 'Delete temporary files'
59
+ task :clear do
60
+ require 'fileutils'
61
+ FileUtils.rm_rf(Dir.glob(File.dirname(__FILE__) + "/tmp/*"))
62
+ end
63
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.2
data/butternut.gemspec ADDED
@@ -0,0 +1,82 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{butternut}
8
+ s.version = "0.2.2"
9
+ s.platform = %q{java}
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.authors = ["Jeremy Stephens"]
13
+ s.date = %q{2010-02-10}
14
+ s.description = %q{Based on Cucumber's HTML formatter, Butternut uses Celerity to capture page sources after each step.}
15
+ s.email = %q{viking415@gmail.com}
16
+ s.extra_rdoc_files = [
17
+ "LICENSE",
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".gitignore",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "butternut.gemspec",
28
+ "lib/butternut.rb",
29
+ "lib/butternut/cucumber.css",
30
+ "lib/butternut/cucumber.sass",
31
+ "lib/butternut/formatter.rb",
32
+ "lib/butternut/helpers.rb",
33
+ "lib/butternut/scenario_extensions.rb",
34
+ "spec/butternut/formatter_spec.rb",
35
+ "spec/butternut/helpers_spec.rb",
36
+ "spec/butternut_spec.rb",
37
+ "spec/fixtures/blargh.html",
38
+ "spec/fixtures/css/bar.css",
39
+ "spec/fixtures/facepalm.jpg",
40
+ "spec/fixtures/foo.css",
41
+ "spec/fixtures/foo.html",
42
+ "spec/fixtures/foo.js",
43
+ "spec/fixtures/picard.jpg",
44
+ "spec/spec.opts",
45
+ "spec/spec_helper.rb",
46
+ "tmp/.gitignore"
47
+ ]
48
+ s.homepage = %q{http://github.com/viking/butternut}
49
+ s.rdoc_options = ["--charset=UTF-8"]
50
+ s.require_paths = ["lib"]
51
+ s.rubygems_version = %q{1.3.5}
52
+ s.summary = %q{A Cucumber formatter that uses Celerity to capture HTML sources (JRuby)}
53
+ s.test_files = [
54
+ "spec/butternut_spec.rb",
55
+ "spec/spec_helper.rb",
56
+ "spec/butternut/formatter_spec.rb",
57
+ "spec/butternut/helpers_spec.rb"
58
+ ]
59
+
60
+ if s.respond_to? :specification_version then
61
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
62
+ s.specification_version = 3
63
+
64
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
65
+ s.add_runtime_dependency(%q<cucumber>, [">= 0.6.0"])
66
+ s.add_runtime_dependency(%q<celerity>, [">= 0"])
67
+ s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
68
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
69
+ else
70
+ s.add_dependency(%q<cucumber>, [">= 0.6.0"])
71
+ s.add_dependency(%q<celerity>, [">= 0"])
72
+ s.add_dependency(%q<nokogiri>, [">= 0"])
73
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<cucumber>, [">= 0.6.0"])
77
+ s.add_dependency(%q<celerity>, [">= 0"])
78
+ s.add_dependency(%q<nokogiri>, [">= 0"])
79
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
80
+ end
81
+ end
82
+
@@ -0,0 +1,112 @@
1
+
2
+
3
+ .cucumber {
4
+ font-family: Verdana, Arial, sans-serif;
5
+ font-size: 15px;
6
+ background: white;
7
+ padding: 1em; }
8
+ .cucumber h1, .cucumber h2, .cucumber h3, .cucumber h4, .cucumber ol, .cucumber li, .cucumber pre, .cucumber p {
9
+ font-size: 1em;
10
+ padding: 0px;
11
+ margin: 0px; }
12
+ .cucumber div.feature {
13
+ border: 1px solid;
14
+ padding: 2px;
15
+ margin: 4px; }
16
+ .cucumber div.feature div.background, .cucumber div.feature div.scenario, .cucumber div.feature p {
17
+ padding: 0em 0em 0em 1em; }
18
+ .cucumber div.feature div.background div.examples, .cucumber div.feature div.scenario div.examples, .cucumber div.feature p div.examples {
19
+ padding: 0em 0em 0em 1em; }
20
+ .cucumber .stats {
21
+ margin: 2em; }
22
+ .cucumber .summary ul.features li {
23
+ display: inline; }
24
+ .cucumber .backtrace {
25
+ margin-top: 0;
26
+ margin-bottom: 0;
27
+ margin-left: 1em; }
28
+ .cucumber a {
29
+ text-decoration: none;
30
+ color: inherit; }
31
+ .cucumber a:hover {
32
+ text-decoration: underline; }
33
+ .cucumber a:visited {
34
+ font-weight: normal; }
35
+ .cucumber a div.examples {
36
+ border: 1px solid;
37
+ padding: 2px;
38
+ margin: 4px; }
39
+ .cucumber table {
40
+ border-collapse: collapse; }
41
+ .cucumber table td, .cucumber table th {
42
+ font-size: 1em;
43
+ border: 1px solid #AAAAAA; }
44
+ .cucumber table td.failed {
45
+ background: #FFC0CB;
46
+ color: #8B0000; }
47
+ .cucumber table td.passed {
48
+ background: #98FB98;
49
+ color: #001111; }
50
+ .cucumber table td.skipped {
51
+ background: #e0ffff;
52
+ color: #001111; }
53
+ .cucumber table td.pending {
54
+ background: #FFFFE0;
55
+ color: #111100; }
56
+ .cucumber table td.undefined {
57
+ background: #FFFFE0;
58
+ color: #111100; }
59
+ .cucumber ol {
60
+ list-style: none; }
61
+ .cucumber ol li {
62
+ margin: 0em 0em 0em 1em;
63
+ padding: 0em 0em 0em 0.2em;
64
+ position: relative; }
65
+ .cucumber ol li span.param {
66
+ font-weight: bold; }
67
+ .cucumber ol li div.page {
68
+ position: absolute;
69
+ right: 0;
70
+ top: 0; }
71
+ .cucumber ol li.failed {
72
+ border-left: 5px solid red;
73
+ border-bottom: 1px solid red;
74
+ background: #ffc0cb;
75
+ color: #8b0000; }
76
+ .cucumber ol li.failed span.param {
77
+ background: !failed_dark; }
78
+ .cucumber ol li.passed {
79
+ border-left: 5px solid lime;
80
+ border-bottom: 1px solid lime;
81
+ background: #98fb98;
82
+ color: #001111; }
83
+ .cucumber ol li.passed span.param {
84
+ background: lime; }
85
+ .cucumber ol li.skipped {
86
+ border-left: 5px solid aqua;
87
+ border-bottom: 1px solid aqua;
88
+ background: #e0ffff;
89
+ color: #001111; }
90
+ .cucumber ol li.skipped span.param {
91
+ background: aqua; }
92
+ .cucumber ol li.pending {
93
+ border-left: 5px solid #ff8000;
94
+ border-bottom: 1px solid #ff8000;
95
+ background: yellow;
96
+ color: #2a1b0a; }
97
+ .cucumber ol li.pending span.param {
98
+ background: #ff8000; }
99
+ .cucumber ol li.undefined {
100
+ border-left: 5px solid #ff8000;
101
+ border-bottom: 1px solid #ff8000;
102
+ background: yellow;
103
+ color: #2a1b0a; }
104
+ .cucumber ol li.undefined span.param {
105
+ background: #ff8000; }
106
+ .cucumber .icon-show {
107
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1gkWFgcEIFayvAAAAB10RVh0Q29tbWVudABDcmVhdGVkIHdpdGggVGhlIEdJTVDvZCVuAAABsElEQVQ4y6WSS2sTURzFf3c6giWktRql7roLqIv5CA1FtBtdpC+6cVGQItFvIz6w0l1F+gG6qa0WxFUIgQhu0hJd9arptJ1mHr33uhgbcjsJFjybC//nOf97xJPK8q6UcoJzKBQKX188f3Wbf8CVUk5UKo9wnKFu8CTo8GZl7RYXgAsQxyG7e00wBmMMN67fBGB2vmwGNY6NXdl+/XKl5AKEYUQQnKQZA99/tJgp3yOKIwQOCGE1x3HCxsanyS6DXjQae/h+ANT7bh4fv4rn3bEl9ML3A96/Wx+oeW5hJnsDpRRJEluHbLd/9dF9DYAkSewBWmvSV3UTtVotM6BUmiK9WcceEEUhnU5oFXuex4W/ceiSSxwlVmJn5yNvV1dRSmWaNje/kMvlWFicWxOz82WTz+eRUlpFDx/cpVgsMjKS50zmqTrFGDBa8WHrM9VqPWXw7OkSxmQ9c3zs02w2EOd8ADB9f4pqtS7d1DuGVutbX42O42RiWmsODnyAIxeg/fsno6MFi8XZVpuY+RsTHB76AL54vLy0tb8vJ9PJA62P44geBobh4ctHWutF/hd/AExBukCW3MYjAAAAAElFTkSuQmCC);
108
+ background-repeat: no-repeat;
109
+ float: left;
110
+ width: 16px;
111
+ height: 16px;
112
+ display: block; }
@@ -0,0 +1,152 @@
1
+ # cucumber.css is generated from cucumber.sass
2
+ # Regenerate with rake sass
3
+
4
+ !step_left = 5px solid
5
+ !step_bottom = 1px solid
6
+
7
+ !failed = #FFC0CB
8
+ !failed_dark = #FF0000
9
+ !failed_text = #8B0000
10
+
11
+ !passed = #98FB98
12
+ !passed_dark = #00FF00
13
+ !passed_text = #001111
14
+
15
+ !skipped = #E0FFFF
16
+ !skipped_dark = #00FFFF
17
+ !skipped_text = #001111
18
+
19
+ !pending = #FFFF00
20
+ !pending_dark = #FF8000
21
+ !pending_text = #2A1B0A
22
+
23
+ !undefined = #FFFF00
24
+ !undefined_dark = #FF8000
25
+ !undefined_text = #2A1B0A
26
+
27
+ .cucumber
28
+ :font-family Verdana, Arial, sans-serif
29
+ :font-size 15px
30
+ :background white
31
+ :padding 1em
32
+
33
+ h1, h2, h3, h4, ol, li, pre, p
34
+ :font-size 1em
35
+ :padding 0px
36
+ :margin 0px
37
+
38
+ div.feature
39
+ :border 1px solid
40
+ :padding 2px
41
+ :margin 4px
42
+
43
+ div.background, div.scenario, p
44
+ :padding 0em 0em 0em 1em
45
+ div.examples
46
+ :padding 0em 0em 0em 1em
47
+
48
+ .stats
49
+ :margin 2em
50
+
51
+ .summary
52
+ ul.features
53
+ li
54
+ :display inline
55
+
56
+ .backtrace
57
+ :margin-top 0
58
+ :margin-bottom 0
59
+ :margin-left 1em
60
+
61
+ a
62
+ :text-decoration none
63
+ :color inherit
64
+
65
+ &:hover
66
+ :text-decoration underline
67
+ &:visited
68
+ :font-weight normal
69
+
70
+ div.examples
71
+ :border 1px solid
72
+ :padding 2px
73
+ :margin 4px
74
+
75
+ table
76
+ :border-collapse collapse
77
+
78
+ td, th
79
+ :font-size 1em
80
+ :border 1px solid #AAAAAA
81
+ td.failed
82
+ :background #FFC0CB
83
+ :color #8B0000
84
+ td.passed
85
+ :background #98FB98
86
+ :color= !passed_text
87
+ td.skipped
88
+ :background= !skipped
89
+ :color= !skipped_text
90
+ td.pending
91
+ :background #FFFFE0
92
+ :color #111100
93
+ td.undefined
94
+ :background #FFFFE0
95
+ :color #111100
96
+
97
+ ol
98
+ :list-style none
99
+
100
+ li
101
+ :margin 0em 0em 0em 1em
102
+ :padding 0em 0em 0em 0.2em
103
+ :position relative
104
+ span.param
105
+ :font-weight bold
106
+ div.page
107
+ :position absolute
108
+ :right 0
109
+ :top 0
110
+ li.failed
111
+ :border-left= !step_left !failed_dark
112
+ :border-bottom= !step_bottom !failed_dark
113
+ :background= !failed
114
+ :color= !failed_text
115
+ span.param
116
+ :background !failed_dark
117
+ li.passed
118
+ :border-left= !step_left !passed_dark
119
+ :border-bottom= !step_bottom !passed_dark
120
+ :background= !passed
121
+ :color= !passed_text
122
+ span.param
123
+ :background= !passed_dark
124
+ li.skipped
125
+ :border-left= !step_left !skipped_dark
126
+ :border-bottom= !step_bottom !skipped_dark
127
+ :background= !skipped
128
+ :color= !skipped_text
129
+ span.param
130
+ :background= !skipped_dark
131
+ li.pending
132
+ :border-left= !step_left !pending_dark
133
+ :border-bottom= !step_bottom !pending_dark
134
+ :background= !pending
135
+ :color= !pending_text
136
+ span.param
137
+ :background= !pending_dark
138
+ li.undefined
139
+ :border-left= !step_left !undefined_dark
140
+ :border-bottom= !step_bottom !undefined_dark
141
+ :background= !undefined
142
+ :color= !undefined_text
143
+ span.param
144
+ :background= !undefined_dark
145
+
146
+ .icon-show
147
+ :background-image url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1gkWFgcEIFayvAAAAB10RVh0Q29tbWVudABDcmVhdGVkIHdpdGggVGhlIEdJTVDvZCVuAAABsElEQVQ4y6WSS2sTURzFf3c6giWktRql7roLqIv5CA1FtBtdpC+6cVGQItFvIz6w0l1F+gG6qa0WxFUIgQhu0hJd9arptJ1mHr33uhgbcjsJFjybC//nOf97xJPK8q6UcoJzKBQKX188f3Wbf8CVUk5UKo9wnKFu8CTo8GZl7RYXgAsQxyG7e00wBmMMN67fBGB2vmwGNY6NXdl+/XKl5AKEYUQQnKQZA99/tJgp3yOKIwQOCGE1x3HCxsanyS6DXjQae/h+ANT7bh4fv4rn3bEl9ML3A96/Wx+oeW5hJnsDpRRJEluHbLd/9dF9DYAkSewBWmvSV3UTtVotM6BUmiK9WcceEEUhnU5oFXuex4W/ceiSSxwlVmJn5yNvV1dRSmWaNje/kMvlWFicWxOz82WTz+eRUlpFDx/cpVgsMjKS50zmqTrFGDBa8WHrM9VqPWXw7OkSxmQ9c3zs02w2EOd8ADB9f4pqtS7d1DuGVutbX42O42RiWmsODnyAIxeg/fsno6MFi8XZVpuY+RsTHB76AL54vLy0tb8vJ9PJA62P44geBobh4ctHWutF/hd/AExBukCW3MYjAAAAAElFTkSuQmCC)
148
+ :background-repeat no-repeat
149
+ :float left
150
+ :width 16px
151
+ :height 16px
152
+ :display block
@@ -0,0 +1,144 @@
1
+ require 'cucumber/formatter/html'
2
+
3
+ require 'fileutils'
4
+ require 'nokogiri'
5
+ require 'uri'
6
+ require 'open-uri'
7
+ require 'net/ftp' # For Net::FTPPermError
8
+
9
+ module Butternut
10
+ class Formatter < Cucumber::Formatter::Html
11
+
12
+ def initialize(step_mother, io, options)
13
+ # find the format options
14
+ format = options[:formats].detect { |(name, _)| name == "Butternut::Formatter" }
15
+ if !format || !format[1].is_a?(String)
16
+ raise "Butternut::Formatter cannot output to STDOUT"
17
+ end
18
+ out = format[1]
19
+
20
+ super
21
+ if File.directory?(out)
22
+ #@assets_dir = out
23
+ #@assets_url = "."
24
+ else
25
+ basename = File.basename(out).sub(/\..*$/, "")
26
+ @assets_dir = File.join(File.dirname(out), basename)
27
+ @assets_url = basename
28
+ if !File.exist?(@assets_dir)
29
+ FileUtils.mkdir(@assets_dir)
30
+ end
31
+ end
32
+ end
33
+
34
+ def before_feature_element(feature_element)
35
+ super
36
+ @feature_element = feature_element
37
+ end
38
+
39
+ def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
40
+ return if @hide_this_step
41
+ # print snippet for undefined steps
42
+ if status == :undefined
43
+ step_multiline_class = @step.multiline_arg ? @step.multiline_arg.class : nil
44
+ @builder.pre do |pre|
45
+ pre << @step_mother.snippet_text(@step.actual_keyword,step_match.instance_variable_get("@name") || '',step_multiline_class)
46
+ end
47
+ end
48
+ add_page_source_link(@builder)
49
+ @builder << '</li>'
50
+ end
51
+
52
+ private
53
+ def add_page_source_link(builder)
54
+ if !@feature_element.respond_to?(:last_page_source) || @feature_element.last_page_source.nil?
55
+ # don't add a link of we haven't interacted with a webpage
56
+ return
57
+ end
58
+
59
+ page_source = @feature_element.last_page_source
60
+ page_url = @feature_element.last_page_url
61
+ @feature_element.last_page_source = nil
62
+ @feature_element.last_page_url = nil
63
+
64
+ page_source = transform_page_source(page_source, page_url)
65
+ path = source_file_name
66
+ File.open(path, "w") { |f| f.print(page_source) }
67
+
68
+ builder.a({:target => "_blank", :href => "#{@assets_url}/#{File.basename(path)}"}) do
69
+ builder << "Source"
70
+ end
71
+ end
72
+
73
+ def source_file_name
74
+ t = Time.now.strftime("%Y%m%d")
75
+ path = nil
76
+ while path.nil?
77
+ path = File.join(@assets_dir, "butternut#{t}-#{$$}-#{rand(0x100000000).to_s(36)}.html")
78
+ path = nil if File.exist?(path)
79
+ end
80
+ path
81
+ end
82
+
83
+ def transform_page_source(page_source, page_url)
84
+ base_uri = URI.parse(page_url)
85
+ base_uri.query = nil
86
+ @already_collected = []
87
+
88
+ doc = Nokogiri.HTML(page_source)
89
+ { :image => ['img', 'src'],
90
+ :stylesheet => ['link[rel=stylesheet]', 'href']
91
+ }.each_pair do |type, (selector, attr)|
92
+ doc.css(selector).each do |elt|
93
+ elt_url = elt[attr]
94
+ next if elt_url.nil? || elt_url.empty?
95
+
96
+ result = save_remote_file(base_uri, type, elt_url)
97
+ elt[attr] = result if result
98
+ end
99
+ end
100
+
101
+ # disable links
102
+ doc.css('a').each { |link| link['href'] = "#" }
103
+
104
+ # turn off scripts
105
+ doc.css('script').each { |s| s.unlink }
106
+
107
+ # disable form elements
108
+ doc.css('input, select, textarea').each { |x| x['disabled'] = 'disabled' }
109
+
110
+ doc.to_s
111
+ end
112
+
113
+ def transform_stylesheet(stylesheet_uri, content)
114
+ content.gsub(%r{url\(([^\)]+)\)}) do |_|
115
+ result = save_remote_file(stylesheet_uri, :image, $1)
116
+ "url(#{result || $1})"
117
+ end
118
+ end
119
+
120
+ def save_remote_file(base_uri, type, url)
121
+ # FIXME: two different files could have the same basename :)
122
+ begin
123
+ uri = URI.parse(url)
124
+ rescue URI::InvalidURIError
125
+ return nil
126
+ end
127
+ remote_uri = uri.absolute? ? uri : base_uri.merge(uri)
128
+ basename = File.basename(uri.path)
129
+
130
+ unless @already_collected.include?(remote_uri)
131
+ begin
132
+ content = open(remote_uri.to_s).read
133
+ content = transform_stylesheet(remote_uri, content) if type == :stylesheet
134
+ local_path = File.join(@assets_dir, basename)
135
+ File.open(local_path, "w") { |f| f.write(content) }
136
+ @already_collected << remote_uri
137
+ rescue IOError, Errno::ENOENT, OpenURI::HTTPError, Net::FTPPermError
138
+ return nil
139
+ end
140
+ end
141
+ basename
142
+ end
143
+ end
144
+ end