falsework 2.1.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. data/README.rdoc +19 -59
  2. data/Rakefile +28 -20
  3. data/bin/falsework +266 -73
  4. data/doc/NEWS.rdoc +24 -0
  5. data/doc/README.rdoc +19 -59
  6. data/doc/TODO.org +16 -6
  7. data/doc/template-tutorial.rdoc +49 -12
  8. data/dynamic.yaml +4 -0
  9. data/falsework.gemspec +10 -1
  10. data/lib/falsework/cliconfig.rb +14 -9
  11. data/lib/falsework/cliutils.rb +0 -2
  12. data/lib/falsework/meta.rb +2 -1
  13. data/lib/falsework/mould.rb +121 -192
  14. data/lib/falsework/upgrader.rb +120 -0
  15. data/lib/falsework/utils.rb +23 -0
  16. data/templates/c-glib/#config.yaml +18 -0
  17. data/{lib/falsework/templates → templates}/c-glib/README +0 -0
  18. data/{lib/falsework/templates → templates}/c-glib/doc/#doc.ascii +0 -0
  19. data/{lib/falsework/templates → templates}/c-glib/doc/%%@project%%.1.asciidoc +0 -0
  20. data/{lib/falsework/templates → templates}/c-glib/doc/LICENSE +0 -0
  21. data/{lib/falsework/templates → templates}/c-glib/doc/Makefile +0 -0
  22. data/{lib/falsework/templates → templates}/c-glib/src/#exe.c +0 -0
  23. data/{lib/falsework/templates → templates}/c-glib/src/#exe.h +0 -0
  24. data/{lib/falsework/templates → templates}/c-glib/src/%%@project%%.c +0 -0
  25. data/{lib/falsework/templates → templates}/c-glib/src/%%@project%%.h +0 -0
  26. data/{lib/falsework/templates → templates}/c-glib/src/Makefile +0 -0
  27. data/{lib/falsework/templates → templates}/c-glib/src/untest.c +0 -0
  28. data/{lib/falsework/templates → templates}/c-glib/src/untest.h +0 -0
  29. data/{lib/falsework/templates → templates}/c-glib/src/utils.c +0 -0
  30. data/{lib/falsework/templates → templates}/c-glib/src/utils.h +0 -0
  31. data/{lib/falsework/templates → templates}/c-glib/test/#test.c +0 -0
  32. data/{lib/falsework/templates → templates}/c-glib/test/Makefile +0 -0
  33. data/{lib/falsework/templates → templates}/c-glib/test/Makefile.test.mk +0 -0
  34. data/{lib/falsework/templates → templates}/c-glib/test/mycat.c +0 -0
  35. data/{lib/falsework/templates → templates}/c-glib/test/semis/text/empty.txt +0 -0
  36. data/{lib/falsework/templates → templates}/c-glib/test/test_utils.c +0 -0
  37. data/templates/ruby-cli/#config.yaml +26 -0
  38. data/{lib/falsework/templates → templates}/ruby-cli/%%@project%%.gemspec +0 -0
  39. data/{lib/falsework/templates → templates}/ruby-cli/.gitignore +0 -0
  40. data/{lib/falsework/templates → templates}/ruby-cli/.gitignore.#erb +0 -0
  41. data/{lib/falsework/templates → templates}/ruby-cli/Gemfile +0 -0
  42. data/{lib/falsework/templates → templates}/ruby-cli/README.rdoc +17 -19
  43. data/{lib/falsework/templates → templates}/ruby-cli/Rakefile +0 -0
  44. data/{lib/falsework/templates → templates}/ruby-cli/bin/%%@project%% +3 -3
  45. data/{lib/falsework/templates → templates}/ruby-cli/doc/#doc.rdoc +16 -18
  46. data/{lib/falsework/templates → templates}/ruby-cli/doc/LICENSE +0 -0
  47. data/{lib/falsework/templates → templates}/ruby-cli/doc/NEWS.rdoc +0 -0
  48. data/{lib/falsework/templates → templates}/ruby-cli/doc/README.rdoc +17 -19
  49. data/templates/ruby-cli/etc/%%@project%%.yaml +2 -0
  50. data/{lib/falsework/templates → templates}/ruby-cli/lib/%%@project%%/cliconfig.rb +14 -11
  51. data/{lib/falsework/templates → templates}/ruby-cli/lib/%%@project%%/cliutils.rb +0 -4
  52. data/{lib/falsework/templates → templates}/ruby-cli/lib/%%@project%%/meta.rb +1 -0
  53. data/{lib/falsework/templates → templates}/ruby-cli/test/helper.rb +0 -0
  54. data/{lib/falsework/templates → templates}/ruby-cli/test/helper_cliutils.rb +0 -3
  55. data/{lib/falsework/templates → templates}/ruby-cli/test/test_%%@project%%.rb +0 -0
  56. data/test/example/note/full +10 -0
  57. data/test/example/note/project-too-old +8 -0
  58. data/test/example/note/template-unknown +8 -0
  59. data/test/helper.rb +11 -0
  60. data/test/helper_cliutils.rb +0 -1
  61. data/test/templates/config-01.yaml +1 -1
  62. data/test/{test_exe.rb → test_cli.rb} +52 -29
  63. data/test/test_mould.rb +41 -16
  64. data/test/test_upgrader.rb +96 -0
  65. metadata +76 -50
  66. data/dynamic.ruby-cli +0 -3
  67. data/lib/falsework/templates/c-glib/#config.yaml +0 -18
  68. data/lib/falsework/templates/ruby-cli/#config.yaml +0 -15
  69. data/lib/falsework/templates/ruby-cli/etc/%%@project%%.yaml +0 -2
  70. data/test/rake_erb_templates.rb +0 -60
  71. data/test/templates/config-02.yaml +0 -2
  72. data/test/test_cl.rb +0 -33
@@ -7,6 +7,17 @@ require 'yaml'
7
7
  require_relative 'cliutils'
8
8
 
9
9
  module Falsework
10
+ class MouldError < StandardError
11
+ def initialize msg
12
+ super msg
13
+ end
14
+
15
+ alias :orig_to_s :to_s
16
+ def to_s
17
+ "generator: #{orig_to_s}"
18
+ end
19
+ end
20
+
10
21
  # The directory with template may have files beginning with # char
11
22
  # which will be ignored in #project_seed (a method that creates a
12
23
  # shiny new project form a template).
@@ -37,22 +48,27 @@ module Falsework
37
48
  class Mould
38
49
  # Where @user, @email & @gecos comes from.
39
50
  GITCONFIG = '~/.gitconfig'
51
+
40
52
  # The possible dirs for templates. The first one is system-wide.
41
- @@template_dirs = [CliUtils::DIR_LIB_SRC + 'templates',
42
- Pathname.new(Dir.home) + ".#{Meta::NAME}" + 'templates']
53
+ @template_dirs = [CliUtils::DIR_LIB_SRC.parent.parent + 'templates',
54
+ Pathname.new(Dir.home) + ".#{Meta::NAME}" + 'templates']
55
+ class << self
56
+ attr_reader :template_dirs
57
+ end
58
+
43
59
  # The template used if user didn't select one.
44
60
  TEMPLATE_DEFAULT = 'ruby-cli'
45
61
  # A file name with configurations for the inject commands.
46
62
  TEMPLATE_CONFIG = '#config.yaml'
47
63
  # A list of files to ignore in any template.
48
64
  IGNORE_FILES = ['.gitignore']
65
+ # Note file name
66
+ NOTE = '.' + Meta::NAME
49
67
 
50
- # A verbose level for -v CLO.
51
- attr_accessor :verbose
52
- # -b CLO.
53
- attr_accessor :batch
54
68
  # A directory of a new generated project.
55
69
  attr_reader :project
70
+ # template configuration
71
+ attr_reader :conf
56
72
 
57
73
  # [project] A name of the future project; may include all crap with spaces.
58
74
  # [template] A name of the template for the project.
@@ -61,34 +77,33 @@ module Falsework
61
77
  # [gecos] A full author name from ~/.gitconfig.
62
78
  def initialize(project, template, user = nil, email = nil, gecos = nil)
63
79
  @project = Mould.name_project project
64
- raise "invalid project name '#{project}'" if !Mould.name_valid? @project
80
+ fail MouldError, "invalid project name '#{project}'" unless Mould.name_valid?(@project)
65
81
  @camelcase = Mould.name_camelcase project
66
82
  @classy = Mould.name_classy project
67
83
 
68
- @verbose = false
69
84
  @batch = false
70
- @template = template
71
- @dir_t = Mould.templates[@template || TEMPLATE_DEFAULT] || CliUtils.errx(1, "no such template: #{template}")
85
+ @template = template || TEMPLATE_DEFAULT
86
+ @dir_t = Mould.templates[@template] || fail(MouldError, "template '#{@template}' not found")
72
87
 
73
88
  # default config
74
89
  @conf = {
75
- exe: [{
76
- src: nil,
77
- dest: 'bin/%s',
78
- mode_int: 0744
79
- }],
80
- doc: [{
81
- src: nil,
82
- dest: 'doc/%s.rdoc',
83
- mode_int: nil
84
- }],
85
- test: [{
86
- src: nil,
87
- dir: 'test/test_%s.rb',
88
- mode_int: nil
89
- }]
90
+ 'exe' => [{
91
+ 'src' => nil,
92
+ 'dest' => 'bin/%s',
93
+ 'mode_int' => 0744
94
+ }],
95
+ 'doc' => [{
96
+ 'src' => nil,
97
+ 'dest' => 'doc/%s.rdoc',
98
+ 'mode_int' => nil
99
+ }],
100
+ 'test' => [{
101
+ 'src' => nil,
102
+ 'dir' => 'test/test_%s.rb',
103
+ 'mode_int' => nil
104
+ }]
90
105
  }
91
- Mould.config_parse(@dir_t + '/' + TEMPLATE_CONFIG, [], @conf)
106
+ configParse
92
107
 
93
108
  gc = Git.global_config rescue gc = {}
94
109
  @user = user || gc['github.user']
@@ -98,7 +113,7 @@ module Falsework
98
113
  [['github.user', @user],
99
114
  ['user.email', @email],
100
115
  ['user.name', @gecos]].each {|i|
101
- CliUtils.errx(1, "missing #{i.first} in #{GITCONFIG}") if i.last.to_s == ''
116
+ fail MouldError, "missing #{i.first} in #{GITCONFIG}" if i.last.to_s == ''
102
117
  }
103
118
  end
104
119
 
@@ -112,7 +127,7 @@ module Falsework
112
127
  if ! File.directory?(idx)
113
128
  CliUtils.warnx "invalid additional template directory: #{idx}"
114
129
  else
115
- @@template_dirs << idx
130
+ @template_dirs << idx
116
131
  end
117
132
  }
118
133
  end
@@ -169,9 +184,9 @@ module Falsework
169
184
  # and corresponding directories.
170
185
  def self.templates
171
186
  r = {}
172
- @@template_dirs.each {|i|
187
+ @template_dirs.each {|i|
173
188
  Dir.glob(i + '*').each {|j|
174
- r[File.basename(j)] = j if File.directory?(j)
189
+ r[File.basename(j)] = Pathname.new(j) if File.directory?(j)
175
190
  }
176
191
  }
177
192
  r
@@ -180,35 +195,35 @@ module Falsework
180
195
  # Generate a new project in @project directory from @template.
181
196
  #
182
197
  # Return false if nothing was extracted.
183
- def project_seed()
198
+ def project_seed
184
199
  uuid = Mould.uuidgen_fake # useful variable for the template
185
200
 
186
201
  # check for existing project
187
- CliUtils.errx(1, "directory '#{@project}' is not empty") if Dir.glob(@project + '/*').size > 0
202
+ fail MouldError, "directory '#{@project}' is not empty" if Dir.glob(@project + '/*').size > 0
188
203
 
189
204
  Dir.mkdir @project unless File.directory?(@project)
190
- puts "Project path: #{File.expand_path(@project)}" if @verbose
205
+ CliUtils.veputs 1, "Project path: #{File.expand_path(@project)}"
191
206
 
192
207
  r = false
193
- puts "Template: #{@dir_t}" if @verbose
208
+ CliUtils.veputs 1, "Template: #{@dir_t}"
194
209
  symlinks = []
195
210
  Dir.chdir(@project) {
196
- Mould.traverse(@dir_t) {|idx|
211
+ Mould.traverse(@dir_t.to_s) {|idx|
197
212
  file = idx.sub(/^#{@dir_t}\//, '')
198
213
  next if IGNORE_FILES.index {|i| file.match(/#{i}$/) }
199
214
 
200
215
  if File.symlink?(idx)
201
216
  # we'll process them later on
202
- is_dir = File.directory?(@dir_t + '/' + File.readlink(idx))
203
- symlinks << [Mould.get_filename(File.readlink(idx), binding),
204
- Mould.get_filename(file, binding)]
217
+ # is_dir = File.directory?(@dir_t + '/' + File.readlink(idx))
218
+ symlinks << [Mould.resolve_filename(File.readlink(idx), getBinding),
219
+ Mould.resolve_filename(file, getBinding)]
205
220
  elsif File.directory?(idx)
206
- puts "D: #{file}" if @verbose
207
- Dir.mkdir Mould.get_filename(file, binding)
221
+ CliUtils.veputs 1, "D: #{file}"
222
+ Dir.mkdir Mould.resolve_filename(file, getBinding)
208
223
  else
209
- puts "N: #{file}" if @verbose
210
- to = Mould.get_filename(file, binding)
211
- Mould.extract(idx, binding, to)
224
+ CliUtils.veputs 1, "N: #{file}"
225
+ to = Mould.resolve_filename file, getBinding
226
+ Mould.extract idx, binding, to # 'binding' to include local uuid
212
227
  end
213
228
  r = true
214
229
  }
@@ -217,8 +232,8 @@ module Falsework
217
232
  symlinks.each {|idx|
218
233
  src = idx[0]
219
234
  dest = idx[1]
220
- puts "L: #{dest} => #{src}" if @verbose
221
- File.symlink(src, dest)
235
+ CliUtils.veputs 1, "L: #{dest} => #{src}"
236
+ File.symlink src, dest
222
237
  }
223
238
  }
224
239
 
@@ -227,33 +242,33 @@ module Falsework
227
242
 
228
243
  # Parse a config. Return false on error.
229
244
  #
230
- # [file] A file to parse.
231
- # [rvars] A list of variable names which must be in the config.
232
- # [hash] a hash to merge results with
233
- def self.config_parse(file, rvars, hash)
234
- r = true
235
-
245
+ # [rvars] A list of variable names which must be in the config.
246
+ def configParse rvars = []
247
+ r = false
248
+
249
+ file = @dir_t + TEMPLATE_CONFIG
236
250
  if File.readable?(file)
237
251
  begin
238
- myconf = YAML.load_file(file)
252
+ myconf = YAML.load_file file
253
+ myconf[:file] = file
254
+ myconf['version'] = '1.0.0' unless myconf['version']
255
+ r = true
239
256
  rescue
240
257
  CliUtils.warnx "cannot parse #{file}: #{$!}"
241
258
  return false
242
259
  end
243
260
  rvars.each { |i|
244
- CliUtils.warnx "missing or nil '#{i}' in #{file}" if ! myconf.key?(i.to_sym) || ! myconf[i.to_sym]
245
- r = false
261
+ CliUtils.warnx "missing or nil '#{i}' in #{file}" if ! myconf.key?(i) || ! myconf[i]
262
+ return false
246
263
  }
247
-
248
- hash.merge!(myconf) if r && hash
249
- else
250
- r = false
264
+
265
+ @conf.merge!(myconf) if r
251
266
  end
252
267
 
253
268
  r
254
269
  end
255
270
 
256
- # Add an executable or a test from the template.
271
+ # Add a file from the template.
257
272
  #
258
273
  # [mode] Is either 'exe', 'doc' or 'test'.
259
274
  # [target] A test/doc/exe file to create.
@@ -269,21 +284,25 @@ module Falsework
269
284
  def add(mode, target)
270
285
  target_orig = target
271
286
  target = Mould.name_project target_orig
272
- raise "invalid target name '#{target_orig}'" if !Mould.name_valid? target
287
+ fail MouldError, "invalid target name '#{target_orig}'" if !Mould.name_valid? target
273
288
  target_camelcase = Mould.name_camelcase target_orig
274
289
  target_classy = Mould.name_classy target_orig
275
290
  uuid = Mould.uuidgen_fake
276
291
 
277
292
  created = []
278
293
 
279
- return [] unless @conf[mode.to_sym][0][:src]
294
+ unless @conf[mode] && @conf[mode][0]['src']
295
+ CliUtils.warnx "hash '#{mode}' is empty in #{@conf[:file]}"
296
+ return []
297
+ end
280
298
 
281
- @conf[mode.to_sym].each {|idx|
282
- to = idx[:dest] % target
299
+ @conf[mode].each {|idx|
300
+ to = idx['dest'] % target
283
301
 
284
302
  begin
285
- Mould.extract(@dir_t + '/' + idx[:src], binding, to)
286
- File.chmod(idx[:mode_int], to) if idx[:mode_int]
303
+ # 'binding' to include local vars
304
+ Mould.extract @dir_t + idx['src'], binding, to
305
+ File.chmod(idx['mode_int'], to) if idx['mode_int']
287
306
  rescue
288
307
  CliUtils.warnx "failed to create '#{to}' (check your #config.yaml): #{$!}"
289
308
  else
@@ -315,159 +334,69 @@ module Falsework
315
334
 
316
335
  # Extract file @from into @to.
317
336
  #
318
- # [binding] A binding for eval.
319
- def self.extract(from, binding, to)
320
- t = ERB.new(File.read(from))
321
- t.filename = from # to report errors relative to this file
337
+ # [bng] A binding for eval.
338
+ def self.extract from, bng, to
339
+ t = ERB.new File.read(from.to_s)
340
+ t.filename = from.to_s # to report errors relative to this file
322
341
  begin
323
- output = t.result(binding)
342
+ output = t.result bng
324
343
  md5_system = Digest::MD5.hexdigest(output)
325
344
  rescue Exception
326
- CliUtils.errx(1, "bogus template file '#{from}': #{$!}")
345
+ fail MouldError, "bogus template file '#{from}': #{$!}"
327
346
  end
328
347
 
329
- if ! File.exists?(to)
348
+ unless File.exists?(to)
330
349
  # write a skeleton
331
350
  begin
351
+ FileUtils.mkdir_p File.dirname(to)
332
352
  File.open(to, 'w+') { |fp| fp.puts output }
333
353
  # transfer the exec bit to the generated result
334
- File.chmod(0744, to) if File.stat(from).executable?
354
+ File.chmod(0744, to) if !defined?(FakeFS) && File.stat(from.to_s).executable?
335
355
  rescue
336
- CliUtils.errx(1, "cannot generate: #{$!}")
356
+ fail MouldError, "cannot generate: #{$!}"
337
357
  end
338
- elsif
358
+ else
339
359
  # warn a careless user
340
- if md5_system != Digest::MD5.file(to).hexdigest
341
- CliUtils.errx(1, "'#{to}' already exists")
342
- end
360
+ CliUtils.warnx "'#{to}' already exists in modified state" if md5_system != Digest::MD5.file(to).hexdigest
343
361
  end
344
362
  end
345
363
 
346
- # Resolve @t from possible %%VARIABLE%% scheme.
347
- def self.get_filename(t, binding)
364
+ # Resolve t from possible %%VARIABLE%% scheme.
365
+ def self.resolve_filename t, bng
348
366
  t || (return '')
349
367
 
350
368
  re = /%%([^%]+)%%/
351
- t = ERB.new(t.gsub(re, '<%= \+ %>')).result(binding) if t =~ re
369
+ t = ERB.new(t.gsub(re, '<%= \+ %>')).result(bng) if t =~ re
352
370
  t.sub(/\.#erb$/, '')
353
371
  end
354
-
355
372
 
356
- # Search for all files in the template directory the line
357
- #
358
- # /^..? :erb:/
359
- #
360
- # in first n lines. If the line is found, the file is considered a
361
- # candidate for an upgrade. Return a hash {target:template}
362
- def upgradable_files()
363
- line_max = 4
364
- r = {}
365
- Mould.traverse(@dir_t) {|i|
366
- next if File.directory?(i)
367
- next if File.symlink?(i) # hm...
368
-
369
- File.open(i) {|fp|
370
- n = 0
371
- while n < line_max && line = fp.gets
372
- if line =~ /^..? :erb:/
373
- t = i.sub(/#{@dir_t}\//, '')
374
- r[Mould.get_filename(t, binding)] = i
375
- break
376
- end
377
- n += 1
378
- end
379
- }
380
- }
381
-
382
- r
373
+ def getBinding
374
+ binding
383
375
  end
384
376
 
385
- # We can upgrade only those files, which were explicitly marked by
386
- # ':erb' sign a the top the file. They are collected by
387
- # upgradable_files() method.
388
- #
389
- # The upgrade can happened only if one following conditions is met:
390
- #
391
- # 1. there is no such files (all or some of them) in the project at
392
- # all;
393
- #
394
- # 2. the files are from the previous version of falsework.
395
- #
396
- # The situation may combine: you may have some missing and some old
397
- # files. But if there is at least 1 file from a newer version of
398
- # falsework then no upgrade is possible--it's considered a user
399
- # decision to intentionally have some files from the old versions of
400
- # falsework.
401
- #
402
- # Neithe we do check for a content of upgradable files nor try to
403
- # merge old with new. (Why?)
404
- def upgrade()
405
- # 0. search for 'new' files in the template
406
- uf = upgradable_files
407
- fail "template #{@template} cannot offer you files for the upgrade" if uf.size == 0
408
- # pp uf
409
-
410
- # 1. analyse 'old' files
411
- u = {}
412
- uf.each {|k, v|
413
- if ! File.readable?(k)
414
- u[k] = v
415
- else
416
- # check for its version
417
- File.open(k) {|fp|
418
- is_versioned = false
419
- while line = fp.gets
420
- if line =~ /^# Don't remove this: falsework\/(#{Gem::Version::VERSION_PATTERN})\/(.+)\/.+/
421
- is_versioned = true
422
- if $3 != (@template || TEMPLATE_DEFAULT)
423
- fail "file #{k} is from '#{$3}' template"
424
- end
425
- if Gem::Version.new(Meta::VERSION) >= Gem::Version.new($1)
426
- # puts "#{k}: #{$1}"
427
- u[k] = v
428
- break
429
- else
430
- fail "file #{k} is from a newer version of #{Meta::NAME}: " + $1
431
- end
432
- end
433
- end
434
-
435
- CliUtils.warnx("#{k}: unversioned") if ! is_versioned
436
- }
437
- end
377
+ # Write a YAML file into a created project directory. This file is
378
+ # required for Upgrader.
379
+ def noteCreate after_upgrade = false
380
+ h = {
381
+ 'project' => {
382
+ 'classy' => @classy,
383
+ 'upgraded' => DateTime.now.iso8601
384
+ },
385
+ 'template' => {
386
+ 'version' => @conf['version'],
387
+ 'name' => @template
388
+ }
438
389
  }
439
- fail "template #{@template || TEMPLATE_DEFAULT} cannot find files for an upgrade" if u.size == 0
440
-
441
- # 2. ask user for a commitment
442
- if ! @batch
443
- puts "Here is a list of files in project #{@project} we can try to upgrade/add:\n\n"
444
- u.each {|k,v| puts "\t#{k}"}
445
- printf %{
446
- Does this look fine? Type y/n and press enter. If you choose 'y', those files
447
- will be replaced with newer versions. Your old files will be preserved with
448
- an '.old' extension. So? }
449
- if STDIN.gets =~ /^y/i
450
- puts ""
451
- else
452
- puts "\nNo? See you later."
453
- exit 0
454
- end
455
- end
456
390
 
457
- # 3. rename & write new
458
- count = 1
459
- total = u.size
460
- tsl = total.to_s.size*2+1
461
- u.each {|k, v|
462
- printf("%#{tsl}s) mv %s %s\n",
463
- "#{count}/#{total}", k, "#{k}.old") if @verbose
464
- File.rename(k, "#{k}.old") rescue CliUtils.warnx('renaming failed')
465
- printf("%#{tsl}s Extracting %s ...\n", "", File.basename(v)) if @verbose
466
- FileUtils.mkdir_p(File.dirname(k))
467
- Mould.extract(v, binding, k)
468
- count += 1
391
+ file = @project + '/' + NOTE
392
+ file = NOTE if after_upgrade # in 'upgrade' mode we are in project dir
393
+ File.open(file, 'w+') {|fp|
394
+ CliUtils.veputs 1, "N: #{File.basename(file)}"
395
+ fp.puts "# DO NOT DELETE THIS FILE"
396
+ fp.puts "# unless you don't want to upgrade scaffolds in the future."
397
+ fp.puts h.to_yaml
469
398
  }
470
399
  end
471
400
 
472
- end # Mould
401
+ end
473
402
  end
@@ -0,0 +1,120 @@
1
+ require 'yaml'
2
+
3
+ require_relative 'mould'
4
+ require_relative 'utils'
5
+
6
+ module Falsework
7
+
8
+ class UpgradeError < StandardError
9
+ def initialize msg
10
+ super msg
11
+ end
12
+
13
+ alias :orig_to_s :to_s
14
+ def to_s
15
+ "upgrade: #{orig_to_s}"
16
+ end
17
+ end
18
+
19
+ class Upgrader
20
+ def self.noteLoad file = Mould::NOTE
21
+ r = YAML.load_file(file) rescue raise
22
+
23
+ ['project', 'template'].each {|idx|
24
+ fail "no #{idx} spec" unless r[idx]
25
+ }
26
+
27
+ fail 'no project name' unless Utils.all_set?(r['project']['classy'])
28
+ fail "no template version" unless Utils.all_set?(r['template']['version'])
29
+ r['template']['version'] = Gem::Version.new r['template']['version']
30
+ fail "no template name" unless Utils.all_set?(r['template']['name'])
31
+
32
+ unless Mould.templates[r['template']['name']]
33
+ fail "unknown template '#{r['template']['name']}'"
34
+ end
35
+
36
+ r
37
+ end
38
+
39
+ def initialize dir, note = Mould::NOTE
40
+ fail UpgradeError, "directory #{dir} is unreadable" unless File.readable?(dir.to_s)
41
+ @dir = Pathname.new File.realpath(dir)
42
+
43
+ begin
44
+ @note = Upgrader.noteLoad(@dir + note)
45
+ rescue
46
+ raise UpgradeError, $!
47
+ end
48
+
49
+ @mould = Mould.new @note['project']['classy'], @note['template']['name']
50
+ @template_dir = Mould.templates[@note['template']['name']]
51
+ @project = @mould.project
52
+
53
+ @batch = false
54
+ end
55
+
56
+ attr_accessor :batch
57
+ attr_reader :project
58
+ attr_reader :template_dir
59
+
60
+ def getProjectBinding
61
+ @mould.getBinding
62
+ end
63
+
64
+ def able?
65
+ return false unless @mould.conf['upgrade']
66
+ return false if Gem::Version.new(@mould.conf['upgrade']['from']) > @note['template']['version']
67
+ return false unless @mould.conf['upgrade']['files'].is_a?(Array) && @mould.conf['upgrade']['files'].size > 0
68
+ true
69
+ end
70
+
71
+ def files
72
+ @mould.conf['upgrade']['files']
73
+ end
74
+
75
+ def obsolete
76
+ @mould.conf['upgrade']['obsolete'] || []
77
+ end
78
+
79
+ # Return true if user enter 'y' or 'a', false otherwise.
80
+ # Always return true for non-batch mode.
81
+ def userSaidYes msg, file
82
+ return true if @batch
83
+
84
+ print "#{msg} '#{file}'? [y/n/a] "
85
+ asnwer = $stdin.gets
86
+
87
+ if asnwer =~ /^a/i
88
+ @batch = true
89
+ return true
90
+ end
91
+
92
+ yes = (asnwer =~ /^y/i)
93
+ return true if yes
94
+
95
+ false
96
+ end
97
+
98
+ def upgrade save_old = false # yield f
99
+ fail UpgradeError, "this project cannot be upgraded" unless able?
100
+
101
+ at_least_one = false
102
+ files.each {|idx|
103
+ f = Mould.resolve_filename idx, @mould.getBinding
104
+
105
+ next unless userSaidYes 'update', f
106
+
107
+ FileUtils.mv f, f + '.orig' if save_old && File.exist?(f)
108
+ Mould.extract @template_dir + idx, @mould.getBinding, f
109
+
110
+ # say 'opa-popa was updated'
111
+ yield f if block_given?
112
+ at_least_one = true
113
+ }
114
+
115
+ # update a note file
116
+ @mould.noteCreate true if at_least_one
117
+ end
118
+
119
+ end
120
+ end
@@ -0,0 +1,23 @@
1
+ module Falsework
2
+ module Utils
3
+ extend self
4
+
5
+ def self.all_set? t
6
+ return false unless t
7
+
8
+ if t.is_a?(Array)
9
+ return false if t.size == 0
10
+
11
+ t.each {|i|
12
+ return false unless i
13
+ return false if i.to_s.strip.size == 0
14
+ }
15
+ end
16
+
17
+ return false if t.to_s.strip.size == 0
18
+ true
19
+ end
20
+
21
+
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ ---
2
+ exe:
3
+ - src: 'src/#exe.h'
4
+ dest: 'src/%s.h'
5
+ mode_int: null
6
+ - src: 'src/#exe.c'
7
+ dest: 'src/%s.c'
8
+ mode_int: null
9
+
10
+ doc:
11
+ - src: 'doc/#doc.ascii'
12
+ dest: 'doc/%s.1.asciidoc'
13
+ mode_int: null
14
+
15
+ test:
16
+ - src: 'test/#test.c'
17
+ dest: 'test/test_%s.c'
18
+ mode_int: null