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