generate_module.rb 11.0 KB
Newer Older
M
Mark VanderVoord 已提交
1 2 3 4
# ==========================================
#   Unity Project - A Test Framework for C
#   Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
#   [Released under MIT License. Please refer to license.txt for details]
5
# ==========================================
M
Mark VanderVoord 已提交
6 7 8 9 10 11 12

# This script creates all the files with start code necessary for a new module.
# A simple module only requires a source file, header file, and test file.
# Triad modules require a source, header, and test file for each triad type (like model, conductor, and hardware).

require 'rubygems'
require 'fileutils'
13
require 'pathname'
M
Mark VanderVoord 已提交
14

15 16
# TEMPLATE_TST
TEMPLATE_TST ||= '#include "unity.h"
M
Mark VanderVoord 已提交
17 18 19 20 21 22 23 24 25 26 27 28
%2$s#include "%1$s.h"

void setUp(void)
{
}

void tearDown(void)
{
}

void test_%1$s_NeedToImplement(void)
{
29
    TEST_IGNORE_MESSAGE("Need to Implement %1$s");
M
Mark VanderVoord 已提交
30
}
31
'.freeze
M
Mark VanderVoord 已提交
32

33 34 35
# TEMPLATE_SRC
TEMPLATE_SRC ||= '%2$s#include "%1$s.h"
'.freeze
M
Mark VanderVoord 已提交
36

37 38
# TEMPLATE_INC
TEMPLATE_INC ||= '#ifndef _%3$s_H
M
Mark VanderVoord 已提交
39 40
#define _%3$s_H
%2$s
M
Mark VanderVoord 已提交
41 42

#endif // _%3$s_H
43
'.freeze
M
Mark VanderVoord 已提交
44

45 46
class UnityModuleGenerator
  ############################
47
  def initialize(options = nil)
48 49 50
    here = File.expand_path(File.dirname(__FILE__)) + '/'

    @options = UnityModuleGenerator.default_options
51 52 53 54 55
    case options
    when NilClass then @options
    when String   then @options.merge!(UnityModuleGenerator.grab_config(options))
    when Hash     then @options.merge!(options)
    else raise 'If you specify arguments, it should be a filename or a hash of options'
56 57 58
    end

    # 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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
    @options[:path_tst] = here + '../test/'   if @options[:path_tst].nil?
    @options[:path_src] += '/'                unless @options[:path_src][-1] == 47
    @options[:path_inc] += '/'                unless @options[:path_inc][-1] == 47
    @options[:path_tst] += '/'                unless @options[:path_tst][-1] == 47

    # Built in patterns
    @patterns = { 'src' => { '' => { inc: [] } },
                  'test' => { '' => { inc: [] } },
                  'dh'  => { 'Driver'   => { inc: [create_filename('%1$s', 'Hardware.h')] },
                             'Hardware' => { inc: [] } },
                  'dih' => { 'Driver'   => { inc: [create_filename('%1$s', 'Hardware.h'), create_filename('%1$s', 'Interrupt.h')] },
                             'Interrupt' => { inc: [create_filename('%1$s', 'Hardware.h')] },
                             'Hardware' => { inc: [] } },
                  'mch' => { 'Model'    => { inc: [] },
                             'Conductor' => { inc: [create_filename('%1$s', 'Model.h'), create_filename('%1$s', 'Hardware.h')] },
                             'Hardware' => { inc: [] } },
                  'mvp' => { 'Model'    => { inc: [] },
                             'Presenter' => { inc: [create_filename('%1$s', 'Model.h'), create_filename('%1$s', 'View.h')] },
                             'View' => { inc: [] } } }
M
Mark VanderVoord 已提交
80 81
  end

82 83 84
  ############################
  def self.default_options
    {
85 86 87 88 89
      pattern: 'src',
      includes:       {
        src: [],
        inc: [],
        tst: []
90
      },
91 92 93 94
      update_svn: false,
      boilerplates: {},
      test_prefix: 'Test',
      mock_prefix: 'Mock'
M
Mark VanderVoord 已提交
95 96 97
    }
  end

98 99
  ############################
  def self.grab_config(config_file)
100 101
    options = default_options
    unless config_file.nil? || config_file.empty?
102 103 104 105 106
      require 'yaml'
      yaml_guts = YAML.load_file(config_file)
      options.merge!(yaml_guts[:unity] || yaml_guts[:cmock])
      raise "No :unity or :cmock section found in #{config_file}" unless options
    end
107
    options
108 109 110
  end

  ############################
111 112
  def files_to_operate_on(module_name, pattern = nil)
    # strip any leading path information from the module name and save for later
113 114 115
    subfolder = File.dirname(module_name)
    module_name = File.basename(module_name)

116
    # create triad definition
117
    prefix = @options[:test_prefix] || 'Test'
118 119 120
    triad = [{ ext: '.c', path: @options[:path_src], prefix: '', template: TEMPLATE_SRC, inc: :src, boilerplate: @options[:boilerplates][:src] },
             { ext: '.h', path: @options[:path_inc], prefix: '',     template: TEMPLATE_INC, inc: :inc, boilerplate: @options[:boilerplates][:inc] },
             { ext: '.c', path: @options[:path_tst], prefix: prefix, template: TEMPLATE_TST, inc: :tst, boilerplate: @options[:boilerplates][:tst] }]
121

122
    # prepare the pattern for use
M
Mark VanderVoord 已提交
123 124
    pattern = (pattern || @options[:pattern] || 'src').downcase
    patterns = @patterns[pattern]
125 126
    raise "ERROR: The design pattern '#{pattern}' specified isn't one that I recognize!" if patterns.nil?

127 128
    # single file patterns (currently just 'test') can reject the other parts of the triad
    triad.select! { |v| v[:inc] == :tst } if pattern == 'test'
M
Mark VanderVoord 已提交
129

130 131
    # Assemble the path/names of the files we need to work with.
    files = []
M
Mark VanderVoord 已提交
132
    triad.each do |cfg|
133
      patterns.each_pair do |pattern_file, pattern_traits|
M
Mark VanderVoord 已提交
134
        submodule_name = create_filename(module_name, pattern_file)
135
        filename = cfg[:prefix] + submodule_name + cfg[:ext]
136
        files << {
137 138 139 140 141 142 143 144
          path: (Pathname.new("#{cfg[:path]}#{subfolder}") + filename).cleanpath,
          name: submodule_name,
          template: cfg[:template],
          boilerplate: cfg[:boilerplate],
          includes: case (cfg[:inc])
                    when :src then (@options[:includes][:src] || []) | pattern_traits[:inc].map { |f| f % [module_name] }
                    when :inc then (@options[:includes][:inc] || [])
                    when :tst then (@options[:includes][:tst] || []) | pattern_traits[:inc].map { |f| "#{@options[:mock_prefix]}#{f}" % [module_name] }
145 146 147 148 149
                       end
        }
      end
    end

150
    files
151 152
  end

M
Mark VanderVoord 已提交
153
  ############################
154
  def create_filename(part1, part2 = '')
M
Mark VanderVoord 已提交
155
    if part2.empty?
156
      case (@options[:naming])
M
Mark VanderVoord 已提交
157 158 159 160 161 162 163
      when 'bumpy' then part1
      when 'camel' then part1
      when 'snake' then part1.downcase
      when 'caps'  then part1.upcase
      else              part1.downcase
      end
    else
164
      case (@options[:naming])
M
Mark VanderVoord 已提交
165 166
      when 'bumpy' then part1 + part2
      when 'camel' then part1 + part2
167 168 169
      when 'snake' then part1.downcase + '_' + part2.downcase
      when 'caps'  then part1.upcase + '_' + part2.upcase
      else              part1.downcase + '_' + part2.downcase
M
Mark VanderVoord 已提交
170 171 172 173
      end
    end
  end

174
  ############################
175
  def generate(module_name, pattern = nil)
176 177
    files = files_to_operate_on(module_name, pattern)

178
    # Abort if all of the module files already exist
179
    all_files_exist = true
180
    files.each do |file|
181
      all_files_exist = false unless File.exist?(file[:path])
182
    end
183
    raise "ERROR: File #{files[0][:name]} already exists. Exiting." if all_files_exist
184 185

    # Create Source Modules
186
    files.each_with_index do |file, _i|
187 188 189 190 191 192
      # If this file already exists, don't overwrite it.
      if File.exist?(file[:path])
        puts "File #{file[:path]} already exists!"
        next
      end
      # Create the path first if necessary.
193
      FileUtils.mkdir_p(File.dirname(file[:path]), verbose: false)
194
      File.open(file[:path], 'w') do |f|
M
Mark VanderVoord 已提交
195
        f.write("#{file[:boilerplate]}\n" % [file[:name]]) unless file[:boilerplate].nil?
196 197 198
        f.write(file[:template] % [file[:name],
                                   file[:includes].map { |f| "#include \"#{f}\"\n" }.join,
                                   file[:name].upcase])
199
      end
200
      if @options[:update_svn]
201
        `svn add \"#{file[:path]}\"`
202
        if $CHILD_STATUS.exitstatus == 0
203 204 205 206
          puts "File #{file[:path]} created and added to source control"
        else
          puts "File #{file[:path]} created but FAILED adding to source control!"
        end
M
Mark VanderVoord 已提交
207
      else
208
        puts "File #{file[:path]} created"
M
Mark VanderVoord 已提交
209 210
      end
    end
211
    puts 'Generate Complete'
M
Mark VanderVoord 已提交
212 213
  end

214
  ############################
215
  def destroy(module_name, pattern = nil)
216 217 218 219 220 221 222 223 224 225 226 227 228 229
    files_to_operate_on(module_name, pattern).each do |filespec|
      file = filespec[:path]
      if File.exist?(file)
        if @options[:update_svn]
          `svn delete \"#{file}\" --force`
          puts "File #{file} deleted and removed from source control"
        else
          FileUtils.remove(file)
          puts "File #{file} deleted"
        end
      else
        puts "File #{file} does not exist so cannot be removed."
      end
    end
230
    puts 'Destroy Complete'
M
Mark VanderVoord 已提交
231
  end
232 233 234
end

############################
235 236
# Handle As Command Line If Called That Way
if $PROGRAM_NAME == __FILE__
237
  destroy = false
238
  options = {}
239 240 241 242
  module_name = nil

  # Parse the command line parameters.
  ARGV.each do |arg|
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
    case arg
    when /^-d/            then destroy = true
    when /^-u/            then options[:update_svn] = true
    when /^-p\"?(\w+)\"?/ then options[:pattern] = Regexp.last_match(1)
    when /^-s\"?(.+)\"?/  then options[:path_src] = Regexp.last_match(1)
    when /^-i\"?(.+)\"?/  then options[:path_inc] = Regexp.last_match(1)
    when /^-t\"?(.+)\"?/  then options[:path_tst] = Regexp.last_match(1)
    when /^-n\"?(.+)\"?/  then options[:naming] = Regexp.last_match(1)
    when /^-y\"?(.+)\"?/  then options = UnityModuleGenerator.grab_config(Regexp.last_match(1))
    when /^(\w+)/
      raise "ERROR: You can't have more than one Module name specified!" unless module_name.nil?
      module_name = arg
    when /^-(h|-help)/
      ARGV = [].freeze
    else
      raise "ERROR: Unknown option specified '#{arg}'"
M
Mark VanderVoord 已提交
259
    end
260 261
  end

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
  unless ARGV[0]
    puts ["\nGENERATE MODULE\n-------- ------",
          "\nUsage: ruby generate_module [options] module_name",
          "  -i\"include\" sets the path to output headers to 'include' (DEFAULT ../src)",
          "  -s\"../src\"  sets the path to output source to '../src'   (DEFAULT ../src)",
          "  -t\"C:/test\" sets the path to output source to 'C:/test'  (DEFAULT ../test)",
          '  -p"MCH"     sets the output pattern to MCH.',
          '              dh   - driver hardware.',
          '              dih  - driver interrupt hardware.',
          '              mch  - model conductor hardware.',
          '              mvp  - model view presenter.',
          '              src  - just a source module, header and test. (DEFAULT)',
          '              test - just a test file.',
          '  -d          destroy module instead of creating it.',
          '  -n"camel"   sets the file naming convention.',
          '              bumpy - BumpyCaseFilenames.',
          '              camel - camelCaseFilenames.',
          '              snake - snake_case_filenames. (DEFAULT)',
          '              caps  - CAPS_CASE_FILENAMES.',
          '  -u          update subversion too (requires subversion command line)',
          '  -y"my.yml"  selects a different yaml config file for module generation',
          ''].join("\n")
284 285 286
    exit
  end

287 288
  raise 'ERROR: You must have a Module name specified! (use option -h for help)' if module_name.nil?
  if destroy
289
    UnityModuleGenerator.new(options).destroy(module_name)
M
Mark VanderVoord 已提交
290
  else
291
    UnityModuleGenerator.new(options).generate(module_name)
M
Mark VanderVoord 已提交
292
  end
293

M
Mark VanderVoord 已提交
294
end