generate_module.rb 10.9 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
    @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
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
    @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 已提交
94 95
  end

96 97 98
  ############################
  def self.default_options
    {
99
      pattern: 'src',
100
      includes: {
101 102 103
        src: [],
        inc: [],
        tst: []
104
      },
105 106 107 108
      update_svn: false,
      boilerplates: {},
      test_prefix: 'Test',
      mock_prefix: 'Mock'
M
Mark VanderVoord 已提交
109 110 111
    }
  end

112 113
  ############################
  def self.grab_config(config_file)
114 115
    options = default_options
    unless config_file.nil? || config_file.empty?
116 117 118 119 120
      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
121
    options
122 123 124
  end

  ############################
125 126
  def files_to_operate_on(module_name, pattern = nil)
    # strip any leading path information from the module name and save for later
127 128 129
    subfolder = File.dirname(module_name)
    module_name = File.basename(module_name)

130
    # create triad definition
131
    prefix = @options[:test_prefix] || 'Test'
132 133 134
    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] }]
135

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

141 142
    # 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 已提交
143

144 145
    # Assemble the path/names of the files we need to work with.
    files = []
M
Mark VanderVoord 已提交
146
    triad.each do |cfg|
147
      patterns.each_pair do |pattern_file, pattern_traits|
M
Mark VanderVoord 已提交
148
        submodule_name = create_filename(module_name, pattern_file)
149
        filename = cfg[:prefix] + submodule_name + cfg[:ext]
150
        files << {
151 152 153 154 155
          path: (Pathname.new("#{cfg[:path]}#{subfolder}") + filename).cleanpath,
          name: submodule_name,
          template: cfg[:template],
          boilerplate: cfg[:boilerplate],
          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 159
                    when :tst then (@options[:includes][:tst] || []) | (pattern_traits[:inc].map { |f| format("#{@options[:mock_prefix]}#{f}", module_name) })
                    end
160 161 162 163
        }
      end
    end

164
    files
165 166
  end

M
Mark VanderVoord 已提交
167
  ############################
168
  def create_filename(part1, part2 = '')
M
Mark VanderVoord 已提交
169
    if part2.empty?
170
      case (@options[:naming])
M
Mark VanderVoord 已提交
171 172 173 174 175 176 177
      when 'bumpy' then part1
      when 'camel' then part1
      when 'snake' then part1.downcase
      when 'caps'  then part1.upcase
      else              part1.downcase
      end
    else
178
      case (@options[:naming])
M
Mark VanderVoord 已提交
179 180
      when 'bumpy' then part1 + part2
      when 'camel' then part1 + part2
181 182 183
      when 'snake' then part1.downcase + '_' + part2.downcase
      when 'caps'  then part1.upcase + '_' + part2.upcase
      else              part1.downcase + '_' + part2.downcase
M
Mark VanderVoord 已提交
184 185 186 187
      end
    end
  end

188
  ############################
189
  def generate(module_name, pattern = nil)
190 191
    files = files_to_operate_on(module_name, pattern)

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 202 203 204 205 206
      # 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.
207
      FileUtils.mkdir_p(File.dirname(file[:path]), verbose: false)
208
      File.open(file[:path], 'w') do |f|
M
Mark VanderVoord 已提交
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 218 219 220
          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 已提交
221
      else
222
        puts "File #{file[:path]} created"
M
Mark VanderVoord 已提交
223 224
      end
    end
225
    puts 'Generate Complete'
M
Mark VanderVoord 已提交
226 227
  end

228
  ############################
229
  def destroy(module_name, pattern = nil)
230 231 232 233 234 235 236 237 238 239 240 241 242 243
    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
244
    puts 'Destroy Complete'
M
Mark VanderVoord 已提交
245
  end
246 247 248
end

############################
249
# Handle As Command Line If Called That Way
250
if $0 == __FILE__
251
  destroy = false
252
  options = {}
253 254 255 256
  module_name = nil

  # Parse the command line parameters.
  ARGV.each do |arg|
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
    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 已提交
273
    end
274 275
  end

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
  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")
298 299 300
    exit
  end

301 302
  raise 'ERROR: You must have a Module name specified! (use option -h for help)' if module_name.nil?
  if destroy
303
    UnityModuleGenerator.new(options).destroy(module_name)
M
Mark VanderVoord 已提交
304
  else
305
    UnityModuleGenerator.new(options).generate(module_name)
M
Mark VanderVoord 已提交
306
  end
307

M
Mark VanderVoord 已提交
308
end