directory_template 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +100 -0
- data/Rakefile +10 -0
- data/directory_template.gemspec +41 -0
- data/documentation/ContentProcessors.markdown +19 -0
- data/documentation/PathProcessors.markdown +17 -0
- data/examples/dir_gem_template/%{gem_name}/%{gem_name}.gemspec.stop.erb +33 -0
- data/examples/dir_gem_template/%{gem_name}/README.markdown.stop.erb +21 -0
- data/examples/dir_gem_template/%{gem_name}/README_DIRECTORIES.stop +9 -0
- data/examples/dir_gem_template/%{gem_name}/Rakefile.stop +10 -0
- data/examples/dir_gem_template/%{gem_name}/lib/%{require_name}/version.rb.stop.erb +11 -0
- data/examples/dir_gem_template/%{gem_name}/lib/%{require_name}.rb.stop.erb +12 -0
- data/examples/yaml_gem_template.yaml +110 -0
- data/lib/directory_template/blank_slate.rb +153 -0
- data/lib/directory_template/erb_template.rb +239 -0
- data/lib/directory_template/process_data.rb +94 -0
- data/lib/directory_template/processor/erb.rb +18 -0
- data/lib/directory_template/processor/format.rb +15 -0
- data/lib/directory_template/processor/markdown.rb +15 -0
- data/lib/directory_template/processor/stop.rb +14 -0
- data/lib/directory_template/processor.rb +127 -0
- data/lib/directory_template/version.rb +13 -0
- data/lib/directory_template.rb +306 -0
- metadata +76 -0
@@ -0,0 +1,306 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
require 'directory_template/process_data'
|
7
|
+
require 'directory_template/processor'
|
8
|
+
require 'directory_template/version'
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
# @version 1.0.0
|
13
|
+
# @author Stefan Rusterholz <stefan.rusterholz@gmail.com>
|
14
|
+
#
|
15
|
+
# DirectoryTemplate
|
16
|
+
# Create directory structures from template directory structures or template data.
|
17
|
+
#
|
18
|
+
# Preregistered processors
|
19
|
+
# * :stop: Stops the preprocessing chain, it's advised to add that to all files for
|
20
|
+
# future-proofness.
|
21
|
+
class DirectoryTemplate
|
22
|
+
|
23
|
+
# All registered processors
|
24
|
+
Processors = []
|
25
|
+
|
26
|
+
# You can register custom processors for templates. They're triggered based on the
|
27
|
+
# pattern.
|
28
|
+
# A processor can change the ProcessData struct passed to it, which will be reflected
|
29
|
+
# when creating the file or directory
|
30
|
+
#
|
31
|
+
# @param [DirectoryTemplate::Processor] processor
|
32
|
+
# The processor to register
|
33
|
+
#
|
34
|
+
def self.register(processor)
|
35
|
+
Processors << processor
|
36
|
+
end
|
37
|
+
Processor.register_all
|
38
|
+
|
39
|
+
# The standard path processor, just replaces `%{sprintf_style_variables}` with their
|
40
|
+
# values.
|
41
|
+
StandardPathProcessor = Processor::Format
|
42
|
+
|
43
|
+
# The default options used by DirectoryTemplate
|
44
|
+
DefaultOptions = {
|
45
|
+
:verbose => false,
|
46
|
+
:silent => false,
|
47
|
+
:out => $stdout,
|
48
|
+
:processors => Processors,
|
49
|
+
:path_processor => StandardPathProcessor,
|
50
|
+
:source => '(unknown)',
|
51
|
+
:meta => {},
|
52
|
+
}
|
53
|
+
|
54
|
+
# Create a DirectoryTemplate from an existing directory structure.
|
55
|
+
def self.directory(template_path, options={})
|
56
|
+
data = Dir.chdir(template_path) {
|
57
|
+
paths = Dir['**/{*,.*}']
|
58
|
+
paths -= paths.grep(/(?:^|\/)\.\.?$/)
|
59
|
+
directories, files = paths.sort.partition { |path| File.directory?(path) }
|
60
|
+
filemap = Hash[files.map { |path| [path, File.read(path)] }]
|
61
|
+
|
62
|
+
{:directories => directories, :files => filemap}
|
63
|
+
}
|
64
|
+
|
65
|
+
new(data, options.merge(:source => template_path))
|
66
|
+
end
|
67
|
+
|
68
|
+
# @private
|
69
|
+
# Converts a recursive hash into a suitable data structure for DirectoryTemplate::new
|
70
|
+
def self.convert_recursive_structure(current, stack=[], dirs=[], files={})
|
71
|
+
current.each do |segment, content|
|
72
|
+
new_stack = stack+[segment]
|
73
|
+
path = new_stack.join('/')
|
74
|
+
case content
|
75
|
+
when String,nil
|
76
|
+
files[path] = content || ''
|
77
|
+
when Hash
|
78
|
+
dirs << path
|
79
|
+
convert_recursive_structure(content, new_stack, dirs, files)
|
80
|
+
else
|
81
|
+
raise "Invalid structure"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
return dirs, files
|
86
|
+
end
|
87
|
+
|
88
|
+
# Create a DirectoryTemplate from a nested hash structure.
|
89
|
+
# The hash should just be a recursive hash of strings. Use an empty hash to indicate
|
90
|
+
# an empty directory. Leaf-strings are considered to be the content of a file. Use nil
|
91
|
+
# to indicate an empty file.
|
92
|
+
def self.from_hash(hash, options=nil)
|
93
|
+
dirs, files = convert_recursive_structure(hash)
|
94
|
+
data = {:directories => dirs, :files => files}
|
95
|
+
|
96
|
+
new(data, options)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Create a DirectoryTemplate from a YAML file.
|
100
|
+
# The yaml should just be a recursive hash of strings. Use an empty hash to indicate
|
101
|
+
# an empty directory. Leaf-strings are considered to be the content of a file. Use nil
|
102
|
+
# to indicate an empty file.
|
103
|
+
def self.yaml_file(path, options={})
|
104
|
+
from_hash(YAML.load_file(path), options.merge(:source => template_path))
|
105
|
+
end
|
106
|
+
|
107
|
+
# Meta information can be used by processors. There's no requirements on them, except
|
108
|
+
# that the toplevel container is a hash.
|
109
|
+
attr_reader :meta
|
110
|
+
|
111
|
+
# @return [Array] All directories of the template
|
112
|
+
attr_reader :directories
|
113
|
+
|
114
|
+
# @return [Hash<String,String>] All files of the template and their content.
|
115
|
+
attr_reader :files
|
116
|
+
|
117
|
+
# @return [Array<DirectoryTemplate::Processor>] The content processors used by this template.
|
118
|
+
attr_reader :processors
|
119
|
+
|
120
|
+
# @return [#call] The path processor used by this template.
|
121
|
+
attr_reader :path_processor
|
122
|
+
|
123
|
+
# @return [IO, #puts] The object on which info and debug messages are printed
|
124
|
+
attr_reader :out
|
125
|
+
|
126
|
+
# @private
|
127
|
+
# @return [Boolean] Whether the current run is a dry-run or not.
|
128
|
+
attr_reader :dry_run
|
129
|
+
|
130
|
+
# When true, will not even output info messages
|
131
|
+
attr_accessor :silent
|
132
|
+
|
133
|
+
# When true, will additionally output debug messages
|
134
|
+
attr_accessor :verbose
|
135
|
+
|
136
|
+
# Create a new DirectoryTemplate
|
137
|
+
#
|
138
|
+
# @param [Hash] data
|
139
|
+
# A hash with the two keys :directories and :files, where :directories contains an
|
140
|
+
# Array of all directory names, and :files contains a Hash of all file names and their
|
141
|
+
# unprocessed content.
|
142
|
+
#
|
143
|
+
# @param [Hash, nil] options
|
144
|
+
# An options hash, @see DirectoryTemplate::DefaultOptions for a list of all available
|
145
|
+
# options.
|
146
|
+
#
|
147
|
+
# @see DirectoryTemplate::directory
|
148
|
+
# To create a DirectoryTemplate from an existing directory structure.
|
149
|
+
# @see DirectoryTemplate::from_yaml
|
150
|
+
# To create a DirectoryTemplate from a description in a YAML file.
|
151
|
+
def initialize(data, options=nil)
|
152
|
+
options = options ? DefaultOptions.merge(options) : DefaultOptions.dup
|
153
|
+
@directories = data[:directories] || []
|
154
|
+
@files = data[:files] || []
|
155
|
+
@source = options.delete(:source)
|
156
|
+
@meta = options.delete(:meta)
|
157
|
+
@verbose = options.delete(:verbose)
|
158
|
+
@silent = options.delete(:silent)
|
159
|
+
@out = options.delete(:out)
|
160
|
+
@processors = options.delete(:processors)
|
161
|
+
@path_processor = options.delete(:path_processor)
|
162
|
+
@dry_run = false
|
163
|
+
raise ArgumentError, "Unknown options: #{options.keys.join(', ')}" unless options.empty?
|
164
|
+
end
|
165
|
+
|
166
|
+
# Same as #materialize, but doesn't actually do anything, except print the debug and
|
167
|
+
# info messages. It additionally prints an info message, containing the file content
|
168
|
+
# of files that would be created.
|
169
|
+
def dry_run(in_path='.', env={}, &on_collision)
|
170
|
+
@dry_run = true
|
171
|
+
materialize(in_path, env, &on_collision)
|
172
|
+
ensure
|
173
|
+
@dry_run = false
|
174
|
+
end
|
175
|
+
|
176
|
+
# Creates all the directories and files from the template in the given path.
|
177
|
+
#
|
178
|
+
# @param [String] in_path
|
179
|
+
# The directory within which to generate the structure.
|
180
|
+
#
|
181
|
+
# @param [Hash] env
|
182
|
+
# A hash with various information used to generate the structure. Most important one
|
183
|
+
# being the :variables value.
|
184
|
+
# @option env [Hash] :variables
|
185
|
+
# A hash with variables available to both, path and content processing
|
186
|
+
# @option env [Hash] :path_variables
|
187
|
+
# A hash with variables available to path processing
|
188
|
+
# @option env [Hash] :content_variables
|
189
|
+
# A hash with variables available to content processing
|
190
|
+
#
|
191
|
+
# @see #dry_run For a way to see what would happen with materialize
|
192
|
+
def materialize(in_path='.', env={}, &on_collision)
|
193
|
+
in_path = File.expand_path(in_path)
|
194
|
+
create_directory(in_path) { "Creating root '#{in_path}'" }
|
195
|
+
|
196
|
+
Dir.chdir(in_path) do
|
197
|
+
if @directories.empty? then
|
198
|
+
info { "No directories to create" }
|
199
|
+
else
|
200
|
+
info { "Creating directories" }
|
201
|
+
@directories.each do |source_dir_path|
|
202
|
+
target_dir_path = process_path(source_dir_path, env)
|
203
|
+
create_directory(target_dir_path) { " #{target_dir_path}" }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
if @files.empty? then
|
208
|
+
info { "No files to create" }
|
209
|
+
else
|
210
|
+
info { "Creating files" }
|
211
|
+
@files.each do |source_file_path, content|
|
212
|
+
target_file_path = process_path(source_file_path, env)
|
213
|
+
data = process_content(target_file_path, content, env)
|
214
|
+
if File.exist?(data.path) then
|
215
|
+
if block_given? && yield(data) then
|
216
|
+
create_file(data.path, data.content) { " #{data.path} (overwrite)" }
|
217
|
+
else
|
218
|
+
info { " #{data.path} (exists already)" }
|
219
|
+
end
|
220
|
+
else
|
221
|
+
create_file(data.path, data.content) { " #{data.path} (new)" }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
self
|
228
|
+
end
|
229
|
+
|
230
|
+
# @private
|
231
|
+
# Preprocesses the given path
|
232
|
+
def process_path(path, env)
|
233
|
+
@path_processor.call(ProcessData.new(self, path, nil, env)).path
|
234
|
+
end
|
235
|
+
|
236
|
+
# @private
|
237
|
+
# Preprocesses the given content
|
238
|
+
def process_content(path, content, env)
|
239
|
+
data = ProcessData.new(self, path, content, env)
|
240
|
+
catch(:stop_processing) {
|
241
|
+
#p :process_content => path, :available => @processors.size, :processors => processors_for(data).tap { |x| x && x.size }
|
242
|
+
while processor = processor_for(data)
|
243
|
+
debug { " -> Applying #{processor.name}" }
|
244
|
+
processor.call(data)
|
245
|
+
end
|
246
|
+
}
|
247
|
+
|
248
|
+
data
|
249
|
+
end
|
250
|
+
|
251
|
+
# @private
|
252
|
+
# Create the given directory and emit an info message (unless in dry_run mode).
|
253
|
+
#
|
254
|
+
# @note The mode param is currently unused.
|
255
|
+
def create_directory(path, mode=0755, &message)
|
256
|
+
unless File.exist?(path) then
|
257
|
+
info(&message)
|
258
|
+
FileUtils.mkdir_p(path) unless @dry_run
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# @private
|
263
|
+
# Create the given file and emit an info message (unless in dry_run mode).
|
264
|
+
#
|
265
|
+
# @note The mode param is currently unused.
|
266
|
+
def create_file(path, content="", mode=0644, &message)
|
267
|
+
info(&message)
|
268
|
+
if @dry_run then
|
269
|
+
info { " Content:\n#{content.gsub(/^/, ' ')}" }
|
270
|
+
else
|
271
|
+
File.open(path, 'wb:binary') do |fh|
|
272
|
+
fh.write(content)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# @private
|
278
|
+
# @param [ProcessData] data
|
279
|
+
# The data which the processor should apply to.
|
280
|
+
#
|
281
|
+
# @return [Processor, nil]
|
282
|
+
# Returns the processor or nil
|
283
|
+
def processor_for(data)
|
284
|
+
@processors.find { |processor| processor === data }
|
285
|
+
end
|
286
|
+
|
287
|
+
# @private
|
288
|
+
# Emit an info string (the return value of the block). Will not be emitted if
|
289
|
+
# DirectoryTemplate#silent is true
|
290
|
+
def info
|
291
|
+
@out.puts yield unless @silent
|
292
|
+
end
|
293
|
+
|
294
|
+
# @private
|
295
|
+
# Emit a debug string (the return value of the block). Will only be emitted if
|
296
|
+
# DirectoryTemplate#debug is true
|
297
|
+
def debug
|
298
|
+
@out.puts yield if @verbose
|
299
|
+
end
|
300
|
+
|
301
|
+
# @private
|
302
|
+
# See Object#inspect
|
303
|
+
def inspect
|
304
|
+
sprintf "#<%s:0x%x source=%p>", self.class, object_id<<1, @source
|
305
|
+
end
|
306
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: directory_template
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Stefan Rusterholz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-03 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! 'Create directories from templates.
|
15
|
+
|
16
|
+
Existing directory structures, yaml files and ruby datastructures can all serve
|
17
|
+
as
|
18
|
+
|
19
|
+
sources of a template.
|
20
|
+
|
21
|
+
Can preprocess pathnames and content.
|
22
|
+
|
23
|
+
Path- and ContentProcessors are exchangeable.'
|
24
|
+
email: stefan.rusterholz@gmail.com
|
25
|
+
executables: []
|
26
|
+
extensions: []
|
27
|
+
extra_rdoc_files: []
|
28
|
+
files:
|
29
|
+
- lib/directory_template/blank_slate.rb
|
30
|
+
- lib/directory_template/erb_template.rb
|
31
|
+
- lib/directory_template/process_data.rb
|
32
|
+
- lib/directory_template/processor/erb.rb
|
33
|
+
- lib/directory_template/processor/format.rb
|
34
|
+
- lib/directory_template/processor/markdown.rb
|
35
|
+
- lib/directory_template/processor/stop.rb
|
36
|
+
- lib/directory_template/processor.rb
|
37
|
+
- lib/directory_template/version.rb
|
38
|
+
- lib/directory_template.rb
|
39
|
+
- examples/dir_gem_template/%{gem_name}/%{gem_name}.gemspec.stop.erb
|
40
|
+
- examples/dir_gem_template/%{gem_name}/lib/%{require_name}/version.rb.stop.erb
|
41
|
+
- examples/dir_gem_template/%{gem_name}/lib/%{require_name}.rb.stop.erb
|
42
|
+
- examples/dir_gem_template/%{gem_name}/Rakefile.stop
|
43
|
+
- examples/dir_gem_template/%{gem_name}/README.markdown.stop.erb
|
44
|
+
- examples/dir_gem_template/%{gem_name}/README_DIRECTORIES.stop
|
45
|
+
- examples/yaml_gem_template.yaml
|
46
|
+
- documentation/ContentProcessors.markdown
|
47
|
+
- documentation/PathProcessors.markdown
|
48
|
+
- directory_template.gemspec
|
49
|
+
- Rakefile
|
50
|
+
- README.markdown
|
51
|
+
homepage: https://github.com/apeiros/directory_template
|
52
|
+
licenses: []
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>'
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.3.1
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.8.24
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Create directories from templates, optionally preprocessing paths and contents.
|
75
|
+
test_files: []
|
76
|
+
has_rdoc:
|