# #-- # Sketches - A Ruby library for live programming and code reloading. # # Copyright (c) 2009 Hal Brodigan (postmodern.mod3 at gmail.com) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software #Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #++ # require 'sketches/exceptions/editor_not_defined' require 'sketches/config' require 'sketches/temp_sketch' require 'thread' require 'fileutils' module Sketches class Sketch # ID of the sketch attr_reader :id # Optional name of the sketch attr_accessor :name # Last modification time of the sketch attr_reader :mtime # # Creates a new sketch object with the specified _id_ and the given # _options_. # # _options_ may contain the following keys: # :name:: The name of the sketch. # :path:: The path to an existing sketch. # def initialize(id,options={}) @id = id @name = options[:name] @mtime = Time.now @checksum = 0 @mutex = Mutex.new if options[:path] @path = options[:path] @name ||= File.basename(@path) else TempSketch.open { |file| @path = file.path } end reload! end # # Provides thread-safe access to the sketch. # def synchronize(&block) @mutex.synchronize(&block) return nil end # # Spawns the Sketches.editor with the path of the sketch. # def edit if Config.editor.nil? raise(EditorNotDefined,"no editor has defined via ENV['EDITOR'] or Sketches.config",caller) elsif Config.editor.kind_of?(Proc) cmd = Config.editor.call(@path) else cmd = "#{Config.editor} #{@path}" end system(cmd) end # # Returns +true+ if the sketch has become stale and needs reloading, # returns +false+ otherwise. # def stale? if File.file?(@path) if File.mtime(@path) > @mtime return crc32 != @checksum end end return false end # # Reloads the sketch. # def reload! if File.file?(@path) @mtime = File.mtime(@path) @checksum = crc32 begin return load(@path) rescue LoadError => e STDERR.puts "#{e.class}: #{e.message}" end end return false end # # Saves the sketch to the specified _path_. # def save(path) if (File.file?(@path) && @path != path) FileUtils.cp(@path,path) return true end return false end # # Returns the String representation of the sketch. # def to_s(verbose=false) str = "##{@id}" str << ": #{@name}" if @name str << "\n\n" if File.file?(@path) File.open(@path) do |file| unless verbose 4.times do if file.eof? str << "\n" unless str[-1..-1] == "\n" str << " ..." break end str << " #{file.readline}" end else file.each_line { |line| str << " #{line}" } end str << "\n" end end return str end protected # # Returns the CRC32 checksum of the sketch file. # def crc32 r = 0xffffffff File.open(@path) do |file| file.each_byte do |b| r ^= b 8.times { r = ((r >> 1) ^ (0xEDB88320 * (r & 1))) } end end return r ^ 0xffffffff end end end