Class: Ramaze::Asset::FileGroup

Inherits:
Object
  • Object
show all
Defined in:
lib/ramaze/asset/file_group.rb

Overview

Ramaze::Asset::FileGroup is used to group a set of files of the same type, such as Javascript files, together. The HTML for these files can be generated as well as a minified version of all the files.

## Creating File Groups

Ramaze::Asset comes with two file groups that are capable of processing Javascript and CSS files. If you need to have a file group for Coffeescript, Less or other files is quite easy to add these yourself. First you must create a class that extends Ramaze::Asset::FileGroup:

class Less < Ramaze::Asset::FileGroup

end

It's important that you define the correct file extensions for your file group. Without this Ramaze::Asset will not be able to find the files for you (unless they have an extension specified) and the minified file will end up not having an extension. Setting an extension can be done by calling the class method "extension()". This method has two parameters, the first one is the extension of the source file, the second the extension for the minified file. In case of Less this would result in the following:

class Less < Ramaze::Asset::FileGroup
  extension '.less', '.css'
end

The next step is to define your own minify() and html_tag() methods. If you don't define these methods they'll raise an error and most likely break things.

class Less < Ramaze::Asset::FileGroup
  extension '.less', '.css'

  def minify(input)

  end

  def html_tag(gestalt, path)

  end
end

The minify() method should return a string containing the minified data. The html_tag() method uses an instance of Ramaze::Gestalt to build a single tag for a given (relative) path.

A full example of Less looks like the following:

require 'tempfile'

class Less < Ramaze::Asset::FileGroup
  extension '.less', '.css'

  # +input+ contains the raw Less data. The command line tool only
  # accepts files so this data has to be written to a temp file.
  def minify(input)
    file = Tempfile.new('less')
    file.write(input)
    file.rewind

    minified = `lessc #{file.path} -x`

    file.close(true)

    return minified
  end

  def html_tag(gestalt, path)
    gestalt.link(
      :rel  => 'stylesheet',
      :href => path,
      :type => 'text/css'
    )
  end
end

Note that it's important to remember that when dealing with files that have to be compiled, such as Less and Coffeescript files, setting :minify to false will not work. Without setting this option to true the minify() method will never be called and thus the raw Less/Coffeescript file would be served.

Author:

Since:

Direct Known Subclasses

CSS, Javascript

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (FileGroup) initialize(files, options = {})

Creates a new instance of the file group and prepares it.

Options Hash (options):

  • :minify (Object)

    When set to true all the files in the group will be minified.

  • :name (Object)

    A name to use for the minified file. By default this is set to a hash of all the file names.

  • :paths (Object)

    An array of file paths to look for the files.

  • :cache_path (Object)

    The path to a directory where the minified files should be saved.

Author:

  • Yorick Peterse

Since:

  • 0.1



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/ramaze/asset/file_group.rb', line 153

def initialize(files, options = {})
  @minified = false
  @files    = files
  @options  = {
    :minify     => false,
    :name       => nil,
    :paths      => [],
    :cache_path => []
  }.merge(options)

  if @options[:paths].empty?
    raise(
      Ramaze::Asset::AssetError,
      'No public directories were specified'
    )
  end

  if !File.directory?(@options[:cache_path])
    raise(
      Ramaze::Asset::AssetError,
      "The directory #{@options[:cache_path]} does not exist"
    )
  end

  if extension.nil?
    raise(
      Ramaze::Asset::AssetError,
      'You need to specify an extension'
    )
  end

  prepare_files

  # When :minify is set :name should also be set.
  if @options[:minify] === true and @options[:name].nil?
    @options[:name] = @files.map { |file| file }.join()
    @options[:name] = Digest::SHA1.new.hexdigest(@options[:name])
  end

  if !@options[:name].nil?
    @options[:name] += extension[:minified]
  end
end

Instance Attribute Details

- (Object) files

Array containing all the files that belong to this group, including their files extensions.

Since:

  • 0.1



99
100
101
# File 'lib/ramaze/asset/file_group.rb', line 99

def files
  @files
end

- (Object) options (readonly)

Hash containing all the options for the file group.

Since:

  • 0.1



102
103
104
# File 'lib/ramaze/asset/file_group.rb', line 102

def options
  @options
end

Class Method Details

+ (Object) extension(source_ext, minified_ext = nil)

Sets the file extensions for the current class. These extensions should start with a dot. If the minified extension is not specified it will be generated by adding a ".min" suffix followed by the source extension. If the source extension is ".css" the minified extension would in this case be ".min.css".

Author:

  • Yorick Peterse

Since:

  • 0.1



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/ramaze/asset/file_group.rb', line 119

def self.extension(source_ext, minified_ext = nil)
  if minified_ext.nil?
    minified_ext = '.min' + source_ext
  end

  if source_ext[0] != '.' or minified_ext[0] != '.'
    raise(
      Ramaze::Asset::AssetError,
      'Extensions should start with a dot'
    )
  end

  self.instance_variable_set(
    :@extension,
    {:source => source_ext, :minified => minified_ext}
  )
end

Instance Method Details

- (Object) build

When the :minify option is set to true this method will merge all files, minify them and cache them in the :cache_path directory.

Author:

  • Yorick Peterse

Since:

  • 0.1



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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/ramaze/asset/file_group.rb', line 215

def build
  return if @options[:minify] != true

  cache_path = File.join(
    @options[:cache_path],
    @options[:name]
  )

  # Minify the file in a sub process so that memory leaks (or just general
  # increases of memory usage) don't affect the master process.
  pid = Process.fork do
    processed  = []
    file_paths = []
    minified   = ''
    write      = true

    # Try to find the paths to the files.
    @options[:paths].each do |directory|
      @files.each do |file|
        path = File.join(directory, file)

        # Only add the file to the list if it hasn't already been added.
        if File.exist?(path) and !processed.include?(file)
          file_paths.push(path)
          processed.push(file)
        end
      end
    end

    file_paths.each do |file|
      minified += minify(File.read(file, File.size(file)))
    end

    # Check if the file already exists. If this is the cache a hash of
    # both files is generated and compared. If it's different the file has
    # to be re-created.
    if File.exist?(cache_path)
      old_hash = Digest::SHA1.new.hexdigest(minified)
      new_hash = Digest::SHA1.new.hexdigest(
        File.read(cache_path, File.size(cache_path))
      )

      if old_hash === new_hash
        write = false
      end
    end

    if write === true
      File.open(cache_path, 'w') do |handle|
        handle.write(minified)
        handle.close
      end
    end

    # Don't call any at_exit() hooks, they're not needed in this process.
    Kernel.exit!
  end

  Process.waitpid(pid)

  # Make sure the cache file is present
  if !File.size?(cache_path)
    raise(
      Ramaze::Asset::AssetError,
      "The cache file #{cache_path} could not be created"
    )
  end

  @minified = true
end

- (String) build_html

Builds the HTML tags for all the current files.

Author:

  • Yorick Peterse

Since:

  • 0.1



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
319
320
321
322
323
324
# File 'lib/ramaze/asset/file_group.rb', line 293

def build_html
  prefix = '/'

  if @options[:minify] === true and @minified === true
    # Get the relative path to the cache directory from one of the public
    # directories.
    @options[:paths].each do |path|
      path = @options[:cache_path].gsub(path, '')

      if path != @options[:cache_path] and !path.empty?
        prefix += path
        break
      end
    end

    files = [('/' + @options[:name]).squeeze('/')]
  else
    files = @files
  end

  # Let's make sure the URLs are actually pointing to the cached
  # directory.
  files.each_with_index do |file, index|
    files[index] = "#{prefix}/#{file}".squeeze('/')
  end

  g = Ramaze::Gestalt.new

  files.each { |file| html_tag(g, file) }

  return g.to_s
end

- (String) extension

Returns the extension of the current file group.

Author:

  • Yorick Peterse

Since:

  • 0.1.



204
205
206
# File 'lib/ramaze/asset/file_group.rb', line 204

def extension
  return self.class.instance_variable_get(:@extension)
end

- (Object) html_tag(gestalt, path)

Builds the HTML tag for a single file using Ramaze::Gestalt.

Raises:

  • NotImplementedError Raised when the sub class didn't implement this method.

Author:

  • Yorick Peterse

Since:

  • 0.1



353
354
355
356
357
358
# File 'lib/ramaze/asset/file_group.rb', line 353

def html_tag(gestalt, path)
  raise(
    NotImplementedError,
    'You need to define your own build_html instance method'
  )
end

- (Object) minify(input)

Minifies a single file.

Raises:

  • NotImplementedError Raised when the sub class didn't implement this method.

Author:

  • Yorick Peterse

Since:

  • 0.1



335
336
337
338
339
340
# File 'lib/ramaze/asset/file_group.rb', line 335

def minify(input)
  raise(
    NotImplementedError,
    'You need to define your own minify() instance method'
  )
end