Class: RSpecKickstarter::Generator

Inherits:
Object
  • Object
show all
Defined in:
lib/rspec_kickstarter/generator.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(spec_dir = './spec') ⇒ Generator



13
14
15
# File 'lib/rspec_kickstarter/generator.rb', line 13

def initialize(spec_dir = './spec')
  @spec_dir = spec_dir.gsub(/\/$/, '')
end

Instance Attribute Details

#spec_dirObject

Returns the value of attribute spec_dir.



11
12
13
# File 'lib/rspec_kickstarter/generator.rb', line 11

def spec_dir
  @spec_dir
end

Instance Method Details

#extract_target_class_or_module(top_level) ⇒ Object

Extracts RDoc::NormalClass/RDoc::NormalModule from RDoc::TopLevel.



145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/rspec_kickstarter/generator.rb', line 145

def extract_target_class_or_module(top_level)
  c = top_level.classes.first
  if c.nil?
    m = top_level.modules.first
    if m.nil?
      top_level.is_a?(RDoc::NormalModule) ? top_level : nil
    else
      extract_target_class_or_module(m)
    end
  else
    c
  end
end

#get_block_code(method) ⇒ Object

e.g. { |a, b| }



256
257
258
259
260
261
262
# File 'lib/rspec_kickstarter/generator.rb', line 256

def get_block_code(method)
  if method.block_params.nil? || method.block_params.empty?
    ""
  else
    " { |#{method.block_params}| }"
  end
end

#get_complete_class_name(c, name = c.name) ⇒ Object

Gets the complete class name from RDoc::NormalClass/RDoc::NormalModule instance.



162
163
164
165
166
167
168
# File 'lib/rspec_kickstarter/generator.rb', line 162

def get_complete_class_name(c, name = c.name)
  if !c.parent.name.nil? && c.parent.is_a?(RDoc::NormalModule)
    get_complete_class_name(c.parent, "#{c.parent.name}::#{name}")
  else
    name
  end
end

#get_instantiation_code(c, method) ⇒ Object

e.g.

a = stub('a')
b = stub('b')
bar_baz = BarBaz.new(a, b)


218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/rspec_kickstarter/generator.rb', line 218

def get_instantiation_code(c, method)
  if method.singleton
    ""
  else
    constructor = c.method_list.find { |m| m.name == 'new' }
    if constructor.nil?
      "\n      #{instance_name(c)} = #{get_complete_class_name(c)}.new"
    else
      get_params_initialization_code(constructor) +
        "\n      #{instance_name(c)} = #{get_complete_class_name(c)}.new(#{to_param_names_array(constructor.params).join(', ')})"
    end
  end
end

#get_method_invocation_code(c, method) ⇒ Object

e.g. BarBaz.do_something(a, b) { |c| }



245
246
247
248
249
250
251
# File 'lib/rspec_kickstarter/generator.rb', line 245

def get_method_invocation_code(c, method)
  if method.singleton
    "#{get_complete_class_name(c)}.#{method.name}(#{to_param_names_array(method.params).join(', ')})#{get_block_code(method)}"
  else
    "#{instance_name(c)}.#{method.name}(#{to_param_names_array(method.params).join(', ')})#{get_block_code(method)}"
  end
end

#get_params_initialization_code(method) ⇒ Object

e.g.

a = stub('a')
b = stub('b')


237
238
239
240
# File 'lib/rspec_kickstarter/generator.rb', line 237

def get_params_initialization_code(method)
  code = to_param_names_array(method.params).map { |p| "      #{p} = stub('#{p}')" }.join("\n")
  code.empty? ? "" : "\n#{code}"
end

#get_ruby_parser(file_path) ⇒ Object

Creates new RDoc::Parser::Ruby instance.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/rspec_kickstarter/generator.rb', line 116

def get_ruby_parser(file_path)
  top_level = RDoc::TopLevel.new(file_path)
  if RUBY_VERSION.to_f < 2.0
    # reset is removed since 2.0
    RDoc::TopLevel.reset()
  end

  # RDoc::Stats initialization
  if RUBY_VERSION.to_f >= 2.0
    # Ruby 2.0 requires RDoc::Store internally.
    store = RDoc::Store.new
    top_level.store = store
    stats = RDoc::Stats.new(store, 1)
  else
    stats = RDoc::Stats.new(1)
  end

  RDoc::Parser::Ruby.new(
    top_level,
    file_path,
    File.read(file_path),
    RDoc::Options.new,
    stats
  )
end

#get_spec_path(file_path) ⇒ Object

Returns spec file path. e.g. “lib/foo/bar_baz.rb” -> “spec/foo/bar_baz_spec.rb”



174
175
176
# File 'lib/rspec_kickstarter/generator.rb', line 174

def get_spec_path(file_path)
  spec_dir + '/' + file_path.gsub(/^\.\//, '').gsub(/^(lib\/)|(app\/)/, '').gsub(/\.rb$/, '_spec.rb')
end

#instance_name(c) ⇒ Object

Returns snake_case name. e.g. FooBar -> “foo_bar”



190
191
192
193
194
195
196
197
# File 'lib/rspec_kickstarter/generator.rb', line 190

def instance_name(c)
  c.name
    .gsub(/::/, '/')
    .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
    .gsub(/([a-z\d])([A-Z])/,'\1_\2')
    .tr("-", "_")
    .downcase
end

#to_param_names_array(params) ⇒ Object

Extracts parameter names as an Array. e.g. “()” -> [] e.g. “(a, b = ‘foo’)” -> [“a”, “b”]



204
205
206
# File 'lib/rspec_kickstarter/generator.rb', line 204

def to_param_names_array(params)
  params.split(',').map { |p| p.gsub(/[\(\)\s]/, '').gsub(/=.+$/, '') }.reject { |p| p.nil? || p.empty? }
end

#to_string_value_to_require(file_path) ⇒ Object

Returns string value to require. e.g. “lib/foo/bar_baz.rb” -> “foo/bar_baz”



182
183
184
# File 'lib/rspec_kickstarter/generator.rb', line 182

def to_string_value_to_require(file_path)
  file_path.gsub(/^(lib\/)|(app\/)/, '').gsub(/\.rb$/, '')
end

#write_spec(file_path, force_write = false, dry_run = false) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/rspec_kickstarter/generator.rb', line 17

def write_spec(file_path, force_write = false, dry_run = false)

  top_level = get_ruby_parser(file_path).scan
  c = extract_target_class_or_module(top_level)

  if c.nil?
    puts "#{file_path} skipped (Class/Module not found)."
  else

    spec_path = get_spec_path(file_path)

    if force_write && File.exist?(spec_path)
      # Append to the existing spec or skip

      existing_spec = File.read(spec_path)
      racking_methods = c.method_list
        .select { |m| m.visibility == :public }
        .reject { |m| existing_spec.match(m.name) }

      if racking_methods.empty? 
        puts "#{spec_path} skipped."
      else
        additional_spec = "\#{racking_methods.map { |method|\n<<EACH_SPEC\n# TODO auto-generated\ndescribe '\#{method.name}' do\n  it 'works' do\#{get_instantiation_code(c, method)}\#{get_params_initialization_code(method)}\n    result = \#{get_method_invocation_code(c, method)}\n    expect(result).not_to be_nil\n  end\nend\nEACH_SPEC\n}.join(\"\\n\")}\n"
        last_end_not_found = true
        code = existing_spec.split("\n").reverse.reject { |line| 
          if last_end_not_found 
            last_end_not_found = line.gsub(/#.+$/, '').strip != "end"
            true
          else
            false
          end
        }.reverse.join("\n") + "\n" + additional_spec + "\nend\n"
        if dry_run
          puts "----- #{spec_path} -----"
          puts code
        else
          File.open(spec_path, 'w') { |f| f.write(code) }
        end
        puts "#{spec_path} modified."
      end

    else
      # Create a new spec 

      self_path = to_string_value_to_require(file_path)
      code = "# -*- encoding: utf-8 -*-\nrequire 'spec_helper'\nrequire '\#{self_path}'\n\ndescribe \#{get_complete_class_name(c)} do\n\n\#{c.method_list\n.select { |m| m.visibility == :public }\n.map { |method| \n<<EACH_SPEC\n# TODO auto-generated\ndescribe '\#{method.name}' do\n  it 'works' do\#{get_instantiation_code(c, method)}\#{get_params_initialization_code(method)}\n    result = \#{get_method_invocation_code(c, method)}\n    expect(result).not_to be_nil\n  end\nend\nEACH_SPEC\n}.join(\"\\n\")}\nend\n"
      if dry_run
        puts "----- #{spec_path} -----"
        puts code
      else
        if File.exist?(spec_path)
          puts "#{spec_path} already exists."
        else
          FileUtils.mkdir_p(File.dirname(spec_path))
          File.open(spec_path, 'w') { |f| f.write(code) }
          puts "#{spec_path} created."
        end
      end
    end
  end

end