falsework 0.2.8 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/{lib/falsework/templates/naive/Gemfile.erb → Gemfile} +1 -1
  2. data/Gemfile.lock +12 -0
  3. data/README.rdoc +45 -21
  4. data/Rakefile +5 -5
  5. data/bin/falsework +64 -28
  6. data/doc/NEWS.rdoc +37 -10
  7. data/doc/README.rdoc +45 -21
  8. data/doc/TODO.org +13 -0
  9. data/doc/template-tutorial.rdoc +113 -0
  10. data/etc/falsework.yaml +1 -1
  11. data/lib/falsework/meta.rb +3 -2
  12. data/lib/falsework/mould.rb +267 -146
  13. data/lib/falsework/templates/c-glib/#config.yaml +18 -0
  14. data/lib/falsework/templates/c-glib/README +24 -0
  15. data/lib/falsework/templates/c-glib/doc/#doc.ascii +46 -0
  16. data/lib/falsework/templates/c-glib/doc/%%@project%%.1.asciidoc +46 -0
  17. data/lib/falsework/templates/{naive/doc/LICENSE.erb → c-glib/doc/LICENSE} +1 -1
  18. data/lib/falsework/templates/c-glib/doc/Makefile +17 -0
  19. data/lib/falsework/templates/c-glib/src/#exe.c +23 -0
  20. data/lib/falsework/templates/c-glib/src/#exe.h +8 -0
  21. data/lib/falsework/templates/c-glib/src/%%@project%%.c +23 -0
  22. data/lib/falsework/templates/c-glib/src/%%@project%%.h +26 -0
  23. data/lib/falsework/templates/c-glib/src/Makefile +28 -0
  24. data/lib/falsework/templates/c-glib/src/untest.c +9 -0
  25. data/lib/falsework/templates/c-glib/src/untest.h +14 -0
  26. data/lib/falsework/templates/c-glib/src/utils.c +232 -0
  27. data/lib/falsework/templates/c-glib/src/utils.h +45 -0
  28. data/lib/falsework/templates/c-glib/test/#test.c +48 -0
  29. data/lib/falsework/templates/c-glib/test/Makefile +78 -0
  30. data/lib/falsework/templates/c-glib/test/Makefile.test.mk +72 -0
  31. data/lib/falsework/templates/c-glib/test/mycat.c +8 -0
  32. data/{test/templates/.keep_me → lib/falsework/templates/c-glib/test/semis/text/empty.txt} +0 -0
  33. data/lib/falsework/templates/c-glib/test/test_utils.c +134 -0
  34. data/lib/falsework/templates/ruby-naive/#config.yaml +15 -0
  35. data/lib/falsework/templates/{naive/.gitignore.erb → ruby-naive/.gitignore.#erb} +0 -0
  36. data/lib/falsework/templates/ruby-naive/Gemfile +4 -0
  37. data/lib/falsework/templates/{naive/doc/README.rdoc.erb → ruby-naive/README.rdoc} +2 -2
  38. data/lib/falsework/templates/{naive/Rakefile.erb → ruby-naive/Rakefile} +1 -1
  39. data/lib/falsework/templates/{naive/bin/%%@project%%.erb → ruby-naive/bin/%%@project%%} +5 -5
  40. data/lib/falsework/templates/{naive/doc/#util.rdoc.erb → ruby-naive/doc/#doc.rdoc} +6 -6
  41. data/lib/falsework/templates/ruby-naive/doc/LICENSE +22 -0
  42. data/lib/falsework/templates/{naive/doc/NEWS.rdoc.erb → ruby-naive/doc/NEWS.rdoc} +0 -0
  43. data/lib/falsework/templates/{naive/README.rdoc.erb → ruby-naive/doc/README.rdoc} +2 -2
  44. data/lib/falsework/templates/{naive/etc/%%@project%%.yaml.erb → ruby-naive/etc/%%@project%%.yaml} +0 -0
  45. data/lib/falsework/templates/{naive/lib/%%@project%%/meta.rb.erb → ruby-naive/lib/%%@project%%/meta.rb} +3 -2
  46. data/lib/falsework/templates/{naive/lib/%%@project%%/trestle.rb.erb → ruby-naive/lib/%%@project%%/trestle.rb} +22 -14
  47. data/lib/falsework/templates/{naive/test/helper.rb.erb → ruby-naive/test/helper.rb} +0 -0
  48. data/lib/falsework/templates/{naive/test/helper_trestle.rb.erb → ruby-naive/test/helper_trestle.rb} +2 -2
  49. data/lib/falsework/templates/{naive/test/rake_git.rb.erb → ruby-naive/test/rake_git.rb} +1 -1
  50. data/lib/falsework/templates/{naive/test/test_%%@project%%.rb.erb → ruby-naive/test/test_%%@project%%.rb} +1 -1
  51. data/lib/falsework/trestle.rb +17 -9
  52. data/test/rake_erb_templates.rb +4 -4
  53. data/test/templates/config-01.yaml +2 -0
  54. data/test/test_cl.rb +29 -0
  55. data/test/test_exe.rb +61 -30
  56. data/test/test_mould.rb +80 -0
  57. metadata +86 -60
  58. data/doc/TODO.rdoc +0 -7
data/doc/TODO.org ADDED
@@ -0,0 +1,13 @@
1
+ * TODO In the distant future [3/6]
2
+
3
+ - [-] more templates [1/4]
4
+ - [X] c glib
5
+ - [ ] java android
6
+ - [ ] ruby sinatra
7
+ - [ ] ruby cli but a lighter one
8
+ - [X] write a small tutorial 'how to write a template'
9
+ - [ ] release to rubygems.org (it must run test, pull to github, tag,
10
+ create gem & upload it)
11
+ - [ ] describe c-glib template
12
+ - [X] normalize 'name' of the project before actually making a template
13
+ - [X] target_uuid variable for exe/doc/test templates
@@ -0,0 +1,113 @@
1
+ = How To Write a Template
2
+
3
+
4
+ == Naming Scheme
5
+
6
+ A recommended scheme is <tt>language-name</tt>, for example,
7
+ <tt>ruby-naive</tt>, <tt>java-android</tt>, <tt>c-gtk</tt>. Try to use
8
+ the name <= 30 characters in length (this is just a advice, not a
9
+ restriction).
10
+
11
+
12
+ == Location
13
+
14
+ Your personal templates must be in <tt>~/.falsework/templates</tt>
15
+ directory. To view all currently available templates, run <tt>falsework
16
+ list</tt>.
17
+
18
+
19
+ == Hierarchy
20
+
21
+ The file hierarchy in your template directory represents the hierarchy
22
+ of a project generated from it. Any file you place in the directory of a
23
+ particular template goes to the future project. (See an exception
24
+ below.) Typically, the name of the file stays the same too.
25
+
26
+ If you don't want some file to appear in the resulting project, prefix
27
+ the file wit a '#' character, for example, <tt>#mytest.c</tt>.
28
+
29
+ There are some files falsework ignores, for example
30
+ <tt>.gitignore</tt>. To include such files, add a <tt>.#erb</tt>
31
+ extension to its name.
32
+
33
+ There is also a special <tt>#config.yaml</tt> file in the root directory
34
+ of the template. It is an instruction for the template how to behave on
35
+ inject falsework commands.
36
+
37
+
38
+ == How Template Files are Processed
39
+
40
+ Every file (except <tt>#config.yaml</tt>) is considered a Ruby erb
41
+ template. Naturally you want to have some dynamic places in your
42
+ template that are different from project to project, for example, its
43
+ name.
44
+
45
+ === The list of useful variables
46
+
47
+ @classy:: A name of the project including spaces, for example,
48
+ 'Foo Bar Pro'
49
+
50
+ @project:: A lowercase derivative from @classy that is suited
51
+ for executable name and Github, for example,
52
+ 'foo_bar_pro'.
53
+
54
+ @camelcase:: Can be used as a module name, for example,
55
+ 'FooBarPro'.
56
+
57
+ uuid:: A string like 'D93E3B05_DAFA_C1F6_8EEA_DBBA1E8DA432'.
58
+ It's unique for every file.
59
+
60
+
61
+ @user:: Github user name.
62
+
63
+ @email:: User email.
64
+
65
+ @gecos:: A full user name.
66
+
67
+ === Variables available only for inject falsework commands
68
+
69
+ target:: Equivalent of @project.
70
+ target_camelcase:: Equivalent of @camelcase.
71
+ target_classy:: Equivalent of @classy.
72
+
73
+
74
+ == Inject Configuration
75
+
76
+ When user types
77
+
78
+ % falsework -t c-glib test foobar
79
+
80
+ falsework looks info <tt>#config.yaml</tt> file in the <tt>c-glib</tt>
81
+ template directory, searches for 'test' key and iterates on its value to
82
+ read some file in the template directory, white that file somewhere and
83
+ set it permission afterwards.
84
+
85
+ The default configuration is:
86
+
87
+ ---
88
+ :exe:
89
+ - :src: null
90
+ :dest: bin/%s'
91
+ :mode_int: 0744
92
+
93
+ :doc:
94
+ - :src: null
95
+ :dest: 'doc/%s.rdoc'
96
+ :mode_int: null
97
+
98
+ :test:
99
+ - :src: null
100
+ :dest: 'test/test_%s.rb'
101
+ :mode_int: null
102
+
103
+ The value of each top level key is an array, so you can inject several
104
+ files at once.
105
+
106
+ src:: A relative path to a file (that usually prefixed with
107
+ '#' & hidden from a generator). When +src+ is +null+, the
108
+ key is ignored and nothing is injected.
109
+
110
+ dest:: Sub-key can have <tt>%s</tt> in it which will be replaced with
111
+ target value ('foobar' in the example above).
112
+
113
+ mode_int:: Permission bits.
data/etc/falsework.yaml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- :foobar: foobar
2
+ :foobar: 'huh?'
@@ -1,7 +1,8 @@
1
+ # :include: ../../README.rdoc
1
2
  module Falsework
2
- module Meta
3
+ module Meta # :nodoc:
3
4
  NAME = 'falsework'
4
- VERSION = '0.2.8'
5
+ VERSION = '1.3.0'
5
6
  AUTHOR = 'Alexander Gromnitsky'
6
7
  EMAIL = 'alexander.gromnitsky@gmail.com'
7
8
  HOMEPAGE = 'http://github.com/gromnitsky/' + NAME
@@ -1,47 +1,95 @@
1
1
  require 'git'
2
2
  require 'erb'
3
3
  require 'digest/md5'
4
+ require 'securerandom'
4
5
 
5
6
  require_relative 'trestle'
6
7
 
7
- # Class Mould heavily uses 'naive' template. Theoretically it can manage
8
- # any template as long as it has files mentioned in #add.
9
- #
10
- # The directory with template may have files beginning with _#_ char
11
- # which will be ignored in #project_seed (a method that creates a shiny
12
- # new project form a template).
13
- #
14
- # If you need to run through erb not only the contents of a file in a
15
- # template but it name itself, then use the following convention:
16
- #
17
- # %%VARIABLE%%
18
- #
19
- # which is equivalent of erb's: <%= VARIABLE %>. See naive template
20
- # directory for examples.
21
- #
22
- # In the template files you may use any Mould instance variables. The
23
- # most usefull are:
24
- #
25
- # [@project] A project name.
26
- # [@user] Github user name.
27
- # [@email] User email.
28
- # [@gecos] A full user name.
29
8
  module Falsework
9
+ # The directory with template may have files beginning with # char
10
+ # which will be ignored in #project_seed (a method that creates a
11
+ # shiny new project form a template).
12
+ #
13
+ # If you need to run through erb not only the contents of a file in a
14
+ # template but it name itself, then use the following convention:
15
+ #
16
+ # %%VARIABLE%%
17
+ #
18
+ # which is equivalent of erb's: <%= VARIABLE %>. See 'ruby-naive'
19
+ # template directory for examples.
20
+ #
21
+ # In the template files you may use any Mould instance variables. The
22
+ # most usefull are:
23
+ #
24
+ # [@classy] An original project name, for example, 'Foobar Pro'
25
+ #
26
+ # [@project] A project name in lowercase, suitable for a name of an
27
+ # executable, for example, 'Foobar Pro' would be
28
+ # 'foobar_pro'.
29
+ #
30
+ # [@camelcase] A 'normalized' project name, for use in source code,
31
+ # for example, 'foobar pro' would be 'FoobarPro'.
32
+ #
33
+ # [@user] Github user name.
34
+ # [@email] User email.
35
+ # [@gecos] A full user name.
30
36
  class Mould
37
+ # Where @user, @email & @gecos comes from.
31
38
  GITCONFIG = '~/.gitconfig'
32
- TEMPLATE_DIRS = [Trestle.gem_libdir + '/templates',
33
- File.expand_path('~/.' + Meta::NAME + '/templates')]
34
- TEMPLATE_DEFAULT = 'naive'
39
+ # The possible dirs for templates. The first one is system-wide.
40
+ @@template_dirs = [Trestle.gem_libdir + '/templates',
41
+ File.expand_path('~/.' + Meta::NAME + '/templates')]
42
+ # The template used if user didn't select one.
43
+ TEMPLATE_DEFAULT = 'ruby-naive'
44
+ # A file name with configurations for the inject commands.
45
+ TEMPLATE_CONFIG = '#config.yaml'
46
+ # A list of files to ignore in any template.
35
47
  IGNORE_FILES = ['.gitignore']
36
48
 
37
- attr_accessor :verbose, :batch
38
-
39
- def initialize(project, user = nil, email = nil, gecos = nil)
49
+ # A verbose level for -v CLO.
50
+ attr_accessor :verbose
51
+ # -b CLO.
52
+ attr_accessor :batch
53
+ # A directory of a new generated project.
54
+ attr_reader :project
55
+
56
+ # [project] A name of the future project; may include all crap with spaces.
57
+ # [template] A name of the template for the project.
58
+ # [user] Github username; if nil we are extracting it from the ~/.gitconfig.
59
+ # [email] Github email
60
+ # [gecos] A full author name from ~/.gitconfig.
61
+ def initialize(project, template, user = nil, email = nil, gecos = nil)
62
+ @project = Mould.name_project project
63
+ raise "invalid project name '#{project}'" if !Mould.name_valid? @project
64
+ @camelcase = Mould.name_camelcase project
65
+ @classy = Mould.name_classy project
66
+
40
67
  @verbose = false
41
68
  @batch = false
69
+ @template = template
70
+ @dir_t = Mould.templates[@template || TEMPLATE_DEFAULT] || Trestle.errx(1, "no such template: #{template}")
71
+
72
+ # default config
73
+ @conf = {
74
+ exe: [{
75
+ src: nil,
76
+ dest: 'bin/%s',
77
+ mode_int: 0744
78
+ }],
79
+ doc: [{
80
+ src: nil,
81
+ dest: 'doc/%s.rdoc',
82
+ mode_int: nil
83
+ }],
84
+ test: [{
85
+ src: nil,
86
+ dir: 'test/test_%s.rb',
87
+ mode_int: nil
88
+ }]
89
+ }
90
+ Mould.config_parse(@dir_t + '/' + TEMPLATE_CONFIG, [], @conf)
42
91
 
43
92
  gc = Git.global_config rescue gc = {}
44
- @project = project
45
93
  @user = user || gc['github.user']
46
94
  @email = email || ENV['GIT_AUTHOR_EMAIL'] || ENV['GIT_COMMITTER_EMAIL'] || gc['user.email']
47
95
  @gecos = gecos || ENV['GIT_AUTHOR_NAME'] || ENV['GIT_COMMITTER_NAME'] || gc['user.name']
@@ -53,11 +101,72 @@ module Falsework
53
101
  }
54
102
  end
55
103
 
104
+ # Modifies an internal list of available template directories
105
+ def self.template_dirs_add(dirs)
106
+ return unless defined? dirs.each
107
+
108
+ dirs.each {|idx|
109
+ if ! File.directory?(idx)
110
+ Trestle.warnx "invalid additional template directory: #{idx}"
111
+ else
112
+ @@template_dirs << idx
113
+ end
114
+ }
115
+ end
116
+
117
+ # Hyper-fast generator of something like uuid suitable for code
118
+ # identifiers. Return a string.
119
+ def self.uuidgen_fake
120
+ loop {
121
+ r = ('%s_%s_%s_%s_%s' % [
122
+ SecureRandom.hex(4),
123
+ SecureRandom.hex(2),
124
+ SecureRandom.hex(2),
125
+ SecureRandom.hex(2),
126
+ SecureRandom.hex(6),
127
+ ]).upcase
128
+ return r if r[0] !~ /\d/
129
+ }
130
+ end
131
+
132
+ # Return false if @t is invalid.
133
+ def self.name_valid?(t)
134
+ return false if !t || t[0] =~ /\d/
135
+ t =~ /^[a-zA-Z0-9_]+$/ ? true : false
136
+ end
137
+
138
+ # Return cleaned version of an original project name, for example,
139
+ # 'Foobar Pro'
140
+ def self.name_classy(t)
141
+ t ? t.gsub(/\s+/, ' ').strip : ''
142
+ end
143
+
144
+ # Return a project name in lowercase, suitable for a name of an
145
+ # executable; for example, 'Foobar Pro' would be 'foobar_pro'.
146
+ def self.name_project(raw)
147
+ raw || (return '')
148
+
149
+ r = raw.gsub(/[^a-zA-Z0-9_]+/, '_').downcase
150
+ r.sub!(/^_/, '');
151
+ r.sub!(/_$/, '');
152
+
153
+ r
154
+ end
155
+
156
+ # Return a 'normalized' project name, for use in source code; for
157
+ # example, 'foobar pro' would be 'FoobarPro'.
158
+ def self.name_camelcase(raw)
159
+ raw || (return '')
160
+ raw.strip.split(/[^a-zA-Z0-9]+/).map{|idx|
161
+ idx[0].upcase + idx[1..-1]
162
+ }.join
163
+ end
164
+
56
165
  # Return a hash {name => dir} with current possible template names
57
166
  # and corresponding directories.
58
167
  def self.templates
59
168
  r = {}
60
- TEMPLATE_DIRS.each {|i|
169
+ @@template_dirs.each {|i|
61
170
  Dir.glob(i + '/*').each {|j|
62
171
  r[File.basename(j)] = j if File.directory?(j)
63
172
  }
@@ -65,111 +174,121 @@ module Falsework
65
174
  r
66
175
  end
67
176
 
68
- # Generate a new project in @project directory from _template_.
69
- #
70
- # [template] If it's nil TEMPLATE_DEFAULT will be used.
71
- # [filter] A regexp for matching particular files in the
72
- # template directory.
177
+ # Generate a new project in @project directory from @template.
73
178
  #
74
179
  # Return false if nothing was extracted.
75
- def project_seed(template, filter)
76
- sl = ->(is_dir, *args) {
77
- is_dir ? Mould.erb_fname(*args) : Mould.erb_fname(*args).sub(/\.erb$/, '')
78
- }
180
+ def project_seed()
181
+ uuid = Mould.uuidgen_fake # useful variable for the template
79
182
 
80
183
  # check for existing project
81
184
  Trestle.errx(1, "directory '#{@project}' is not empty") if Dir.glob(@project + '/*').size > 0
82
185
 
83
- Dir.mkdir(@project) unless File.directory?(@project)
84
- prjdir = File.expand_path(@project)
85
- puts "Project path: #{prjdir}" if @verbose
86
-
87
- origdir = Dir.pwd;
88
- Dir.chdir @project
186
+ Dir.mkdir @project unless File.directory?(@project)
187
+ puts "Project path: #{File.expand_path(@project)}" if @verbose
89
188
 
90
189
  r = false
91
- start = Mould.templates[template || TEMPLATE_DEFAULT] || Trestle.errx(1, "no such template: #{template}")
92
- puts "Template: #{start}" if @verbose
190
+ puts "Template: #{@dir_t}" if @verbose
93
191
  symlinks = []
94
- Mould.traverse(start) {|i|
95
- file = i.sub(/^#{start}\//, '')
96
- next if filter ? file =~ filter : false
97
- next if IGNORE_FILES.index {|ign| file.match(/#{ign}$/) }
192
+ Dir.chdir(@project) {
193
+ Mould.traverse(@dir_t) {|idx|
194
+ file = idx.sub(/^#{@dir_t}\//, '')
195
+ next if IGNORE_FILES.index {|i| file.match(/#{i}$/) }
98
196
 
99
- if File.symlink?(i)
100
- # we'll process them later on
101
- is_dir = File.directory?(start + '/' + File.readlink(i))
102
- symlinks << [sl.call(is_dir, File.readlink(i), binding),
103
- sl.call(is_dir, file, binding)]
104
- elsif File.directory?(i)
105
- puts("D: #{file}") if @verbose
106
- file = Mould.erb_fname(file, binding)
107
- # FileUtils.mkdir_p(prjdir + '/' + file)
108
- Dir.mkdir(prjdir + '/' + file)
109
- Dir.chdir(prjdir + '/' + file)
110
- else
111
- puts("N: #{file}") if @verbose
112
- to = File.basename(Mould.erb_fname(file, binding), '.erb')
113
- Mould.extract(start + '/' + file, binding, to)
114
- # make files in bin/ executable
115
- File.chmod(0744, to) if file =~ /bin\//
116
- end
117
- r = true
118
- }
197
+ if File.symlink?(idx)
198
+ # we'll process them later on
199
+ is_dir = File.directory?(@dir_t + '/' + File.readlink(idx))
200
+ symlinks << [Mould.get_filename(File.readlink(idx), binding),
201
+ Mould.get_filename(file, binding)]
202
+ elsif File.directory?(idx)
203
+ puts "D: #{file}" if @verbose
204
+ Dir.mkdir Mould.get_filename(file, binding)
205
+ else
206
+ puts "N: #{file}" if @verbose
207
+ to = Mould.get_filename(file, binding)
208
+ Mould.extract(idx, binding, to)
209
+ end
210
+ r = true
211
+ }
119
212
 
120
- # create saved symlinks
121
- Dir.chdir prjdir
122
- symlinks.each {|i|
123
- # src = i[0].sub(/#{File.extname(i[0])}$/, '')
124
- # dest = i[1].sub(/#{File.extname(i[1])}$/, '')
125
- src = i[0]
126
- dest = i[1]
127
- puts "L: #{dest} => #{src}" if @verbose
128
- File.symlink(src, dest)
213
+ # create saved symlinks
214
+ symlinks.each {|idx|
215
+ src = idx[0]
216
+ dest = idx[1]
217
+ puts "L: #{dest} => #{src}" if @verbose
218
+ File.symlink(src, dest)
219
+ }
129
220
  }
130
- Dir.chdir origdir
221
+
131
222
  r
132
223
  end
133
224
 
134
- # Add an executable or a test from the _template_.
225
+ # Parse a config. Return false on error.
135
226
  #
136
- # [mode] Is either 'exe' or 'test'.
137
- # [what] A test/exe file to create.
227
+ # [file] A file to parse.
228
+ # [rvars] A list of variable names which must be in the config.
229
+ # [hash] a hash to merge results with
230
+ def self.config_parse(file, rvars, hash)
231
+ r = true
232
+
233
+ if File.readable?(file)
234
+ begin
235
+ myconf = YAML.load_file(file)
236
+ rescue
237
+ Trestle.warnx "cannot parse #{file}: #{$!}"
238
+ return false
239
+ end
240
+ rvars.each { |i|
241
+ Trestle.warnx "missing or nil '#{i}' in #{file}" if ! myconf.key?(i.to_sym) || ! myconf[i.to_sym]
242
+ r = false
243
+ }
244
+
245
+ hash.merge!(myconf) if r && hash
246
+ else
247
+ r = false
248
+ end
249
+
250
+ r
251
+ end
252
+
253
+ # Add an executable or a test from the template.
254
+ #
255
+ # [mode] Is either 'exe', 'doc' or 'test'.
256
+ # [target] A test/doc/exe file to create.
257
+ #
258
+ # Return a list of a created files.
138
259
  #
139
- # Return a name of a created file.
140
- def add(template, mode, what)
141
- start = Mould.templates[template || TEMPLATE_DEFAULT] || Trestle.errx(1, "no such template: #{template}")
142
- r = []
260
+ # Useful variables in the template:
261
+ #
262
+ # [target]
263
+ # [target_camelcase]
264
+ # [target_classy]
265
+ # [uuid]
266
+ def add(mode, target)
267
+ target_orig = target
268
+ target = Mould.name_project target_orig
269
+ raise "invalid target name '#{target_orig}'" if !Mould.name_valid? target
270
+ target_camelcase = Mould.name_camelcase target_orig
271
+ target_classy = Mould.name_classy target_orig
272
+ uuid = Mould.uuidgen_fake
273
+
274
+ created = []
143
275
 
144
- case mode
145
- when 'exe'
146
- # script
147
- f = {}
148
- f[:from] = start + '/' + 'bin/%%@project%%.erb'
149
- f[:exe] = true
150
- f[:to] = "bin/#{what}"
151
- r << f
276
+ return [] unless @conf[mode.to_sym][0][:src]
152
277
 
153
- # doc (reading an 'ignored' file from the template)
154
- f = {}
155
- f[:from] = start + '/' + 'doc/#util.rdoc.erb'
156
- f[:exe] = false
157
- f[:to] = "doc/#{what}.rdoc"
158
- r << f
159
- when 'test'
160
- f = {}
161
- f[:from] = start + '/' + 'test/test_%%@project%%.rb.erb'
162
- f[:exe] = false
163
- f[:to] = "#{mode}/test_#{what}.rb"
164
- r << f
165
- else
166
- fail "invalid mode #{mode}"
167
- end
278
+ @conf[mode.to_sym].each {|idx|
279
+ to = idx[:dest] % target
168
280
 
169
- r.each {|i|
170
- Mould.extract(i[:from], binding, i[:to])
171
- File.chmod(0744, i[:to]) if i[:exe]
281
+ begin
282
+ Mould.extract(@dir_t + '/' + idx[:src], binding, to)
283
+ File.chmod(idx[:mode_int], to) if idx[:mode_int]
284
+ rescue
285
+ Trestle.warnx "failed to create '#{to}' (check your #config.yaml): #{$!}"
286
+ else
287
+ created << to
288
+ end
172
289
  }
290
+
291
+ created
173
292
  end
174
293
 
175
294
  # Walk through a directory tree, executing a block for each file or
@@ -191,53 +310,56 @@ module Falsework
191
310
  }
192
311
  end
193
312
 
194
- # Extract into the current directory 1 file from _path_.
313
+ # Extract file @from into @to.
195
314
  #
196
- # [bin] A binding for eval.
197
- # [to] If != nil write to a particular, not guessed file name.
198
- def self.extract(path, bin, to = nil)
199
- t = ERB.new(File.read(path))
200
- t.filename = path # to report errors relative to this file
315
+ # [binding] A binding for eval.
316
+ def self.extract(from, binding, to)
317
+ t = ERB.new(File.read(from))
318
+ t.filename = from # to report errors relative to this file
201
319
  begin
202
- # pp t.result
203
- md5_system = Digest::MD5.hexdigest(t.result(bin))
320
+ output = t.result(binding)
321
+ md5_system = Digest::MD5.hexdigest(output)
204
322
  rescue Exception
205
- Trestle.errx(1, "cannot read the template file: #{$!}")
323
+ Trestle.errx(1, "bogus template file '#{from}': #{$!}")
206
324
  end
207
325
 
208
- skeleton = to || File.basename(path, '.erb')
209
- if ! File.exists?(skeleton)
326
+ if ! File.exists?(to)
210
327
  # write a skeleton
211
328
  begin
212
- File.open(skeleton, 'w+') { |fp| fp.puts t.result(bin) }
329
+ File.open(to, 'w+') { |fp| fp.puts output }
330
+ # transfer the exec bit to the generated result
331
+ File.chmod(0744, to) if File.stat(from).executable?
213
332
  rescue
214
- Trestle.errx(1, "cannot write the skeleton: #{$!}")
333
+ Trestle.errx(1, "cannot generate: #{$!}")
215
334
  end
216
335
  elsif
217
336
  # warn a careless user
218
- if md5_system != Digest::MD5.file(skeleton).hexdigest
219
- Trestle.errx(1, "#{skeleton} already exists")
337
+ if md5_system != Digest::MD5.file(to).hexdigest
338
+ Trestle.errx(1, "'#{to}' already exists")
220
339
  end
221
340
  end
222
341
  end
223
342
 
224
- def self.erb_fname(t, bin)
225
- re = /%%([^.]+)?%%/
226
- return ERB.new(t.gsub(re, '<%= \1 %>')).result(bin) if t =~ re
227
- return t
343
+ # Resolve @t from possible %%VARIABLE%% scheme.
344
+ def self.get_filename(t, binding)
345
+ t || (return '')
346
+
347
+ re = /%%([^%]+)%%/
348
+ t = ERB.new(t.gsub(re, '<%= \+ %>')).result(binding) if t =~ re
349
+ t.sub /\.#erb$/, ''
228
350
  end
229
351
 
230
352
 
231
- # Search for all files in the _template_ for the line
353
+ # Search for all files in the template directory the line
232
354
  #
233
355
  # /^..? :erb:/
234
356
  #
235
357
  # in first n lines. If the line is found, the file is considered a
236
358
  # candidate for an upgrade. Return a hash {target:template}
237
- def upgradable_files(template)
359
+ def upgradable_files()
238
360
  line_max = 4
239
361
  r = {}
240
- Falsework::Mould.traverse(template) {|i|
362
+ Falsework::Mould.traverse(@dir_t) {|i|
241
363
  next if File.directory?(i)
242
364
  next if File.symlink?(i) # hm...
243
365
 
@@ -245,8 +367,8 @@ module Falsework
245
367
  n = 0
246
368
  while n < line_max && line = fp.gets
247
369
  if line =~ /^..? :erb:/
248
- t = i.sub(/#{template}\//, '')
249
- r[Mould.erb_fname(t, binding).sub(/\.erb$/, '')] = i
370
+ t = i.sub(/#{@dir_t}\//, '')
371
+ r[Mould.get_filename(t, binding)] = i
250
372
  break
251
373
  end
252
374
  n += 1
@@ -276,11 +398,10 @@ module Falsework
276
398
  #
277
399
  # Neithe we do check for a content of upgradable files nor try to
278
400
  # merge old with new. (Why?)
279
- def upgrade(template)
401
+ def upgrade()
280
402
  # 0. search for 'new' files in the template
281
- t = Mould.templates[template || TEMPLATE_DEFAULT] || Trestle.errx(1, "no such template: #{template}")
282
- uf = upgradable_files(t)
283
- fail "template #{template} cannot offer to you files for an upgrade" if uf.size == 0
403
+ uf = upgradable_files
404
+ fail "template #{@template} cannot offer you files for the upgrade" if uf.size == 0
284
405
  # pp uf
285
406
 
286
407
  # 1. analyse 'old' files
@@ -293,10 +414,10 @@ module Falsework
293
414
  File.open(k) {|fp|
294
415
  is_versioned = false
295
416
  while line = fp.gets
296
- if line =~ /^# Don't remove this: falsework\/(#{Gem::Version::VERSION_PATTERN})\/(\w+)\/.+/
417
+ if line =~ /^# Don't remove this: falsework\/(#{Gem::Version::VERSION_PATTERN})\/(.+)\/.+/
297
418
  is_versioned = true
298
- if $3 != (template || TEMPLATE_DEFAULT)
299
- fail "file #{k} is from #{$3} template"
419
+ if $3 != (@template || TEMPLATE_DEFAULT)
420
+ fail "file #{k} is from '#{$3}' template"
300
421
  end
301
422
  if Gem::Version.new(Meta::VERSION) >= Gem::Version.new($1)
302
423
  # puts "#{k}: #{$1}"
@@ -312,7 +433,7 @@ module Falsework
312
433
  }
313
434
  end
314
435
  }
315
- fail "template #{template || TEMPLATE_DEFAULT} cannot find files for an upgrade" if u.size == 0
436
+ fail "template #{@template || TEMPLATE_DEFAULT} cannot find files for an upgrade" if u.size == 0
316
437
 
317
438
  # 2. ask user for a commitment
318
439
  if ! @batch