generate_module.rb 10.8 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"
17

M
Mark VanderVoord 已提交
18 19 20 21 22 23 24 25 26 27 28 29
%2$s#include "%1$s.h"

void setUp(void)
{
}

void tearDown(void)
{
}

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

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

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

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

46 47
class UnityModuleGenerator
  ############################
48
  def initialize(options = nil)
49
    @options = UnityModuleGenerator.default_options
50 51 52 53 54
    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'
55 56 57
    end

    # Create default file paths if none were provided
J
John Lindgren 已提交
58 59 60
    @options[:path_src] = "#{__dir__}/../src/"   if @options[:path_src].nil?
    @options[:path_inc] = @options[:path_src]    if @options[:path_inc].nil?
    @options[:path_tst] = "#{__dir__}/../test/"  if @options[:path_tst].nil?
61 62 63 64 65
    @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
66 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
    @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 已提交
93 94
  end

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

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

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

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

135
    # prepare the pattern for use
M
Mark VanderVoord 已提交
136 137
    pattern = (pattern || @options[:pattern] || 'src').downcase
    patterns = @patterns[pattern]
138 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
    triad.select! { |v| v[:inc] == :tst } if pattern == 'test'
M
Mark VanderVoord 已提交
142

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

163
    files
164 165
  end

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

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

191
    # Abort if all of the module files already exist
192
    all_files_exist = true
193
    files.each do |file|
194
      all_files_exist = false unless File.exist?(file[:path])
195
    end
196
    raise "ERROR: File #{files[0][:name]} already exists. Exiting." if all_files_exist
197 198

    # Create Source Modules
199
    files.each_with_index do |file, _i|
200 201 202 203 204 205
      # 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.
206
      FileUtils.mkdir_p(File.dirname(file[:path]), verbose: false)
207
      File.open(file[:path], 'w') do |f|
M
Mark VanderVoord 已提交
208
        f.write("#{file[:boilerplate]}\n" % [file[:name]]) unless file[:boilerplate].nil?
209
        f.write(file[:template] % [file[:name],
210
                                   file[:includes].map { |ff| "#include \"#{ff}\"\n" }.join,
211
                                   file[:name].upcase])
212
      end
213
      if @options[:update_svn]
214
        `svn add \"#{file[:path]}\"`
215
        if $!.exitstatus.zero?
216 217 218 219
          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 已提交
220
      else
221
        puts "File #{file[:path]} created"
M
Mark VanderVoord 已提交
222 223
      end
    end
224
    puts 'Generate Complete'
M
Mark VanderVoord 已提交
225 226
  end

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

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

  # Parse the command line parameters.
  ARGV.each do |arg|
256 257 258 259 260 261 262 263 264 265 266
    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?
M
mvandervoord 已提交
267

268 269 270 271 272
      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
  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.',
293
          '              snake - snake_case_filenames.',
294 295 296 297
          '              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
  raise 'ERROR: You must have a Module name specified! (use option -h for help)' if module_name.nil?
M
mvandervoord 已提交
302

303
  if destroy
304
    UnityModuleGenerator.new(options).destroy(module_name)
M
Mark VanderVoord 已提交
305
  else
306
    UnityModuleGenerator.new(options).generate(module_name)
M
Mark VanderVoord 已提交
307
  end
308

M
Mark VanderVoord 已提交
309
end