rant 0.4.2 → 0.4.4

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.
Files changed (56) hide show
  1. data/NEWS +14 -0
  2. data/README +13 -7
  3. data/Rantfile +11 -0
  4. data/doc/md5.rdoc +49 -0
  5. data/doc/rantfile.rdoc +1 -1
  6. data/lib/rant/coregen.rb +193 -0
  7. data/lib/rant/import/archive/zip.rb +2 -0
  8. data/lib/rant/import/archive.rb +10 -2
  9. data/lib/rant/import/autoclean.rb +16 -7
  10. data/lib/rant/import/c/dependencies.rb +1 -1
  11. data/lib/rant/import/directedrule.rb +2 -2
  12. data/lib/rant/import/md5.rb +16 -0
  13. data/lib/rant/import/metadata.rb +162 -0
  14. data/lib/rant/import/nodes/default.rb +490 -0
  15. data/lib/rant/import/nodes/signed.rb +84 -0
  16. data/lib/rant/import/package/zip.rb +2 -0
  17. data/lib/rant/import/rubydoc.rb +5 -1
  18. data/lib/rant/import/rubypackage.rb +2 -1
  19. data/lib/rant/import/signature/md5.rb +38 -0
  20. data/lib/rant/import/signedfile.rb +235 -0
  21. data/lib/rant/import/subfile.rb +1 -1
  22. data/lib/rant/import.rb +5 -1
  23. data/lib/rant/node.rb +165 -0
  24. data/lib/rant/plugin/csharp.rb +2 -0
  25. data/lib/rant/rantlib.rb +64 -9
  26. data/lib/rant/rantsys.rb +39 -27
  27. data/lib/rant/rantvar.rb +32 -2
  28. data/misc/TODO +66 -0
  29. data/test/import/c/dependencies/test_on_the_fly.rb +52 -0
  30. data/test/import/metadata/Rantfile +16 -0
  31. data/test/import/metadata/sub/Rantfile +17 -0
  32. data/test/import/metadata/test_metadata.rb +126 -0
  33. data/test/import/nodes/signed/Rantfile +89 -0
  34. data/test/import/nodes/signed/sub1/Rantfile +6 -0
  35. data/test/import/nodes/signed/test_signed.rb +455 -0
  36. data/test/import/package/md5.rf +10 -0
  37. data/test/import/package/test_package.rb +127 -1
  38. data/test/import/signeddirectory/Rantfile +15 -0
  39. data/test/import/signeddirectory/test_signeddirectory.rb +84 -0
  40. data/test/import/signedfile/Rantfile +90 -0
  41. data/test/import/signedfile/sub1/Rantfile +4 -0
  42. data/test/import/signedfile/test_signedfile.rb +338 -0
  43. data/test/project1/Rantfile +0 -9
  44. data/test/project1/test_project.rb +2 -0
  45. data/test/project_rb1/test_project_rb1.rb +27 -10
  46. data/test/rant-import/test_rant-import.rb +46 -9
  47. data/test/subdirs/sub2/sub/rantfile +0 -5
  48. data/test/subdirs/test_subdirs.rb +0 -9
  49. data/test/test_examples.rb +131 -3
  50. data/test/test_filelist.rb +44 -0
  51. data/test/test_sys.rb +19 -1
  52. data/test/test_task.rb +2 -2
  53. data/test/tutil.rb +9 -3
  54. metadata +34 -4
  55. data/lib/rant/rantfile.rb +0 -897
  56. data/test/test_lighttask.rb +0 -68
@@ -0,0 +1,235 @@
1
+
2
+ # signedfile.rb - File tasks with checksum change recognition.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ module Rant
7
+ def self.init_import_signedfile(rac, *rest)
8
+ rac.import "signature/md5" unless rac.var._get("__signature__")
9
+ rac.import "metadata" unless rac.var._get("__metadata__")
10
+ end
11
+ module Generators
12
+ class SignedFile
13
+ include Node
14
+
15
+ def self.rant_gen(rac, ch, args, &block)
16
+ unless args.size == 1
17
+ rac.abort_at(ch, "SignedFile: too many arguments")
18
+ end
19
+ rac.prepare_task(args.first, block, ch) { |name,pre,blk|
20
+ self.new(rac, name, pre, &blk)
21
+ }
22
+ end
23
+
24
+ def initialize(rac, name, prerequisites, &block)
25
+ super()
26
+ @rac = rac
27
+ @name = name
28
+ @pre = prerequisites or
29
+ raise ArgumentError, "prerequisites required"
30
+ @block = block
31
+ @run = false
32
+ @success = nil
33
+ @needed_blk = nil
34
+ end
35
+ def prerequisites
36
+ @pre
37
+ end
38
+ alias deps prerequisites
39
+ # first prerequisite
40
+ def source
41
+ @pre.first.to_s
42
+ end
43
+ def has_actions?
44
+ !!@block
45
+ end
46
+ def <<(pre)
47
+ @pre << pre
48
+ end
49
+ def invoked?
50
+ !@success.nil?
51
+ end
52
+ def fail?
53
+ @success == false
54
+ end
55
+ def done?
56
+ @success
57
+ end
58
+ def enhance(deps = nil, &blk)
59
+ @pre.concat(deps) if deps
60
+ if @block
61
+ if blk
62
+ first_block = @block
63
+ @block = lambda { |t|
64
+ first_block[t]
65
+ blk[t]
66
+ }
67
+ end
68
+ else
69
+ @block = blk
70
+ end
71
+ end
72
+ def needed?
73
+ invoke(:needed? => true)
74
+ end
75
+ def needed(&blk)
76
+ @needed_blk = blk
77
+ end
78
+ def invoke(opt = INVOKE_OPT)
79
+ return circular_dep if @run
80
+ @run = true
81
+ begin
82
+ return if done?
83
+ goto_task_home
84
+ @cur_checksums = []
85
+ @sigs = @rac.var._get("__signature__")
86
+ key = "prerequisites_sig_#{@sigs.name}"
87
+ target_key = "target_sig_#{@sigs.name}"
88
+ up = signed_process_prerequisites(opt)
89
+ up ||= opt[:force]
90
+ if @needed_blk
91
+ up = true if @needed_blk.call(self)
92
+ end
93
+ @cur_checksums.sort!
94
+ check_str = @cur_checksums.join
95
+ @cur_checksums = nil
96
+ metadata = @rac.var._get("__metadata__")
97
+ old_check_str = metadata.fetch(key, @name)
98
+ old_target_str = metadata.fetch(target_key, @name)
99
+ # check explicitely for plain file, thus allow the
100
+ # target of a SignedFile to be a directory ;)
101
+ if test(?f, @name)
102
+ target_str = @sigs.signature_for_file(@name)
103
+ else
104
+ target_str = ""
105
+ up ||= !File.exist?(@name)
106
+ end
107
+ check_str_changed = old_check_str != check_str
108
+ target_changed = old_target_str != target_str
109
+ up ||= check_str_changed || target_changed
110
+ return up if opt[:needed?]
111
+ return false unless up
112
+ # run action and save checksums
113
+ run
114
+ goto_task_home
115
+ target_str = test(?f, @name) ?
116
+ @sigs.signature_for_file(@name) : ""
117
+ target_changed = target_str != old_target_str
118
+ if target_changed
119
+ metadata.set(target_key, target_str, @name)
120
+ end
121
+ if check_str_changed
122
+ metadata.set(key, check_str, @name)
123
+ end
124
+ return target_changed
125
+ rescue TaskFail => e
126
+ raise
127
+ rescue Exception => e
128
+ self.fail(nil, e)
129
+ ensure
130
+ @sigs = nil
131
+ @run = false
132
+ end
133
+ end
134
+ def each_target
135
+ goto_task_home
136
+ yield @name
137
+ end
138
+ def timestamp
139
+ File.exist?(@name) ? File.mtime(@name) : T0
140
+ end
141
+ def signature
142
+ goto_task_home
143
+ sigs = @rac.var._get("__signature__")
144
+ md = @rac.var._get("__metadata__")
145
+ key = "target_sig_#{sigs.name}"
146
+ md.fetch(key, @name)
147
+ end
148
+ private
149
+ # returns true if update required
150
+ def signed_process_prerequisites(opt)
151
+ up = false
152
+ # set with already handled prerequisites, don't
153
+ # handle on prerequisite multiple times
154
+ handled = {@name => true}
155
+ my_subdir = project_subdir
156
+ @pre.each { |dep|
157
+ dep_str = dep.to_rant_target
158
+ next if handled.include? dep_str
159
+ if Node === dep
160
+ up = true if handle_node(dep, dep_str, opt)
161
+ else
162
+ tasks = @rac.resolve(dep_str, my_subdir)
163
+ if tasks.empty?
164
+ if test(?d, dep_str)
165
+ handle_dir(dep_str)
166
+ elsif File.exist?(dep_str)
167
+ handle_file(dep_str)
168
+ else
169
+ rac.err_msg @rac.pos_text(rantfile.path, line_number),
170
+ "in prerequisites: no such file or task: `#{dep_str}'"
171
+ self.fail
172
+ end
173
+ else
174
+ tasks.each { |t|
175
+ up = true if handle_node(t, dep_str, opt)
176
+ }
177
+ end
178
+ end
179
+ handled[dep_str] = true
180
+ }
181
+ up
182
+ end
183
+ def handle_node(node, dep_str, opt)
184
+ up = node.invoke(opt)
185
+ if node.respond_to? :signature
186
+ @cur_checksums << node.signature
187
+ elsif test(?f, dep_str)
188
+ # calculate checksum for plain file
189
+ handle_file(dep_str)
190
+ elsif File.exist?(dep_str)
191
+ @cur_checksums << @sigs.signature_for_string(dep_str)
192
+ end
193
+ goto_task_home
194
+ up
195
+ end
196
+ def handle_file(path)
197
+ @cur_checksums << @sigs.signature_for_file(path)
198
+ end
199
+ def handle_dir(path)
200
+ @cur_checksums << @sigs.signature_for_string(path)
201
+ end
202
+ end # class SignedFile
203
+
204
+ class AutoSubSignedFile < SignedFile
205
+ include AutoInvokeDirNode
206
+ end
207
+
208
+ class SignedDirectory < SignedFile
209
+ def respond_to?(meth)
210
+ if meth == :signature
211
+ @block
212
+ else
213
+ super
214
+ end
215
+ end
216
+ def signature
217
+ goto_task_home
218
+ sigs = @rac.var._get("__signature__")
219
+ md = @rac.var._get("__metadata__")
220
+ key = "prerequisites_sig_#{sigs.name}"
221
+ md.fetch(key, @name)
222
+ end
223
+ private
224
+ def run
225
+ @rac.cx.sys.mkdir @name unless test ?d, @name
226
+ if @block
227
+ @block.arity == 0 ? @block.call : @block[self]
228
+ goto_task_home
229
+ # for compatibility with mtime based tasks
230
+ @rac.cx.sys.touch @name
231
+ end
232
+ end
233
+ end # class SignedDirectory
234
+ end # module Generators
235
+ end # module Rant
@@ -49,7 +49,7 @@ class Rant::Generators::SubFile
49
49
  pre << dirp
50
50
  end
51
51
  rac.cx.desc file_desc
52
- ::Rant::FileTask.new(rac, name, pre, &blk)
52
+ rac.node_factory.new_file(rac, name, pre, blk)
53
53
  }
54
54
  end
55
55
  end
data/lib/rant/import.rb CHANGED
@@ -103,6 +103,10 @@ module Rant
103
103
  @imports.concat(@rantapp.imports)
104
104
  @plugins.concat(@rantapp.plugins.map { |p| p.name })
105
105
  end
106
+
107
+ #unless @imports.include? "nodes/default"
108
+ #@imports.unshift "nodes/default"
109
+ #end
106
110
 
107
111
  if File.exist?(@mono_fn) && !@force
108
112
  abort("#{@mono_fn} exists. Rant won't overwrite this file.",
@@ -298,7 +302,7 @@ EOF
298
302
  next if line =~ /^#! ?(\/|\\)?\w/
299
303
  # uncomment line if +uncomment+ directive given
300
304
  if line =~ /^\s*#.*#\s?rant-import:\s?uncomment\s*$/
301
- line.sub! '#', ''
305
+ line.sub!(/#/, '')
302
306
  end
303
307
  # skip line if +remove+ directive given
304
308
  next if line =~ /#\s?rant-import:\s?remove\s*$/
data/lib/rant/node.rb ADDED
@@ -0,0 +1,165 @@
1
+
2
+ # node.rb - Base of Rant nodes.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ module Rant
7
+
8
+ class TaskFail < StandardError
9
+ def initialize(task, orig, msg)
10
+ @task = task
11
+ @orig = orig
12
+ @msg = msg
13
+ end
14
+ def exception
15
+ self
16
+ end
17
+ def task
18
+ @task
19
+ end
20
+ def tname
21
+ @task ? @task.name : nil
22
+ end
23
+ # the exception which caused the task to fail
24
+ def orig
25
+ @orig
26
+ end
27
+ def msg
28
+ @msg
29
+ end
30
+ end
31
+
32
+ class Rantfile
33
+ attr_reader :tasks, :path
34
+ attr_accessor :project_subdir
35
+ def initialize(path)
36
+ @path = path or raise ArgumentError, "path required"
37
+ @tasks = []
38
+ @project_subdir = nil
39
+ end
40
+ def to_s
41
+ @path
42
+ end
43
+ end # class Rantfile
44
+
45
+ # Any +object+ is considered a _task_ if
46
+ # <tt>Rant::Node === object</tt> is true.
47
+ #
48
+ # Most important classes including this module are the Rant::Task
49
+ # class and the Rant::FileTask class.
50
+ module Node
51
+
52
+ INVOKE_OPT = {}.freeze
53
+
54
+ T0 = Time.at(0).freeze
55
+
56
+ # Name of the task, this is always a string.
57
+ attr_reader :name
58
+ # A reference to the Rant compiler this task belongs to.
59
+ attr_reader :rac
60
+ # Description for this task.
61
+ attr_accessor :description
62
+ # The rantfile this task was defined in.
63
+ # Should be a Rant::Rantfile instance.
64
+ attr_accessor :rantfile
65
+ # The linenumber in rantfile where this task was defined.
66
+ attr_accessor :line_number
67
+ # The directory in which this task was defined, relative to
68
+ # the projects root directory.
69
+ attr_accessor :project_subdir
70
+
71
+ def initialize
72
+ @description = nil
73
+ @rantfile = nil
74
+ @line_number = nil
75
+ @run = false
76
+ @project_subdir = ""
77
+ end
78
+
79
+ # Returns the name of this task.
80
+ def to_s
81
+ name
82
+ end
83
+
84
+ def to_rant_target
85
+ name
86
+ end
87
+
88
+ # Basically project_subdir/name
89
+ #
90
+ # The Rant compiler (or application) references tasks by their
91
+ # full_name.
92
+ def full_name
93
+ sd = project_subdir
94
+ sd.empty? ? name : File.join(sd, name)
95
+ end
96
+
97
+ # Change current working directory to the directory this task
98
+ # was defined in.
99
+ #
100
+ # Important for subclasses: Call this method always before
101
+ # invoking code from Rantfiles (e.g. task action blocks).
102
+ def goto_task_home
103
+ @rac.goto_project_dir project_subdir
104
+ end
105
+
106
+ def done?
107
+ @done
108
+ end
109
+
110
+ def needed?
111
+ !done?
112
+ end
113
+
114
+ # True during invoke. Used to encounter circular dependencies.
115
+ def run?
116
+ @run
117
+ end
118
+
119
+ # +opt+ is a Hash and shouldn't be modified.
120
+ # All objects implementing the Rant::Node protocol should
121
+ # know about the following +opt+ values:
122
+ # <tt>:needed?</tt>::
123
+ # Just check if this task is needed. Should do the same
124
+ # as calling Node#needed?
125
+ # <tt>:force</tt>::
126
+ # Run task action even if needed? is false.
127
+ # Returns true if task action was run.
128
+ def invoke(opt = INVOKE_OPT)
129
+ return circular_dep if run?
130
+ @run = true
131
+ begin
132
+ return needed? if opt[:needed?]
133
+ self.run if opt[:force] || self.needed?
134
+ ensure
135
+ @run = false
136
+ end
137
+ end
138
+
139
+ # Cause task to fail. Usually called from inside the block
140
+ # given to +act+.
141
+ def fail msg = nil, orig = nil
142
+ raise TaskFail.new(self, orig, msg)
143
+ end
144
+
145
+ # Change pwd to task home directory and yield for each created
146
+ # file/directory.
147
+ #
148
+ # Override in subclasses if your task instances create files.
149
+ def each_target
150
+ end
151
+
152
+ def run
153
+ return unless @block
154
+ goto_task_home
155
+ @block.arity == 0 ? @block.call : @block[self]
156
+ end
157
+ private :run
158
+
159
+ def circular_dep
160
+ rac.warn_msg "Circular dependency on task `#{full_name}'."
161
+ false
162
+ end
163
+ private :circular_dep
164
+ end # module Node
165
+ end
@@ -3,6 +3,8 @@
3
3
 
4
4
  require 'rant/plugin_methods'
5
5
  require 'rant/cs_compiler'
6
+ # TODO
7
+ require 'rant/import/nodes/default'
6
8
 
7
9
  module Rant
8
10
 
data/lib/rant/rantlib.rb CHANGED
@@ -10,8 +10,10 @@
10
10
  require 'getoptlong'
11
11
  require 'rant/rantvar'
12
12
  require 'rant/rantenv'
13
- require 'rant/rantfile'
14
13
  require 'rant/rantsys'
14
+ require 'rant/node'
15
+ require 'rant/import/nodes/default' # could be optimized away
16
+ require 'rant/coregen'
15
17
 
16
18
  # There is one problem with executing Rantfiles in a special context:
17
19
  # In the top-level execution environment, there are some methods
@@ -23,11 +25,19 @@ require 'rant/rantsys'
23
25
  # this object.
24
26
  Rant::MAIN_OBJECT = self
25
27
 
26
- unless Process::Status.method_defined?(:success?)
28
+ unless Process::Status.method_defined?(:success?) # new in 1.8.2
27
29
  class Process::Status
28
30
  def success?; exitstatus == 0; end
29
31
  end
30
32
  end
33
+ unless Regexp.respond_to? :union # new in 1.8.1
34
+ def Regexp.union(*patterns)
35
+ # let's hope it comes close to ruby-1.8.1 and upwards...
36
+ return /(?!)/ if patterns.empty?
37
+ # i guess the options are lost
38
+ Regexp.new(patterns.join("|"))
39
+ end
40
+ end
31
41
  if RUBY_VERSION < "1.8.2"
32
42
  class Array
33
43
  def flatten
@@ -54,6 +64,13 @@ if RUBY_VERSION < "1.8.2"
54
64
  end
55
65
  end
56
66
  end
67
+ if RUBY_VERSION < "1.8.1"
68
+ module FileUtils
69
+ def fu_list(arg)
70
+ arg.respond_to?(:to_ary) ? arg.to_ary : [arg]
71
+ end
72
+ end
73
+ end
57
74
 
58
75
  class Array
59
76
 
@@ -194,12 +211,12 @@ class RantAppContext
194
211
  include RantContext
195
212
 
196
213
  def initialize(app)
197
- @rac = app
214
+ @__rac__ = app
198
215
  end
199
216
 
200
217
  # +rac+ stands for "rant compiler"
201
218
  def rac
202
- @rac
219
+ @__rac__
203
220
  end
204
221
 
205
222
  def method_missing(sym, *args)
@@ -271,6 +288,16 @@ end # module Rant
271
288
  class Rant::RantApp
272
289
  include Rant::Console
273
290
 
291
+ class AutoLoadNodeFactory
292
+ def initialize(rac)
293
+ @rac = rac
294
+ end
295
+ def method_missing(sym, *args, &block)
296
+ @rac.import "nodes/default"
297
+ @rac.node_factory.send(sym, *args, &block)
298
+ end
299
+ end
300
+
274
301
  # Important: We try to synchronize all tasks referenced indirectly
275
302
  # by @rantfiles with the task hash @tasks. The task hash is
276
303
  # intended for fast task lookup per task name.
@@ -305,6 +332,7 @@ class Rant::RantApp
305
332
  # internal use. A private option is distuingished from others
306
333
  # by having +nil+ as description!
307
334
 
335
+ [ "--import", "-i", GetoptLong::REQUIRED_ARGUMENT, nil ],
308
336
  [ "--stop-after-load", GetoptLong::NO_ARGUMENT, nil ],
309
337
  # Print caller to $stderr on abort.
310
338
  [ "--trace-abort", GetoptLong::NO_ARGUMENT, nil ],
@@ -347,6 +375,8 @@ class Rant::RantApp
347
375
  # Note: Might change before 1.0
348
376
  attr_reader :resolve_hooks
349
377
 
378
+ attr_accessor :node_factory
379
+
350
380
  def initialize(*args)
351
381
  unless args.empty?
352
382
  STDERR.puts caller[0]
@@ -381,6 +411,8 @@ class Rant::RantApp
381
411
  @orig_pwd = nil
382
412
  @current_subdir = ""
383
413
  @resolve_hooks = []
414
+
415
+ @node_factory = AutoLoadNodeFactory.new(self)
384
416
  end
385
417
 
386
418
  def [](opt)
@@ -531,6 +563,8 @@ class Rant::RantApp
531
563
  return 1
532
564
  ensure
533
565
  # TODO: exception handling!
566
+ hooks = var._get("__at_return__")
567
+ hooks.each { |hook| hook.call } if hooks
534
568
  @plugins.each { |plugin| plugin.rant_plugin_stop }
535
569
  @plugins.each { |plugin| plugin.rant_quit }
536
570
  # restore pwd
@@ -550,13 +584,13 @@ class Rant::RantApp
550
584
 
551
585
  def task(targ, &block)
552
586
  prepare_task(targ, block) { |name,pre,blk|
553
- Rant::Task.new(self, name, pre, &blk)
587
+ @node_factory.new_task(self, name, pre, blk)
554
588
  }
555
589
  end
556
590
 
557
591
  def file(targ, &block)
558
592
  prepare_task(targ, block) { |name,pre,blk|
559
- Rant::FileTask.new(self, name, pre, &blk)
593
+ @node_factory.new_file(self, name, pre, blk)
560
594
  }
561
595
  end
562
596
 
@@ -594,6 +628,8 @@ class Rant::RantApp
594
628
  end
595
629
  Rant::CODE_IMPORTS << arg.dup
596
630
  end
631
+ init_msg = "init_import_#{arg.gsub(/[^\w]/, '__')}"
632
+ Rant.send init_msg, self if Rant.respond_to? init_msg
597
633
  @imports << arg.dup
598
634
  end
599
635
  }
@@ -661,7 +697,7 @@ class Rant::RantApp
661
697
  end
662
698
  warn_msg "enhance \"#{name}\": no such task",
663
699
  "Generating a new file task with the given name."
664
- Rant::FileTask.new(self, name, pre, &blk)
700
+ @node_factory.new_file(self, name, pre, blk)
665
701
  }
666
702
  end
667
703
 
@@ -924,7 +960,7 @@ class Rant::RantApp
924
960
  tn = nil
925
961
  prepare_task(targ, block, ch) { |name,pre,blk|
926
962
  tn = name
927
- Rant::FileTask.new(self, name, pre, &blk)
963
+ @node_factory.new_file(self, name, pre, blk)
928
964
  }
929
965
  build(tn)
930
966
  elsif target.respond_to? :to_rant_target
@@ -938,7 +974,7 @@ class Rant::RantApp
938
974
  # create a file task
939
975
  ch ||= Rant::Lib.parse_caller_elem(caller[1])
940
976
  prepare_task(rt, block, ch) { |name,pre,blk|
941
- Rant::FileTask.new(self, name, pre, &blk)
977
+ @node_factory.new_file(self, name, pre, blk)
942
978
  }
943
979
  build(rt)
944
980
  else
@@ -1017,6 +1053,18 @@ class Rant::RantApp
1017
1053
  end
1018
1054
  public :at_resolve
1019
1055
 
1056
+ # block will be called before this rac returns from #run
1057
+ # pwd will be the projects root directory
1058
+ def at_return(&block)
1059
+ hooks = var._get("__at_return__")
1060
+ if hooks
1061
+ hooks << block
1062
+ else
1063
+ var._set("__at_return__", [block])
1064
+ end
1065
+ end
1066
+ public :at_return
1067
+
1020
1068
  # Returns a list with all tasks for which yield
1021
1069
  # returns true.
1022
1070
  def select_tasks
@@ -1130,6 +1178,8 @@ class Rant::RantApp
1130
1178
  @arg_rantfiles << value
1131
1179
  when "--force-run"
1132
1180
  @force_targets << value
1181
+ when "--import"
1182
+ import value
1133
1183
  else
1134
1184
  # simple switch
1135
1185
  @opts[opt.sub(/^--/, '').tr('-', "_").to_sym] = true
@@ -1305,6 +1355,11 @@ class Rant::RantApp
1305
1355
  msg << orig.backtrace[0..4] unless ch
1306
1356
  end
1307
1357
  end
1358
+ if e.msg && !e.msg.empty?
1359
+ ch = get_ch_from_backtrace(e.backtrace)
1360
+ t_msg.unshift(e.msg)
1361
+ t_msg.unshift(pos_text(ch[:file], ch[:ln])) if ch
1362
+ end
1308
1363
  err_msg msg unless msg.empty?
1309
1364
  err_msg t_msg
1310
1365
  end