directory_template 1.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.
- 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:
|