transpec 0.0.1

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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rubocop.yml +13 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +9 -0
  6. data/Guardfile +14 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +37 -0
  9. data/Rakefile +27 -0
  10. data/bin/transpec +8 -0
  11. data/lib/transpec/ast/scanner.rb +51 -0
  12. data/lib/transpec/ast/scope_stack.rb +76 -0
  13. data/lib/transpec/cli.rb +162 -0
  14. data/lib/transpec/configuration.rb +40 -0
  15. data/lib/transpec/git.rb +24 -0
  16. data/lib/transpec/rewriter.rb +109 -0
  17. data/lib/transpec/syntax/double.rb +21 -0
  18. data/lib/transpec/syntax/matcher.rb +60 -0
  19. data/lib/transpec/syntax/method_stub.rb +142 -0
  20. data/lib/transpec/syntax/send_node_syntax.rb +39 -0
  21. data/lib/transpec/syntax/should.rb +49 -0
  22. data/lib/transpec/syntax/should_receive.rb +120 -0
  23. data/lib/transpec/syntax.rb +58 -0
  24. data/lib/transpec/util.rb +50 -0
  25. data/lib/transpec/version.rb +14 -0
  26. data/lib/transpec.rb +17 -0
  27. data/spec/.rubocop.yml +19 -0
  28. data/spec/spec_helper.rb +33 -0
  29. data/spec/spec_spec.rb +54 -0
  30. data/spec/support/file_helper.rb +25 -0
  31. data/spec/support/shared_context.rb +63 -0
  32. data/spec/transpec/ast/scanner_spec.rb +177 -0
  33. data/spec/transpec/ast/scope_stack_spec.rb +94 -0
  34. data/spec/transpec/cli_spec.rb +290 -0
  35. data/spec/transpec/configuration_spec.rb +52 -0
  36. data/spec/transpec/git_spec.rb +85 -0
  37. data/spec/transpec/rewriter_spec.rb +203 -0
  38. data/spec/transpec/syntax/double_spec.rb +88 -0
  39. data/spec/transpec/syntax/matcher_spec.rb +407 -0
  40. data/spec/transpec/syntax/method_stub_spec.rb +386 -0
  41. data/spec/transpec/syntax/should_receive_spec.rb +286 -0
  42. data/spec/transpec/syntax/should_spec.rb +262 -0
  43. data/spec/transpec/util_spec.rb +48 -0
  44. data/transpec.gemspec +32 -0
  45. metadata +233 -0
@@ -0,0 +1,60 @@
1
+ # coding: utf-8
2
+
3
+ module Transpec
4
+ class Syntax
5
+ class Matcher < Syntax
6
+ include SendNodeSyntax, Util
7
+
8
+ def initialize(node, in_example_group_context, source_rewriter)
9
+ @node = node
10
+ @in_example_group_context = in_example_group_context
11
+ @source_rewriter = source_rewriter
12
+ end
13
+
14
+ def correct_operator!(parenthesize_arg = true)
15
+ case method_name
16
+ when :==
17
+ @source_rewriter.replace(selector_range, 'eq')
18
+ parenthesize!(parenthesize_arg)
19
+ when :===, :<, :<=, :>, :>=
20
+ @source_rewriter.insert_before(selector_range, 'be ')
21
+ when :=~
22
+ if arg_node.type == :array
23
+ @source_rewriter.replace(selector_range, 'match_array')
24
+ else
25
+ @source_rewriter.replace(selector_range, 'match')
26
+ end
27
+ parenthesize!(parenthesize_arg)
28
+ end
29
+ end
30
+
31
+ def parenthesize!(always = true)
32
+ return if here_document?(arg_node)
33
+
34
+ case left_parenthesis_range.source
35
+ when ' '
36
+ if always || arg_node.type == :hash
37
+ @source_rewriter.replace(left_parenthesis_range, '(')
38
+ @source_rewriter.insert_after(expression_range, ')')
39
+ end
40
+ when "\n", "\r"
41
+ @source_rewriter.insert_before(left_parenthesis_range, '(')
42
+ linefeed = left_parenthesis_range.source
43
+ matcher_line_indentation = indentation_of_line(@node)
44
+ right_parenthesis = "#{linefeed}#{matcher_line_indentation})"
45
+ @source_rewriter.insert_after(expression_range, right_parenthesis)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def left_parenthesis_range
52
+ Parser::Source::Range.new(
53
+ selector_range.source_buffer,
54
+ selector_range.end_pos,
55
+ selector_range.end_pos + 1
56
+ )
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,142 @@
1
+ # coding: utf-8
2
+
3
+ require 'English'
4
+
5
+ module Transpec
6
+ class Syntax
7
+ class MethodStub < Syntax
8
+ include SendNodeSyntax, Util
9
+
10
+ def self.target_node?(node)
11
+ return false unless node.type == :send
12
+ receiver_node, method_name, *_ = *node
13
+ return false unless receiver_node
14
+ [:stub, :unstub, :stub!, :unstub!].include?(method_name)
15
+ end
16
+
17
+ def allowize!
18
+ # There's no way of unstubbing in #allow syntax.
19
+ return unless [:stub, :stub!].include?(method_name)
20
+
21
+ if @replaced_deprecated_method
22
+ fail 'Already replaced deprecated method, cannot allowize.'
23
+ end
24
+
25
+ unless in_example_group_context?
26
+ fail NotInExampleGroupContextError.new(expression_range, "##{method_name}", '#allow')
27
+ end
28
+
29
+ if arg_node.type == :hash
30
+ expressions = build_allow_expressions_from_hash_node(arg_node)
31
+ @source_rewriter.replace(expression_range, expressions)
32
+ else
33
+ expression = build_allow_expression(arg_node)
34
+ @source_rewriter.replace(expression_range, expression)
35
+ end
36
+
37
+ @allowized = true
38
+ end
39
+
40
+ def replace_deprecated_method!
41
+ replacement_method_name = case method_name
42
+ when :stub! then 'stub'
43
+ when :unstub! then 'unstub'
44
+ end
45
+
46
+ return unless replacement_method_name
47
+
48
+ if @allowized
49
+ fail 'Already allowized, cannot replace deprecated method.'
50
+ end
51
+
52
+ @source_rewriter.replace(selector_range, replacement_method_name)
53
+
54
+ @replaced_deprecated_method = true
55
+ end
56
+
57
+ private
58
+
59
+ def build_allow_expressions_from_hash_node(hash_node)
60
+ expressions = []
61
+
62
+ hash_node.children.each_with_index do |pair_node, index|
63
+ key_node, value_node = *pair_node
64
+ expression = build_allow_expression(key_node, value_node, false)
65
+ expression.prepend(indentation_of_line(@node)) if index > 0
66
+ expressions << expression
67
+ end
68
+
69
+ expressions.join($RS)
70
+ end
71
+
72
+ def build_allow_expression(message_node, return_value_node = nil, keep_form_around_arg = true)
73
+ expression = ''
74
+
75
+ expression << if any_instance?
76
+ class_source = class_node_of_any_instance.loc.expression.source
77
+ "allow_any_instance_of(#{class_source})"
78
+ else
79
+ "allow(#{subject_range.source})"
80
+ end
81
+
82
+ expression << range_in_between_subject_and_selector.source
83
+ expression << 'to receive'
84
+ expression << (keep_form_around_arg ? range_in_between_selector_and_arg.source : '(')
85
+ expression << message_source(message_node)
86
+ expression << (keep_form_around_arg ? range_after_arg.source : ')')
87
+
88
+ if return_value_node
89
+ return_value_source = return_value_node.loc.expression.source
90
+ expression << ".and_return(#{return_value_source})"
91
+ end
92
+
93
+ expression
94
+ end
95
+
96
+ def class_node_of_any_instance
97
+ return nil unless subject_node.type == :send
98
+ return nil unless subject_node.children.count == 2
99
+ receiver_node, method_name = *subject_node
100
+ return nil unless method_name == :any_instance
101
+ return nil unless receiver_node.type == :const
102
+ receiver_node
103
+ end
104
+
105
+ def any_instance?
106
+ !class_node_of_any_instance.nil?
107
+ end
108
+
109
+ def message_source(node)
110
+ message_source = node.loc.expression.source
111
+ if node.type == :sym && !message_source.start_with?(':')
112
+ message_source.prepend(':')
113
+ end
114
+ message_source
115
+ end
116
+
117
+ def range_in_between_subject_and_selector
118
+ Parser::Source::Range.new(
119
+ subject_range.source_buffer,
120
+ subject_range.end_pos,
121
+ selector_range.begin_pos
122
+ )
123
+ end
124
+
125
+ def range_in_between_selector_and_arg
126
+ Parser::Source::Range.new(
127
+ selector_range.source_buffer,
128
+ selector_range.end_pos,
129
+ arg_range.begin_pos
130
+ )
131
+ end
132
+
133
+ def range_after_arg
134
+ Parser::Source::Range.new(
135
+ arg_range.source_buffer,
136
+ arg_range.end_pos,
137
+ expression_range.end_pos
138
+ )
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+
3
+ module Transpec
4
+ class Syntax
5
+ module SendNodeSyntax
6
+ def receiver_node
7
+ @node.children[0]
8
+ end
9
+
10
+ alias_method :subject_node, :receiver_node
11
+
12
+ def method_name
13
+ @node.children[1]
14
+ end
15
+
16
+ def arg_node
17
+ @node.children[2]
18
+ end
19
+
20
+ def expression_range
21
+ @node.loc.expression
22
+ end
23
+
24
+ def selector_range
25
+ @node.loc.selector
26
+ end
27
+
28
+ def receiver_range
29
+ receiver_node.loc.expression
30
+ end
31
+
32
+ alias_method :subject_range, :receiver_range
33
+
34
+ def arg_range
35
+ arg_node.loc.expression
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,49 @@
1
+ # coding: utf-8
2
+
3
+ module Transpec
4
+ class Syntax
5
+ class Should < Syntax
6
+ include SendNodeSyntax, Util
7
+
8
+ def self.target_node?(node)
9
+ return false unless node.type == :send
10
+ receiver_node, method_name, *_ = *node
11
+ return false unless receiver_node
12
+ [:should, :should_not].include?(method_name)
13
+ end
14
+
15
+ def positive?
16
+ method_name == :should
17
+ end
18
+
19
+ def expectize!(negative_form = 'not_to', parenthesize_matcher_arg = true)
20
+ unless in_example_group_context?
21
+ fail NotInExampleGroupContextError.new(expression_range, "##{method_name}", '#expect')
22
+ end
23
+
24
+ if proc_literal?(subject_node)
25
+ send_node = subject_node.children.first
26
+ range_of_subject_method_taking_block = send_node.loc.expression
27
+ @source_rewriter.replace(range_of_subject_method_taking_block, 'expect')
28
+ elsif subject_range.source[0] == '('
29
+ @source_rewriter.insert_before(subject_range, 'expect')
30
+ else
31
+ @source_rewriter.insert_before(subject_range, 'expect(')
32
+ @source_rewriter.insert_after(subject_range, ')')
33
+ end
34
+
35
+ @source_rewriter.replace(selector_range, positive? ? 'to' : negative_form)
36
+
37
+ matcher.correct_operator!(parenthesize_matcher_arg)
38
+ end
39
+
40
+ def matcher
41
+ @matcher ||= Matcher.new(matcher_node, in_example_group_context?, @source_rewriter)
42
+ end
43
+
44
+ def matcher_node
45
+ arg_node || parent_node
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,120 @@
1
+ # coding: utf-8
2
+
3
+ module Transpec
4
+ class Syntax
5
+ class ShouldReceive < Syntax
6
+ include SendNodeSyntax
7
+
8
+ def self.target_node?(node)
9
+ return false unless node.type == :send
10
+ receiver_node, method_name, *_ = *node
11
+ return false unless receiver_node
12
+ [:should_receive, :should_not_receive].include?(method_name)
13
+ end
14
+
15
+ def positive?
16
+ method_name == :should_receive
17
+ end
18
+
19
+ def expectize!(negative_form = 'not_to')
20
+ unless in_example_group_context?
21
+ fail NotInExampleGroupContextError.new(expression_range, "##{method_name}", '#expect')
22
+ end
23
+
24
+ if any_instance?(subject_node)
25
+ @source_rewriter.insert_before(subject_range, 'expect_any_instance_of(')
26
+ map = subject_node.loc
27
+ dot_any_instance_range = map.dot.join(map.selector)
28
+ @source_rewriter.replace(dot_any_instance_range, ')')
29
+ else
30
+ @source_rewriter.insert_before(subject_range, 'expect(')
31
+ @source_rewriter.insert_after(subject_range, ')')
32
+ end
33
+
34
+ to_receive = "#{positive? ? 'to' : negative_form} receive"
35
+ @source_rewriter.replace(selector_range, to_receive)
36
+
37
+ correct_block_style!
38
+ end
39
+
40
+ def correct_block_style!
41
+ broken_block_nodes = [
42
+ block_node_taken_by_with_method_with_no_normal_args,
43
+ block_node_followed_by_message_expectation_method
44
+ ].compact.uniq
45
+
46
+ return if broken_block_nodes.empty?
47
+
48
+ broken_block_nodes.each do |block_node|
49
+ map = block_node.loc
50
+ next if map.begin.source == '{'
51
+ @source_rewriter.replace(map.begin, '{')
52
+ @source_rewriter.replace(map.end, '}')
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def any_instance?(node)
59
+ return false unless node.type == :send
60
+ return false unless node.children.count == 2
61
+ receiver_node, method_name = *node
62
+ return false unless method_name == :any_instance
63
+ receiver_node.type == :const
64
+ end
65
+
66
+ # subject.should_receive(:method_name).once.with do |block_arg|
67
+ # end
68
+ #
69
+ # (block
70
+ # (send
71
+ # (send
72
+ # (send
73
+ # (send nil :subject) :should_receive
74
+ # (sym :method_name)) :once) :with)
75
+ # (args
76
+ # (arg :block_arg)) nil)
77
+ def block_node_taken_by_with_method_with_no_normal_args
78
+ @ancestor_nodes.reverse.reduce(@node) do |child_node, parent_node|
79
+ return nil unless [:send, :block].include?(parent_node.type)
80
+ return nil unless parent_node.children.first == child_node
81
+
82
+ if parent_node.type == :block
83
+ return nil unless child_node.children[1] == :with
84
+ return nil if child_node.children[2]
85
+ return parent_node
86
+ end
87
+
88
+ parent_node
89
+ end
90
+
91
+ nil
92
+ end
93
+
94
+ # subject.should_receive(:method_name) do |block_arg|
95
+ # end.once
96
+ #
97
+ # (send
98
+ # (block
99
+ # (send
100
+ # (send nil :subject) :should_receive
101
+ # (sym :method_name))
102
+ # (args
103
+ # (arg :block_arg)) nil) :once)
104
+ def block_node_followed_by_message_expectation_method
105
+ @ancestor_nodes.reverse.reduce(@node) do |child_node, parent_node|
106
+ return nil unless [:send, :block].include?(parent_node.type)
107
+ return nil unless parent_node.children.first == child_node
108
+
109
+ if child_node.type == :block && parent_node.type == :send
110
+ return child_node
111
+ end
112
+
113
+ parent_node
114
+ end
115
+
116
+ nil
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,58 @@
1
+ # coding: utf-8
2
+
3
+ module Transpec
4
+ class Syntax
5
+ class NotInExampleGroupContextError < StandardError
6
+ attr_reader :message, :source_range
7
+
8
+ def initialize(source_range, original_syntax, target_syntax)
9
+ @source_range = source_range
10
+ @message = build_message(original_syntax, target_syntax)
11
+ end
12
+
13
+ def source_buffer
14
+ @source_range.source_buffer
15
+ end
16
+
17
+ private
18
+
19
+ def build_message(original_syntax, target_syntax)
20
+ "Cannot convert #{original_syntax} into #{target_syntax} " +
21
+ "since #{target_syntax} is not available in the context."
22
+ end
23
+ end
24
+
25
+ attr_reader :node, :ancestor_nodes, :in_example_group_context, :source_rewriter
26
+ alias_method :in_example_group_context?, :in_example_group_context
27
+
28
+ def self.all
29
+ @subclasses ||= []
30
+ end
31
+
32
+ def self.inherited(subclass)
33
+ all << subclass
34
+ end
35
+
36
+ def self.snake_case_name
37
+ @snake_cake_name ||= begin
38
+ class_name = name.split('::').last
39
+ class_name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
40
+ end
41
+ end
42
+
43
+ def self.target_node?(node)
44
+ false
45
+ end
46
+
47
+ def initialize(node, ancestor_nodes, in_example_group_context, source_rewriter)
48
+ @node = node
49
+ @ancestor_nodes = ancestor_nodes
50
+ @in_example_group_context = in_example_group_context
51
+ @source_rewriter = source_rewriter
52
+ end
53
+
54
+ def parent_node
55
+ @ancestor_nodes.last
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,50 @@
1
+ # coding: utf-8
2
+
3
+ module Transpec
4
+ module Util
5
+ module_function
6
+
7
+ def proc_literal?(node)
8
+ return false unless node.type == :block
9
+
10
+ send_node = node.children.first
11
+ receiver_node, method_name, *_ = *send_node
12
+
13
+ if receiver_node.nil? || const_name(receiver_node) == 'Kernel'
14
+ [:lambda, :proc].include?(method_name)
15
+ elsif const_name(receiver_node) == 'Proc'
16
+ method_name == :new
17
+ else
18
+ false
19
+ end
20
+ end
21
+
22
+ def const_name(node)
23
+ return nil if node.nil? || node.type != :const
24
+
25
+ const_names = []
26
+ const_node = node
27
+
28
+ loop do
29
+ namespace_node, name = *const_node
30
+ const_names << name
31
+ break unless namespace_node
32
+ break if namespace_node.type == :cbase
33
+ const_node = namespace_node
34
+ end
35
+
36
+ const_names.reverse.join('::')
37
+ end
38
+
39
+ def here_document?(node)
40
+ return false unless [:str, :dstr].include?(node.type)
41
+ node.loc.begin.source.start_with?('<<')
42
+ end
43
+
44
+ def indentation_of_line(node)
45
+ line = node.loc.expression.source_line
46
+ /^(?<indentation>\s*)\S/ =~ line
47
+ indentation
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,14 @@
1
+ # coding: utf-8
2
+
3
+ module Transpec
4
+ # http://semver.org/
5
+ module Version
6
+ MAJOR = 0
7
+ MINOR = 0
8
+ PATCH = 1
9
+
10
+ def self.to_s
11
+ [MAJOR, MINOR, PATCH].join('.')
12
+ end
13
+ end
14
+ end
data/lib/transpec.rb ADDED
@@ -0,0 +1,17 @@
1
+ # coding: utf-8
2
+
3
+ require 'transpec/configuration'
4
+ require 'transpec/cli'
5
+ require 'transpec/git'
6
+ require 'transpec/rewriter'
7
+ require 'transpec/util'
8
+ require 'transpec/version'
9
+ require 'transpec/ast/scanner'
10
+ require 'transpec/ast/scope_stack'
11
+ require 'transpec/syntax'
12
+ require 'transpec/syntax/send_node_syntax'
13
+ require 'transpec/syntax/double'
14
+ require 'transpec/syntax/matcher'
15
+ require 'transpec/syntax/method_stub'
16
+ require 'transpec/syntax/should'
17
+ require 'transpec/syntax/should_receive'
data/spec/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+
2
+ inherit_from: ../.rubocop.yml
3
+
4
+ # Avoid warning "Possibly useless use of == in void context"
5
+ # for `should ==`
6
+ Void:
7
+ Enabled: false
8
+
9
+ # Lengthen for long descriptions.
10
+ LineLength:
11
+ Max: 110
12
+
13
+ # raise_error matcher always requires {} form block.
14
+ Blocks:
15
+ Enabled: false
16
+
17
+ # Sometimes block alignment does not suit RSpec syntax.
18
+ BlockAlignment:
19
+ Enabled: false
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+
3
+ RSpec.configure do |config|
4
+ config.expect_with :rspec do |c|
5
+ # Yes, I'm writing specs in should syntax intentionally!
6
+ c.syntax = :should
7
+ end
8
+
9
+ config.treat_symbols_as_metadata_keys_with_true_values = true
10
+ config.filter_run_excluding do_not_run_in_transpeced_spec: ENV['TRANSPECED_SPEC']
11
+ end
12
+
13
+ Dir[File.join(File.dirname(__FILE__), 'support', '*')].each do |path|
14
+ require path
15
+ end
16
+
17
+ require 'simplecov'
18
+ SimpleCov.coverage_dir(File.join('spec', 'coverage'))
19
+
20
+ if ENV['TRAVIS']
21
+ require 'coveralls'
22
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
23
+ elsif ENV['CI']
24
+ require 'simplecov-rcov'
25
+ SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
26
+ end
27
+
28
+ SimpleCov.start do
29
+ add_filter '/spec/'
30
+ add_filter '/vendor/bundle/'
31
+ end
32
+
33
+ require 'transpec'
data/spec/spec_spec.rb ADDED
@@ -0,0 +1,54 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'tmpdir'
5
+ require 'English'
6
+
7
+ describe 'Transpec project spec', :do_not_run_in_transpeced_spec do
8
+ around do |example|
9
+ Dir.chdir(project_root) do
10
+ example.run
11
+ end
12
+ end
13
+
14
+ let(:project_root) do
15
+ File.expand_path('..', File.dirname(__FILE__))
16
+ end
17
+
18
+ let(:spec_dir) do
19
+ File.join(project_root, 'spec')
20
+ end
21
+
22
+ TRANSPECED_SPEC_DIR = File.join(Dir.mktmpdir, 'transpeced_spec')
23
+
24
+ def silent_system(*args)
25
+ original_env = ENV.to_hash
26
+
27
+ if args.first.is_a?(Hash)
28
+ custom_env = args.shift
29
+ ENV.update(custom_env)
30
+ end
31
+
32
+ command = args.shelljoin
33
+ `#{command}`
34
+
35
+ ENV.replace(original_env)
36
+ $CHILD_STATUS.success?
37
+ rescue
38
+ ENV.replace(original_env)
39
+ false
40
+ end
41
+
42
+ it 'can be converted by Transpec itself without error' do
43
+ FileUtils.cp_r(spec_dir, TRANSPECED_SPEC_DIR)
44
+ silent_system('./bin/transpec', '--force', TRANSPECED_SPEC_DIR).should be_true
45
+ end
46
+
47
+ describe 'converted spec' do
48
+ it 'passes all' do
49
+ pending 'Need to rewrite syntax configuration in RSpec.configure'
50
+ env = { 'TRANSPECED_SPEC' => 'true' }
51
+ silent_system(env, 'rspec', TRANSPECED_SPEC_DIR).should be_true
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ require 'fileutils'
4
+
5
+ module FileHelper
6
+ module_function
7
+
8
+ def create_file(file_path, content)
9
+ file_path = File.expand_path(file_path)
10
+
11
+ dir_path = File.dirname(file_path)
12
+ FileUtils.makedirs(dir_path) unless File.exists?(dir_path)
13
+
14
+ File.open(file_path, 'w') do |file|
15
+ case content
16
+ when String
17
+ file.puts content
18
+ when Array
19
+ file.puts content.join("\n")
20
+ else
21
+ fail 'Unsupported type!'
22
+ end
23
+ end
24
+ end
25
+ end