rake-pipeline-web-filters 0.5.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +1 -0
  3. data/Rakefile +8 -0
  4. data/lib/rake-pipeline-web-filters.rb +17 -3
  5. data/lib/rake-pipeline-web-filters/cache_buster_filter.rb +42 -0
  6. data/lib/rake-pipeline-web-filters/chained_filter.rb +116 -0
  7. data/lib/rake-pipeline-web-filters/coffee_script_filter.rb +41 -0
  8. data/lib/rake-pipeline-web-filters/filter_with_dependencies.rb +27 -0
  9. data/lib/rake-pipeline-web-filters/gzip_filter.rb +59 -0
  10. data/lib/rake-pipeline-web-filters/handlebars_filter.rb +62 -0
  11. data/lib/rake-pipeline-web-filters/helpers.rb +100 -18
  12. data/lib/rake-pipeline-web-filters/iife_filter.rb +38 -0
  13. data/lib/rake-pipeline-web-filters/less_filter.rb +55 -0
  14. data/lib/rake-pipeline-web-filters/markdown_filter.rb +70 -0
  15. data/lib/rake-pipeline-web-filters/minispade_filter.rb +21 -5
  16. data/lib/rake-pipeline-web-filters/neuter_filter.rb +110 -0
  17. data/lib/rake-pipeline-web-filters/sass_filter.rb +87 -0
  18. data/lib/rake-pipeline-web-filters/stylus_filter.rb +59 -0
  19. data/lib/rake-pipeline-web-filters/tilt_filter.rb +16 -3
  20. data/lib/rake-pipeline-web-filters/uglify_filter.rb +66 -0
  21. data/lib/rake-pipeline-web-filters/version.rb +1 -1
  22. data/lib/rake-pipeline-web-filters/yui_css_filter.rb +70 -0
  23. data/lib/rake-pipeline-web-filters/yui_javascript_filter.rb +59 -0
  24. data/rake-pipeline-web-filters.gemspec +10 -1
  25. data/spec/cache_buster_filter_spec.rb +105 -0
  26. data/spec/chained_filter_spec.rb +76 -0
  27. data/spec/coffee_script_filter_spec.rb +110 -0
  28. data/spec/gzip_filter_spec.rb +49 -0
  29. data/spec/handlebars_filter_spec.rb +70 -0
  30. data/spec/helpers_spec.rb +112 -18
  31. data/spec/iife_filter_spec.rb +55 -0
  32. data/spec/less_filter_spec.rb +59 -0
  33. data/spec/markdown_filter_spec.rb +86 -0
  34. data/spec/minispade_filter_spec.rb +47 -15
  35. data/spec/neuter_filter_spec.rb +204 -0
  36. data/spec/sass_filter_spec.rb +147 -0
  37. data/spec/spec_helper.rb +10 -1
  38. data/spec/stylus_filter_spec.rb +69 -0
  39. data/spec/tilt_filter_spec.rb +25 -1
  40. data/spec/uglify_filter_spec.rb +82 -0
  41. data/spec/yui_css_filter_spec.rb +88 -0
  42. data/spec/yui_javascript_filter_spec.rb +68 -0
  43. metadata +225 -19
  44. data/lib/rake-pipeline-web-filters/ordering_concat_filter.rb +0 -38
  45. data/lib/rake-pipeline-web-filters/sass_compiler.rb +0 -53
  46. data/spec/ordering_concat_filter_spec.rb +0 -39
  47. data/spec/sass_compiler_spec.rb +0 -89
@@ -0,0 +1,86 @@
1
+ describe "MarkdownFilter" do
2
+ MarkdownFilter ||= Rake::Pipeline::Web::Filters::MarkdownFilter
3
+ MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper
4
+
5
+ let(:markdown_input) { <<-MARKDOWN }
6
+ ## This is an H2
7
+
8
+ Some *important* text. It might have a link: http://foo.com/
9
+
10
+ Some code
11
+
12
+ That's all.
13
+ MARKDOWN
14
+
15
+ let(:expected_html_output) { <<-HTML }
16
+ <h2>This is an H2</h2>
17
+
18
+ <p>Some <em>important</em> text. It might have a link: http://foo.com/</p>
19
+
20
+ <pre><code>Some code
21
+ </code></pre>
22
+
23
+ <p>That&#39;s all.</p>
24
+ HTML
25
+
26
+ def input_file(name, content)
27
+ MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content)
28
+ end
29
+
30
+ def output_file(name)
31
+ MemoryFileWrapper.new("/path/to/output", name, "UTF-8")
32
+ end
33
+
34
+ def setup_filter(filter)
35
+ filter.file_wrapper_class = MemoryFileWrapper
36
+ filter.input_files = [input_file("page.md", markdown_input)]
37
+ filter.output_root = "/path/to/output"
38
+ filter.rake_application = Rake::Application.new
39
+ filter
40
+ end
41
+
42
+ it "generates output" do
43
+ filter = setup_filter MarkdownFilter.new
44
+
45
+ filter.output_files.should == [output_file("page.html")]
46
+
47
+ tasks = filter.generate_rake_tasks
48
+ tasks.each(&:invoke)
49
+
50
+ file = MemoryFileWrapper.files["/path/to/output/page.html"]
51
+ file.body.should == expected_html_output
52
+ file.encoding.should == "UTF-8"
53
+ end
54
+
55
+ describe "naming output files" do
56
+ it "translates .md extensions to .html by default" do
57
+ filter = setup_filter MarkdownFilter.new
58
+ filter.output_files.first.path.should == "page.html"
59
+ end
60
+
61
+ it "accepts a block to customize output file names" do
62
+ filter = setup_filter(MarkdownFilter.new { |input| "octopus" })
63
+ filter.output_files.first.path.should == "octopus"
64
+ end
65
+ end
66
+
67
+ it "passes options to the Markdown compiler" do
68
+ filter = setup_filter(MarkdownFilter.new(:autolink => true))
69
+ filter.input_files = [input_file("page.md", markdown_input)]
70
+ tasks = filter.generate_rake_tasks
71
+ tasks.each(&:invoke)
72
+ file = MemoryFileWrapper.files["/path/to/output/page.html"]
73
+ file.body.should =~ %r{<a href="http://foo\.com/">}
74
+ end
75
+
76
+ it "accepts a :compiler option" do
77
+ filter = setup_filter(MarkdownFilter.new(:compiler => proc { |text, options| text }))
78
+ filter.input_files = [input_file("page.md", markdown_input)]
79
+ tasks = filter.generate_rake_tasks
80
+ tasks.each(&:invoke)
81
+ file = MemoryFileWrapper.files["/path/to/output/page.html"]
82
+ file.body.should == markdown_input
83
+ end
84
+
85
+ end
86
+
@@ -1,11 +1,11 @@
1
+ require "json"
2
+
1
3
  describe "MinispadeFilter" do
2
- MemoryFileWrapper = Rake::Pipeline::SpecHelpers::MemoryFileWrapper
4
+ MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper
3
5
 
4
- let(:input_files) {
5
- [
6
- MemoryFileWrapper.new("/path/to/input", "foo.js", "UTF-8", "var foo = 'bar';")
7
- ]
8
- }
6
+ def input_file(contents="var foo = 'bar'; // last-line comment", path="/path/to/input", name="foo.js")
7
+ MemoryFileWrapper.new(path, name, "UTF-8", contents)
8
+ end
9
9
 
10
10
  let(:output_files) {
11
11
  [
@@ -17,10 +17,10 @@ describe "MinispadeFilter" do
17
17
  MemoryFileWrapper.files["/path/to/output/foo.js"]
18
18
  }
19
19
 
20
- def make_filter(*args)
20
+ def make_filter(input_file, *args)
21
21
  filter = Rake::Pipeline::Web::Filters::MinispadeFilter.new(*args)
22
22
  filter.file_wrapper_class = MemoryFileWrapper
23
- filter.input_files = input_files
23
+ filter.input_files = [input_file]
24
24
  filter.output_root = "/path/to/output"
25
25
  filter.rake_application = Rake::Application.new
26
26
  filter.generate_rake_tasks.each(&:invoke)
@@ -28,20 +28,52 @@ describe "MinispadeFilter" do
28
28
  end
29
29
 
30
30
  it "generates output" do
31
- filter = make_filter
32
-
31
+ filter = make_filter(input_file)
33
32
  filter.output_files.should == output_files
34
- output_file.body.should == "minispade.register('/path/to/input/foo.js',function() { var foo = 'bar'; });"
35
33
  output_file.encoding.should == "UTF-8"
34
+ output_file.body.should ==
35
+ "minispade.register('/path/to/input/foo.js', function() {var foo = 'bar'; // last-line comment\n});"
36
36
  end
37
37
 
38
38
  it "uses strict if asked" do
39
- filter = make_filter(:use_strict => true)
40
- output_file.body.should == "minispade.register('/path/to/input/foo.js',function() { \"use strict\"; var foo = 'bar'; });"
39
+ filter = make_filter(input_file, :use_strict => true)
40
+ output_file.body.should ==
41
+ "minispade.register('/path/to/input/foo.js', function() {\"use strict\";\nvar foo = 'bar'; // last-line comment\n});"
42
+ end
43
+
44
+ it "compiles a string if asked" do
45
+ filter = make_filter(input_file, :string => true)
46
+ output_file.body.should ==
47
+ %{minispade.register('/path/to/input/foo.js', "(function() {var foo = 'bar'; // last-line comment\\n})();\\n//@ sourceURL=/path/to/input/foo.js");}
41
48
  end
42
49
 
43
50
  it "takes a proc to name the module" do
44
- filter = make_filter(:module_id_generator => proc { |input| "octopus" })
45
- output_file.body.should == "minispade.register('octopus',function() { var foo = 'bar'; });"
51
+ filter = make_filter(input_file, :module_id_generator => proc { |input| "octopus" })
52
+ output_file.body.should ==
53
+ "minispade.register('octopus', function() {var foo = 'bar'; // last-line comment\n});"
54
+ end
55
+
56
+ it "rewrites requires if asked" do
57
+ filter = make_filter(input_file("require('octopus');"), :rewrite_requires => true)
58
+ output_file.body.should ==
59
+ "minispade.register('/path/to/input/foo.js', function() {minispade.require('octopus');\n});"
60
+ end
61
+
62
+ it "rewrites requires if asked even spaces wrap tokens in the require statement" do
63
+ filter = make_filter(input_file("require ( 'octopus');"), :rewrite_requires => true)
64
+ output_file.body.should ==
65
+ "minispade.register('/path/to/input/foo.js', function() {minispade.require('octopus');\n});"
66
+ end
67
+
68
+ it "rewrites requireAll if asked" do
69
+ filter = make_filter(input_file("requireAll('octopus');"), :rewrite_requires => true)
70
+ output_file.body.should ==
71
+ "minispade.register('/path/to/input/foo.js', function() {minispade.requireAll('octopus');\n});"
72
+ end
73
+
74
+ it "rewrites requireAll if asked even spaces wrap tokens in the require statement" do
75
+ filter = make_filter(input_file("requireAll ( 'octopus');"), :rewrite_requires => true)
76
+ output_file.body.should ==
77
+ "minispade.register('/path/to/input/foo.js', function() {minispade.requireAll('octopus');\n});"
46
78
  end
47
79
  end
@@ -0,0 +1,204 @@
1
+ require 'stringio'
2
+
3
+ describe "NeuterFilter" do
4
+ MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper
5
+
6
+ def make_input(name, data)
7
+ make_data(name, data)
8
+ MemoryFileWrapper.new("/path/to/input", name, "UTF-8")
9
+ end
10
+
11
+ def make_data(name, data)
12
+ MemoryFileWrapper.data["/path/to/input/#{name}"] = data
13
+ end
14
+
15
+ def make_filter(input_files, *args)
16
+ opts = args.last.is_a?(Hash) ? args.pop : {}
17
+ opts[:additional_dependencies] ||= proc{|input| %w(b c) }
18
+ args.push(opts)
19
+
20
+ filter = Rake::Pipeline::Web::Filters::NeuterFilter.new(*args)
21
+ filter.file_wrapper_class = MemoryFileWrapper
22
+ filter.input_files = input_files
23
+ filter.output_root = "/path/to/output"
24
+ filter.rake_application = Rake::Application.new
25
+ filter.generate_rake_tasks.each(&:invoke)
26
+ filter
27
+ end
28
+
29
+ def make_filter_with_inputs(inputs, options={})
30
+ input_file = make_input(inputs[0][0], inputs[0][1])
31
+ inputs[1..-1].each{|input| make_data(input[0], input[1]) }
32
+ make_filter([input_file], "processed", options)
33
+ end
34
+
35
+ def capture(*streams)
36
+ streams.map! { |stream| stream.to_s }
37
+ begin
38
+ result = StringIO.new
39
+ streams.each { |stream| eval "$#{stream} = result" }
40
+ yield
41
+ ensure
42
+ streams.each { |stream| eval("$#{stream} = #{stream.upcase}") }
43
+ end
44
+ result.string
45
+ end
46
+
47
+ after(:each) do
48
+ MemoryFileWrapper.data.clear
49
+ end
50
+
51
+ let(:output_files) {
52
+ [
53
+ MemoryFileWrapper.new("/path/to/output", "processed", "BINARY")
54
+ ]
55
+ }
56
+
57
+ let(:output_file) {
58
+ MemoryFileWrapper.files["/path/to/output/processed"]
59
+ }
60
+
61
+ it "generates basic output" do
62
+ input_file = make_input("contents", "data")
63
+ filter = make_filter([input_file], "processed")
64
+
65
+ filter.output_files.should == output_files
66
+ # ConcatFilter forces Binary, not sure if this is right in this case
67
+ output_file.encoding.should == "BINARY"
68
+ output_file.body.should == "data"
69
+ end
70
+
71
+ it "orders required files" do
72
+ make_filter_with_inputs([
73
+ ["a", "require('b');\nA"],
74
+ ["b", "require('c');\nB"],
75
+ ["c", "C"]
76
+ ])
77
+
78
+ output_file.body.should == "C\n\nB\n\nA"
79
+ end
80
+
81
+ it "works with paths" do
82
+ make_filter_with_inputs([
83
+ ["lib/a", "require('lib/b');\nA"],
84
+ ["lib/b", "require('lib/c');\nB"],
85
+ ["lib/c", "C"]
86
+ ], :additional_dependencies => proc{ %w(lib/b lib/c) })
87
+
88
+ output_file.body.should == "C\n\nB\n\nA"
89
+ end
90
+
91
+ it "should handle circular requires" do
92
+ make_filter_with_inputs([
93
+ ["a", "require('b');\nA"],
94
+ ["b", "require('c');\nB"],
95
+ ["c", "require('a');\nC"]
96
+ ])
97
+
98
+ output_file.body.should == "C\n\nB\n\nA"
99
+ end
100
+
101
+ it "should not require the same file twice" do
102
+ make_filter_with_inputs([
103
+ ["a", "require('b');\nrequire('c');\nA"],
104
+ ["b", "require('c');\nB"],
105
+ ["c", "require('a');\nC"]
106
+ ])
107
+
108
+ output_file.body.should == "C\n\nB\n\nA"
109
+ end
110
+
111
+ # Feature not yet supported
112
+ it "does not duplicate files both matched and required"
113
+
114
+ describe "config" do
115
+ describe "require_regexp" do
116
+ it "works with minispade format" do
117
+ make_filter_with_inputs([
118
+ ["a", "minispade.require('b');\nA"],
119
+ ["b", "minispade.require('c');\nB"],
120
+ ["c", "C"]
121
+ ], :require_regexp => %r{^\s*minispade\.require\(['"]([^'"]*)['"]\);?\s*})
122
+
123
+ output_file.body.should == "C\n\nB\n\nA"
124
+ end
125
+
126
+ it "works with sprockets format" do
127
+ make_filter_with_inputs([
128
+ ["a", "//= require b\nA"],
129
+ ["b", "//= require c\nB"],
130
+ ["c", "C"]
131
+ ], :require_regexp => %r{^//= require (\S+)\s*})
132
+
133
+ output_file.body.should == "C\n\nB\n\nA"
134
+ end
135
+ end
136
+
137
+ describe "path_transform" do
138
+ it "converts paths" do
139
+ make_filter_with_inputs([
140
+ ["lib/a.js", "require('b');\nA"],
141
+ ["lib/b.js", "require('c');\nB"],
142
+ ["lib/c.js", "C"]
143
+ ], :path_transform => proc{|path| "lib/#{path}.js" },
144
+ :additional_dependencies => proc{ %w(lib/b.js lib/c.js) })
145
+
146
+ output_file.body.should == "C\n\nB\n\nA"
147
+ end
148
+ end
149
+
150
+ describe "closure_wrap" do
151
+ it "wraps in a javascript closure" do
152
+ make_filter_with_inputs([
153
+ ["a", "require('b');\nA"],
154
+ ["b", "require('c');\nB"],
155
+ ["c", "C"]
156
+ ], :closure_wrap => true)
157
+
158
+ output_file.body.should == "(function() {\nC\n})();\n\n\n\n(function() {\nB\n})();\n\n\n\n(function() {\nA\n})();\n\n"
159
+ end
160
+
161
+ # Not yet supported
162
+ it "allows other wrapper types"
163
+ end
164
+
165
+ describe "filename_comment" do
166
+ it "shows a comment with the filename" do
167
+ make_filter_with_inputs([
168
+ ["a", "require('b');\nA"],
169
+ ["b", "require('c');\nB"],
170
+ ["c", "C"],
171
+ ], :filename_comment => proc{|input| "/* #{input.fullpath} */" })
172
+
173
+ output_file.body.should == "/* /path/to/input/c */\nC\n\n/* /path/to/input/b */\nB\n\n/* /path/to/input/a */\nA"
174
+ end
175
+ end
176
+
177
+ describe "additional_dependencies" do
178
+ it "warns if required file is not contained" do
179
+ output = capture(:stderr) do
180
+ make_filter_with_inputs([
181
+ ["d", "require('e');\nD"],
182
+ ["e", "require('f');\nE"],
183
+ ["f", "F"]
184
+ ])
185
+ end
186
+
187
+ output.should include("Included '/path/to/input/e', which is not listed in :additional_dependencies. The pipeline may not invalidate properly.")
188
+ output.should include("Included '/path/to/input/f', which is not listed in :additional_dependencies. The pipeline may not invalidate properly.")
189
+ end
190
+
191
+ it "does not warn if full paths are provided" do
192
+ output = capture(:stderr) do
193
+ make_filter_with_inputs([
194
+ ["d", "require('e');\nD"],
195
+ ["e", "require('f');\nE"],
196
+ ["f", "F"]
197
+ ], :additional_dependencies => proc{ %w(/path/to/input/e /path/to/input/f) })
198
+ end
199
+
200
+ output.should == ""
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,147 @@
1
+ describe "SassFilter" do
2
+ SassFilter ||= Rake::Pipeline::Web::Filters::SassFilter
3
+ MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper
4
+
5
+ let(:scss_input) { <<-SCSS }
6
+ $blue: #3bbfce;
7
+
8
+ .border {
9
+ border-color: $blue;
10
+ }
11
+ SCSS
12
+
13
+ let(:sass_input) { <<-SASS }
14
+ $blue: #3bbfce
15
+
16
+ .border
17
+ border-color: $blue
18
+ SASS
19
+
20
+ def expected_css_output(filename)
21
+ <<-CSS
22
+ /* line 3, /path/to/input/#{filename} */
23
+ .border {
24
+ border-color: #3bbfce;
25
+ }
26
+ CSS
27
+ end
28
+
29
+ def input_file(name, content)
30
+ MemoryFileWrapper.new("/path/to/input", name, "UTF-8", content)
31
+ end
32
+
33
+ def output_file(name)
34
+ MemoryFileWrapper.new("/path/to/output", name, "UTF-8")
35
+ end
36
+
37
+ def setup_filter(filter, input_files=nil)
38
+ filter.file_wrapper_class = MemoryFileWrapper
39
+ filter.input_files = input_files || [input_file("border.scss", scss_input)]
40
+ filter.output_root = "/path/to/output"
41
+ filter.rake_application = Rake::Application.new
42
+ filter
43
+ end
44
+
45
+ it "generates output" do
46
+ filter = setup_filter SassFilter.new
47
+ filter.output_files.should == [output_file("border.css")]
48
+
49
+ tasks = filter.generate_rake_tasks
50
+ tasks.each(&:invoke)
51
+
52
+ file = MemoryFileWrapper.files["/path/to/output/border.css"]
53
+ file.body.should == expected_css_output("border.scss")
54
+ file.encoding.should == "UTF-8"
55
+ end
56
+
57
+ describe "naming output files" do
58
+ it "translates .scss extensions to .css by default" do
59
+ filter = setup_filter SassFilter.new
60
+ filter.output_files.first.path.should == "border.css"
61
+ end
62
+
63
+ it "accepts a block to customize output file names" do
64
+ filter = setup_filter(SassFilter.new { |input| "octopus" })
65
+ filter.output_files.first.path.should == "octopus"
66
+ end
67
+ end
68
+
69
+ it "accepts options to pass to the Sass compiler" do
70
+ input_files = [input_file("border.sass_file", sass_input)]
71
+ filter = setup_filter(SassFilter.new(:syntax => :sass), input_files)
72
+ tasks = filter.generate_rake_tasks
73
+ tasks.each(&:invoke)
74
+ file = MemoryFileWrapper.files["/path/to/output/border.sass_file"]
75
+ file.body.should == expected_css_output("border.sass_file")
76
+ end
77
+
78
+ it "compiles files with a .sass extension as sass" do
79
+ input_files = [input_file("border.sass", sass_input)]
80
+ filter = setup_filter(SassFilter.new, input_files)
81
+ tasks = filter.generate_rake_tasks
82
+ tasks.each(&:invoke)
83
+ file = MemoryFileWrapper.files["/path/to/output/border.css"]
84
+ file.body.should == expected_css_output("border.sass")
85
+ end
86
+
87
+ it "passes Compass's options to the Sass compiler" do
88
+ Compass.configuration do |c|
89
+ c.preferred_syntax = :sass
90
+ end
91
+
92
+ input_files = [input_file("border.css", scss_input)]
93
+ filter = setup_filter(SassFilter.new, input_files)
94
+ tasks = filter.generate_rake_tasks
95
+ tasks.each(&:invoke)
96
+ file = MemoryFileWrapper.files["/path/to/output/border.css"]
97
+ file.body.should == expected_css_output("border.css")
98
+ end
99
+
100
+ describe "additional load paths" do
101
+ it "is empty by default" do
102
+ filter = setup_filter(SassFilter.new)
103
+ filter.additional_load_paths == []
104
+ end
105
+
106
+ it "transforms to array" do
107
+ filter = setup_filter(SassFilter.new(:additional_load_paths => "additional"))
108
+ filter.additional_load_paths == ["additional"]
109
+ end
110
+
111
+ it "accepts array" do
112
+ filter = setup_filter(SassFilter.new(:additional_load_paths => ["additional", "extra"]))
113
+ filter.additional_load_paths == ["additional", "extra"]
114
+ end
115
+ end
116
+
117
+ describe "additional dependencies" do
118
+ def write_input_file(filename, contents='', root=tmp)
119
+ mkdir_p root
120
+ File.open(File.join(root, filename), 'w') { |f| f.puts contents }
121
+ Rake::Pipeline::FileWrapper.new(root, filename)
122
+ end
123
+
124
+ let(:main_scss) { '@import "blue";' }
125
+ let(:blue_scss) { '$blue: #3bbfce;' }
126
+ let!(:main) { write_input_file('main.scss', main_scss) }
127
+ let!(:blue) { write_input_file('blue.scss', blue_scss) }
128
+
129
+ before do
130
+ File.open(main.fullpath, "w") { |f| f.puts main_scss }
131
+ File.open(blue.fullpath, "w") { |f| f.puts blue_scss }
132
+ end
133
+
134
+ it "includes @imported files" do
135
+ filter = SassFilter.new
136
+ filter.input_files = [main]
137
+ filter.output_root = "#{tmp}/output"
138
+ filter.rake_application = Rake::Application.new
139
+
140
+ filter.additional_dependencies(main).should include(blue.fullpath)
141
+
142
+ tasks = filter.generate_rake_tasks
143
+ tasks.each(&:invoke)
144
+ end
145
+ end
146
+
147
+ end