• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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