require 'fileutils' require 'reap/extensions' require 'facets/ziputils' module Reap module Utilities attr_accessor :dryrun attr_accessor :trace attr_accessor :force attr_accessor :verbose def dryrun? ; @dryrun ; end def trace? ; @trace ; end def force? ; @force ; end def verbose? ; @verbose ; end # Internal status report. # Only output if dryrun or trace mode. def status(message) if dryrun? or trace? puts message end end # Shell runner. def sh(cmd) if dryrun? puts cmd true else puts "--> system call: #{cmd}" if trace? system(cmd) end end # Convenient method to get simple console reply. def ask(question, answers=nil) print "#{question}" print " [#{answers}] " if answers until inp = $stdin.gets ; sleep 1 ; end inp.strip end # Ask for a password. (FIXME: only for unix so far) def password(prompt=nil) msg ||= "Enter Password: " inp = '' print "#{prompt} " begin #system "stty -echo" #inp = gets.chomp until inp = $stdin.gets sleep 1 end ensure #system "stty echo" end return inp.chomp end # Delegate access to FileUtils. def fileutils dryrun? ? ::FileUtils::DryRun : ::FileUtils end # Add FileUtils Features ::FileUtils.private_instance_methods(false).each do |meth| next if meth =~ /^fu_/ module_eval %{ def #{meth}(*a,&b) fileutils.#{meth}(*a,&b) end } end # Add FileTest Features ::FileTest.private_instance_methods(false).each do |meth| next if meth =~ /^fu_/ module_eval %{ def #{meth}(*a,&b) FileTest.#{meth}(*a,&b) end } end # Specific. def rm_r(*a) if dryrun? puts "rm_r #{a.join(' ')}" else fileutils.rm_r(*a) end end # Bonus FileUtils features. def cd(*a,&b) puts "cd #{a}" if dryrun? or trace? fileutils.chdir(*a,&b) end # Read file. def read(path) File.read(path) end # Write file. def write(path, text) if dryrun? puts "write #{path}" else File.open(path, 'w'){ |f| f << text } end end # Assert that a path exists. def exists?(path) paths = Dir.glob(path) paths.not_empty? end alias_method :exist?, :exists? ; module_function :exist? alias_method :path?, :exists? ; module_function :path? # Assert that a path exists. def exists!(*paths) abort "path not found #{path}" unless paths.any?{|path| exists?(path)} end alias_method :exist!, :exists! ; module_function :exist! alias_method :path!, :exists! ; module_function :path! # Is a given path a regular file? If +path+ is a glob # then checks to see if all matches are refular files. def file?(path) paths = Dir.glob(path) paths.not_empty? && paths.all?{ |f| FileTest.file?(f) } end # Assert that a given path is a file. def file!(*paths) abort "file not found #{path}" unless paths.any?{|path| file?(path)} end # Is a given path a directory? If +path+ is a glob # checks to see if all matches are directories. def dir?(path) paths = Dir.glob(path) paths.not_empty? && paths.all?{ |f| FileTest.directory?(f) } end alias_method :directory?, :dir? ; module_function :directory? # Assert that a given path is a directory. def dir!(*paths) paths.each do |path| abort "Directory not found: '#{path}'." unless dir?(path) end end alias_method :directory!, :dir! ; module_function :directory! # Return a cached list of the PATH environment variable. # This is a support method used by #bin? def command_paths @command_paths ||= ENV['PATH'].split(/[:;]/) end # Is a file a command executable? # # TODO: Make more robust. Probably needs to be fixed for Windows. def bin?(fname) #@command_paths ||= ENV['PATH'].split(/[:;]/) is_bin = command_paths.any? do |f| FileTest.exist?(File.join(f, fname)) end #is_bin ? File.basename(fname) : false is_bin ? fname : false end # Is a path considered reasonably "safe"? # # TODO: Make more robust. def safe?(path) case path when *[ '/', '/*', '/**/*' ] return false end true end # Does a path need updating, based on given +sources+? # This compares mtimes of give paths. Returns false # if the path needs to be updated. def out_of_date?(path, *sources) return true unless File.exist?(path) sources = sources.collect{ |source| Dir.glob(source) }.flatten mtimes = sources.collect{ |file| File.mtime(file) } return true if mtimes.empty? # TODO: This the way to go here? File.mtime(path) < mtimes.max end # Glob files. def glob(*args, &blk) Dir.glob(*args, &blk) end def multiglob(*args, &blk) Dir.multiglob(*args, &blk) end def multiglob_r(*args, &blk) Dir.multiglob_r(*args, &blk) end # Stage package by hard linking included files to a stage directory. # Stage files in a directory. # # stage_directory Stage directory. # files Files to link to stage. def stage(stage_directory, files) return stage_directory if dryrun? # Don't link to stage if dryrun. if File.directory?(stage_directory) # Ensure existance of staging area. #raise(???Error, stage_directory) unless force? rm_r(stage_directory) end mkdir_p(stage_directory) #dir = File.expand_path(stage) #files = package.filelist #+ [package.manifest_file] # TODO Dryrun test here or before folder creation? files.each do |f| # Link files into staging area. file = File.join(stage_directory, f) if File.directory?(f) mkdir_p(file) else unless File.exist?(file) and File.mtime(file) >= File.mtime(f) ln(f, file) #safe_ln ? end end end # stage manifest ? return stage_directory end # Create manifest for a directory. def stage_manifest(directory) cd(directory) do #sh 'manifest up' files = Dir['**/*'] File.open('MANIFEST', 'w'){|f| f << file.join("\n") } end end # Delegate access to ZipUtils. def ziputils dryrun? ? ::ZipUtils::DryRun : ::ZipUtils end # Zip folder into file. def zip(folder, file=nil, options={}) ziputils.zip(folder, file, options) end # BZip and tarball folder into file. def tar_bzip(folder, file=nil, options={}) ziputils.tar_bzip(folder, file, options) end # GZip and tarball folder into file. Shortcut for ziputils.tgz. def tgz(folder, file=nil, options={}) ziputils.tgz(folder, file, options) end # Email function to easily send out an email. # # Settings: # # subject Subject of email message. # from Message FROM address [email]. # to Email address to send announcemnt. # server Email server to route message. # port Email server's port. # port_secure Email server's port. # domain Email server's domain name. # account Email account name if needed. # password Password for login.. # login Login type: plain, cram_md5 or login [plain]. # secure Uses TLS security, true or false? [false] # message Mesage to send -or- # file File that contains message. def email(message, settings) settings ||= {} settings.rekey server = settings[:server] account = settings[:account] || ENV['EMAIL_ACCOUNT'] passwd = settings[:password] || ENV['EMAIL_PASSWORD'] login = settings[:login] subject = settings[:subject] mail_to = settings[:to] || settings[:mail_to] mail_from = settings[:from] || settings[:mail_from] secure = settings[:secure] domain = settings[:domain] || server port = (settings[:port_secure] || 465) if secure port = (settings[:port] || 25) unless secure account ||= mail_from login ||= :plain login = login.to_sym #mail_to = nil if mail_to.empty? raise ArgumentError, "missing email field -- server" unless server raise ArgumentError, "missing email field -- account" unless account raise ArgumentError, "missing email field -- subject" unless subject raise ArgumentError, "missing email field -- to" unless mail_to raise ArgumentError, "missing email field -- from" unless mail_from passwd ||= password("#{account} password:") mail_to = [mail_to].flatten.compact msg = "" msg << "From: #{mail_from}\n" msg << "To: #{mail_to.join(';')}\n" msg << "Subject: #{subject}\n" msg << "" msg << message if secure Net::SMTP.send(:include, Net::SMTP::TLS) Net::SMTP.enable_tls #if secure #if Net::SMTP.respond_to?(:enable_tls) and secure end begin Net::SMTP.start(server, port, domain, account, passwd, login) do |smtp| smtp.send_message(msg, mail_from, mail_to) end puts "Email sent successfully to #{mail_to.join(';')}." return true rescue => e if trace? raise e else abort "Email delivery failed." end end end end end