butternut 0.2.2-java

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/.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