falsework 2.1.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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