Class: RSpecKickstarter::Generator

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

Overview

RSpec Code Generator

Constant Summary collapse

RAILS_RESOURCE_METHOD_AND_HTTPMETHOD =
{
  'index'   => 'get',
  'new'     => 'get',
  'create'  => 'post',
  'show'    => 'get',
  'edit'    => 'get',
  'update'  => 'put',
  'destroy' => 'delete'
}

Constants included from ERBTemplates

ERBTemplates::BASIC_METHODS_PART_TEMPLATE, ERBTemplates::BASIC_NEW_SPEC_TEMPLATE, ERBTemplates::RAILS_CONTROLLER_METHODS_PART_TEMPLATE, ERBTemplates::RAILS_CONTROLLER_NEW_SPEC_TEMPLATE, ERBTemplates::RAILS_HELPER_METHODS_PART_TEMPLATE, ERBTemplates::RAILS_HELPER_NEW_SPEC_TEMPLATE

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(spec_dir = './spec', delta_template = nil, full_template = nil) ⇒ Generator

Returns a new instance of Generator.



17
18
19
20
21
# File 'lib/rspec_kickstarter/generator.rb', line 17

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

Instance Attribute Details

#delta_templateObject

Returns the value of attribute delta_template.



15
16
17
# File 'lib/rspec_kickstarter/generator.rb', line 15

def delta_template
  @delta_template
end

#full_templateObject

Returns the value of attribute full_template.



15
16
17
# File 'lib/rspec_kickstarter/generator.rb', line 15

def full_template
  @full_template
end

#spec_dirObject

Returns the value of attribute spec_dir.



15
16
17
# File 'lib/rspec_kickstarter/generator.rb', line 15

def spec_dir
  @spec_dir
end

Instance Method Details

#append_to_existing_spec(class_or_module, dry_run, rails_mode, spec_path) ⇒ Object

Appends new tests to the existing spec.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/rspec_kickstarter/generator.rb', line 129

def append_to_existing_spec(class_or_module, dry_run, rails_mode, spec_path)
  existing_spec = File.read(spec_path)
  lacking_methods = class_or_module.method_list
    .select { |m| m.visibility == :public }
    .reject { |m| existing_spec.match(m.name) }

  if lacking_methods.empty?
    puts "#{spec_path} skipped."
  else
    # These names are used in ERB template, don't delete.
    methods_to_generate = lacking_methods
    c = class_or_module

    erb = RSpecKickstarter::ERBFactory.new(@delta_template).get_instance_for_appending(rails_mode, spec_path)
    additional_spec = erb.result(binding)

    last_end_not_found = true
    code = existing_spec.split("\n").reverse.reject { |line|
      before_modified = last_end_not_found
      last_end_not_found = line.gsub(/#.+$/, '').strip != 'end' if before_modified
      before_modified
    }.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
end

#create_new_spec(class_or_module, dry_run, rails_mode, file_path, spec_path) ⇒ Object

Creates new spec.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/rspec_kickstarter/generator.rb', line 102

def create_new_spec(class_or_module, dry_run, rails_mode, file_path, spec_path)

  # These names are used in ERB template, don't delete.
  methods_to_generate = class_or_module.method_list.select { |m| m.visibility == :public }
  c = class_or_module
  self_path = to_string_value_to_require(file_path)

  erb = RSpecKickstarter::ERBFactory.new(@full_template).get_instance_for_new_spec(rails_mode, file_path)
  code = erb.result(binding)

  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

#get_block_code(method) ⇒ Object

e.g. { |a, b| }



214
215
216
217
218
219
220
# File 'lib/rspec_kickstarter/generator.rb', line 214

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

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

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



43
44
45
46
47
48
49
# File 'lib/rspec_kickstarter/generator.rb', line 43

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

#get_instantiation_code(c, method) ⇒ Object

e.g.

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


172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/rspec_kickstarter/generator.rb', line 172

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

#get_method_invocation_code(c, method) ⇒ Object

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



199
200
201
202
# File 'lib/rspec_kickstarter/generator.rb', line 199

def get_method_invocation_code(c, method)
  target = method.singleton ? get_complete_class_name(c) : instance_name(c)
  "#{target}.#{method.name}#{to_params_part(method.params)}#{get_block_code(method)}"
end

#get_params_initialization_code(method) ⇒ Object

e.g.

a = double('a')
b = double('b')


191
192
193
194
# File 'lib/rspec_kickstarter/generator.rb', line 191

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

#get_rails_helper_method_invocation_code(method) ⇒ Object

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



207
208
209
# File 'lib/rspec_kickstarter/generator.rb', line 207

def get_rails_helper_method_invocation_code(method)
  "#{method.name}#{to_params_part(method.params)}#{get_block_code(method)}"
end

#get_rails_http_method(method_name) ⇒ Object



222
223
224
225
# File 'lib/rspec_kickstarter/generator.rb', line 222

def get_rails_http_method(method_name)
  http_method = RAILS_RESOURCE_METHOD_AND_HTTPMETHOD[method_name]
  http_method.nil? ? 'get' : http_method
end

#get_spec_path(file_path) ⇒ Object

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



55
56
57
# File 'lib/rspec_kickstarter/generator.rb', line 55

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

#instance_name(c) ⇒ Object

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



71
72
73
74
75
76
77
78
# File 'lib/rspec_kickstarter/generator.rb', line 71

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”]



85
86
87
# File 'lib/rspec_kickstarter/generator.rb', line 85

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

#to_params_part(params) ⇒ Object

Returns params part e.g. [“a”,“b”] -> “(a, b)” e.g. [] -> “”



94
95
96
97
# File 'lib/rspec_kickstarter/generator.rb', line 94

def to_params_part(params)
  param_csv = to_param_names_array(params).join(', ')
  param_csv.empty? ? '' : "(#{param_csv})"
end

#to_string_value_to_require(file_path) ⇒ Object

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



63
64
65
# File 'lib/rspec_kickstarter/generator.rb', line 63

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

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

Writes new spec or appends to the existing spec.



26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/rspec_kickstarter/generator.rb', line 26

def write_spec(file_path, force_write = false, dry_run = false, rails_mode = false)
  class_or_module = RSpecKickstarter::RDocFactory.get_rdoc_class_or_module(file_path)
  if class_or_module
    spec_path = get_spec_path(file_path)
    if force_write && File.exist?(spec_path)
      append_to_existing_spec(class_or_module, dry_run, rails_mode, spec_path)
    else
      create_new_spec(class_or_module, dry_run, rails_mode, file_path, spec_path)
    end
  else
    puts "#{file_path} skipped (Class/Module not found)."
  end
end