require 'yaml' require 'redcarpet' require 'erb' require 'pipeline.rb' require 'date' require 'image' 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, static: [], collections: {}, 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]) } unless $gemsmith[:static].empty? task site_name => "#{site_name}_static" static_assets = $gemsmith[:static] target_dirs = static_assets.map { |a,b| File.join(out_path, b) } target_dirs.each { |d| directory d } target_dirs.push out_path task "#{site_name}_static" => target_dirs do static_assets.each { |in_path, out_in_dir| system("rsync -a #{in_path}/* #{File.join(out_path, out_in_dir)}") } end end end def apply raise "Applying effects to pages outside of a site block" if $gemsmith[:active].nil? $gemsmith[:active].each { |f| yield f } end def extract_date_from_filename apply { |f| d = /(\d\d\d\d)-(\d\d?)-(\d\d?)/.match File.basename(f[:in_path]) raise "Expecting date in the name of '#{File.basename f[:in_path]}'" if d.nil? f[:date] = Time.new(d[1].to_i, d[2].to_i, d[3].to_i) } end def create_collection(name) curr = nil collection = [] $gemsmith[:active].each { |f| f["#{name}_prev".to_sym] = curr f["#{name}_next".to_sym] = nil curr["#{name}_next".to_sym] = f unless curr.nil? curr = f collection.push(f) } $gemsmith[:collections][name] = collection end def get_collection(name) $gemsmith[:collections][name] end def extract_headers apply { |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 fields = YAML.load(yaml.join) if fields.has_key? "dependencies" fields[:dependencies] = f.fetch(:dependencies, []) + fields["dependencies"] end f.merge!(fields) 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(), options = {}) markdown = Redcarpet::Markdown.new(renderer, options) apply { |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 apply { |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(default_template) erb = Hash.new { |h,k| h[k] = ERB.new(File.open(k) { |fp| fp.read }) } apply { |f| template = f.fetch(:template){ f.fetch("template", default_template) } if template != "None" 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[template].result(b) } end } 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_static(in_path, out_path) $gemsmith[:static].push([in_path,out_path]) 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