https://svn.lrde.epita.fr/svn/lrdetools/trunk
Index: ChangeLog
from Nicolas Pouillard <ertai(a)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(a)lrde.epita.fr>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(a)lrde.epita.fr>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: #{(a)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