generate_module.rb 11 KB

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