1# ========================================== 2# Unity Project - A Test Framework for C 3# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams 4# [Released under MIT License. Please refer to license.txt for details] 5# ========================================== 6 7# This script creates all the files with start code necessary for a new module. 8# A simple module only requires a source file, header file, and test file. 9# Triad modules require a source, header, and test file for each triad type (like model, conductor, and hardware). 10 11require 'rubygems' 12require 'fileutils' 13require 'pathname' 14 15# TEMPLATE_TST 16TEMPLATE_TST ||= '#include "unity.h" 17 18%2$s#include "%1$s.h" 19 20void setUp(void) 21{ 22} 23 24void tearDown(void) 25{ 26} 27 28void test_%1$s_NeedToImplement(void) 29{ 30 TEST_IGNORE_MESSAGE("Need to Implement %1$s"); 31} 32'.freeze 33 34# TEMPLATE_SRC 35TEMPLATE_SRC ||= '%2$s#include "%1$s.h" 36'.freeze 37 38# TEMPLATE_INC 39TEMPLATE_INC ||= '#ifndef %3$s_H 40#define %3$s_H 41%2$s 42 43#endif // %3$s_H 44'.freeze 45 46class UnityModuleGenerator 47 ############################ 48 def initialize(options = nil) 49 @options = UnityModuleGenerator.default_options 50 case options 51 when NilClass then @options 52 when String then @options.merge!(UnityModuleGenerator.grab_config(options)) 53 when Hash then @options.merge!(options) 54 else raise 'If you specify arguments, it should be a filename or a hash of options' 55 end 56 57 # Create default file paths if none were provided 58 @options[:path_src] = "#{__dir__}/../src/" if @options[:path_src].nil? 59 @options[:path_inc] = @options[:path_src] if @options[:path_inc].nil? 60 @options[:path_tst] = "#{__dir__}/../test/" if @options[:path_tst].nil? 61 @options[:path_src] += '/' unless @options[:path_src][-1] == 47 62 @options[:path_inc] += '/' unless @options[:path_inc][-1] == 47 63 @options[:path_tst] += '/' unless @options[:path_tst][-1] == 47 64 65 # Built in patterns 66 @patterns = { 67 'src' => { 68 '' => { inc: [] } 69 }, 70 'test' => { 71 '' => { inc: [] } 72 }, 73 'dh' => { 74 'Driver' => { inc: [create_filename('%1$s', 'Hardware.h')] }, 75 'Hardware' => { inc: [] } 76 }, 77 'dih' => { 78 'Driver' => { inc: [create_filename('%1$s', 'Hardware.h'), create_filename('%1$s', 'Interrupt.h')] }, 79 'Interrupt' => { inc: [create_filename('%1$s', 'Hardware.h')] }, 80 'Hardware' => { inc: [] } 81 }, 82 'mch' => { 83 'Model' => { inc: [] }, 84 'Conductor' => { inc: [create_filename('%1$s', 'Model.h'), create_filename('%1$s', 'Hardware.h')] }, 85 'Hardware' => { inc: [] } 86 }, 87 'mvp' => { 88 'Model' => { inc: [] }, 89 'Presenter' => { inc: [create_filename('%1$s', 'Model.h'), create_filename('%1$s', 'View.h')] }, 90 'View' => { inc: [] } 91 } 92 } 93 end 94 95 ############################ 96 def self.default_options 97 { 98 pattern: 'src', 99 includes: { 100 src: [], 101 inc: [], 102 tst: [] 103 }, 104 update_svn: false, 105 boilerplates: {}, 106 test_prefix: 'Test', 107 mock_prefix: 'Mock' 108 } 109 end 110 111 ############################ 112 def self.grab_config(config_file) 113 options = default_options 114 unless config_file.nil? || config_file.empty? 115 require 'yaml' 116 yaml_guts = YAML.load_file(config_file) 117 options.merge!(yaml_guts[:unity] || yaml_guts[:cmock]) 118 raise "No :unity or :cmock section found in #{config_file}" unless options 119 end 120 options 121 end 122 123 ############################ 124 def files_to_operate_on(module_name, pattern = nil) 125 # strip any leading path information from the module name and save for later 126 subfolder = File.dirname(module_name) 127 module_name = File.basename(module_name) 128 129 # create triad definition 130 prefix = @options[:test_prefix] || 'Test' 131 triad = [{ ext: '.c', path: @options[:path_src], prefix: '', template: TEMPLATE_SRC, inc: :src, boilerplate: @options[:boilerplates][:src] }, 132 { ext: '.h', path: @options[:path_inc], prefix: '', template: TEMPLATE_INC, inc: :inc, boilerplate: @options[:boilerplates][:inc] }, 133 { ext: '.c', path: @options[:path_tst], prefix: prefix, template: TEMPLATE_TST, inc: :tst, boilerplate: @options[:boilerplates][:tst] }] 134 135 # prepare the pattern for use 136 pattern = (pattern || @options[:pattern] || 'src').downcase 137 patterns = @patterns[pattern] 138 raise "ERROR: The design pattern '#{pattern}' specified isn't one that I recognize!" if patterns.nil? 139 140 # single file patterns (currently just 'test') can reject the other parts of the triad 141 triad.select! { |v| v[:inc] == :tst } if pattern == 'test' 142 143 # Assemble the path/names of the files we need to work with. 144 files = [] 145 triad.each do |cfg| 146 patterns.each_pair do |pattern_file, pattern_traits| 147 submodule_name = create_filename(module_name, pattern_file) 148 filename = cfg[:prefix] + submodule_name + cfg[:ext] 149 files << { 150 path: (Pathname.new("#{cfg[:path]}#{subfolder}") + filename).cleanpath, 151 name: submodule_name, 152 template: cfg[:template], 153 boilerplate: cfg[:boilerplate], 154 includes: case (cfg[:inc]) 155 when :src then (@options[:includes][:src] || []) | (pattern_traits[:inc].map { |f| format(f, module_name) }) 156 when :inc then (@options[:includes][:inc] || []) 157 when :tst then (@options[:includes][:tst] || []) | (pattern_traits[:inc].map { |f| format("#{@options[:mock_prefix]}#{f}", module_name) }) 158 end 159 } 160 end 161 end 162 163 files 164 end 165 166 ############################ 167 def create_filename(part1, part2 = '') 168 if part2.empty? 169 case (@options[:naming]) 170 when 'bumpy' then part1 171 when 'camel' then part1 172 when 'snake' then part1.downcase 173 when 'caps' then part1.upcase 174 else part1 175 end 176 else 177 case (@options[:naming]) 178 when 'bumpy' then part1 + part2 179 when 'camel' then part1 + part2 180 when 'snake' then part1.downcase + '_' + part2.downcase 181 when 'caps' then part1.upcase + '_' + part2.upcase 182 else part1 + '_' + part2 183 end 184 end 185 end 186 187 ############################ 188 def generate(module_name, pattern = nil) 189 files = files_to_operate_on(module_name, pattern) 190 191 # Abort if all of the module files already exist 192 all_files_exist = true 193 files.each do |file| 194 all_files_exist = false unless File.exist?(file[:path]) 195 end 196 raise "ERROR: File #{files[0][:name]} already exists. Exiting." if all_files_exist 197 198 # Create Source Modules 199 files.each_with_index do |file, _i| 200 # If this file already exists, don't overwrite it. 201 if File.exist?(file[:path]) 202 puts "File #{file[:path]} already exists!" 203 next 204 end 205 # Create the path first if necessary. 206 FileUtils.mkdir_p(File.dirname(file[:path]), verbose: false) 207 File.open(file[:path], 'w') do |f| 208 f.write("#{file[:boilerplate]}\n" % [file[:name]]) unless file[:boilerplate].nil? 209 f.write(file[:template] % [file[:name], 210 file[:includes].map { |ff| "#include \"#{ff}\"\n" }.join, 211 file[:name].upcase]) 212 end 213 if @options[:update_svn] 214 `svn add \"#{file[:path]}\"` 215 if $!.exitstatus.zero? 216 puts "File #{file[:path]} created and added to source control" 217 else 218 puts "File #{file[:path]} created but FAILED adding to source control!" 219 end 220 else 221 puts "File #{file[:path]} created" 222 end 223 end 224 puts 'Generate Complete' 225 end 226 227 ############################ 228 def destroy(module_name, pattern = nil) 229 files_to_operate_on(module_name, pattern).each do |filespec| 230 file = filespec[:path] 231 if File.exist?(file) 232 if @options[:update_svn] 233 `svn delete \"#{file}\" --force` 234 puts "File #{file} deleted and removed from source control" 235 else 236 FileUtils.remove(file) 237 puts "File #{file} deleted" 238 end 239 else 240 puts "File #{file} does not exist so cannot be removed." 241 end 242 end 243 puts 'Destroy Complete' 244 end 245end 246 247############################ 248# Handle As Command Line If Called That Way 249if $0 == __FILE__ 250 destroy = false 251 options = {} 252 module_name = nil 253 254 # Parse the command line parameters. 255 ARGV.each do |arg| 256 case arg 257 when /^-d/ then destroy = true 258 when /^-u/ then options[:update_svn] = true 259 when /^-p\"?(\w+)\"?/ then options[:pattern] = Regexp.last_match(1) 260 when /^-s\"?(.+)\"?/ then options[:path_src] = Regexp.last_match(1) 261 when /^-i\"?(.+)\"?/ then options[:path_inc] = Regexp.last_match(1) 262 when /^-t\"?(.+)\"?/ then options[:path_tst] = Regexp.last_match(1) 263 when /^-n\"?(.+)\"?/ then options[:naming] = Regexp.last_match(1) 264 when /^-y\"?(.+)\"?/ then options = UnityModuleGenerator.grab_config(Regexp.last_match(1)) 265 when /^(\w+)/ 266 raise "ERROR: You can't have more than one Module name specified!" unless module_name.nil? 267 268 module_name = arg 269 when /^-(h|-help)/ 270 ARGV = [].freeze 271 else 272 raise "ERROR: Unknown option specified '#{arg}'" 273 end 274 end 275 276 unless ARGV[0] 277 puts ["\nGENERATE MODULE\n-------- ------", 278 "\nUsage: ruby generate_module [options] module_name", 279 " -i\"include\" sets the path to output headers to 'include' (DEFAULT ../src)", 280 " -s\"../src\" sets the path to output source to '../src' (DEFAULT ../src)", 281 " -t\"C:/test\" sets the path to output source to 'C:/test' (DEFAULT ../test)", 282 ' -p"MCH" sets the output pattern to MCH.', 283 ' dh - driver hardware.', 284 ' dih - driver interrupt hardware.', 285 ' mch - model conductor hardware.', 286 ' mvp - model view presenter.', 287 ' src - just a source module, header and test. (DEFAULT)', 288 ' test - just a test file.', 289 ' -d destroy module instead of creating it.', 290 ' -n"camel" sets the file naming convention.', 291 ' bumpy - BumpyCaseFilenames.', 292 ' camel - camelCaseFilenames.', 293 ' snake - snake_case_filenames.', 294 ' caps - CAPS_CASE_FILENAMES.', 295 ' -u update subversion too (requires subversion command line)', 296 ' -y"my.yml" selects a different yaml config file for module generation', 297 ''].join("\n") 298 exit 299 end 300 301 raise 'ERROR: You must have a Module name specified! (use option -h for help)' if module_name.nil? 302 303 if destroy 304 UnityModuleGenerator.new(options).destroy(module_name) 305 else 306 UnityModuleGenerator.new(options).generate(module_name) 307 end 308 309end 310