Skip to content

Instantly share code, notes, and snippets.

@dux
Last active August 29, 2015 14:21
Show Gist options
  • Save dux/d898ad776593e571587b to your computer and use it in GitHub Desktop.
Save dux/d898ad776593e571587b to your computer and use it in GitHub Desktop.
Assets parser, raw and uninteligent. parses coffee, less, csss
#!/usr/bin/env ruby
# gem install sass
# npm install -g clean-css
# npm install -g uglify-js
# less, scss, coffee
require 'colorize'
require 'pathname'
require 'json'
require 'listen'
require 'optparse'
class Asset
def r(what)
puts what.red
exit
end
def initialize(file, opts={})
ext = file.split('.').reverse[0].to_sym
unless [:js, :css, :coffee, :less, :scss, :sass, :haml, :coffe].index(ext)
puts "Unsuported file type #{file}"
exit
end
@file = file
@dir = Pathname.new(file).dirname
@opts = opts
@opts[:root] ||= './'
@opts[:cache] ||= '.assets_cache'
Dir.mkdir(@opts[:cache]) unless Dir.exists?(@opts[:cache])
Dir.mkdir('./public/assets') unless Dir.exists?('./public/assets')
puts "Wathing #{file.yellow}"
end
def cache_file(original_file, ext_only=false)
file = original_file.sub(/\.root\./, '.')
file.sub!(/\.(\w+)$/,'')
original_ext = $1.to_sym
ext = [:js, :coffee].index(original_ext) ? :js : :css
return ext if ext_only
file.sub!(/\.\w+$/,'')
"#{@opts[:cache]}/#{file.gsub(/\/+/,'-')}.#{ext}"
end
def run
@time = Time.new
@compile_needed = false
@original_file_names = []
data = File.read(@file)
ret = []
files_to_process = []
lines = data.split("\n")
lines.each_with_index do |line, i|
elms = line.split(/\s+/)
if ['//=','#='].index(elms[0])
directive, source = elms[1], elms[2]
to_load = (@dir + source).to_s
if directive == 'require_tree' || source.index('*')
to_load += '/*' unless to_load.index('*')
files = Dir[to_load.sub(/\/+/,'/')]
exit puts "No files in #{source}" unless files.length > 0
for file in files.sort
files_to_process.push compile(file)
end
else
files_to_process.push compile(to_load)
end
else
ret.push line
end
end
master_file_cache = "#{@opts[:cache]}/#{@file.gsub('/','-')}"
public_file = 'public/assets/'[email protected]('assets/')[1].sub(/\.[^\/]+$/,'').gsub('/','-')+".#{cache_file(@file, :ext)}"
@compile_needed = true if !File.exist?(master_file_cache) || !File.exist?(public_file) || File.mtime(public_file) < File.mtime(@file)
if @compile_needed
data = ret.join("\n")
File.open(master_file_cache, 'w') { |f| f.write(data) }
master_file_compiled = compile(master_file_cache)
public_data = []
@original_file_names.pop
@original_file_names.push @file
public_data.push "/*\n Original files in use\n"+@original_file_names.map{ |el| " - #{el}"}.join("\n") +"\n*/"
for file in files_to_process
public_data.push "\n/* File: #{file.split('/')[1].gsub('-','/')} */\n"+File.read(file)
end
public_data.push File.read master_file_compiled
File.open(public_file, 'w') { |f| f.write(public_data.join("\n\n")) }
puts " compiled to #{public_file.green} in #{speed}"
if @opts[:compress]
if public_file.index('.css')
`cleancss --s0 '#{public_file_location}' -o '#{public_file_location}'`
else
`uglifyjs --screw-ie8 '#{public_file}' > #{public_file}.tmp`
`rm '#{public_file}'`
`mv '#{public_file}.tmp' '#{public_file}'`
end
end
end
public_file
end
def speed
ms = ((Time.new-@time)*1000).ceil
return "#{ms.to_s.yellow} ms" if ms < 1000
ms.to_s.sub(/(\d)(\d{3})$/, '\1.\2').to_s.yellow + ' sec'
end
def compile(file, cache=nil)
@original_file_names.push file
ext = file.split('.').reverse[0].to_sym
cache ||= cache_file(file)
if [:scss, :sass].index(ext)
do_compile = false
do_compile = true unless File.exist?(cache)
if File.exists?("#{cache}.map")
hash = JSON.parse File.read("#{cache}.map")
files = hash['sources'].map{ |el| el.sub(/^\.\./,'.') } + [file]
cache_time = File.mtime(cache)
for el in files
next if do_compile
do_compile = true if !File.exist?(el) || cache_time < File.mtime(el)
end
else
do_compile = true
end
if do_compile
`scss -t compact '#{file}' --cache-location '.assets_cache' '#{cache}'`
@compile_needed = true
end
return cache
end
ext = file.split('.').reverse[0].to_sym
return file if [:js, :css].index(ext)
if !File.exist?(cache) || File.mtime(cache) < File.mtime(file)
@compile_needed = true
case ext
when :less
`lessc '#{file}' > '#{cache}'`
when :haml
`haml '#{file}' > '#{cache}'`
when :coffee
`coffee -p -c '#{file}' > '#{cache}'`;
else
return file
end
end
cache
end
end
options = {}
OptionParser.new do |opts|
opts.banner = "by twitter.com/@dux in 2015\nUsage: assets.rb [options]"
opts.banner += %[\nExamples: (File has to have #{'.root'.red} extension for tool to work)]
opts.banner += "\n assets.rb -f app/assets/admin/index#{'.root'.red}.coffee"
opts.banner += "\n assets.rb -l # live mode"
opts.banner += "\n assets.rb -c # compile all for production"
# opts.on("-h", "--help", "This help") { |v| options[:help] = v }
opts.on("-v", "--version", "Version") { |v| options[:version] = v }
opts.on("-c", "--compress", "Compress JS and CSS files") { |v| options[:compress] = v }
opts.on("-l", "--live", "Live reload root folders") { |v| options[:live] = v }
opts.on("-fFILE", "--file=FILE", "Compile single file") { |v| options[:file] = v }
end.parse!
if options[:version]
puts "0.0.1".yellow
exit
end
if options[:file]
Asset.new(options[:file], options).run
exit
end
files = `find app/assets | grep .root`.split("\n")
Dir['public/assets/*'].map{ |el| File.unlink(el) }
runner = Proc.new do
for file in files
Asset.new(file, options).run
end
end
runner.call
listener = Listen.to('./app/assets') do |modified, added, removed|
file = (modified || added || removed)[0].split('/app/', 2)[1]
puts "\nEvent on #{file.yellow}"
runner.call
end
listener.start
if options[:live]
sleep
else
puts 'Exiting, use -l option for live listening'
end
by twitter.com/@dux in 2015
Usage: assets.rb [options]
Examples: (File has to have .root extension for tool to work)
assets.rb -f app/assets/admin/index.root.coffee
assets.rb -l # live mode
assets.rb -c # compile all for production
-v, --version Version
-c, --compress Compress JS and CSS files
-l, --live Live reload root folders
-f, --file=FILE Compile single file
//= req foo/bar.js
#= req foo/baz.coffee
//= req foo/bar/*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment