Files
Unity/auto/generate_module.rb
Matt Chernosky 7b51355e5a Module generator finishes for partially existing files
This resolves #219. When generating a new module, if all the files to
generate already exist then it fails as before. If some of the files
already exist, then the files that need to be created are created. Any
existing files are not changed.

Also added a bunch of tests for this feature via rspec. Run them from
the test folder with `rake spec`.
2016-12-02 13:49:07 -07:00

312 lines
11 KiB
Ruby

# ==========================================
# Unity Project - A Test Framework for C
# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
# [Released under MIT License. Please refer to license.txt for details]
# ==========================================
# This script creates all the files with start code necessary for a new module.
# A simple module only requires a source file, header file, and test file.
# Triad modules require a source, header, and test file for each triad type (like model, conductor, and hardware).
require 'rubygems'
require 'fileutils'
require 'pathname'
#TEMPLATE_TST
TEMPLATE_TST ||= %q[#include "unity.h"
%2$s#include "%1$s.h"
void setUp(void)
{
}
void tearDown(void)
{
}
void test_%1$s_NeedToImplement(void)
{
TEST_IGNORE_MESSAGE("Need to Implement %1$s");
}
]
#TEMPLATE_SRC
TEMPLATE_SRC ||= %q[%2$s#include "%1$s.h"
]
#TEMPLATE_INC
TEMPLATE_INC ||= %q[#ifndef _%3$s_H
#define _%3$s_H
%2$s
#endif // _%3$s_H
]
class UnityModuleGenerator
############################
def initialize(options=nil)
here = File.expand_path(File.dirname(__FILE__)) + '/'
@options = UnityModuleGenerator.default_options
case(options)
when NilClass then @options
when String then @options.merge!(UnityModuleGenerator.grab_config(options))
when Hash then @options.merge!(options)
else raise "If you specify arguments, it should be a filename or a hash of options"
end
# Create default file paths if none were provided
@options[:path_src] = here + "../src/" if @options[:path_src].nil?
@options[:path_inc] = @options[:path_src] if @options[:path_inc].nil?
@options[:path_tst] = here + "../test/" if @options[:path_tst].nil?
@options[:path_src] += '/' unless (@options[:path_src][-1] == 47)
@options[:path_inc] += '/' unless (@options[:path_inc][-1] == 47)
@options[:path_tst] += '/' unless (@options[:path_tst][-1] == 47)
#Built in patterns
@patterns = { 'src' => {'' => { :inc => [] } },
'test'=> {'' => { :inc => [] } },
'dh' => {'Driver' => { :inc => [create_filename('%1$s','Hardware.h')] },
'Hardware' => { :inc => [] }
},
'dih' => {'Driver' => { :inc => [create_filename('%1$s','Hardware.h'), create_filename('%1$s','Interrupt.h')] },
'Interrupt'=> { :inc => [create_filename('%1$s','Hardware.h')] },
'Hardware' => { :inc => [] }
},
'mch' => {'Model' => { :inc => [] },
'Conductor'=> { :inc => [create_filename('%1$s','Model.h'), create_filename('%1$s','Hardware.h')] },
'Hardware' => { :inc => [] }
},
'mvp' => {'Model' => { :inc => [] },
'Presenter'=> { :inc => [create_filename('%1$s','Model.h'), create_filename('%1$s','View.h')] },
'View' => { :inc => [] }
}
}
end
############################
def self.default_options
{
:pattern => "src",
:includes =>
{
:src => [],
:inc => [],
:tst => [],
},
:update_svn => false,
:boilerplates => {},
:test_prefix => 'Test',
:mock_prefix => 'Mock',
}
end
############################
def self.grab_config(config_file)
options = self.default_options
unless (config_file.nil? or config_file.empty?)
require 'yaml'
yaml_guts = YAML.load_file(config_file)
options.merge!(yaml_guts[:unity] || yaml_guts[:cmock])
raise "No :unity or :cmock section found in #{config_file}" unless options
end
return(options)
end
############################
def files_to_operate_on(module_name, pattern=nil)
#strip any leading path information from the module name and save for later
subfolder = File.dirname(module_name)
module_name = File.basename(module_name)
#create triad definition
prefix = @options[:test_prefix] || 'Test'
triad = [ { :ext => '.c', :path => @options[:path_src], :prefix => "", :template => TEMPLATE_SRC, :inc => :src, :boilerplate => @options[:boilerplates][:src] },
{ :ext => '.h', :path => @options[:path_inc], :prefix => "", :template => TEMPLATE_INC, :inc => :inc, :boilerplate => @options[:boilerplates][:inc] },
{ :ext => '.c', :path => @options[:path_tst], :prefix => prefix, :template => TEMPLATE_TST, :inc => :tst, :boilerplate => @options[:boilerplates][:tst] },
]
#prepare the pattern for use
pattern = (pattern || @options[:pattern] || 'src').downcase
patterns = @patterns[pattern]
raise "ERROR: The design pattern '#{pattern}' specified isn't one that I recognize!" if patterns.nil?
#single file patterns (currently just 'test') can reject the other parts of the triad
if (pattern == 'test')
triad.reject!{|v| v[:inc] != :tst }
end
# Assemble the path/names of the files we need to work with.
files = []
triad.each do |cfg|
patterns.each_pair do |pattern_file, pattern_traits|
submodule_name = create_filename(module_name, pattern_file)
filename = cfg[:prefix] + submodule_name + cfg[:ext]
files << {
:path => (Pathname.new("#{cfg[:path]}#{subfolder}") + filename).cleanpath,
:name => submodule_name,
:template => cfg[:template],
:boilerplate => cfg[:boilerplate],
:includes => case(cfg[:inc])
when :src then (@options[:includes][:src] || []) | pattern_traits[:inc].map{|f| f % [module_name]}
when :inc then (@options[:includes][:inc] || [])
when :tst then (@options[:includes][:tst] || []) | pattern_traits[:inc].map{|f| "#{@options[:mock_prefix]}#{f}" % [module_name]}
end
}
end
end
return files
end
############################
def create_filename(part1, part2="")
if part2.empty?
case(@options[:naming])
when 'bumpy' then part1
when 'camel' then part1
when 'snake' then part1.downcase
when 'caps' then part1.upcase
else part1.downcase
end
else
case(@options[:naming])
when 'bumpy' then part1 + part2
when 'camel' then part1 + part2
when 'snake' then part1.downcase + "_" + part2.downcase
when 'caps' then part1.upcase + "_" + part2.upcase
else part1.downcase + "_" + part2.downcase
end
end
end
############################
def generate(module_name, pattern=nil)
files = files_to_operate_on(module_name, pattern)
#Abort if all of the module files already exist
all_files_exist = true
files.each do |file|
if not File.exist?(file[:path])
all_files_exist = false
end
end
raise "ERROR: File #{files[0][:name]} already exists. Exiting." if all_files_exist
# Create Source Modules
files.each_with_index do |file, i|
# If this file already exists, don't overwrite it.
if File.exist?(file[:path])
puts "File #{file[:path]} already exists!"
next
end
# Create the path first if necessary.
FileUtils.mkdir_p(File.dirname(file[:path]), :verbose => false)
File.open(file[:path], 'w') do |f|
f.write("#{file[:boilerplate]}\n" % [file[:name]]) unless file[:boilerplate].nil?
f.write(file[:template] % [ file[:name],
file[:includes].map{|f| "#include \"#{f}\"\n"}.join,
file[:name].upcase ]
)
end
if (@options[:update_svn])
`svn add \"#{file[:path]}\"`
if $?.exitstatus == 0
puts "File #{file[:path]} created and added to source control"
else
puts "File #{file[:path]} created but FAILED adding to source control!"
end
else
puts "File #{file[:path]} created"
end
end
puts 'Generate Complete'
end
############################
def destroy(module_name, pattern=nil)
files_to_operate_on(module_name, pattern).each do |filespec|
file = filespec[:path]
if File.exist?(file)
if @options[:update_svn]
`svn delete \"#{file}\" --force`
puts "File #{file} deleted and removed from source control"
else
FileUtils.remove(file)
puts "File #{file} deleted"
end
else
puts "File #{file} does not exist so cannot be removed."
end
end
puts "Destroy Complete"
end
end
############################
#Handle As Command Line If Called That Way
if ($0 == __FILE__)
destroy = false
options = { }
module_name = nil
# Parse the command line parameters.
ARGV.each do |arg|
case(arg)
when /^-d/ then destroy = true
when /^-u/ then options[:update_svn] = true
when /^-p\"?(\w+)\"?/ then options[:pattern] = $1
when /^-s\"?(.+)\"?/ then options[:path_src] = $1
when /^-i\"?(.+)\"?/ then options[:path_inc] = $1
when /^-t\"?(.+)\"?/ then options[:path_tst] = $1
when /^-n\"?(.+)\"?/ then options[:naming] = $1
when /^-y\"?(.+)\"?/ then options = UnityModuleGenerator.grab_config($1)
when /^(\w+)/
raise "ERROR: You can't have more than one Module name specified!" unless module_name.nil?
module_name = arg
when /^-(h|-help)/
ARGV = []
else
raise "ERROR: Unknown option specified '#{arg}'"
end
end
if (!ARGV[0])
puts [ "\nGENERATE MODULE\n-------- ------",
"\nUsage: ruby generate_module [options] module_name",
" -i\"include\" sets the path to output headers to 'include' (DEFAULT ../src)",
" -s\"../src\" sets the path to output source to '../src' (DEFAULT ../src)",
" -t\"C:/test\" sets the path to output source to 'C:/test' (DEFAULT ../test)",
" -p\"MCH\" sets the output pattern to MCH.",
" dh - driver hardware.",
" dih - driver interrupt hardware.",
" mch - model conductor hardware.",
" mvp - model view presenter.",
" src - just a source module, header and test. (DEFAULT)",
" test - just a test file.",
" -d destroy module instead of creating it.",
" -n\"camel\" sets the file naming convention.",
" bumpy - BumpyCaseFilenames.",
" camel - camelCaseFilenames.",
" snake - snake_case_filenames. (DEFAULT)",
" caps - CAPS_CASE_FILENAMES.",
" -u update subversion too (requires subversion command line)",
" -y\"my.yml\" selects a different yaml config file for module generation",
"" ].join("\n")
exit
end
raise "ERROR: You must have a Module name specified! (use option -h for help)" if module_name.nil?
if (destroy)
UnityModuleGenerator.new(options).destroy(module_name)
else
UnityModuleGenerator.new(options).generate(module_name)
end
end