Class: Falsework::Mould
- Inherits:
-
Object
- Object
- Falsework::Mould
- Defined in:
- lib/falsework/mould.rb
Overview
The directory with template may have files beginning with # char which will be ignored in #project_seed (a method that creates a shiny new project form a template).
If you need to run through erb not only the contents of a file in a template but it name itself, then use the following convention:
%%VARIABLE%%
which is equivalent of erb’s: <%= VARIABLE %>. See ‘ruby-cli’ template directory for examples.
In the template files you may use any Mould instance variables. The most usefull are:
- @classy
-
An original project name, for example, ‘Foobar Pro’
- @project
-
A project name in lowercase, suitable for a name of an executable, for example, ‘Foobar Pro’ would be ‘foobar_pro’.
- @camelcase
-
A ‘normalized’ project name, for use in source code, for example, ‘foobar pro’ would be ‘FoobarPro’.
- @user
-
Github user name.
-
User email.
- @gecos
-
A full user name.
Constant Summary collapse
- GITCONFIG =
Where @user, @email & @gecos comes from.
'~/.gitconfig'
- TEMPLATE_DEFAULT =
The template used if user didn’t select one.
'ruby-cli'
- TEMPLATE_CONFIG =
A file name with configurations for the inject commands.
'#config.yaml'
- IGNORE_FILES =
A list of files to ignore in any template.
['.gitignore']
- NOTE =
Note file name
'.' + Meta::NAME
Class Attribute Summary collapse
-
.template_dirs ⇒ Object
readonly
Returns the value of attribute template_dirs.
Instance Attribute Summary collapse
-
#conf ⇒ Object
readonly
template configuration.
-
#project ⇒ Object
readonly
A directory of a new generated project.
Class Method Summary collapse
-
.extract(from, bng, to) ⇒ Object
Extract file @from into @to.
-
.name_camelcase(raw) ⇒ Object
Return a ‘normalized’ project name, for use in source code; for example, ‘foobar pro’ would be ‘FoobarPro’.
-
.name_classy(t) ⇒ Object
Return cleaned version of an original project name, for example, ‘Foobar Pro’.
-
.name_project(raw) ⇒ Object
Return a project name in lowercase, suitable for a name of an executable; for example, ‘Foobar Pro’ would be ‘foobar_pro’.
-
.name_valid?(t) ⇒ Boolean
Return false if @t is invalid.
-
.resolve_filename(t, bng) ⇒ Object
Resolve t from possible %%VARIABLE%% scheme.
-
.template_dirs_add(dirs) ⇒ Object
Modifies an internal list of available template directories.
-
.templates ⇒ Object
Return a hash => dir with current possible template names and corresponding directories.
-
.traverse(start, &block) ⇒ Object
Walk through a directory tree, executing a block for each file or directory.
-
.uuidgen_fake ⇒ Object
Hyper-fast generator of something like uuid suitable for code identifiers.
Instance Method Summary collapse
-
#add(mode, target) ⇒ Object
Add a file from the template.
-
#configParse(rvars = []) ⇒ Object
Parse a config.
- #getBinding ⇒ Object
-
#initialize(project, template, user = nil, email = nil, gecos = nil) ⇒ Mould
constructor
- project
-
A name of the future project; may include all crap with spaces.
-
#noteCreate(after_upgrade = false) ⇒ Object
Write a YAML file into a created project directory.
-
#project_seed ⇒ Object
Generate a new project in @project directory from @template.
Constructor Details
#initialize(project, template, user = nil, email = nil, gecos = nil) ⇒ Mould
- project
-
A name of the future project; may include all crap with spaces.
- template
-
A name of the template for the project.
- user
-
Github username; if nil we are extracting it from the ~/.gitconfig.
-
Github email
- gecos
-
A full author name from ~/.gitconfig.
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 |
# File 'lib/falsework/mould.rb', line 78 def initialize(project, template, user = nil, email = nil, gecos = nil) @project = Mould.name_project project fail MouldError, "invalid project name '#{project}'" unless Mould.name_valid?(@project) @camelcase = Mould.name_camelcase project @classy = Mould.name_classy project @batch = false @template = template || TEMPLATE_DEFAULT @dir_t = Mould.templates[@template] || fail(MouldError, "template '#{@template}' not found") # default config @conf = { 'exe' => [{ 'src' => nil, 'dest' => 'bin/%s', 'mode_int' => 0744 }], 'doc' => [{ 'src' => nil, 'dest' => 'doc/%s.rdoc', 'mode_int' => nil }], 'test' => [{ 'src' => nil, 'dir' => 'test/test_%s.rb', 'mode_int' => nil }], 'version' => '1.0.0' } configParse gc = Git.global_config rescue gc = {} @user = user || gc['github.user'] @email = email || ENV['GIT_AUTHOR_EMAIL'] || ENV['GIT_COMMITTER_EMAIL'] || gc['user.email'] @gecos = gecos || ENV['GIT_AUTHOR_NAME'] || ENV['GIT_COMMITTER_NAME'] || gc['user.name'] [['github.user', @user], ['user.email', @email], ['user.name', @gecos]].each {|i| fail MouldError, "missing #{i.first} in #{GITCONFIG}" if i.last.to_s == '' } end |
Class Attribute Details
.template_dirs ⇒ Object (readonly)
Returns the value of attribute template_dirs.
56 57 58 |
# File 'lib/falsework/mould.rb', line 56 def template_dirs @template_dirs end |
Instance Attribute Details
#conf ⇒ Object (readonly)
template configuration
71 72 73 |
# File 'lib/falsework/mould.rb', line 71 def conf @conf end |
#project ⇒ Object (readonly)
A directory of a new generated project.
69 70 71 |
# File 'lib/falsework/mould.rb', line 69 def project @project end |
Class Method Details
.extract(from, bng, to) ⇒ Object
Extract file @from into @to.
- bng
-
A binding for eval.
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 |
# File 'lib/falsework/mould.rb', line 342 def self.extract from, bng, to t = ERB.new File.read(from.to_s) t.filename = from.to_s # to report errors relative to this file begin output = t.result bng md5_system = Digest::MD5.hexdigest(output) rescue Exception fail MouldError, "bogus template file '#{from}': #{$!}" end unless File.exists?(to) # write a skeleton begin FileUtils.mkdir_p File.dirname(to) File.open(to, 'w+') { |fp| fp.puts output } # transfer the exec bit to the generated result File.chmod(0744, to) if !defined?(FakeFS) && File.stat(from.to_s).executable? rescue fail MouldError, "cannot generate: #{$!}" end else # warn a careless user CliUtils.warnx "'#{to}' already exists in modified state" if md5_system != Digest::MD5.file(to).hexdigest end end |
.name_camelcase(raw) ⇒ Object
Return a ‘normalized’ project name, for use in source code; for example, ‘foobar pro’ would be ‘FoobarPro’.
177 178 179 180 181 182 |
# File 'lib/falsework/mould.rb', line 177 def self.name_camelcase(raw) raw || (return '') raw.strip.split(/[^a-zA-Z0-9]+/).map{|idx| idx[0].upcase + idx[1..-1] }.join end |
.name_classy(t) ⇒ Object
Return cleaned version of an original project name, for example, ‘Foobar Pro’
159 160 161 |
# File 'lib/falsework/mould.rb', line 159 def self.name_classy(t) t ? t.gsub(/\s+/, ' ').strip : '' end |
.name_project(raw) ⇒ Object
Return a project name in lowercase, suitable for a name of an executable; for example, ‘Foobar Pro’ would be ‘foobar_pro’.
165 166 167 168 169 170 171 172 173 |
# File 'lib/falsework/mould.rb', line 165 def self.name_project(raw) raw || (return '') r = raw.gsub(/[^a-zA-Z0-9_]+/, '_').downcase r.sub!(/^_/, ''); r.sub!(/_$/, ''); r end |
.name_valid?(t) ⇒ Boolean
Return false if @t is invalid.
152 153 154 155 |
# File 'lib/falsework/mould.rb', line 152 def self.name_valid?(t) return false if !t || t[0] =~ /\d/ t =~ /^[a-zA-Z0-9_]+$/ ? true : false end |
.resolve_filename(t, bng) ⇒ Object
Resolve t from possible %%VARIABLE%% scheme.
369 370 371 372 373 374 375 |
# File 'lib/falsework/mould.rb', line 369 def self.resolve_filename t, bng t || (return '') re = /%%([^%]+)%%/ t = ERB.new(t.gsub(re, '<%= \+ %>')).result(bng) if t =~ re t.sub(/\.#erb$/, '') end |
.template_dirs_add(dirs) ⇒ Object
Modifies an internal list of available template directories
122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/falsework/mould.rb', line 122 def self.template_dirs_add(dirs) return unless defined? dirs.each dirs.each {|idx| fail "#{idx} is not a Pathname" unless idx.instance_of?(Pathname) if ! File.directory?(idx) CliUtils.warnx "invalid additional template directory: #{idx}" else @template_dirs << idx end } end |
.templates ⇒ Object
Return a hash => dir with current possible template names and corresponding directories.
186 187 188 189 190 191 192 193 194 |
# File 'lib/falsework/mould.rb', line 186 def self.templates r = {} @template_dirs.each {|i| Dir.glob(i + '*').each {|j| r[File.basename(j)] = Pathname.new(j) if File.directory?(j) } } r end |
.traverse(start, &block) ⇒ Object
Walk through a directory tree, executing a block for each file or directory. Ignores ., .. and files starting with _#_ character.
- start
-
The directory to start with.
325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/falsework/mould.rb', line 325 def self.traverse(start, &block) l = Dir.glob(start + '/*', File::FNM_DOTMATCH).delete_if {|i| i.match(/\/?\.\.?$/) || i.match(/^#|\/#/) } # stop if directory is empty (contains only . and ..) return if l.size == 0 l.sort.each {|i| yield i # recursion! self.traverse(i) {|j| block.call j} if File.directory?(i) } end |
.uuidgen_fake ⇒ Object
Hyper-fast generator of something like uuid suitable for code identifiers. Return a string.
138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/falsework/mould.rb', line 138 def self.uuidgen_fake loop { r = ('%s_%s_%s_%s_%s' % [ SecureRandom.hex(4), SecureRandom.hex(2), SecureRandom.hex(2), SecureRandom.hex(2), SecureRandom.hex(6), ]).upcase return r if r[0] !~ /\d/ } end |
Instance Method Details
#add(mode, target) ⇒ Object
Add a file from the template.
- mode
-
Is either ‘exe’, ‘doc’ or ‘test’.
- target
-
A test/doc/exe file to create.
Return a list of a created files.
Useful variables in the template:
- target
- target_camelcase
- target_classy
- uuid
- target_camelcase
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 313 314 315 316 317 318 |
# File 'lib/falsework/mould.rb', line 284 def add(mode, target) target_orig = target target = Mould.name_project target_orig fail MouldError, "invalid target name '#{target_orig}'" if !Mould.name_valid? target target_camelcase = Mould.name_camelcase target_orig target_classy = Mould.name_classy target_orig uuid = Mould.uuidgen_fake created = [] unless @conf[mode] && @conf[mode][0]['src'] if @conf[:file] CliUtils.warnx "hash '#{mode}' is empty in #{@conf[:file]}" else CliUtils.warnx "template '#{@template}' doesn't have '#{TEMPLATE_CONFIG}' file" end return [] end @conf[mode].each {|idx| to = idx['dest'] % target begin # 'binding' to include local vars Mould.extract @dir_t + idx['src'], binding, to File.chmod(idx['mode_int'], to) if idx['mode_int'] rescue CliUtils.warnx "failed to create '#{to}' (check your #config.yaml): #{$!}" else created << to end } created end |
#configParse(rvars = []) ⇒ Object
Parse a config. Return false on error.
- rvars
-
A list of variable names which must be in the config.
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/falsework/mould.rb', line 247 def configParse rvars = [] r = false file = @dir_t + TEMPLATE_CONFIG if File.readable?(file) begin myconf = YAML.load_file file myconf[:file] = file r = true rescue CliUtils.warnx "cannot parse #{file}: #{$!}" return false end rvars.each { |i| CliUtils.warnx "missing or nil '#{i}' in #{file}" if ! myconf.key?(i) || ! myconf[i] return false } @conf.merge!(myconf) if r end r end |
#getBinding ⇒ Object
377 378 379 |
# File 'lib/falsework/mould.rb', line 377 def getBinding binding end |
#noteCreate(after_upgrade = false) ⇒ Object
Write a YAML file into a created project directory. This file is required for Upgrader.
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 |
# File 'lib/falsework/mould.rb', line 383 def noteCreate after_upgrade = false h = { 'project' => { 'classy' => @classy, 'upgraded' => DateTime.now.iso8601 }, 'template' => { 'version' => @conf['version'], 'name' => @template } } file = @project + '/' + NOTE file = NOTE if after_upgrade # in 'upgrade' mode we are in project dir File.open(file, 'w+') {|fp| CliUtils.veputs 1, "N: #{File.basename(file)}" fp.puts "# DO NOT DELETE THIS FILE" fp.puts "# unless you don't want to upgrade scaffolds in the future." fp.puts h.to_yaml } end |
#project_seed ⇒ Object
Generate a new project in @project directory from @template.
Return false if nothing was extracted.
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 |
# File 'lib/falsework/mould.rb', line 199 def project_seed uuid = Mould.uuidgen_fake # useful variable for the template # check for existing project fail MouldError, "directory '#{@project}' is not empty" if Dir.glob(@project + '/*').size > 0 Dir.mkdir @project unless File.directory?(@project) CliUtils.veputs 1, "Project path: #{File.(@project)}" r = false CliUtils.veputs 1, "Template: #{@dir_t}" symlinks = [] Dir.chdir(@project) { Mould.traverse(@dir_t.to_s) {|idx| file = idx.sub(/^#{@dir_t}\//, '') next if IGNORE_FILES.index {|i| file.match(/#{i}$/) } if File.symlink?(idx) # we'll process them later on # is_dir = File.directory?(@dir_t + '/' + File.readlink(idx)) symlinks << [Mould.resolve_filename(File.readlink(idx), getBinding), Mould.resolve_filename(file, getBinding)] elsif File.directory?(idx) CliUtils.veputs 1, "D: #{file}" Dir.mkdir Mould.resolve_filename(file, getBinding) else CliUtils.veputs 1, "N: #{file}" to = Mould.resolve_filename file, getBinding Mould.extract idx, binding, to # 'binding' to include local uuid end r = true } # create saved symlinks symlinks.each {|idx| src = idx[0] dest = idx[1] CliUtils.veputs 1, "L: #{dest} => #{src}" File.symlink src, dest } } r end |