gherkin 0.0.3-universal-java-1.5 → 0.0.4-universal-java-1.5
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/.gitignore +2 -3
- data/README.rdoc +19 -0
- data/Rakefile +16 -15
- data/VERSION.yml +1 -1
- data/bin/gherkin +2 -2
- data/features/pretty_printer.feature +5 -2
- data/features/step_definitions/pretty_printer_steps.rb +6 -1
- data/gherkin.gemspec +16 -52
- data/lib/.gitignore +4 -2
- data/lib/gherkin/c_lexer.rb +3 -3
- data/lib/gherkin/core_ext/array.rb +5 -0
- data/lib/gherkin/lexer.rb +6 -5
- data/lib/gherkin/rb_lexer.rb +3 -2
- data/lib/gherkin/tools/pretty_printer.rb +10 -4
- data/nativegems.sh +5 -0
- data/ragel/lexer.c.rl.erb +28 -12
- data/ragel/lexer.java.rl.erb +5 -3
- data/ragel/lexer.rb.rl.erb +14 -15
- data/ragel/lexer_common.rl.erb +6 -6
- data/spec/gherkin/fixtures/1.feature +8 -0
- data/spec/gherkin/fixtures/complex.feature +2 -2
- data/spec/gherkin/shared/lexer_spec.rb +17 -4
- data/spec/gherkin/shared/py_string_spec.rb +12 -0
- data/tasks/bench.rake +28 -11
- data/tasks/compile.rake +70 -0
- data/tasks/ragel_task.rb +83 -0
- metadata +136 -165
- data/ext/gherkin_lexer/.gitignore +0 -6
- data/ext/gherkin_lexer/extconf.rb +0 -6
- data/tasks/ext.rake +0 -49
- data/tasks/ragel.rake +0 -94
data/ragel/lexer.java.rl.erb
CHANGED
@@ -7,7 +7,7 @@ import gherkin.Lexer;
|
|
7
7
|
import gherkin.Listener;
|
8
8
|
import gherkin.LexingError;
|
9
9
|
|
10
|
-
public class <%=
|
10
|
+
public class <%= @i18n.capitalize %> implements Lexer {
|
11
11
|
%%{
|
12
12
|
machine lexer;
|
13
13
|
alphtype byte;
|
@@ -72,10 +72,12 @@ public class <%= i18n_lexer_class_name %> implements Lexer {
|
|
72
72
|
|
73
73
|
action store_comment_content {
|
74
74
|
listener.comment(substring(data, contentStart, p).trim(), lineNumber);
|
75
|
+
keywordStart = -1;
|
75
76
|
}
|
76
77
|
|
77
78
|
action store_tag_content {
|
78
79
|
listener.tag(substring(data, contentStart, p).trim(), currentLine);
|
80
|
+
keywordStart = -1;
|
79
81
|
}
|
80
82
|
|
81
83
|
action inc_line_number {
|
@@ -134,12 +136,12 @@ public class <%= i18n_lexer_class_name %> implements Lexer {
|
|
134
136
|
}
|
135
137
|
}
|
136
138
|
|
137
|
-
include lexer_common "lexer_common.<%=
|
139
|
+
include lexer_common "lexer_common.<%= @i18n %>.rl";
|
138
140
|
}%%
|
139
141
|
|
140
142
|
private final Listener listener;
|
141
143
|
|
142
|
-
public <%=
|
144
|
+
public <%= @i18n.capitalize %>(Listener listener) {
|
143
145
|
this.listener = listener;
|
144
146
|
}
|
145
147
|
|
data/ragel/lexer.rb.rl.erb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
|
+
require 'gherkin/core_ext/array'
|
2
|
+
|
1
3
|
module Gherkin
|
2
4
|
module RbLexer
|
3
|
-
class <%=
|
5
|
+
class <%= @i18n.capitalize %> #:nodoc:
|
4
6
|
%%{
|
5
|
-
# patterns:
|
6
|
-
# * data[start...end].pack("c*").strip_of_some_sort
|
7
|
-
# * changing the end point of the range according to next_keyword_start
|
8
|
-
# * methods taking the machine state because Ragel doesn't seem to know about ivars
|
9
|
-
|
10
7
|
machine lexer;
|
11
8
|
|
12
9
|
action begin_content {
|
@@ -24,7 +21,7 @@ module Gherkin
|
|
24
21
|
}
|
25
22
|
|
26
23
|
action store_pystring_content {
|
27
|
-
con = unindent(@start_col, data[@content_start...@next_keyword_start-1].
|
24
|
+
con = unindent(@start_col, data[@content_start...@next_keyword_start-1].utf8_pack("c*").sub(/(\r?\n)?( )*\Z/, ''))
|
28
25
|
@listener.py_string(con, @current_line)
|
29
26
|
}
|
30
27
|
|
@@ -59,18 +56,20 @@ module Gherkin
|
|
59
56
|
}
|
60
57
|
|
61
58
|
action store_step_content {
|
62
|
-
con = data[@content_start...p].
|
59
|
+
con = data[@content_start...p].utf8_pack("c*").strip
|
63
60
|
@listener.step(@keyword, con, @current_line)
|
64
61
|
}
|
65
62
|
|
66
63
|
action store_comment_content {
|
67
|
-
con = data[@content_start...p].
|
64
|
+
con = data[@content_start...p].utf8_pack("c*").strip
|
68
65
|
@listener.comment(con, @line_number)
|
66
|
+
@keyword_start = nil
|
69
67
|
}
|
70
68
|
|
71
69
|
action store_tag_content {
|
72
|
-
con = data[@content_start...p].
|
70
|
+
con = data[@content_start...p].utf8_pack("c*").strip
|
73
71
|
@listener.tag(con, @current_line)
|
72
|
+
@keyword_start = nil
|
74
73
|
}
|
75
74
|
|
76
75
|
action inc_line_number {
|
@@ -86,7 +85,7 @@ module Gherkin
|
|
86
85
|
}
|
87
86
|
|
88
87
|
action end_keyword {
|
89
|
-
@keyword = data[@keyword_start...p].
|
88
|
+
@keyword = data[@keyword_start...p].utf8_pack("c*").sub(/:$/,'').strip
|
90
89
|
@keyword_start = nil
|
91
90
|
}
|
92
91
|
|
@@ -109,7 +108,7 @@ module Gherkin
|
|
109
108
|
}
|
110
109
|
|
111
110
|
action store_cell_content {
|
112
|
-
con = data[@content_start...p].
|
111
|
+
con = data[@content_start...p].utf8_pack("c*").strip
|
113
112
|
current_row << con
|
114
113
|
}
|
115
114
|
|
@@ -130,7 +129,7 @@ module Gherkin
|
|
130
129
|
end
|
131
130
|
}
|
132
131
|
|
133
|
-
include lexer_common "lexer_common.<%=
|
132
|
+
include lexer_common "lexer_common.<%= @i18n %>.rl";
|
134
133
|
}%%
|
135
134
|
|
136
135
|
def initialize(listener)
|
@@ -159,13 +158,13 @@ module Gherkin
|
|
159
158
|
|
160
159
|
def store_keyword_content(event, data, p, eof)
|
161
160
|
end_point = (!@next_keyword_start or (p == eof)) ? p : @next_keyword_start
|
162
|
-
con = yield data[@content_start...end_point].
|
161
|
+
con = yield data[@content_start...end_point].utf8_pack("c*")
|
163
162
|
@listener.send(event, @keyword, con, @current_line)
|
164
163
|
end
|
165
164
|
|
166
165
|
def current_line_content(data, p)
|
167
166
|
rest = data[@last_newline..-1]
|
168
|
-
rest[0..rest.index(10)||-1].
|
167
|
+
rest[0..rest.index(10)||-1].utf8_pack("c*").strip
|
169
168
|
end
|
170
169
|
end
|
171
170
|
end
|
data/ragel/lexer_common.rl.erb
CHANGED
@@ -2,12 +2,12 @@
|
|
2
2
|
machine lexer_common;
|
3
3
|
|
4
4
|
# Language specific
|
5
|
-
I18N_Feature = <%=
|
6
|
-
I18N_Background = <%=
|
7
|
-
I18N_ScenarioOutline = <%=
|
8
|
-
I18N_Scenario = <%=
|
9
|
-
I18N_Step = (<%=
|
10
|
-
I18N_Examples = <%=
|
5
|
+
I18N_Feature = <%= keywords['feature'] %> >start_keyword %end_keyword;
|
6
|
+
I18N_Background = <%= keywords['background'] %> >start_keyword %end_keyword;
|
7
|
+
I18N_ScenarioOutline = <%= keywords['scenario_outline'] %> >start_keyword %end_keyword;
|
8
|
+
I18N_Scenario = <%= keywords['scenario'] %> >start_keyword %end_keyword;
|
9
|
+
I18N_Step = (<%= keywords['given'] %> | <%= keywords['when'] %> | <%= keywords['and'] %> | <%= keywords['then'] %> | <%= keywords['but'] %>) >start_keyword %end_keyword;
|
10
|
+
I18N_Examples = <%= keywords['examples'] %> >start_keyword %end_keyword;
|
11
11
|
|
12
12
|
EOF = '%_FEATURE_END_%'; # Explicit EOF added before scanning begins
|
13
13
|
EOL = ('\r'? '\n') @inc_line_number @last_newline;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#Comment on line 1
|
2
|
+
#Comment on line 2
|
2
3
|
@tag1 @tag2
|
3
|
-
#Comment on line 3
|
4
4
|
Feature: Feature Text
|
5
5
|
In order to test multiline forms
|
6
6
|
As a ragel writer
|
@@ -40,4 +40,4 @@ Feature: Feature Text
|
|
40
40
|
"""
|
41
41
|
Makes Homer something something
|
42
42
|
"""
|
43
|
-
Then crazy
|
43
|
+
Then crazy
|
@@ -362,15 +362,28 @@ Given I am a step
|
|
362
362
|
]
|
363
363
|
end
|
364
364
|
end
|
365
|
-
|
365
|
+
|
366
|
+
describe "Comment or tag between Feature elements where previous narrative starts with same letter as a keyword" do
|
367
|
+
it "should lex this feature properly" do
|
368
|
+
scan_file("1.feature")
|
369
|
+
@listener.to_sexp.should == [
|
370
|
+
[:feature, "Feature", "Logging in\nSo that I can be myself", 1],
|
371
|
+
[:comment, "# Comment", 3],
|
372
|
+
[:scenario, "Scenario", "Anonymous user can get a login form.\nScenery here", 4],
|
373
|
+
[:tag, "tag", 7],
|
374
|
+
[:scenario, "Scenario", "Another one", 8]
|
375
|
+
]
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
366
379
|
describe "A complex feature with tags, comments, multiple scenarios, and multiple steps and tables" do
|
367
380
|
it "should find things in the right order" do
|
368
381
|
scan_file("complex.feature")
|
369
382
|
@listener.to_sexp.should == [
|
370
383
|
[:comment, "#Comment on line 1", 1],
|
371
|
-
[:
|
372
|
-
[:tag, "
|
373
|
-
[:
|
384
|
+
[:comment, "#Comment on line 2", 2],
|
385
|
+
[:tag, "tag1", 3],
|
386
|
+
[:tag, "tag2", 3],
|
374
387
|
[:feature, "Feature", "Feature Text\nIn order to test multiline forms\nAs a ragel writer\nI need to check for complex combinations", 4],
|
375
388
|
[:comment, "#Comment on line 9", 9],
|
376
389
|
[:comment, "#Comment on line 11", 11],
|
@@ -107,6 +107,18 @@ EOS
|
|
107
107
|
@listener.should_receive(:py_string).with(" Line one", 1)
|
108
108
|
@lexer.scan(str)
|
109
109
|
end
|
110
|
+
|
111
|
+
it "should preserve the last newline(s) at the end of a py_string" do
|
112
|
+
str = <<EOS
|
113
|
+
"""
|
114
|
+
PyString text
|
115
|
+
|
116
|
+
|
117
|
+
"""
|
118
|
+
EOS
|
119
|
+
@listener.should_receive(:py_string).with("PyString text\n\n",1)
|
120
|
+
@lexer.scan(str)
|
121
|
+
end
|
110
122
|
end
|
111
123
|
end
|
112
124
|
end
|
data/tasks/bench.rake
CHANGED
@@ -74,6 +74,7 @@ class Benchmarker
|
|
74
74
|
def report_all
|
75
75
|
Benchmark.bmbm do |x|
|
76
76
|
x.report("c_gherkin:") { run_c_gherkin }
|
77
|
+
x.report("c_gherkin_no_parser:") { run_c_gherkin_no_parser }
|
77
78
|
x.report("rb_gherkin:") { run_rb_gherkin }
|
78
79
|
x.report("cucumber:") { run_cucumber }
|
79
80
|
x.report("tt:") { run_tt }
|
@@ -93,32 +94,42 @@ class Benchmarker
|
|
93
94
|
def run_tt
|
94
95
|
require 'cucumber'
|
95
96
|
# Using Cucumber's Treetop lexer, but never calling #build to build the AST
|
96
|
-
lexer = Cucumber::Parser::NaturalLanguage.new(nil, 'en').
|
97
|
+
lexer = Cucumber::Parser::NaturalLanguage.new(nil, 'en').parser
|
97
98
|
@features.each do |file|
|
98
99
|
source = IO.read(file)
|
99
100
|
parse_tree = lexer.parse(source)
|
100
101
|
if parse_tree.nil?
|
101
|
-
raise Cucumber::
|
102
|
+
raise Cucumber::Parser::SyntaxError.new(lexer, file, 0)
|
102
103
|
end
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
106
107
|
def run_rb_gherkin
|
107
108
|
require 'gherkin'
|
109
|
+
require 'gherkin/rb_lexer'
|
108
110
|
require 'null_listener'
|
109
|
-
listener = NullListener.new
|
110
111
|
@features.each do |feature|
|
111
|
-
|
112
|
+
parser = Gherkin::Parser.new(NullListener.new, true, "root")
|
113
|
+
lexer = Gherkin::RbLexer['en'].new(parser)
|
112
114
|
lexer.scan(File.read(feature))
|
113
115
|
end
|
114
116
|
end
|
115
117
|
|
116
|
-
def run_c_gherkin
|
118
|
+
def run_c_gherkin
|
117
119
|
require 'gherkin'
|
118
120
|
require 'null_listener'
|
119
|
-
|
120
|
-
|
121
|
-
lexer = Gherkin::
|
121
|
+
@features.each do |feature|
|
122
|
+
parser = Gherkin::Parser.new(NullListener.new, true, "root")
|
123
|
+
lexer = Gherkin::CLexer['en'].new(parser)
|
124
|
+
lexer.scan(File.read(feature))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def run_c_gherkin_no_parser
|
129
|
+
require 'gherkin'
|
130
|
+
require 'null_listener'
|
131
|
+
@features.each do |feature|
|
132
|
+
lexer = Gherkin::CLexer['en'].new(NullListener.new)
|
122
133
|
lexer.scan(File.read(feature))
|
123
134
|
end
|
124
135
|
end
|
@@ -144,24 +155,30 @@ namespace :bench do
|
|
144
155
|
benchmarker.report("cucumber")
|
145
156
|
end
|
146
157
|
|
147
|
-
desc "Benchmark the Treetop
|
158
|
+
desc "Benchmark the Treetop parser with the features in tasks/bench/generated"
|
148
159
|
task :tt do
|
149
160
|
benchmarker = Benchmarker.new
|
150
161
|
benchmarker.report("tt")
|
151
162
|
end
|
152
163
|
|
153
|
-
desc "Benchmark the Ruby Gherkin lexer with the features in tasks/bench/generated"
|
164
|
+
desc "Benchmark the Ruby Gherkin lexer+parser with the features in tasks/bench/generated"
|
154
165
|
task :rb_gherkin do
|
155
166
|
benchmarker = Benchmarker.new
|
156
167
|
benchmarker.report("rb_gherkin")
|
157
168
|
end
|
158
169
|
|
159
|
-
desc "Benchmark the C Gherkin lexer with the features in tasks/bench/generated"
|
170
|
+
desc "Benchmark the C Gherkin lexer+parser with the features in tasks/bench/generated"
|
160
171
|
task :c_gherkin do
|
161
172
|
benchmarker = Benchmarker.new
|
162
173
|
benchmarker.report("c_gherkin")
|
163
174
|
end
|
164
175
|
|
176
|
+
desc "Benchmark the C Gherkin lexer (no parser) with the features in tasks/bench/generated"
|
177
|
+
task :c_gherkin_no_parser do
|
178
|
+
benchmarker = Benchmarker.new
|
179
|
+
benchmarker.report("c_gherkin_no_parser")
|
180
|
+
end
|
181
|
+
|
165
182
|
desc "Show basic statistics about the features in tasks/bench/generated"
|
166
183
|
task :stats do
|
167
184
|
["Feature", "Scenario", "Given"].each do |kw|
|
data/tasks/compile.rake
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/ragel_task'
|
2
|
+
|
3
|
+
CLEAN.include [
|
4
|
+
'**/*.{o,bundle,jar,so,obj,pdb,lib,def,exp,log}', 'ext',
|
5
|
+
'java/target',
|
6
|
+
'ragel/i18n/*.rl',
|
7
|
+
'lib/gherkin/rb_lexer/*.rb',
|
8
|
+
'ext/**/*.c',
|
9
|
+
'java/src/gherkin/lexer/*.java'
|
10
|
+
]
|
11
|
+
|
12
|
+
desc "Compile the Java extensions"
|
13
|
+
task :jar do
|
14
|
+
sh("ant -f java/build.xml")
|
15
|
+
end
|
16
|
+
|
17
|
+
YAML.load_file(File.dirname(__FILE__) + '/../lib/gherkin/i18n.yml').each do |i18n, keywords|
|
18
|
+
i18n = i18n.gsub(/[\s-]/, '')
|
19
|
+
|
20
|
+
java = RagelTask.new('java', i18n, keywords)
|
21
|
+
rb = RagelTask.new('rb', i18n, keywords)
|
22
|
+
|
23
|
+
task :jar => java.target
|
24
|
+
task :jar => rb.target
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'rake/extensiontask'
|
28
|
+
c = RagelTask.new('c', i18n, keywords)
|
29
|
+
|
30
|
+
extconf = "ext/gherkin_lexer_#{i18n}/extconf.rb"
|
31
|
+
|
32
|
+
file extconf do
|
33
|
+
FileUtils.mkdir(File.dirname(extconf)) unless File.directory?(File.dirname(extconf))
|
34
|
+
File.open(extconf, "w") do |io|
|
35
|
+
io.write(<<-EOF)
|
36
|
+
require 'mkmf'
|
37
|
+
dir_config("gherkin_lexer_#{i18n}")
|
38
|
+
have_library("c", "main")
|
39
|
+
create_makefile("gherkin_lexer_#{i18n}")
|
40
|
+
EOF
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Rake::ExtensionTask.new("gherkin_lexer_#{i18n}") do |ext|
|
45
|
+
if ENV['RUBY_CC_VERSION']
|
46
|
+
ext.cross_compile = true
|
47
|
+
ext.cross_platform = 'i386-mingw32'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
task :compile => c.target
|
52
|
+
task :compile => rb.target
|
53
|
+
|
54
|
+
# The way tasks are defined with compile:xxx (but without namespace) in rake-compiler forces us
|
55
|
+
# to use these hacks for setting up dependencies. Ugly!
|
56
|
+
Rake::Task["compile:gherkin_lexer_#{i18n}"].prerequisites.unshift(extconf)
|
57
|
+
Rake::Task["compile:gherkin_lexer_#{i18n}"].prerequisites.unshift(c.target)
|
58
|
+
Rake::Task["compile:gherkin_lexer_#{i18n}"].prerequisites.unshift(rb.target)
|
59
|
+
|
60
|
+
Rake::Task["compile"].prerequisites.unshift(extconf)
|
61
|
+
Rake::Task["compile"].prerequisites.unshift(c.target)
|
62
|
+
Rake::Task["compile"].prerequisites.unshift(rb.target)
|
63
|
+
rescue LoadError
|
64
|
+
unless defined?($c_warned)
|
65
|
+
warn "WARNING: Rake::ExtensionTask not installed. Skipping C compilation."
|
66
|
+
$c_warned = true
|
67
|
+
task :compile # no-op
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/tasks/ragel_task.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
class RagelTask
|
5
|
+
RL_OUTPUT_DIR = File.dirname(__FILE__) + "/../ragel/i18n"
|
6
|
+
|
7
|
+
def initialize(lang, i18n, keywords)
|
8
|
+
@lang = lang
|
9
|
+
@i18n = i18n
|
10
|
+
@keywords = keywords
|
11
|
+
define_tasks
|
12
|
+
end
|
13
|
+
|
14
|
+
def define_tasks
|
15
|
+
file target => [lang_ragel, common_ragel] do
|
16
|
+
mkdir_p(File.dirname(target)) unless File.directory?(File.dirname(target))
|
17
|
+
sh "ragel #{flags} #{lang_ragel} -o #{target}"
|
18
|
+
end
|
19
|
+
|
20
|
+
file lang_ragel => lang_erb do
|
21
|
+
impl = ERB.new(IO.read(lang_erb)).result(binding)
|
22
|
+
write(impl, lang_ragel)
|
23
|
+
end
|
24
|
+
|
25
|
+
file common_ragel => common_erb do
|
26
|
+
keywords = prep_keywords
|
27
|
+
common = ERB.new(IO.read(common_erb)).result(binding)
|
28
|
+
write(common, common_ragel)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def target
|
33
|
+
{
|
34
|
+
'c' => "ext/gherkin_lexer_#{@i18n}/gherkin_lexer_#{@i18n}.c",
|
35
|
+
'java' => "java/src/gherkin/lexer/#{@i18n.capitalize}.java",
|
36
|
+
'rb' => "lib/gherkin/rb_lexer/#{@i18n}.rb"
|
37
|
+
}[@lang]
|
38
|
+
end
|
39
|
+
|
40
|
+
def common_ragel
|
41
|
+
RL_OUTPUT_DIR + "/lexer_common.#{@i18n}.rl"
|
42
|
+
end
|
43
|
+
|
44
|
+
def common_erb
|
45
|
+
File.dirname(__FILE__) + '/../ragel/lexer_common.rl.erb'
|
46
|
+
end
|
47
|
+
|
48
|
+
def lang_ragel
|
49
|
+
RL_OUTPUT_DIR + "/#{@i18n}.#{@lang}.rl"
|
50
|
+
end
|
51
|
+
|
52
|
+
def lang_erb
|
53
|
+
File.dirname(__FILE__) + "/../ragel/lexer.#{@lang}.rl.erb"
|
54
|
+
end
|
55
|
+
|
56
|
+
def flags
|
57
|
+
{
|
58
|
+
'c' => '-C',
|
59
|
+
'java' => '-J',
|
60
|
+
'rb' => '-R'
|
61
|
+
}[@lang]
|
62
|
+
end
|
63
|
+
|
64
|
+
def prep_keywords
|
65
|
+
keywords = @keywords.dup
|
66
|
+
delimited_keywords = %w{feature background scenario scenario_outline examples}
|
67
|
+
bare_keywords = %w{given when then and but}
|
68
|
+
all_keywords = delimited_keywords + bare_keywords
|
69
|
+
|
70
|
+
all_keywords.each { |k| keywords[k] = keywords[k].split("|") }
|
71
|
+
delimited_keywords.each { |k| keywords[k].map! { |v| v += ':'} }
|
72
|
+
bare_keywords.each { |k| keywords[k].map! { |v| (v + ' ').sub(/< $/, '')} }
|
73
|
+
all_keywords.each { |k| keywords[k] = '("' + keywords[k].join('" | "') + '")' }
|
74
|
+
keywords
|
75
|
+
end
|
76
|
+
|
77
|
+
def write(content, filename)
|
78
|
+
mkdir_p(File.dirname(filename)) unless File.directory?(File.dirname(filename))
|
79
|
+
File.open(filename, "wb") do |file|
|
80
|
+
file.write(content)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|