vcs 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@ require 'vcs/svn'
10
10
  class Svn
11
11
 
12
12
  def revision! ( *args )
13
- puts info.read[/^Revision:\s+(\d+)/, 1]
13
+ puts info(*args).read[/^Revision:\s+(\d+)/, 1]
14
14
  end
15
15
 
16
16
  alias_command :rev, :revision
@@ -1,19 +1,25 @@
1
1
  # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
2
2
  # Copyright:: Copyright (c) 2005 LRDE. All rights reserved.
3
3
  # License:: GNU General Public License (GPL).
4
- # Revision:: $Id: script.rb 221 2005-07-07 14:41:23Z pouill_n $
4
+ # Revision:: $Id: script.rb 261 2005-10-03 00:45:53Z pouill_n $
5
5
 
6
6
  require 'vcs/vcs'
7
7
 
8
8
  class Vcs
9
9
 
10
- def script! ( code, *args )
10
+ def script ( files=[], options={} )
11
+ puts script!(files, options)
12
+ end
13
+
14
+ def script! ( files=[], options={} )
11
15
  begin
12
- eval(code)
16
+ eval(files.join(' '))
17
+ rescue SystemExit => ex
18
+ raise ex
13
19
  rescue Exception => ex
14
- LOG.error { 'Vcs#script: during the client execution' }
15
- LOG.error { ex.long_pp }
20
+ logger.error { 'Vcs#script: during the client execution' }
21
+ logger.error { ex.long_pp }
16
22
  end
17
23
  end
18
24
 
19
- end # class Svn
25
+ end # class Vcs
@@ -3,15 +3,87 @@
3
3
  # License:: GNU General Public License (GPL).
4
4
  # Revision:: $Id$
5
5
 
6
+ require 'vcs/svn'
7
+
6
8
  class Svn
7
9
 
8
- def from_status ( *args, &block )
9
- status(*args).each_line do |line|
10
+ class StatusEntry
11
+ attr_accessor :line, :file_st, :prop_st, :cpy, :file, :category, :comment
12
+
13
+ def initialize ( high_line, aString )
14
+ @high_line = high_line
15
+ m = /^(.)(.)(.)(.)(.\s*)(.*)$/.match(aString)
16
+ line, file_st, bl1, prop_st, cpy, bl2, file = m.to_a
17
+ @file = file.to_path
18
+ @file_st, @prop_st, @cpy = file_st[0], prop_st[0], cpy[0]
19
+ @category = Vcs.classify(@file, @file_st)
20
+ if @file_st == ??
21
+ @file_st = @@category_symbol[@category] || @file_st
22
+ end
23
+ @line = "#{@file_st.chr}#{bl1}#{prop_st}#{cpy}#{bl2}#{file}"
24
+ end
25
+
26
+ def colorize!
27
+ @line[0] = @high_line.color(@file_st.chr, *@@style[@file_st])
28
+ end
29
+
30
+ def to_s
31
+ @file.to_s
32
+ end
33
+
34
+ @@category_symbol ||=
35
+ {
36
+ :precious => ?+,
37
+ :unmask => ?\\,
38
+ :junk => ?,
39
+ }
40
+
41
+ @@style ||=
42
+ {
43
+ ?A => [:GREEN],
44
+ ?M => [:GREEN],
45
+ ?\\ => [:YELLOW],
46
+ ?, => [:YELLOW],
47
+ ?+ => [:YELLOW],
48
+ ?G => [:BLUE],
49
+ ?X => [:BLUE],
50
+ ?D => [:MAGENTA],
51
+ ?R => [:MAGENTA],
52
+ ?~ => [:RED],
53
+ ?! => [:RED],
54
+ ?? => [:BLINK, :RED],
55
+ ?C => [:BLINK, :RED],
56
+ }
57
+ end # class StatusEntry
58
+
59
+ def status ( *args, &block )
60
+ return status_(*args) if block.nil?
61
+ result = PathList.new
62
+ status_(*args).each_line do |line|
10
63
  next unless line =~ /^.{5} /
11
- m = /^(.)(.).(.).\s*(.*)$/.match(line)
12
- block[*m.to_a]
64
+ status_entry = StatusEntry.new(@h, line)
65
+ next if status_entry.category == :exclude
66
+ result << status_entry
67
+ end
68
+ result.sort_with_regex_list! Vcs.regex_list
69
+ result.each(&block)
70
+ end
71
+
72
+ def color_status! ( *args )
73
+ status(*args) do |status_entry|
74
+ status_entry.colorize!
75
+ puts status_entry.line
76
+ end
77
+ end
78
+
79
+ def status! ( *args )
80
+ if color?
81
+ color_status!(*args)
82
+ else
83
+ status(*args) do |status_entry|
84
+ puts status_entry.line
85
+ end
13
86
  end
14
87
  end
15
- protected :from_status
16
88
 
17
89
  end # class Svn
@@ -1,16 +1,7 @@
1
- # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
2
- # Copyright:: Copyright (c) 2004 LRDE. All rights reserved.
3
- # License:: GNU General Public License (GPL).
4
-
5
- # $LastChangedBy: ertai $
6
- # $Id: header 98 2004-09-29 12:07:43Z ertai $
7
-
8
- require 'vcs/vcs'
9
-
10
- CL = Pathname.new('ChangeLog')
11
- ADD_CL = Pathname.new(',ChangeLog')
12
- TMP_CL = Pathname.new(',,ChangeLog')
13
- META = Pathname.new(',meta')
1
+ # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
2
+ # Copyright:: Copyright (c) 2004, 2005 LRDE. All rights reserved.
3
+ # License:: GNU General Public License (GPL).
4
+ # Revision:: $Id: header 98 2004-09-29 12:07:43Z ertai $
14
5
 
15
6
  class Svn < Vcs
16
7
 
@@ -19,6 +10,10 @@ class Svn < Vcs
19
10
 
20
11
  def initialize ( aCmd='svn' )
21
12
  super
13
+ @@svn_option_controller ||=
14
+ OptionController.new(Svn, @@options_specification +
15
+ Vcs.specific_options.to_a.join("\n"))
16
+ self.option_controller = @@svn_option_controller
22
17
  end
23
18
 
24
19
  %w[ blame cat cleanup copy export import list log merge mkdir move propedit
@@ -26,4 +21,49 @@ class Svn < Vcs
26
21
  add_basic_method(m)
27
22
  end
28
23
 
24
+ @@options_specification ||= "
25
+ --auto-props
26
+ --config-dir DIR
27
+ --diff-cmd CMD
28
+ --diff3-cmd CMD
29
+ --dry-run
30
+ --editor-cmd CMD
31
+ --encoding ENC
32
+ --extensions (-x) ARGS
33
+ --file (-F) FILENAME
34
+ --force
35
+ --force-log
36
+ --help (-h or -?)
37
+ --ignore-ancestry
38
+ --ignore-externals
39
+ --incremental
40
+ --limit NUM {Integer}
41
+ --message (-m) MESSAGE
42
+ --new ARG
43
+ --no-auth-cache
44
+ --no-auto-props
45
+ --no-diff-added
46
+ --no-diff-deleted
47
+ --no-ignore
48
+ --no-unlock
49
+ --non-interactive
50
+ --non-recursive (-N)
51
+ --notice-ancestry
52
+ --old ARG
53
+ --password PASS
54
+ --quiet (-q)
55
+ --recursive (-R)
56
+ --relocate FROM TO [PATH...]
57
+ --revision (-r) REV
58
+ --revprop
59
+ --show-updates (-u)
60
+ --stop-on-copy
61
+ --strict
62
+ --targets FILENAME
63
+ --username NAME
64
+ --verbose (-v)
65
+ --version
66
+ --xml
67
+ "
68
+
29
69
  end # class Svn
@@ -1,16 +1,12 @@
1
1
  # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
2
2
  # Copyright:: Copyright (c) 2004 LRDE. All rights reserved.
3
3
  # License:: GNU General Public License (GPL).
4
- # Revision:: $Id: url.rb 221 2005-07-07 14:41:23Z pouill_n $
4
+ # Revision:: $Id: url.rb 236 2005-09-26 13:08:22Z pouill_n $
5
5
 
6
- require 'vcs/svn'
7
-
8
- class Svn
6
+ class Vcs
9
7
 
10
8
  def url! ( *args )
11
- puts info.read[/^URL:\s+(.*)$/, 1]
9
+ puts info(*args).read[/^URL:\s+(.*)$/, 1]
12
10
  end
13
11
 
14
- alias_command :date, :last_changed_date
15
-
16
- end # class Svn
12
+ end # class Vcs
@@ -1,22 +1,42 @@
1
- # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
2
- # Copyright:: Copyright (c) 2004 LRDE. All rights reserved.
3
- # License:: GNU General Public License (GPL).
4
-
5
- # $LastChangedBy: ertai $
6
- # $Id: header 98 2004-09-29 12:07:43Z ertai $
7
-
8
- require 'rubygems'
9
- require_gem 'ruby_ex'
10
- require 'ruby_ex'
1
+ # Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
2
+ # Copyright:: Copyright (c) 2004, 2005 LRDE. All rights reserved.
3
+ # License:: GNU General Public License (GPL).
4
+ # Revision:: $Id: header 98 2004-09-29 12:07:43Z ertai $
5
+
6
+ require 'pathname'
7
+ lib = Pathname.new(__FILE__).dirname.parent
8
+ vendor = lib.parent + 'vendor'
9
+ unless defined? RubyEx
10
+ $CORE_EX_VENDORS ||= []
11
+ $CORE_EX_VENDORS << vendor
12
+ file = vendor + 'ruby_ex' + 'lib' + 'ruby_ex.rb'
13
+ if file.exist?
14
+ require file.to_s
15
+ else
16
+ require 'rubygems'
17
+ require_gem 'ruby_ex'
18
+ require 'ruby_ex'
19
+ end
20
+ end
21
+ lib.load_path!
22
+ RubyEx.import!
11
23
  Commands.import!
12
24
  Yaml::ChopHeader.import!
13
25
 
26
+ require 'logger'
27
+ require 'optparse'
28
+ require 'etc'
29
+ require 'ostruct'
30
+ ENV['LC_ALL'] = 'C'
31
+
32
+ unless defined? Vcs
33
+
14
34
  # The abstract class for a Vcs wrapper.
15
35
  # Conventions:
16
36
  # example:
17
37
  # svn checkout http://foo.bar/proj # alias
18
38
  # vcs-svn checkout http://foo.bar/proj # wrapper
19
- # vcs --vcs Svn checkout http://foo.bar/proj # manual
39
+ # vcs --vcs svn checkout http://foo.bar/proj # manual
20
40
  #
21
41
  # checkout
22
42
  # checkout_
@@ -24,6 +44,112 @@ Yaml::ChopHeader.import!
24
44
  # checkout_!
25
45
  #
26
46
  class Vcs
47
+ @@version ||= '0.4.0'
48
+ @@user_conf ||= OpenStruct.new(
49
+ :exclude => [/^-/],
50
+ :unmask => [/^\\/],
51
+ :junk => [/^,/],
52
+ :precious => [/^(\+|\.vcs)/],
53
+ :color => :auto,
54
+ :sorting => [],
55
+ :sign => true
56
+ )
57
+ @@output_io_methods ||= %w[ print puts putc ] # FIXME and so ...
58
+ @@specific_options ||= Set.new
59
+
60
+ @@user_defined_categories ||= [:precious, :unmask, :exclude, :junk]
61
+ @@symbol_category ||=
62
+ {
63
+ ?A => :add,
64
+ ?C => :conflict,
65
+ ?D => :delete,
66
+ ?G => :merge,
67
+ ?I => :ignore,
68
+ ?M => :modify,
69
+ ?R => :replace,
70
+ ?X => :external,
71
+ ?? => :unrecognize,
72
+ ?! => :missing,
73
+ ?~ => :obstruct,
74
+ }
75
+ @@categories ||= @@user_defined_categories + @@symbol_category.values
76
+
77
+
78
+ cattr_accessor :version
79
+ cattr_accessor :default
80
+ cattr_accessor :user_conf
81
+ cattr_accessor :output_io_methods
82
+ cattr_accessor :logger
83
+ cattr_accessor :specific_options
84
+ cattr_accessor :user_defined_categories
85
+ cattr_accessor :symbol_category
86
+ cattr_accessor :categories
87
+ class_inheritable_accessor :option_controller
88
+
89
+
90
+ class Logger < ::Logger
91
+
92
+ attr_accessor :color
93
+
94
+ def initialize ( *a, &b )
95
+ super
96
+ @high_line = HighLine.new
97
+ @color = false
98
+ end
99
+
100
+ def header ( progname, severity )
101
+ @@headers[[progname, severity]] ||= [
102
+ '[', 'vcs', ']', ' ', progname, severity, ':', ' '
103
+ ].compact.map { |x| stylize x }.join
104
+ end
105
+
106
+ def mk_message ( severity, progname, msg )
107
+ progname += ': ' unless progname.nil? or progname.empty?
108
+ msg.inject([]) do |accu, line|
109
+ accu << header(progname, severity) << line.chomp << "\n"
110
+ end.join
111
+ end
112
+
113
+ if ::Logger.const_defined? :VERSION and ::Logger::VERSION >= '1.2.6'
114
+ def format_message(severity, timestamp, progname, msg)
115
+ mk_message(severity, progname, msg)
116
+ end
117
+ else
118
+ def format_message(severity, timestamp, msg, progname)
119
+ mk_message(severity, progname, msg)
120
+ end
121
+ end
122
+
123
+ @@headers ||= {}
124
+
125
+ @@style =
126
+ {
127
+ :vcs => [:cyan],
128
+ :debug => [:magenta],
129
+ :info => [:green],
130
+ :warn => [:yellow],
131
+ :error => [:red],
132
+ :fatal => [:red, :blink],
133
+ :'[' => [:blue],
134
+ :']' => [:blue],
135
+ :':' => [:red],
136
+ }
137
+
138
+ def stylize ( aString )
139
+ aString = aString.downcase
140
+ if @color
141
+ @high_line.color(aString, *@@style[aString.to_sym])
142
+ else
143
+ aString
144
+ end
145
+ end
146
+
147
+ end # class Logger
148
+
149
+ self.logger = Logger.new(STDERR)
150
+ ENV['VCS_SEVERITY'] ||= 'info'
151
+ logger.level = Logger.const_get(ENV['VCS_SEVERITY'].upcase)
152
+ logger.color = STDERR.tty?
27
153
 
28
154
  class Failure < Exception
29
155
  end
@@ -32,61 +158,165 @@ class Vcs
32
158
 
33
159
  attr_reader :out_io
34
160
 
35
- def puts ( *a, &b )
36
- @out_io.puts(*a, &b)
37
- end
38
-
39
161
  def output= ( anObject )
40
162
  super
41
163
  @out_io = @output.to_io_for_commands
42
164
  end
43
165
 
166
+ def to_s
167
+ begin
168
+ @output.read
169
+ rescue IOError => ex
170
+ super
171
+ end
172
+ end
173
+
44
174
  end # class VcsCmdData
45
-
46
- class VcsCmdDataFactory < Commands::Datas::Factory
47
175
 
48
- alias :real_new_command_data :new_command_data
176
+ class VcsCmdDataFactory < Commands::Datas::Factory
49
177
 
50
178
  def initialize ( values )
51
179
  super
52
180
  @command_data_class = VcsCmdData
53
- @__instance = real_new_command_data
54
181
  end
55
182
 
56
183
  def new_command_data
184
+ @__instance ||= super
57
185
  @__instance.status = nil
58
186
  @__instance
59
187
  end
60
188
 
61
189
  end # class VcsCmdDataFactory
62
-
190
+
191
+
192
+
193
+ class Switch
194
+ cattr_reader :shortcuts
195
+ attr_reader :name, :shortcuts, :argument
196
+
197
+ @@re ||=
198
+ /^
199
+ \s*
200
+ --([\w-]+) # The option (--foo)
201
+ \s*
202
+ (?: \(
203
+ (-\w) # A shortcut (-f)
204
+ (?:\sor\s(-(?:\?|\w)))? # Another one (-f or -o)
205
+ \)
206
+ )?
207
+ \s*
208
+ (.*?) # An argument (--foo NUM)
209
+ \s*
210
+ (?:\{(.*)\})? # A type (--foo NUM {Integer})
211
+ $/x
212
+
213
+ def initialize ( aString )
214
+ match = @@re.match(aString)
215
+ raise "Cannot parse switch: `#{aString}'" if match.nil?
216
+ @name, @argument, @type = match[1], match[4], match[5]
217
+ @type = eval(@type) unless @type.nil?
218
+ @shortcuts = match[2..3].compact
219
+ end
220
+
221
+ def to_s
222
+ '--' + @name
223
+ end
224
+
225
+ def to_a_for_option_parser
226
+ argument = (@argument.nil? || @argument.empty?)? '' : ' ' + @argument
227
+ @shortcuts + ["--#@name#{argument}", @type].compact
228
+ end
229
+
230
+ def to_sym
231
+ @name.gsub('-', '_').to_sym
232
+ end
233
+
234
+ end # class Switch
235
+
236
+
237
+
238
+ class OptionController
239
+
240
+ attr_reader :switches, :shortcuts, :vcs_name, :option_parser, :options
241
+ protected :options
242
+
243
+ def initialize ( aVcsClass, aString )
244
+ Vcs.logger.debug { "Creating an option_controller for #{aVcsClass}..." }
245
+ @switches = []
246
+ @shortcuts = {}
247
+ aString.each_line do |line|
248
+ next if line.blank?
249
+ switch = Switch.new(line)
250
+ @switches << switch
251
+ switch.shortcuts.each do |shortcut|
252
+ @shortcuts[shortcut] = switch.name
253
+ end
254
+ end
255
+ @option_parser = OptionParser.new do |o|
256
+ o.banner = "Type '#{$0} help' for usage."
257
+ o.separator ''
258
+ @switches.each do |switch|
259
+ o.on(*switch.to_a_for_option_parser) do |a, *b|
260
+ raise unless b.empty?
261
+ options[switch.to_sym] = a
262
+ end
263
+ end
264
+ end
265
+ Vcs.logger.debug { @option_parser.to_s }
266
+ end
267
+
268
+ def parse ( argv )
269
+ @options = {}
270
+ [@options, @option_parser.parse(argv)]
271
+ end
272
+
273
+ def short_to_long ( short_option )
274
+ @shortcuts[short_option]
275
+ end
276
+
277
+ def to_strings ( options )
278
+ result = []
279
+ options.each do |k, v|
280
+ raise if v == false
281
+ result << '--' + k.to_s.gsub('_', '-')
282
+ result << v.to_s if v != true
283
+ end
284
+ result
285
+ end
286
+
287
+ end # class OptionController
288
+
289
+
63
290
  def initialize ( aCmd )
64
291
  @cmd = aCmd.to_cmd
65
- @handlers = Set.new
66
292
  @runner = Commands::Runners::System.new
67
293
  @h = HighLine.new
68
294
  self.cmd_data_factory = VcsCmdDataFactory.new(:output => STDOUT, :error => STDERR)
69
295
 
70
296
  @runner.subscribe_hook(:failure) do |data|
71
297
  if data.output == STDOUT
72
- LOG.debug { raise data.to_yaml }
298
+ logger.debug { raise data.to_yaml }
73
299
  exit((data.status)? data.status.exitstatus : 1)
74
300
  else
75
301
  raise data.to_yaml
76
302
  end
77
303
  end
78
304
  @runner.subscribe_hook(:display_command) do |cmd|
79
- LOG.debug { "running: #{cmd.to_sh}" }
305
+ logger.debug { "running: #{cmd.to_sh}" }
306
+ end
307
+ @runner.subscribe_hook(:before_exec) do
308
+ STDOUT.flush
309
+ STDERR.flush
80
310
  end
81
311
  end
82
312
 
83
313
  def self.add_basic_method ( meth )
84
314
  class_eval <<-end_eval
85
315
  def #{meth}! ( *args )
86
- run!("#{meth}", *args)
316
+ run!("#{meth}", *args)
87
317
  end
88
318
  def #{meth}_! ( *args )
89
- run!("#{meth}", *args)
319
+ run!("#{meth}", *args)
90
320
  end
91
321
  end_eval
92
322
  end
@@ -103,14 +333,18 @@ class Vcs
103
333
  def #{m1} ( *args )
104
334
  #{m2}(*args)
105
335
  end
106
- def #{m1}_ ( *args )
107
- #{m2}_(*args)
336
+ unless method_defined? :#{m1}
337
+ def #{m1}_ ( *args )
338
+ #{m2}_(*args)
339
+ end
108
340
  end
109
341
  def #{m1}! ( *args )
110
342
  #{m2}!(*args)
111
343
  end
112
- def #{m1}_! ( *args )
113
- #{m2}_!(*args)
344
+ unless method_defined? :#{m1}!
345
+ def #{m1}_! ( *args )
346
+ #{m2}_!(*args)
347
+ end
114
348
  end
115
349
  end_eval
116
350
  end
@@ -126,29 +360,52 @@ class Vcs
126
360
 
127
361
  attr_reader :cmd_data
128
362
 
363
+ def self.delegate_to_cmd_data ( *syms )
364
+ syms.flatten.each do |meth|
365
+ define_method(meth) do |*a|
366
+ raise if block_given?
367
+ @cmd_data.out_io.send(meth, *a)
368
+ end
369
+ end
370
+ end
371
+
372
+ delegate_to_cmd_data Vcs.output_io_methods
373
+
129
374
  @@checkers = Set.new
375
+ OrderedHash.import!
376
+ @@handlers = OHash.new
130
377
 
131
- def run! ( *args )
132
- (@cmd + args).run(@runner)
378
+ def run! ( command, files=[], options={} )
379
+ flush
380
+ cmd_options = option_controller.to_strings(options)
381
+ (@cmd + command + cmd_options + '--' + files).run(@runner)
133
382
  end
134
383
 
135
- def sub_vcs ( out, err )
384
+ def sub_vcs ( out, err, &block )
136
385
  copy = self.class.new(@cmd)
137
386
  copy.cmd_data_factory = VcsCmdDataFactory.new(:output => out, :error => err)
138
- copy
387
+ if block.nil?
388
+ copy
389
+ else
390
+ copy.instance_eval(&block)
391
+ end
139
392
  end
140
393
 
141
- def sub_vcs_with_name ( name )
142
- sub_vcs(TempPath.new("#{name}-out"), TempPath.new("#{name}-err"))
394
+ def sub_vcs_with_name ( name, &block )
395
+ sub_vcs(TempPath.new("#{name}-out"), TempPath.new("#{name}-err"), &block)
143
396
  end
144
397
 
145
- def with ( io )
398
+ def with ( io, &block )
146
399
  io.flush if io.respond_to? :flush
147
- sub_vcs(io, io)
400
+ sub_vcs(io, io, &block)
401
+ end
402
+
403
+ def output
404
+ @cmd_data.out_io
148
405
  end
149
406
 
150
- def puts ( *a, &b )
151
- @cmd_data.puts(*a, &b)
407
+ def flush
408
+ output.flush
152
409
  end
153
410
 
154
411
  def run ( *args )
@@ -156,16 +413,29 @@ class Vcs
156
413
  end
157
414
 
158
415
  def run_missing! ( name, orig, *args )
159
- return help!(*args) if name == '--help'
160
416
  if name =~ /^(.*)_$/
161
417
  run!($1, *args)
162
418
  else
163
- LOG.warn { "unknown method #{orig}" }
419
+ logger.warn { "unknown method #{orig}" }
164
420
  run!(name, *args)
165
421
  end
166
422
  end
423
+ protected :run_missing!
167
424
 
168
- %w[ checkout help delete diff status log add update commit ].each do |m|
425
+ def run_argv ( argv )
426
+ options, files = option_controller.parse(argv)
427
+ if files.empty?
428
+ options.delete(:help)
429
+ meth = :help!
430
+ else
431
+ meth = files.shift.dup
432
+ meth.sub!(/([^!])$/, '\1!') if meth != 'script'
433
+ end
434
+ logger.debug { "meth: #{meth}, files: #{files.inspect}, options: #{options.inspect}" }
435
+ send(meth, files, options)
436
+ end
437
+
438
+ %w[ checkout delete diff status log add update commit ].each do |m|
169
439
  add_basic_method(m)
170
440
  end
171
441
 
@@ -173,22 +443,58 @@ class Vcs
173
443
  meth = meth.to_s
174
444
  if meth =~ /^(.*)!$/
175
445
  no_bang = $1
176
- super if respond_to? no_bang
177
- run_missing!(no_bang, meth, *args)
446
+ if respond_to? no_bang
447
+ puts send(no_bang, *args)
448
+ else
449
+ run_missing!(no_bang, meth, *args)
450
+ end
178
451
  else
179
452
  with_bang = meth + '!'
180
453
  return run_missing!(meth, meth, *args) unless respond_to? with_bang
181
454
  copy = sub_vcs_with_name(meth)
182
- res = copy.send(with_bang, *args)
183
- return res unless res.nil?
455
+ copy.send(with_bang, *args)
184
456
  out = copy.cmd_data
185
- out.out_io.flush
457
+ out.out_io.close
186
458
  out
187
459
  end
188
460
  end
189
461
 
190
- def help! ( *args )
191
- return help_!(*args) unless args.empty?
462
+ @@cache ||= {}
463
+
464
+ def with_cache ( path=nil, description=nil, &block )
465
+ loc = block.source_location # FIXME verify that this type of cache is working
466
+ return @@cache[loc].dup if @@cache.has_key? loc
467
+ unless path.nil?
468
+ if description.nil?
469
+ raise ArgumentError, "need a description for #{path}"
470
+ end
471
+ error_handling(path) do
472
+ logger.info "#{path}: Contains your #{description}" if path.exist?
473
+ end
474
+ end
475
+ if path.exist?
476
+ logger.info "#{path} already exists"
477
+ return path.read
478
+ end
479
+ begin
480
+ logger.info "Creating a new `#{path}' file ..."
481
+ path.open('w') { |f| result = with(f, &block) }
482
+ rescue Exception => ex
483
+ logger.error "Removing `#{path}' ..."
484
+ path.unlink
485
+ raise ex
486
+ end
487
+ path.read
488
+ end
489
+ protected :with_cache
490
+
491
+ def with_cache! ( *a, &b )
492
+ puts with_cache(*a, &b)
493
+ end
494
+ protected :with_cache!
495
+
496
+ def help! ( files=[], options={} )
497
+ return help_!(files, options) unless files.empty? and options.empty?
192
498
  puts "usage: #{@cmd.command} <subcommand> [options] [args]
193
499
  |Type '#{@cmd.command} help <subcommand>' for help on a specific subcommand.
194
500
  |
@@ -198,7 +504,7 @@ class Vcs
198
504
  |
199
505
  |Available subcommands:".head_cut!
200
506
  cmds = []
201
- methods.each do |meth|
507
+ public_methods.each do |meth|
202
508
  next if meth =~ /_!?$/
203
509
  next unless meth =~ /^(.+)!$/
204
510
  cmd = $1
@@ -217,6 +523,22 @@ class Vcs
217
523
  end
218
524
  end
219
525
 
526
+ def color?
527
+ Vcs.color? { output.tty? }
528
+ end
529
+
530
+ def color ( aString, *someStyles )
531
+ if color?
532
+ @h.color(aString, *someStyles)
533
+ else
534
+ aString
535
+ end
536
+ end
537
+
538
+ CL = Pathname.new('ChangeLog') unless defined? CL
539
+ TMP_CL = Pathname.new(',,ChangeLog') unless defined? TMP_CL
540
+
541
+
220
542
  alias_command :ann, :blame
221
543
  alias_command :annotate, :blame
222
544
  alias_command :praise, :blame
@@ -260,20 +582,83 @@ class Vcs
260
582
  alias_command :checkin, :commit
261
583
  alias_command :populate, :add
262
584
 
263
- def error_handling ( meth )
264
- @handlers << meth
585
+ def error_handling ( meth_or_path, &block )
586
+ @@handlers[meth_or_path] = (block.nil?)? method(meth_or_path) : block
265
587
  end
266
588
 
267
589
  def call_handlers
268
- @handlers.each { |meth| send(meth) }
269
- end
270
-
271
- def self.add_conf_checker ( meth )
272
- @@checkers << meth
590
+ @@handlers.each { |k, v| (v.arity.abs == 1)? v[k] : v[] }
273
591
  end
274
592
 
275
593
  def call_conf_checkers
276
- @@checkers.each { |meth| send(meth) }
594
+ @@checkers.each { |x| (x.is_a? Proc)? x[] : send(x) }
277
595
  end
278
596
 
597
+ class << self
598
+
599
+ def add_conf_checker ( meth=nil, &block )
600
+ @@checkers << (block.nil?)? meth : block
601
+ end
602
+
603
+ def user_conf_match ( sym, file )
604
+ if user_conf.nil? or (regexps = user_conf.send(sym)).nil?
605
+ return false
606
+ end
607
+ regexps.each do |re|
608
+ return true if re.match(file) or re.match(file.basename)
609
+ end
610
+ return false
611
+ end
612
+
613
+ def classify ( file, status=nil )
614
+ if status and category = Vcs.symbol_category[status]
615
+ return category unless category == :unrecognize
616
+ end
617
+ Vcs.user_defined_categories.each do |category|
618
+ return category if Vcs.user_conf_match(category, file)
619
+ end
620
+ return :unrecognize
621
+ end
622
+
623
+ def color? ( &auto_block )
624
+ case color = Vcs.user_conf.color
625
+ when :never then return false
626
+ when :auto then return (auto_block.nil?)? false : auto_block[]
627
+ when :always then return true
628
+ else raise ArgumentError, "Bad value for `color' (#{color})"
629
+ end
630
+ end
631
+
632
+ def regex_list
633
+ @@regex_list ||= RegexList.new(user_conf.sorting)
634
+ end
635
+
636
+ def merge_user_conf ( conf )
637
+ conf = YAML.load(conf.read) if conf.is_a? Pathname
638
+ conf.each do |k, v|
639
+ v = v.to_sym if v.is_a? String
640
+ user_conf.send("#{k}=", (v.is_a? Array)? ((user_conf.send(k) || []) + v) : v)
641
+ end
642
+ end
643
+
644
+ # Here we can handle version conflicts with vcs extensions
645
+ def protocol_version ( aVersion )
646
+ if aVersion != '0.1'
647
+ raise ArgumentError, "Bad protocol version #{aVersion} but 0.1 is needed"
648
+ end
649
+ end
650
+
651
+ # Set the given method as default commit action, use commit_ for the old one
652
+ # This method can be called just once to avoid conflicts
653
+ def default_commit ( meth )
654
+ just_once do
655
+ alias_command :commit, meth
656
+ alias_command :ci, meth
657
+ end
658
+ end
659
+
660
+ end # class << self
661
+
279
662
  end # class Vcs
663
+
664
+ end