lib/bones/main.rb in bones-2.0.3 vs lib/bones/main.rb in bones-2.1.0

- old
+ new

@@ -1,131 +1,190 @@ -# $Id$ require 'fileutils' require 'optparse' require 'erb' module Bones class Main - attr_writer :update - attr_accessor :name, :data, :verbose, :output_dir + attr_reader :options # Create a new instance of Main, and run the +bones+ application given # the command line _args_. # def self.run( args ) bones = self.new - bones.parse args + bones.parse(args) + bones.run + end - if bones.update? then bones.update - else bones.create end + # Create a new main instance using _io_ for standard output and _err_ for + # error messages. + # + def initialize( io = STDOUT, err = STDERR ) + @io = io + @err = err + @options = { + :skeleton_dir => File.join(mrbones_dir, 'data'), + :with_tasks => false, + :verbose => false, + :name => nil, + :output_dir => nil, + :action => nil + } + @options[:skeleton_dir] = ::Bones.path('data') unless test(?d, skeleton_dir) end + # The output directory where files will be written. + # + def output_dir + options[:output_dir] + end + + # The directory where the project skeleton is located. + # + def skeleton_dir + options[:skeleton_dir] + end + + # The project name from the command line. + # + def name + options[:name] + end + + # Returns the project name but converted to be usable as a Ruby class + # name. + # + def classname + name.tr('-','_').split('_').map {|x| x.capitalize}.join + end + + # Returns +true+ if we are updating an already existing project. Returns + # +false+ if we are creating a new project. + # + def update? + test(?e, output_dir) and with_tasks? + end + + # Returns +true+ if we are going to copy the Mr Bones tasks into the + # destination directory. Normally this will return +false+. + # + def with_tasks? + options[:with_tasks] + end + + # Returns +true+ if the user has requested verbose messages. + # + def verbose? + options[:verbose] + end + # Parse the command line arguments and store the values for later use by # the +create+ and +update+ methods. # def parse( args ) - self.data = File.join(mrbones_dir, 'data') - self.data = ::Bones.path('data') unless test(?d, data) - self.update = false - self.verbose = false - opts = OptionParser.new opts.banner << ' project_name' opts.separator '' - opts.on('-u', '--update', - 'update the rake tasks for the project') {self.update = true} - opts.on('-v', '--verbose', - 'enable verbose output') {self.verbose = true} + opts.on('-v', '--verbose', 'enable verbose output') { + options[:verbose] = true + } opts.on('-d', '--directory DIRECTORY', String, - 'project directory to create', - '(defaults to project_name)') {|dir| self.output_dir = dir} + 'project directory to create', '(defaults to project_name)') {|dir| + options[:output_dir] = dir + } opts.on('-s', '--skeleton NAME', String, - 'project skeleton to use') do |name| - path = File.join(mrbones_dir, name) + 'project skeleton to use') do |skeleton_name| + path = File.join(mrbones_dir, skeleton_name) if test(?d, path) - self.data = path - elsif test(?d, name) - self.data = name + options[:skeleton_dir] = path + elsif test(?d, skeleton_name) + options[:skeleton_dir] = skeleton_name else - STDOUT.puts " unknown skeleton '#{name}'" + @io.puts " unknown skeleton '#{skeleton_name}'" exit end end + opts.on('--with-tasks', 'copy rake tasks to the project folder') { + options[:with_tasks] = true + } opts.separator '' - opts.on('--freeze', 'freeze the project skeleton') {freeze; exit} - opts.on('--unfreeze', 'use the standard project skeleton') {unfreeze; exit} - opts.on('-i', '--info', - 'report on the project skeleton being used') do - STDOUT.puts "the project skeleton will be copied from" - STDOUT.write " " - STDOUT.puts data - exit - end + opts.on('--freeze', 'freeze the project skeleton') { + options[:action] = :freeze + } + opts.on('--unfreeze', 'use the standard project skeleton') { + options[:action] = :unfreeze + } + opts.on('-i', '--info', 'report on the project skeleton being used') { + options[:action] = :info + } opts.separator '' opts.separator 'common options:' - - opts.on_tail( '-h', '--help', 'show this message' ) do - STDOUT.puts opts + opts.on_tail( '-h', '--help', 'show this message' ) { + @io.puts opts exit - end - - opts.on_tail( '--version', 'show version' ) do - STDOUT.puts "Mr Bones #{::Bones::VERSION}" + } + opts.on_tail( '--version', 'show version' ) { + @io.puts "Mr Bones #{::Bones::VERSION}" exit - end + } # parse the command line arguments opts.parse! args - self.name = args.empty? ? nil : args.join('_') + options[:name] = args.empty? ? nil : args.join('_') - if name.nil? - STDOUT.puts opts + if options[:action].nil? and name.nil? + @io.puts opts ::Kernel.abort end - self.output_dir = name if output_dir.nil? + options[:output_dir] = name if output_dir.nil? nil end - # Returns +true+ if we are updating an already existing project. Returns - # +false+ if we are creating a new project. + # Run the main application. # - def update? - @update + def run + case options[:action] + when :freeze + freeze + when :unfreeze + unfreeze + when :info + @io.puts "the project skeleton will be copied from" + @io.write " " + @io.puts skeleton_dir + else + if update? then update + else create end + end end - # Returns the project name but converted to be usable as a Ruby class - # name. - # - def classname - name.tr('-','_').split('_').map {|x| x.capitalize}.join - end - # Create a new project from the bones/data project template. # def create # see if the directory already exists abort "'#{output_dir}' already exists" if test ?e, output_dir begin files_to_copy.each {|fn| cp fn} + copy_tasks(File.join(output_dir, 'tasks')) if with_tasks? pwd = File.expand_path(FileUtils.pwd) msg = "created '#{name}'" msg << " in directory '#{output_dir}'" if name != output_dir - STDOUT.puts msg + @io.puts msg if test(?f, File.join(output_dir, 'Rakefile')) begin FileUtils.cd output_dir system "rake manifest:create 2>&1 > #{::Bones::DEV_NULL}" - STDOUT.puts "now you need to fix these files" + @io.puts "now you need to fix these files" system "rake notes" ensure FileUtils.cd pwd end end @@ -142,131 +201,106 @@ # def update abort "'#{output_dir}' does not exist" unless test ?e, output_dir task_dir = File.join(output_dir, 'tasks') - abort "no tasks directory found in '#{output_dir}'" unless test ?d, task_dir + FileUtils.mkdir_p(task_dir) unless test(?d, task_dir) archive_dir = File.join(task_dir, 'archive') FileUtils.rm_rf archive_dir FileUtils.mkdir archive_dir Dir.glob(File.join(task_dir, '*')).each do |fn| next if fn == archive_dir FileUtils.cp fn, archive_dir end - files_to_copy.each do |fn| - next unless %r/^tasks\// =~ fn - cp fn - end + copy_tasks(task_dir) msg = "updated tasks for '#{name}'" msg << " in directory '#{output_dir}'" if name != output_dir - STDOUT.puts msg + @io.puts msg end # Freeze the project skeleton to the current Mr Bones gem. If the project # skeleton has already been frozen, then it will be archived before being # overwritten by the current Mr Bones skeleton. # # If the project skeleton is already frozen, only the tasks from the Mr # Bones skeleton will be copied to the user's data directory. # def freeze - self.data = ::Bones.path('data') - data_dir = File.join(mrbones_dir, 'data') - archive_dir = File.join(mrbones_dir, 'archive') - tasks_only = false + options[:skeleton_dir] = ::Bones.path('data') + options[:name] = 'data' if name.nil? + options[:output_dir] = File.join(mrbones_dir, name) - if test(?d, data_dir) - STDOUT.puts "archiving #{data_dir}" if verbose - FileUtils.rm_rf(archive_dir) - FileUtils.mkdir(archive_dir) - FileUtils.cp_r(File.join(data_dir, '.'), archive_dir) - tasks_only = true - else - FileUtils.mkdir_p(data_dir) - end + archive(output_dir, "#{output_dir}.archive") + FileUtils.mkdir_p(output_dir) files_to_copy.each do |file| - next if tasks_only && !(%r/^tasks\// =~ file) + src = File.join(skeleton_dir, file) + dst = File.join(output_dir, file) - src = File.join(data, file) - dst = File.join(data_dir, file) - - STDOUT.puts "freezing #{dst}" if verbose + @io.puts "freezing #{dst}" if verbose? FileUtils.mkdir_p(File.dirname(dst)) FileUtils.cp(src, dst) end + copy_tasks(File.join(output_dir, 'tasks'), 'freezing') if with_tasks? + File.open(frozen_version_file, 'w') {|fd| fd.puts ::Bones::VERSION} - if tasks_only - STDOUT.puts "project skeleton tasks have been updated " << - "from Mr Bones #{::Bones::VERSION}" - else - STDOUT.puts "project skeleton has been frozen " << + @io.puts "project skeleton has been frozen " << "to Mr Bones #{::Bones::VERSION}" - end end # Unfreeze the project skeleton. The default Mr Bones skeleton will be # used insetad. This method will archive the current frozen skeleton if # one exists. # def unfreeze - data_dir = File.join(mrbones_dir, 'data') - archive_dir = File.join(mrbones_dir, 'archive') + options[:name] = 'data' if name.nil? + options[:output_dir] = File.join(mrbones_dir, name) - if test(?d, data_dir) - STDOUT.puts "archiving #{data_dir}" if verbose - FileUtils.rm_rf(archive_dir) - FileUtils.mkdir(archive_dir) - FileUtils.cp_r(File.join(data_dir, '.'), archive_dir) - FileUtils.rm_rf(data_dir) - - STDOUT.puts "project skeleton has been unfrozen" - STDOUT.puts "(default Mr Bones #{::Bones::VERSION} sekeleton will be used)" + if archive(output_dir, "#{output_dir}.archive") + @io.puts "project skeleton has been unfrozen" + @io.puts "(default Mr Bones #{::Bones::VERSION} skeleton will be used)" else - STDOUT.puts "project skeleton is not frozen (no action taken)" + @io.puts "project skeleton is not frozen (no action taken)" end FileUtils.rm_f frozen_version_file end # Returns a list of the files to copy from the bones/data directory to # the new project directory # def files_to_copy - rgxp = %r/\A#{data}\/?/o + rgxp = %r/\A#{skeleton_dir}\/?/o exclude = %r/tmp$|bak$|~$|CVS|\.svn/o - ary = Dir.glob(File.join(data, '**', '*')).map do |filename| + ary = Dir.glob(File.join(skeleton_dir, '**', '*')).map do |filename| next if exclude =~ filename next if test(?d, filename) filename.sub rgxp, '' end ary.compact! ary.sort! ary end - - private - # Copy a file from the Bones prototype project location to the user # specified project location. A message will be displayed to the screen # indicating that the file is being created. # def cp( file ) dir = File.dirname(file).sub('NAME', name) dir = (dir == '.' ? output_dir : File.join(output_dir, dir)) dst = File.join(dir, File.basename(file, '.erb').sub('NAME', name)) - src = File.join(data, file) + src = File.join(skeleton_dir, file) - STDOUT.puts(test(?e, dst) ? "updating #{dst}" : "creating #{dst}") if verbose + @io.puts(test(?e, dst) ? "updating #{dst}" : "creating #{dst}") if verbose? FileUtils.mkdir_p(dir) if '.erb' == File.extname(file) txt = ERB.new(File.read(src), nil, '-').result(binding) File.open(dst, 'w') {|fd| fd.write(txt)} @@ -275,13 +309,53 @@ end FileUtils.chmod(File.stat(src).mode, dst) end + # Archive the contents of the _from_ directory into the _to_ directory. + # The _to_ directory will be deleted if it currently exists. After the + # copy the _from_ directory will be deleted. + # + # Returns +true+ if the directory was archived. Returns +false+ if the + # directory was not archived. + # + def archive( from, to ) + if test(?d, from) + @io.puts "archiving #{from}" if verbose? + FileUtils.rm_rf(to) + FileUtils.mkdir(to) + FileUtils.cp_r(File.join(from, '.'), to) + FileUtils.rm_rf(from) + true + else + false + end + end + + # Copy the Mr Bones tasks into the _to_ directory. If we are in verbose + # mode, then a message will be displayed to the user. This message can be + # passed in as the _msg_ argument. The current file being copied is + # appened to the end of the message. + # + def copy_tasks( to, msg = nil ) + Dir.glob(::Bones.path(%w[lib bones tasks *])).sort.each do |src| + dst = File.join(to, File.basename(src)) + + if msg + @io.puts "#{msg} #{dst}" if verbose? + else + @io.puts(test(?e, dst) ? "updating #{dst}" : "creating #{dst}") if verbose? + end + + FileUtils.mkdir_p(File.dirname(dst)) + FileUtils.cp(src, dst) + end + end + # Prints an abort _msg_ to the screen and then exits the Ruby interpreter. # def abort( msg ) - STDERR.puts msg + @err.puts msg exit 1 end # Returns the .bones resource directory in the user's home directory. #