generate_module.rb 11 KB

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