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.
@@ -0,0 +1,239 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ require 'erb'
6
+ require 'directory_template/blank_slate'
7
+
8
+
9
+
10
+ class DirectoryTemplate
11
+
12
+ # @author Stefan Rusterholz <stefan.rusterholz@gmail.com>
13
+ #
14
+ # A helper class for ERB. Allows constructs like the one in the examples to
15
+ # enable simple use of variables/methods in templates.
16
+ #
17
+ # @example A simple ERB template being rendered
18
+ # tmpl = DirectoryTemplate::ErbTemplate.new("Hello <%= name %>!")
19
+ # tmpl.result(self, :name => 'world') # => 'Hello World!'
20
+ #
21
+ class ErbTemplate
22
+
23
+ # @author Stefan Rusterholz <stefan.rusterholz@gmail.com>
24
+ #
25
+ # Variables is similar to OpenStruct, but slightly optimized for create once, use once
26
+ # and giving diagnostics on exceptions/missing keys.
27
+ #
28
+ # @example
29
+ # variables = Variables.new(delegator, :x => "Value of X") { |exception|
30
+ # do_something_with(exception)
31
+ # }
32
+ # variables.x # => "Value of X"
33
+ #
34
+ class Variables < BlankSlate
35
+
36
+ # A proc for &on_error in DirectoryTemplate::ErbTemplate::Variables::new or
37
+ # DirectoryTemplate::ErbTemplate#result.
38
+ # Raises the error further on.
39
+ Raiser = proc { |e|
40
+ raise(e)
41
+ }
42
+
43
+ # A proc for &on_error in DirectoryTemplate::ErbTemplate::Variables.new or
44
+ # DirectoryTemplate::ErbTemplate#result.
45
+ # Inserts <<error_class: error_message>> in the place where the error
46
+ # occurred.
47
+ Teller = proc { |e|
48
+ "<<#{e.class}: #{e}>>"
49
+ }
50
+
51
+ # An empty Hash
52
+ EmptyHash = {}.freeze
53
+
54
+ # Regex to match setter method names
55
+ SetterPattern = /=\z/.freeze
56
+
57
+ # @param [Object] delegate
58
+ # All method calls and undefined variables are delegated to this object as method
59
+ # call.
60
+ # @param [Hash<Symbol,Object>] variables
61
+ # A hash with variables in it, keys must be Symbols.
62
+ # @param [Symbol] on_error_name
63
+ # Instead of a block you can pass the name of an existing handler, e.g. :Raiser
64
+ # or :Teller.
65
+ #
66
+ # @yield [exception] description
67
+ # The block is yielded in case of an exception with the exception as argument.
68
+ #
69
+ def initialize(delegate=nil, variables={}, on_error_name=nil, &on_error)
70
+ @delegate = delegate
71
+ @table = (@delegate ? Hash.new { |h,k| @delegate.send(k) } : EmptyHash).merge(variables)
72
+ if !on_error && on_error_name then
73
+ @on_error = self.class.const_get(on_error_name)
74
+ else
75
+ @on_error = on_error || Raiser
76
+ end
77
+ end
78
+
79
+ # @return [Array<Symbol>]
80
+ # All keys this Variables instance provides, if the include_delegate argument is
81
+ # true and the object to delegate to responds to __keys__, then it will add the
82
+ # keys of the delegate.
83
+ def __keys__(include_delegate=true)
84
+ @table.keys + ((include_delegate && @delegate.respond_to?(:__keys__)) ? @delegate.__keys__ : [])
85
+ end
86
+
87
+ # @return [Binding] Make the binding publicly available
88
+ def __binding__
89
+ binding
90
+ end
91
+
92
+ # @private
93
+ # @see Object#respond_to_missing?
94
+ def respond_to_missing?(key)
95
+ @table.respond_to?(key) || (@delegate && @delegate.respond_to?(key))
96
+ end
97
+
98
+ # @private
99
+ # Set or get the value associated with the key matching the method name.
100
+ def method_missing(m, *args) # :nodoc:
101
+ argn = args.length
102
+ if argn.zero? && @table.has_key?(m) then
103
+ @table[m]
104
+ elsif argn == 1 && m.to_s =~ SetterPattern
105
+ @table[m] = args.first
106
+ elsif @delegate
107
+ @delegate.send(m, *args)
108
+ end
109
+ rescue => e
110
+ @on_error.call(e)
111
+ end
112
+
113
+ # @private
114
+ # See Object#inspect
115
+ def inspect # :nodoc:
116
+ sprintf "#<%s:0x%08x @delegate=%s %s>",
117
+ self.class,
118
+ __id__,
119
+ @table.map { |k,v| "#{k}=#{v.inspect}" }.join(', '),
120
+ @delegate ? "#<%s:0x%08x ...>" % [@delegate.class, @delegate.object_id << 1] : "nil"
121
+ end
122
+ end
123
+
124
+ # Option defaults
125
+ Opt = {
126
+ :safe_level => nil,
127
+ :trim_mode => '%<>',
128
+ :eoutvar => '_erbout'
129
+ }
130
+
131
+ # An UnboundMethod instance of instance_eval
132
+ InstanceEvaler = Object.instance_method(:instance_eval)
133
+
134
+
135
+ # A proc for &on_error in DirectoryTemplate::ErbTemplate::Variables::new or DirectoryTemplate::ErbTemplate#result.
136
+ # Raises the error further on.
137
+ Raiser = proc { |e|
138
+ raise
139
+ }
140
+
141
+ # A proc for &on_error in DirectoryTemplate::ErbTemplate::Variables::new or DirectoryTemplate::ErbTemplate#result.
142
+ # Inserts <<error_class: error_message>> in the place where the error
143
+ # occurred.
144
+ Teller = proc { |e|
145
+ "<<#{e.class}: #{e}>>"
146
+ }
147
+
148
+ # The template string
149
+ attr_reader :string
150
+
151
+ # Like ErbTemplate.new, but instead of a template string, the path to the file
152
+ # containing the template. Sets :filename.
153
+ #
154
+ # @param [String] path
155
+ # The path to the file to use as a template
156
+ #
157
+ # @param [Hash] options
158
+ # See ErbTemplate::new for the options
159
+ def self.file(path, options=nil)
160
+ options = options ? options.merge(:filename => path) : {:filename => path}
161
+
162
+ new(File.read(path), options)
163
+ end
164
+
165
+ # @param [String] string
166
+ # The template string
167
+ # @param [Hash] options
168
+ # A couple of options
169
+ #
170
+ # @option options [String] :filename
171
+ # The filename used for the evaluation (useful for error messages)
172
+ # @option options [Integer] :safe_level
173
+ # See ERB.new
174
+ # @option options [String] :trim_mode
175
+ # See ERB.new
176
+ # @option options [Symbol, String] :eoutvar
177
+ # See ERB.new
178
+ def initialize(string, options={})
179
+ options, string = string, nil if string.kind_of?(Hash)
180
+ options = Opt.merge(options)
181
+ filename = options.delete(:filename)
182
+ raise ArgumentError, "String or filename must be given" unless string || filename
183
+
184
+ @string = string || File.read(filename)
185
+ @erb = ERB.new(@string, *options.values_at(:safe_level, :trim_mode, :eoutvar))
186
+ @erb.filename = filename if filename
187
+ end
188
+
189
+ # @return [String]
190
+ # The evaluated template. Default &on_error is the
191
+ # DirectoryTemplate::ErbTemplate::Raiser proc.
192
+ def result(variables=nil, on_error_name=nil, &on_error)
193
+ variables ||= {}
194
+ on_error ||= Raiser
195
+ variables = Variables.new(nil, variables, on_error_name, &on_error)
196
+ @erb.result(variables.__binding__)
197
+ rescue NameError => e
198
+ raise NameError, e.message+" for #{self.inspect} with #{variables.inspect}", e.backtrace
199
+ end
200
+
201
+ # @param [Hash] options
202
+ # A couple of options
203
+ #
204
+ # @option options [Hash] :variables
205
+ # A hash with all the variables that should be available in the template.
206
+ # @option options [Object] :delegate
207
+ # An object, to which methods should be delegated.
208
+ # @option options [Proc, Symbol, #to_proc] :on_error
209
+ # The callback to use in case of an exception.
210
+ #
211
+ # @return [String]
212
+ # The evaluated template. Default &on_error is the
213
+ # DirectoryTemplate::ErbTemplate::Raiser proc.
214
+ def result_with(options, &block)
215
+ options = options.dup
216
+ variables = options.delete(:variables) || {}
217
+ delegate = options.delete(:delegate)
218
+ on_error = options.delete(:on_error) || block
219
+ if on_error.is_a?(Symbol) then
220
+ on_error_name = on_error
221
+ on_error = nil
222
+ end
223
+ variables = Variables.new(delegate, variables, on_error_name, &on_error)
224
+
225
+ @erb.result(variables.__binding__)
226
+ rescue NameError => e
227
+ raise NameError, e.message+" for #{self.inspect} with #{variables.inspect}", e.backtrace
228
+ end
229
+
230
+ # @private
231
+ # See Object#inspect
232
+ def inspect # :nodoc:
233
+ sprintf "#<%s:0x%x string=%s>",
234
+ self.class,
235
+ object_id << 1,
236
+ @string.inspect
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,94 @@
1
+ class DirectoryTemplate
2
+
3
+ # ProcessData is the value that gets passed to processors.
4
+ # The processor must mutate it.
5
+ class ProcessData
6
+
7
+ # A reference to the DirectoryTemplate of which this file/directory is part of
8
+ attr_reader :directory_template
9
+
10
+ # The path as it is in the current stage of processing.
11
+ attr_reader :path
12
+
13
+ # The filename, as it is in the current stage of processing.
14
+ attr_reader :filename
15
+
16
+ # The directory, as it is in the current stage of processing.
17
+ attr_reader :directory
18
+
19
+ # The suffix, as it is in the current stage of processing.
20
+ attr_reader :suffix
21
+
22
+ # The file-content, as it is in the current stage of processing (nil for directories).
23
+ attr_accessor :content
24
+
25
+ # Variables to be used for path preprocessing
26
+ attr_reader :path_variables
27
+
28
+ # Variables to be used for filecontent preprocessing
29
+ attr_reader :content_variables
30
+
31
+ # Variables to be used for both, path- and filecontent preprocessing
32
+ attr_reader :variables
33
+
34
+
35
+ # A content of nil means this is a directory
36
+ # @param [Hash] env
37
+ # A hash with additional information, used to parametrize and preprocess the template.
38
+ # @options env [Hash<Symbol,String>] :variables
39
+ # Variables used for both, path- and filecontent processing.
40
+ # @options env [Hash<Symbol,String>] :path_variables
41
+ # Variables only used for path-processing.
42
+ # @options env [Hash<Symbol,String>] :content_variables
43
+ # Variables only used for filecontent processing.
44
+ def initialize(directory_template, path, content, env)
45
+ @directory_template = directory_template
46
+ @content = content
47
+ @variables = env[:variables] || {}
48
+ @path_variables = @variables.merge(env[:path_variables] || {})
49
+ @content_variables = @variables.merge(env[:content_variables] || {})
50
+ self.path = path
51
+ end
52
+
53
+ # Whether the processor is suitable for the given ProcessData.
54
+ # Simply delegates the job to the Processor.
55
+ #
56
+ # @see Processor#===
57
+ def ===(processor)
58
+ processor === self
59
+ end
60
+
61
+ # @return [Boolean]
62
+ # Whether the item is a file. The alternative is, that it is a directory.
63
+ #
64
+ # @see #directory?
65
+ def file?
66
+ !!@content
67
+ end
68
+
69
+ # @return [Boolean]
70
+ # Whether the item is a directory. The alternative is, that it is a file.
71
+ #
72
+ # @see #file?
73
+ def directory?
74
+ !@content
75
+ end
76
+
77
+ # @param [String] value
78
+ # The new path
79
+ #
80
+ # Sets the path, filename, directory and suffix of this item.
81
+ def path=(value)
82
+ @path = value
83
+ @filename = File.basename(value)
84
+ @directory = File.dirname(value)
85
+ @suffix = File.extname(value)
86
+ end
87
+
88
+ # Removes the current suffix. E.g. "foo/bar.baz.quuz" would be "foo/bar.baz" after the
89
+ # operation.
90
+ def chomp_suffix!
91
+ self.path = @path.chomp(@suffix)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ require 'directory_template/erb_template'
6
+
7
+
8
+
9
+ class DirectoryTemplate
10
+ class Processor
11
+
12
+ # The ERB Processor treats the file-content as ERB template.
13
+ Erb = Processor.register(:erb, '*.erb', 'ERB Template Processor') do |data|
14
+ data.content = ErbTemplate.new(data.content).result(data.content_variables)
15
+ data.chomp_suffix!
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ class DirectoryTemplate
6
+ class Processor
7
+
8
+ # The standard processor for file- and directory-paths. It simply uses String#% style
9
+ # keyword replacement. I.e., `%{key}` is replaced by the variable value passed with
10
+ # :key.
11
+ Format = Processor.new(:format, nil, '%{variable} format processor') do |data|
12
+ data.path = data.path % data.path_variables if data.path_variables
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ class DirectoryTemplate
6
+ class Processor
7
+
8
+ # The ERB Processor treats the file-content as ERB template.
9
+ Markdown = Processor.register(:markdown_to_html, '*.html.markdown', 'Markdown to HTML Template Processor') do |data|
10
+ Markdown.require 'kramdown'
11
+ data.content = Kramdown::Document.new(data.content).to_html
12
+ data.chomp_suffix!
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ class DirectoryTemplate
6
+ class Processor
7
+
8
+ # The
9
+ Stop = Processor.register(:stop, '*.stop', 'Terminate processing queue', 'After .stop, no processor will be run anymore') do |data|
10
+ data.chomp_suffix!
11
+ throw :stop_processing
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+
3
+ class DirectoryTemplate
4
+
5
+ # The definition of a processor
6
+ #
7
+ # Use {Processor.register} to register a processor. A registered processor is available
8
+ # in all DirectoryTemplate instances.
9
+ #
10
+ # Processor::register takes the same arguments as {Processor#initialize Processor::new}, so look there for how
11
+ # to define a processor.
12
+ #
13
+ # Take a look at {ProcessData} to see what data your processor gets and can work on.
14
+ class Processor
15
+
16
+ # A matcher-proc to never match
17
+ Never = proc { |data| false }
18
+
19
+ # Searches for all processors and registers them
20
+ def self.register_all
21
+ $LOAD_PATH.each do |path|
22
+ Dir.glob(File.join(path, 'directory_template', 'processor', '**', '*.rb')) do |processor|
23
+ require processor
24
+ end
25
+ end
26
+ end
27
+
28
+ # Creates a Processor and registers it.
29
+ # The arguments are passed verbatim to Processor::new.
30
+ #
31
+ # @return [Processor]
32
+ def self.register(*arguments, &block)
33
+ processor = new(*arguments, &block)
34
+ DirectoryTemplate.register(processor)
35
+
36
+ processor
37
+ end
38
+
39
+ # The pattern matching proc used to figure whether the processor applies to a
40
+ # ProcessData or not.
41
+ attr_reader :pattern
42
+
43
+ # The source used to create the pattern proc. I.e., the value passed to ::new as the
44
+ # pattern parameter.
45
+ attr_reader :pattern_source
46
+
47
+ # A human identifiable name
48
+ attr_reader :name
49
+
50
+ # A human understandable description of the processor
51
+ attr_reader :description
52
+
53
+ # The implementation of the processor. I.e., the block passed to ::new.
54
+ attr_reader :execute
55
+
56
+ # @param [Symbol] id
57
+ # A (in the set of Processors) unique id
58
+ #
59
+ # @param [String, Regexp, Proc, #to_proc, nil] pattern
60
+ # The pattern determines upon what {ProcessData} this processor is being invoked.
61
+ #
62
+ # If you provide a String, it is interpreted as a glob-like-pattern, e.g.
63
+ # '*.html.haml' will match any files whose suffix is '.html.haml'.
64
+ # See File::fnmatch for an extensive documentation of glob-patterns.
65
+ #
66
+ # If you provide a Regexp, the filename is matched against that regexp.
67
+ #
68
+ # If you provide a Proc (or anything that is converted to a proc), the proc gets
69
+ # the ProcessData as sole argument and should return true/false, to indicate,
70
+ # whether the Processor should be invoked or not.
71
+ #
72
+ # If you pass nil as pattern, the Processor will never be invoked. This is useful
73
+ # for processors that serve only as path processors.
74
+ #
75
+ # @param [String] name
76
+ # The name of the processor
77
+ #
78
+ # @param [String] description
79
+ # A description, what the processor does
80
+ #
81
+ # @param [#call] execute
82
+ # The implementation of the processor
83
+ def initialize(id, pattern, name=nil, description=nil, &execute)
84
+ raise ArgumentError, "ID must be a Symbol" unless id.is_a?(Symbol)
85
+ @id = id
86
+ @pattern_source = pattern
87
+ @pattern = case pattern
88
+ when String then proc { |data| File.fnmatch?(pattern, data.path) }
89
+ when Regexp then proc { |data| pattern =~ data.path }
90
+ when Proc then pattern
91
+ when nil then Never
92
+ else
93
+ raise ArgumentError, "Expected a String, Regexp or Proc as pattern, but got #{pattern.class}"
94
+ end
95
+ @name = name
96
+ @description = description
97
+ @execute = execute
98
+ end
99
+
100
+ # @return [Boolean]
101
+ # Whether the processor is suitable for the given ProcessData
102
+ def ===(process_data)
103
+ @pattern.call(process_data)
104
+ end
105
+
106
+ # Apply the processor on a ProcessData instance.
107
+ #
108
+ # @param [DirectoryTemplate::ProcessData] process_data
109
+ # The process data to apply this processor on.
110
+ #
111
+ # @return [DirectoryTemplate::ProcessData]
112
+ # The process_data passed to this method.
113
+ def call(process_data)
114
+ @execute.call(process_data)
115
+
116
+ process_data
117
+ end
118
+
119
+ # Invokes Kernel#require, and fails with a custom error message.
120
+ # @see Kernel#require
121
+ def require(lib)
122
+ super(lib)
123
+ rescue LoadError
124
+ raise "The #{@name || @id} processor requires #{lib} in order to work"
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'rubygems/version' # newer rubygems use this
5
+ rescue LoadError
6
+ require 'gem/version' # older rubygems use this
7
+ end
8
+
9
+ class DirectoryTemplate
10
+
11
+ # The version of DirectoryTemplate
12
+ Version = Gem::Version.new("1.0.0")
13
+ end