require 'yaml' require 'redcarpet' require 'erb' require 'pipeline.rb' class Array def where clone.delete_if { |i| not yield i } end end module GemSmith def reset_gemsmith() $gemsmith = { active: nil, in_path: nil, out_path: nil, assets: {} } end def load_site(in_path, out_path) if File.symlink? in_path then $stderr.puts "Warning: Skipping symlink #{f}" [] elsif File.directory? in_path then full_out_dir = File.join($gemsmith[:out_path], out_path) directory "#{full_out_dir}" => [File.dirname("#{full_out_dir}")] Dir["#{in_path}/*"].map do |f| out_f = File.join(out_path, File.basename(f)) load_site(f, out_f) end.flatten(1) else [{ modified: File.mtime(in_path), in_path: in_path, dependencies: [in_path, File.dirname(File.join($gemsmith[:out_path], out_path))], out_path: out_path, rel_dir: File.dirname(out_path), stream: Pipeline.new(in_path), title: File.basename(out_path, ".*").gsub(/[_-]/, " ").capitalize }] end end def for_files(pattern) tmp = $gemsmith[:active] $gemsmith[:active] = $gemsmith[:active].where {|f| pattern =~ f[:out_path] } yield $gemsmith[:active] = tmp end def exclude_files(pattern) tmp = $gemsmith[:active] $gemsmith[:active] = $gemsmith[:active].where {|f| not pattern =~ f[:out_path] } yield $gemsmith[:active] = tmp end def site(site_name, params = {}) reset_gemsmith() in_path = params.fetch(:src, "src") out_path = params.fetch(:out, File.join("build", site_name.to_s)) $gemsmith[:in_path] = in_path $gemsmith[:out_path] = out_path $gemsmith[:active] = load_site(in_path, "") directory out_path task site_name => [ in_path, out_path ] yield $gemsmith[:active].each { |f| file File.join(out_path, f[:out_path]) => f[:dependencies] do error = nil; File.open(File.join(out_path, f[:out_path]), "w+") { |out| begin f[:stream].pipe_to(out) rescue => e error = e end } unless error.nil? File.unlink(File.join(out_path, f[:out_path])) raise error end end task site_name => File.join(out_path, f[:out_path]) } end def activate(op) raise "Using apply outside of a site block" if $gemsmith[:active].nil? $gemsmith[:active].each { |f| op.call(f) } end def extract_headers lambda { |f| yaml = ["---\n"] pos = nil f[:stream].consume { |fp| if /^---$/ =~ fp.readline then l = fp.readline until /^---$/ =~ l do yaml.push l l = fp.readline end pos = fp.pos end } unless pos.nil? begin f.merge!(YAML.load(yaml.join)) rescue => e raise "Error parsing YAML ('#{f[:in_path]}'):\n#{e}\n#{yaml.join}---" end f[:stream].skip_to(pos) end } end def render_markdown(renderer = Redcarpet::Render::HTML.new()) markdown = Redcarpet::Markdown.new(renderer) lambda { |f| f[:out_path] = File.join(File.dirname(f[:out_path]), "#{File.basename(f[:out_path], ".*")}.html") f[:stream].transform_all { |body| markdown.render(body) } } end def render_erb lambda { |f| f[:out_path] = File.join(File.dirname(f[:out_path]), "#{File.basename(f[:out_path], ".*")}.html") f[:stream].transform_all { |body| b = binding() f.each { |k,v| b.local_variable_set(k, v) } $gemsmith[:now_rendering] = File.join(f[:rel_dir], File.basename(f[:out_path])) ERB.new(body).result(b) } } end def apply_template(template) erb = ERB.new(File.open(template) { |fp| fp.read }) lambda { |f| f[:dependencies].push(template) f[:stream].transform_all { |body| b = binding() f.each { |k,v| b.local_variable_set(k, v) } $gemsmith[:now_rendering] = File.join(f[:rel_dir], File.basename(f[:out_path])) erb.result(b) } } end def add_dependency(dep) $gemsmith[:active].each { |f| f[:dependencies].push dep } end def add_assets(assets, params = {}) in_dir = params.fetch(:source, "assets") out_dir = params.fetch(:dir, "assets") assets.each { |a| $gemsmith[:active] += load_site(File.join(in_dir, a), File.join(out_dir, a)) $gemsmith[:assets][a] = File.join(out_dir, a) } end def add_file(in_path, out_path) $gemsmith[:active].push({ dependencies: [in_path, File.join($gemsmith[:out_path], File.dirname(out_path))], out_path: out_path, in_path: in_path, rel_dir: File.dirname(out_path), stream: Pipeline.new(in_path) }) end def add_dir(in_path, out_path) if File.directory? in_path directory File.join($gemsmith[:out_path], out_path) => File.join($gemsmith[:out_path], File.dirname(out_path)) Dir["#{in_path}/*"].each { |f| add_dir(f, File.join(out_path, File.basename(f))) } else add_file(in_path, out_path) end end def add_file_string(out_path, data) $gemsmith[:active].push({ dependencies: [], out_path: out_path, rel_dir: File.dirname(out_path), stream: Pipeline.new(string: data) }) end def root_path(file = nil) context = $gemsmith[:now_rendering].split(/#{File::SEPARATOR}/) context.pop context.shift if context[0] == "" return file if context.empty? ret = File.join( *([".."] * context.length)) return File.join(ret, file) unless file.nil? return ret end def asset_path(asset) target = $gemsmith[:assets][asset] return "???" if target.nil? root_path(target) end def all_outputs $gemsmith[:active].map { |x| x[:out_path] } end def metadata_for(path) $gemsmith[:active].find { |x| x[:out_path] == path } end end