summaryrefslogtreecommitdiff
path: root/FreeRTOS-Plus/Test/CMock/vendor/unity/auto/generate_module.rb
blob: 3d9ee8d6d3d9262eda5dcbc3b076b6e9a9cda736 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# ==========================================
#   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]
# ==========================================

# 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'
require 'pathname'

# TEMPLATE_TST
TEMPLATE_TST ||= '#include "unity.h"

%2$s#include "%1$s.h"

void setUp(void)
{
}

void tearDown(void)
{
}

void test_%4$s_NeedToImplement(void)
{
    TEST_IGNORE_MESSAGE("Need to Implement %1$s");
}
'.freeze

# TEMPLATE_SRC
TEMPLATE_SRC ||= '%2$s#include "%1$s.h"
'.freeze

# TEMPLATE_INC
TEMPLATE_INC ||= '#ifndef %3$s_H
#define %3$s_H
%2$s

#endif // %3$s_H
'.freeze

class UnityModuleGenerator
  ############################
  def initialize(options = nil)
    @options = UnityModuleGenerator.default_options
    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'
    end

    # Create default file paths if none were provided
    @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?
    @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: [] }
      }
    }
  end

  ############################
  def self.default_options
    {
      pattern: 'src',
      includes: {
        src: [],
        inc: [],
        tst: []
      },
      update_svn: false,
      boilerplates: {},
      test_prefix: 'Test',
      mock_prefix: 'Mock'
    }
  end

  ############################
  def self.grab_config(config_file)
    options = default_options
    unless config_file.nil? || config_file.empty?
      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
    options
  end

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

    # create triad definition
    prefix = @options[:test_prefix] || 'Test'
    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] }]

    # prepare the pattern for use
    pattern = (pattern || @options[:pattern] || 'src').downcase
    patterns = @patterns[pattern]
    raise "ERROR: The design pattern '#{pattern}' specified isn't one that I recognize!" if patterns.nil?

    # single file patterns (currently just 'test') can reject the other parts of the triad
    triad.select! { |v| v[:inc] == :tst } if pattern == 'test'

    # Assemble the path/names of the files we need to work with.
    files = []
    triad.each do |cfg|
      patterns.each_pair do |pattern_file, pattern_traits|
        submodule_name = create_filename(module_name, pattern_file)
        filename = cfg[:prefix] + submodule_name + cfg[:ext]
        files << {
          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| format(f, module_name) })
                    when :inc then (@options[:includes][:inc] || [])
                    when :tst then (@options[:includes][:tst] || []) | (pattern_traits[:inc].map { |f| format("#{@options[:mock_prefix]}#{f}", module_name) })
                    end
        }
      end
    end

    files
  end

  ############################
  def neutralize_filename(name, start_cap=true)
    return name if name.empty?
    name = name.split(/(?:\s+|_|(?=[A-Z][a-z]))|(?<=[a-z])(?=[A-Z])/).map{|v|v.capitalize}.join('_')
    if start_cap
      return name
    else
      return name[0].downcase + name[1..-1]
    end
  end

  ############################
  def create_filename(part1, part2 = '')
    name = part2.empty? ? part1 : part1 + '_' + part2
    case (@options[:naming])
    when 'bumpy' then neutralize_filename(name,false).gsub('_','')
    when 'camel' then neutralize_filename(name).gsub('_','')
    when 'snake' then neutralize_filename(name).downcase
    when 'caps'  then neutralize_filename(name).upcase
    else              name
    end
  end

  ############################
  def generate(module_name, pattern = nil)
    files = files_to_operate_on(module_name, pattern)

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

    # Create Source Modules
    files.each_with_index do |file, _i|
      # 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.
      FileUtils.mkdir_p(File.dirname(file[:path]), verbose: false)
      File.open(file[:path], 'w') do |f|
        f.write("#{file[:boilerplate]}\n" % [file[:name]]) unless file[:boilerplate].nil?
        f.write(file[:template] % [file[:name],
                                   file[:includes].map { |ff| "#include \"#{ff}\"\n" }.join,
                                   file[:name].upcase.gsub(/-/, '_'),
                                   file[:name].gsub(/-/, '_')])
      end
      if @options[:update_svn]
        `svn add \"#{file[:path]}\"`
        if $!.exitstatus.zero?
          puts "File #{file[:path]} created and added to source control"
        else
          puts "File #{file[:path]} created but FAILED adding to source control!"
        end
      else
        puts "File #{file[:path]} created"
      end
    end
    puts 'Generate Complete'
  end

  ############################
  def destroy(module_name, pattern = nil)
    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
    puts 'Destroy Complete'
  end
end

############################
# Handle As Command Line If Called That Way
if $0 == __FILE__
  destroy = false
  options = {}
  module_name = nil

  # Parse the command line parameters.
  ARGV.each do |arg|
    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}'"
    end
  end

  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.',
          '              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")
    exit
  end

  raise 'ERROR: You must have a Module name specified! (use option -h for help)' if module_name.nil?

  if destroy
    UnityModuleGenerator.new(options).destroy(module_name)
  else
    UnityModuleGenerator.new(options).generate(module_name)
  end

end