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