[LrdeTools][Vcs] 235: Refactor and improve code linked to common_commit.

https://svn.lrde.epita.fr/svn/lrdetools/trunk Index: ChangeLog from Nicolas Pouillard <ertai@lrde.epita.fr> Refactor and improve code linked to common_commit. * vcs/lib/vcs/vcs.rb: Add a generalized cache support and a generalized delegation support for output (puts, print ...). * vcs/lib/vcs/changelog.rb: Actions are now mk_log_entry, mk_changelog_entry, and mk_message_entry. * vcs/lib/vcs/common_commit.rb: Simplify. Generalize the backup system for all cached files (,messages). * vcs/lib/vcs/form.rb: New. Handle the big editable file, spawned during the common_commit. * vcs/lib/vcs/message.rb: Simplify, because it is now based on mk_*_entry. changelog.rb | 173 +++++++++++++++++-------------------------------------- common_commit.rb | 76 ++++++++++-------------- form.rb | 93 +++++++++++++++++++++++++++++ message.rb | 55 ++++++----------- vcs.rb | 110 +++++++++++++++++++++++++++------- 5 files changed, 287 insertions(+), 220 deletions(-) Index: vcs/lib/vcs/form.rb --- vcs/lib/vcs/form.rb (revision 0) +++ vcs/lib/vcs/form.rb (revision 0) @@ -0,0 +1,93 @@ +# Author:: Nicolas Pouillard <ertai@lrde.epita.fr>. +# Copyright:: Copyright (c) 2005 LRDE. All rights reserved. +# License:: GNU General Public License (GPL). +# Revision:: $Id$ + +class Vcs + + def edit_form! ( *args ) + mk_from(*args) + edit! Form if Form.read =~ /\A---/ + if Form.read =~ /\A---/ + raise Failure, "You must fill this file: `#{Form}' (and remove the first line)" + else + instanciate_form(*args) + return YAML.load(IForm.read)['commited'] + end + end + + + def instanciate_form! ( *args ) + with_cache! IForm, 'instanciated form (title, subject, ChangeLog entry, diff)' do + cl = mk_from(*args).read + ls = [] + YAML.each_document("--- |\n" + cl) { |x| (ls.size == 2)? break : ls << x } + title_subject, input = ls + header = { 'title' => title_subject[/^title: (.*)$/, 1], + 'subject' => title_subject[/^subject: (.*)$/, 1] } + rev = revision.read.to_i + 1 + header.merge!('revision' => rev, 'commited' => false) + if header['title'].nil? or header['title'].blank? + raise Failure, "No title found. Reopen `#{Form}' and add it" + end + header['title'].sub!(/\.$/, '') + b = getBinding(header.merge(:rev => rev)) + input = ERB.new(input, nil, '<-%->', '$erbout_').result(b) + LogEntry.open('w') { |f| f.print input } + puts ERB.new(header.to_yaml, nil, '<-%->', '$erbout_').result(b) + end + end + IForm = ',iform'.to_path unless defined? Vcs::IForm + + + @@subject_format ||= '<%= rev %>: <%= title %>' + + def mk_from! ( *args ) + with_cache! Form, 'complete form (title, subject, ChangeLog entry, diff)' do + + puts " + |--- | ########## Fill this file correctly and remove this line ########## | --- + |title: + |subject: #{@@subject_format}. + | + |--- | ###################### Your ChangeLog entrie ############## 80c| # | --- + |<%= title %>. + | + |".head_cut! + + mk_log_entry!(*args) + + puts "| + |--- | ########### This line, and those below, will be ignored #### 80c| # | --- + |Instructions: + |- The first line must be removed when this file is filled. + |- After you must specify a title, for the news/mail subject. + | The line containing <%= title %> will be replaced by your title, + | <%= subject %> by the subject line, <%= rev %> by the revision... + |- Everywhere in the document you can get/compute some values with + | these tags <%= aRubyExpression %> even some vcs specific call. + | For example <%= status.read %> will include the 'svn status' output. + |- Tabulations and stars ('*') will be added in the ChangeLog before each line. + |- The '80c|' on the fourth line is here to show you where will be the + | limit. The '|' is the 79th char. + | + |".head_cut! + + diffw!(*args) + end + end + alias_command :mkf, :mk_form + Form = ',form'.to_path unless defined? Vcs::Form + + def getBinding ( header ) + code = [] + header.each do |k, v| + code << "#{k} = #{v.inspect}" + end + code << 'binding' + eval(code.join('; ')) + end + protected :getBinding + +end # class Vcs + Property changes on: vcs/lib/vcs/form.rb ___________________________________________________________________ Name: svn:keywords + Id Index: vcs/lib/vcs/common_commit.rb --- vcs/lib/vcs/common_commit.rb (revision 234) +++ vcs/lib/vcs/common_commit.rb (working copy) @@ -3,85 +3,77 @@ # License:: GNU General Public License (GPL). # Revision:: $Id: header 98 2004-09-29 12:07:43Z ertai $ -require 'vcs/vcs' - class Vcs - COMMITED = Pathname.new(',commited') - def common_commit! ( subject_format, *args, &block ) + unless CL.exist? + raise Failure, "No `#{CL}', you are probably not in a valid directory." + end + opts, args = args.partition { |a| a =~ /^-/ } @@subject_format = subject_format update! - unless COMMITED.exist? - - begin - mkchangelog(*args) - rescue MustBeFilled => ex - edit! ex.file - end + Vcs.commited = edit_form!(*args) message(*args) + edit! Message - edit! @@message + iform = nil + + if commited? + iform ||= YAML.load(IForm.read) + else unless @h.agree 'Committing, are you sure? (y/n)', true commit_failed end - cl_entry = concat_changelog!(*args) - - #pager! diff - #pager! status + concat_changelog!(*args) args << 'ChangeLog' unless args.grep(/^[^-]/).empty? - # FIXME simplify cl_entry contents - begin - commit_!('--message', cl_entry, *(opts + args)) - ADD_CL.rename(COMMITED) + commit_!('--message', mk_log_entry(*args), *(opts + args)) + iform = YAML.load(IForm.read).merge('commited' => true) + IForm.open('w') { |f| f.print iform.to_yaml } TMP_CL.delete if TMP_CL.exist? - rescue - commit_failed + rescue Exception => ex + commit_failed ex end update! - else - - message(*args) - edit! @@message - end - header ||= YAML::load(META.read) - - block[header['subject']] if block_given? + block[iform['subject']] if block_given? LOG.info 'Deleting junk files...' TMP_CL.delete if TMP_CL.exist? - ADD_CL.delete if ADD_CL.exist? - COMMITED.delete if COMMITED.exist? - META.delete if META.exist? - messages = Pathname.new(',messages') - messages.mkpath unless messages.directory? - message_rev = messages + "#{@@message}.#{header['rev']}" - LOG.info "Moving `#{@@message}' to `#{message_rev}'..." - @@message.rename(message_rev) - LOG.info "You can remove `#{message_rev}' if everything is ok." + destdir = ',messages'.to_path/iform['revision'].to_s + destdir.mkpath unless destdir.directory? + [LogEntry, Form, IForm, Message, MAIL, NEWS].each do |path| + next unless path.exist? + dest = destdir/path + LOG.info "Moving `#{path}' to `#{dest}'..." + path.rename(dest) + end end + protected :common_commit! - def commit_failed - LOG.info "#{COMMITED}: Contains your ChangeLog entry" if COMMITED.exist? - LOG.error 'Aborting' + def commit_failed ( ex=nil ) + LOG.error "Aborting #{ex}" LOG.info 'You can rerun the same command to resume the commit' raise 'Commit failed' end + cattr_accessor :commited + def commited? + Vcs.commited + end + end # class Vcs Index: vcs/lib/vcs/changelog.rb --- vcs/lib/vcs/changelog.rb (revision 234) +++ vcs/lib/vcs/changelog.rb (working copy) @@ -1,165 +1,104 @@ # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>. -# Copyright:: Copyright (c) 2004 LRDE. All rights reserved. +# Copyright:: Copyright (c) 2004, 2005 LRDE. All rights reserved. # License:: GNU General Public License (GPL). # Revision:: $Id: header 98 2004-09-29 12:07:43Z ertai $ -require 'vcs/vcs' -require 'vcs/svn' -class Svn +class Vcs - @@file_st = + @@file_st ||= { - 'A' => 'New', - 'D' => 'Remove', + ?A => 'new', + ?D => 'remove', } - @@file_st.default = '' - @@prop_st = + @@prop_st ||= { - 'M' => 'Changed property' + ?M => 'changed property' } - def mkchangelog_from_status ( *args ) - result = [] + @@entries ||= {} + + def mk_log_entry_contents ( *args ) + return @@entries[args] if @@entries.has_key? args + @@entries[args] = result = [] from_status(*args) do |line, file_st, prop_st, copy_st, file| next if file_st =~ /[?X\\,]/ - next if file == 'ChangeLog' - ls = [] - ls << @@file_st[file_st] if @@file_st.has_key? file_st - ls << @@prop_st[prop_st].downcase if @@prop_st.has_key? prop_st + next if file.to_s == 'ChangeLog' + ls = [@@file_st[file_st[0]], @@prop_st[prop_st[0]]].compact! + ls.first.capitalize! unless ls.empty? result << [file, ls.join(', ')] end raise Failure, 'No changes, so no ChangeLog entry.' if result.empty? result end - private :mkchangelog_from_status + private :mk_log_entry_contents -end # class Svn + def mk_log_entry! ( *args ) + with_cache! LogEntry, 'Log entry' do + mk_log_entry_contents(*args).each do |file, comments| + puts "- #{file}: #{comments}." + end + end + end + alias_command :mkl, :mk_log_entry + LogEntry = ',log'.to_path unless defined? LogEntry -class Vcs + def log_to_changelog ( aString ) + if aString.blank? + puts + else + putc ?\t + aString.sub!(/^-/, '*') + puts aString + end + end + private :log_to_changelog - class MustBeFilled < Exception - attr_reader :file - def initialize ( file ) - @file = file - super("You must fill this file: `#{file.to_s}' (and remove the first line)") - end - end - - @@subject_format = '<%= rev %>: <%= title %>' - - def mkchangelog! ( *args ) - error_handling :changelog_failed - - unless CL.exist? - raise Failure, "No `#{CL}', you are probably not in a valid directory." - end - - cl = ADD_CL - - # Already filled if ,ChangeLog.add exists and not begin by --- - if cl.exist? - just_one_time { LOG.info "#{cl} already exists" } - raise MustBeFilled, cl if cl.read =~ /\A---/ - require 'erb' - ls = [] - YAML.each_document("--- |\n" + cl.read) { |x| (ls.size == 2)? break : ls << x } - title_subject, input = ls - header = { 'title' => title_subject[/^title: (.*)$/, 1], - 'subject' => title_subject[/^subject: (.*)$/, 1] } - rev = revision.read.to_i + 1 - header.merge!('rev' => rev, 'revision' => rev) - raise "no title: reopen #{ADD_CL}" if header['title'].nil? - header['title'].sub!(/\.$/, '') - b = getBinding(header) - header = ERB.new(header.to_yaml, $SAFE, '<-%->', '$erbout_').result(b) - META.open('w') { |f| f.puts header } - output = ERB.new(input, $SAFE, '<-%->', '$erbout_').result(b) - return output - end - - cl_add = mkchangelog_from_status(*args) - LOG.info "Creating a new `#{cl}' file" - cl.open('w') do |f| - head = Time.now.strftime("%Y-%m-%d #{Vcs.full_email}") - f.puts " -|--- | ########## Fill this file correctly and remove this line ########## | --- -|title: -|subject: #{@@subject_format}. -| -|--- | ###################### Your ChangeLog entrie ###################### | --- - |#{head} - | - |\t<%= title %>. - | - |".head_cut! - - cl_add.each do |file, str| - f.puts "\t* #{file}: #{str}." - end - - f.puts "| -|--- | ########### This line, and those below, will be ignored ########### | --- - |Instructions: - |\t* The first line must be removed when this file is filled. - |\t* After you must specify a title, for the news/mail subject. - |\t* The line containing <%= title %> will be replaced by your title, - |\t <%= subject %> by the subject line, <%= rev %> by the revision... - |\t* Everywhere in the document you can get/compute some values with - |\t these tags <%= aRubyExpression %> even some vcs specific call. - |\t For example <%= status.read %> will include the `svn status' output. - | - |".head_cut! - with(f).diffw!(*args) + def mk_changelog_entry! ( *args ) + puts Time.now.strftime("%Y-%m-%d #{Vcs.full_email}") + puts + mk_log_entry(*args).each_line(&method(:log_to_changelog)) end + alias_command :mkcl, :mk_changelog_entry + - raise MustBeFilled, cl + def mk_message_entry! ( *args ) + puts 'Index: ChangeLog' + puts "from #{Vcs.full_email}" + puts + mk_log_entry(*args).each_line(&method(:log_to_changelog)) end + alias_command :mkml, :mk_message_entry + - def concat_changelog! ( *args ) - error_handling :changelog_failed - if cl_entry = mkchangelog(*args) + def concat_changelog! ( *args ) + error_handling :concat_changelog_failed unless TMP_CL.exist? - LOG.info "Moving `#{CL}' to `#{TMP_CL}' ..." + LOG.info "Backup your `#{CL}' to `#{TMP_CL}' ..." CL.rename(TMP_CL) end CL.open('w') do |file| - LOG.info "Prepending `#{ADD_CL}' to `#{CL}' ..." - file.print cl_entry + LOG.info "#{CL}: Writing your new entry ..." + with(file).mk_changelog_entry!(*args) file.puts + LOG.info "#{CL}: Writing the others ..." file.print TMP_CL.read end - - return cl_entry - - end end + alias_command :catcl, :concat_changelog - def changelog_failed + def concat_changelog_failed if TMP_CL.exist? LOG.info "Restoring `#{CL}' from `#{TMP_CL}' ..." TMP_CL.rename(CL) end - LOG.info "#{ADD_CL}: Contains your ChangeLog entry" if ADD_CL.exist? - end - - def getBinding ( header ) - code = [] - header.each do |k, v| - code << "#{k} = #{v.inspect}" - end - code << 'binding' - eval(code.join('; ')) end - protected :getBinding - alias_command :mkcl, :mkchangelog - alias_command :ctcl, :concat_changelog end # class Vcs Index: vcs/lib/vcs/vcs.rb --- vcs/lib/vcs/vcs.rb (revision 234) +++ vcs/lib/vcs/vcs.rb (working copy) @@ -14,6 +14,8 @@ require 'ostruct' ENV['LC_ALL'] = 'C' +unless defined? Vcs + # The abstract class for a Vcs wrapper. # Conventions: # example: @@ -27,14 +29,16 @@ # checkout_! # class Vcs - @@version ||= '0.3.0' @@user_conf ||= OpenStruct.new @@color ||= :auto + @@output_io_methods ||= %w[ print puts putc ] # FIXME and so ... + cattr_accessor :version cattr_accessor :default cattr_accessor :user_conf cattr_accessor :color + cattr_accessor :output_io_methods class Failure < Exception end @@ -43,10 +47,6 @@ attr_reader :out_io - def puts ( *a, &b ) - @out_io.puts(*a, &b) - end - def output= ( anObject ) super @out_io = @output.to_io_for_commands @@ -87,6 +87,10 @@ @runner.subscribe_hook(:display_command) do |cmd| LOG.debug { "running: #{cmd.to_sh}" } end + @runner.subscribe_hook(:before_exec) do + STDOUT.flush + STDERR.flush + end end def self.add_basic_method ( meth ) @@ -139,35 +143,51 @@ attr_reader :cmd_data + def self.delegate_to_cmd_data ( *syms ) + syms.flatten.each do |meth| + define_method(meth) do |*a| + raise if block_given? + @cmd_data.out_io.send(meth, *a) + end + end + end + + delegate_to_cmd_data Vcs.output_io_methods + @@checkers = Set.new def run! ( *args ) + flush (@cmd + args).run(@runner) end - def sub_vcs ( out, err ) + def sub_vcs ( out, err, &block ) copy = self.class.new(@cmd) copy.cmd_data_factory = VcsCmdDataFactory.new(:output => out, :error => err) + if block.nil? copy + else + copy.instance_eval(&block) end - - def sub_vcs_with_name ( name ) - sub_vcs(TempPath.new("#{name}-out"), TempPath.new("#{name}-err")) end - def with ( io ) - io.flush if io.respond_to? :flush - sub_vcs(io, io) + def sub_vcs_with_name ( name, &block ) + sub_vcs(TempPath.new("#{name}-out"), TempPath.new("#{name}-err"), &block) end - def puts ( *a, &b ) - @cmd_data.puts(*a, &b) + def with ( io, &block ) + io.flush if io.respond_to? :flush + sub_vcs(io, io, &block) end def output @cmd_data.out_io end + def flush + output.flush + end + def run ( *args ) sub_vcs.run!(*args) end @@ -182,7 +202,7 @@ end end - %w[ checkout help delete diff status log add update commit ].each do |m| + %w[ checkout delete diff status log add update commit ].each do |m| add_basic_method(m) end @@ -190,20 +210,54 @@ meth = meth.to_s if meth =~ /^(.*)!$/ no_bang = $1 - super if respond_to? no_bang + if respond_to? no_bang + puts send(no_bang, *args) + else run_missing!(no_bang, meth, *args) + end else with_bang = meth + '!' return run_missing!(meth, meth, *args) unless respond_to? with_bang copy = sub_vcs_with_name(meth) - res = copy.send(with_bang, *args) - return res unless res.nil? + copy.send(with_bang, *args) out = copy.cmd_data - out.out_io.flush + out.out_io.close out end end + @@cache ||= {} + + def with_cache ( path=nil, description=nil, &block ) + loc = block.source_location # FIXME verify that this type of cache is working + return @@cache[loc].dup if @@cache.has_key? loc + if path.exist? + LOG.info "#{path} already exists" + return path.read + end + unless path.nil? + if description.nil? + raise ArgumentError, "need a description for #{path}" + end + error_handling do + LOG.info "#{path}: Contains your #{description}" if path.exist? + end + end + begin + LOG.info "Creating a new `#{path}' file ..." + path.open('w') { |f| result = with(f, &block) } + rescue Exception => ex + LOG.error "Removing `#{path}' ..." + path.unlink + raise ex + end + path.read + end + + def with_cache! ( *a, &b ) + puts with_cache(*a, &b) + end + def help! ( *args ) return help_!(*args) unless args.empty? puts "usage: #{@cmd.command} <subcommand> [options] [args] @@ -234,6 +288,10 @@ end end + CL = Pathname.new('ChangeLog') unless defined? CL + TMP_CL = Pathname.new(',,ChangeLog') unless defined? TMP_CL + + alias_command :ann, :blame alias_command :annotate, :blame alias_command :praise, :blame @@ -277,22 +335,22 @@ alias_command :checkin, :commit alias_command :populate, :add - def error_handling ( meth ) - @handlers << meth + def error_handling ( meth=nil, &block ) + @handlers << ((block.nil?)? method(meth) : block) end def call_handlers - @handlers.each { |meth| send(meth) } + @handlers.each { |block| block[] } end def call_conf_checkers - @@checkers.each { |meth| send(meth) } + @@checkers.each { |x| (x.is_a? Proc)? x[] : send(x) } end class << self - def add_conf_checker ( meth ) - @@checkers << meth + def add_conf_checker ( meth=nil, &block ) + @@checkers << (block.nil?)? meth : block end def match ( regexps, file ) @@ -328,3 +386,5 @@ end # class << self end # class Vcs + +end Index: vcs/lib/vcs/message.rb --- vcs/lib/vcs/message.rb (revision 234) +++ vcs/lib/vcs/message.rb (working copy) @@ -3,13 +3,8 @@ # License:: GNU General Public License (GPL). # Revision:: $Id: header 98 2004-09-29 12:07:43Z ertai $ -require 'vcs/vcs' -require 'vcs/changelog' - class Vcs - @@message = Pathname.new(',message') - def print_body ( file, options ) LOG.info "Creating a new `#{file}' file" file.open('w') do |f| @@ -17,50 +12,38 @@ f.puts options.to_yaml f.puts '---' f.puts - f.print message.read + with(f).message! end end private :print_body - def message ( *args ) - error_handling :message_failed - unless @@message.exist? - cl_entry = mkchangelog(*args) - TempPath.new('message') do |tmp| - tmp.open('w') do |f| - with(f).url! + def message! ( *args ) + with_cache! Message, 'generated message (ChangeLog, diffstat, diff)' do + url! if defined? COLLABOA - f.puts - f.puts 'You can also view this changeset here:' - f.puts + puts + puts 'You can also view this changeset here:' + puts next_rev = rev.output.read.to_i - next_rev += 1 unless ',commited'.to_path.exist? - f.puts "http://#{COLLABOA}/repository/changesets/#{next_rev}" + next_rev += 1 unless commited? + puts "http://#{COLLABOA}/repository/changesets/#{next_rev}" end - f.puts - f.puts 'Index: ChangeLog' - f.print cl_entry.sub(/^\d+-\d+-\d+/, 'from') - f.puts - with(f).diffstat!(*args) - f.puts + puts + flush + mk_message_entry!(*args) + puts + flush + diffstat!(*args) + puts + flush diffw(*args).each_line do |line| - f.print line if line !~ /^=+$/ - end - end - tmp.mv(@@message) + print line if line !~ /^=+$/ end end - @@message.open('r') end - alias_command :msg, :message + Message = ',message'.to_path unless defined? Message - def message_failed - if @@message.exist? - LOG.info "#{@@message}: Contains the generated message" - LOG.info ' (the ChangeLog entry, the diffstat, the diff)' - end - end end # class Vcs
participants (1)
-
Nicolas Pouillard