rant 0.3.4 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,13 +26,16 @@ methods with the Rant application:
26
26
  "rubypackage"</tt> first.
27
27
  +plugin+:: Instantiate a plugin. Example: <tt>plugin
28
28
  :Configure</tt> instantiates the Configure plugin.
29
- +sys+:: When given argument(s), runs an external program.
30
- Otherwise you can call methods to interact with the OS
31
- on it.
29
+ +sys+:: Run external commands or call portable file system
30
+ manipulation methods (copy files, unlink files,
31
+ install, ...).
32
32
  +source+:: Takes a filename as argument and causes Rant to read
33
33
  it in as Rantfile.
34
34
  +subdirs+:: Takes a list of subdirectories in which Rant should
35
35
  look for Rantfiles.
36
+ +var+:: Provides access to variables accessible in Rantfiles
37
+ and from the commandline.
38
+ +rac+:: The "Rant compiler" which is compiling the Rantfiles.
36
39
 
37
40
  === Defining a task
38
41
 
@@ -203,23 +206,31 @@ Note:: You are highly encouraged to not <tt>include Rant::Sys</tt>
203
206
  read the ri docs to get an overview:
204
207
  ri Dir::glob
205
208
 
209
+ From now on we'll call <tt>sys[...]</tt> the <it>glob
210
+ operator</it>.
211
+
206
212
  You can give more patterns:
207
213
  c_files = sys["**/*.h", "**/*.c"]
208
214
  gives a list of all files ending in ".h" or ".c" in the current
209
215
  directory and all subdirectories.
210
216
 
211
- The object returned by sys#[] _behaves_ like a list of string, so
212
- it is possible to pass it to methods expecting an array. If you're
213
- getting errors or experience strange behaviour convert the list
214
- explicetely to an array:
217
+ The object returned by the glob operator _behaves_ like a list of
218
+ string, so it is possible to pass it to methods expecting an array.
219
+ If you're getting errors or experience strange behaviour convert
220
+ the list explicetely to an array:
215
221
  sys.touch c_files.to_a
216
222
 
217
223
  === Generators
218
224
 
219
225
  The *gen* function takes a generator which usually creates one or more
220
- tasks for you. Currently are two generators immediately available:
226
+ tasks for you. The following list of generators is immediately
227
+ available:
221
228
  +Directory+:: Create directories.
222
229
  +Task+:: Define custom task.
230
+ +Rule+:: Define a rule (a rule produces tasks on the fly).
231
+ +Action+:: Run a block of code immediately.
232
+ The Action generator is discussed in
233
+ doc/advanced.rdoc[link:files/doc/advanced_rdoc.html].
223
234
 
224
235
  === The +Directory+ generator
225
236
 
@@ -294,11 +305,20 @@ last dot with the given string.
294
305
 
295
306
  The +import+ function lets you import additional generators.
296
307
  Currently the following are coming with Rant:
308
+ +Clean+:: Remove selected files.
309
+ +AutoClean+:: Remove all files generated by any file task (including
310
+ those generated by rules).
311
+ +DirectedRule+:: A Rule which takes sources from one or more
312
+ directories and puts generated files into a specified
313
+ directory.
297
314
  +RubyTest+:: Run Test::Unit tests for your Ruby code.
298
315
  +RubyDoc+:: Run RDoc.
299
316
  +RubyPackage+:: Generate tar, zip and gem packages of your Ruby
300
317
  application/library.
301
- As these are all Ruby specific, please read the Ruby project howto.
318
+
319
+ Read doc/advanced.rdoc[link:files/doc/advanced_rdoc.html] and
320
+ doc/rubyproject.rdoc[link:files/doc/rubyproject_rdoc.html] for
321
+ documentation.
302
322
 
303
323
  === The +subdirs+ command
304
324
 
@@ -19,9 +19,6 @@ module Rant
19
19
  class RantImport
20
20
  include Rant::Console
21
21
 
22
- # TODO: We currently only look for imports and plugins
23
- # relative to this LIB_DIR. We should also look in all pathes
24
- # in $LOAD_PATH after looking in LIB_DIR.
25
22
  LIB_DIR = File.expand_path(File.dirname(__FILE__))
26
23
 
27
24
  OPTIONS = [
@@ -244,8 +241,8 @@ EOF
244
241
  rs = ""
245
242
  @imports.each { |name|
246
243
  next if @included_imports.include? name
247
- path = File.join(LIB_DIR, "import", "#{name}.rb")
248
- unless File.exist? path
244
+ path = get_lib_rant_path "import/#{name}.rb"
245
+ unless path
249
246
  abort("No such import - #{name}")
250
247
  end
251
248
  msg "Including import `#{name}'", path
@@ -260,7 +257,7 @@ EOF
260
257
  @plugins.each { |name|
261
258
  lc_name = name.downcase
262
259
  next if @included_plugins.include? lc_name
263
- path = File.join(LIB_DIR, "plugin", "#{lc_name}.rb")
260
+ path = get_lib_rant_path "plugin/#{lc_name}.rb"
264
261
  unless File.exist? path
265
262
  abort("No such plugin - #{name}")
266
263
  end
@@ -275,8 +272,6 @@ EOF
275
272
  # code by directly inserting the code.
276
273
  def resolve_requires script
277
274
  rs = ""
278
- # TODO: skip multiline comments (=begin, =end) only of
279
- # @skip_comments is true
280
275
  in_ml_comment = false
281
276
  script.each { |line|
282
277
  if in_ml_comment
@@ -293,10 +288,15 @@ EOF
293
288
  in_ml_comment = true
294
289
  next if @skip_comments
295
290
  end
291
+ name = nil
296
292
  if line =~ /\s*(require|load)\s+('|")rant\/(\w+)(\.rb)?('|")/
297
293
  name = $3
294
+ elsif line =~ /\s*(require|load)\s+('|")rant\/(import\/\w+)(\.rb)?('|")/
295
+ name = $3
296
+ end
297
+ if name
298
298
  next if @core_imports.include? name
299
- path = File.join(LIB_DIR, "#{name}.rb")
299
+ path = get_lib_rant_path "#{name}.rb"
300
300
  msg "Including `#{name}'", path
301
301
  @core_imports << name
302
302
  rs << resolve_requires(File.read(path))
@@ -308,5 +308,15 @@ EOF
308
308
  rs
309
309
  end
310
310
 
311
+ def get_lib_rant_path(fn)
312
+ path = File.join(LIB_DIR, fn)
313
+ return path if File.exist?(path)
314
+ $:.each { |lib_dir|
315
+ path = File.join(lib_dir, "rant", fn)
316
+ return path if File.exist?(path)
317
+ }
318
+ nil
319
+ end
320
+
311
321
  end # class RantImport
312
322
  end # module Rant
@@ -0,0 +1,65 @@
1
+
2
+ # autoclean.rb - "AutoClean" generator for Rant.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ require 'rant/rantlib'
7
+ require 'rant/import/clean'
8
+
9
+ class Rant::Generators::AutoClean
10
+ def self.rant_generate(rac, ch, args, &block)
11
+ # validate args
12
+ if args.size > 1
13
+ rac.abort_at(ch,
14
+ "AutoClean doesn't take more than one argument.")
15
+ end
16
+ tname = args.first || "autoclean"
17
+
18
+ # we generate a normal clean task too, so that the user can
19
+ # add files to clean via a var
20
+ ::Rant::Generators::Clean.rant_generate(rac, ch, [tname])
21
+
22
+ # create task
23
+ rac.task :__caller__ => ch, tname => [] do |t|
24
+ rac.tasks.each { |n, worker|
25
+ worker.each_target { |entry|
26
+ if test ?e, entry
27
+ if test ?f, entry
28
+ rac.cx.sys.rm_f entry
29
+ else
30
+ rac.cx.sys.rm_rf entry
31
+ end
32
+ end
33
+ }
34
+ }
35
+ target_rx = nil
36
+ rac.resolve_hooks.each { |hook|
37
+ if hook.respond_to? :each_target
38
+ hook.each_target { |entry|
39
+ if test ?f, entry
40
+ rac.cx.sys.rm_f entry
41
+ else
42
+ rac.cx.sys.rm_rf entry
43
+ end
44
+ }
45
+ elsif hook.respond_to? :target_rx
46
+ next(rx) unless (t_rx = hook.target_rx)
47
+ target_rx = target_rx.nil? ? t_rx :
48
+ Regexp.union(target_rx, t_rx)
49
+ end
50
+ }
51
+ if target_rx
52
+ rac.msg 1, "searching for rule products"
53
+ rac.cx.sys["**/*"].each { |entry|
54
+ if entry =~ target_rx
55
+ if test ?f, entry
56
+ rac.cx.sys.rm_f entry
57
+ else
58
+ rac.cx.sys.rm_rf entry
59
+ end
60
+ end
61
+ }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,45 @@
1
+
2
+ # clean.rb - "Clean" generator for Rant.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ require 'rant/rantlib'
7
+
8
+ class Rant::Generators::Clean
9
+ def self.rant_generate(rac, ch, args, &block)
10
+ # validate args
11
+ if args.size > 1
12
+ rac.abort_at(ch, "Clean doesn't take more than one argument.")
13
+ end
14
+ tname = args.first || "clean"
15
+
16
+ # set var with task name to a MultiFileList
17
+ case rac.var[tname]
18
+ when nil
19
+ rac.var[tname] = Rant::MultiFileList.new(rac)
20
+ when Rant::RacFileList
21
+ ml = Rant::MultiFileList.new(rac)
22
+ rac.var[tname] = ml.add(rac.var[tname])
23
+ when Rant::MultiFileList
24
+ # ok, nothing to do
25
+ else
26
+ # TODO: refine error message
27
+ rac.abort_at(ch,
28
+ "var `#{tname}' already exists.",
29
+ "Clean uses var with the same name as the task name.")
30
+ end
31
+
32
+ # create task
33
+ rac.task :__caller__ => ch, tname => [] do |t|
34
+ rac.var[tname].each_entry { |entry|
35
+ if test ?e, entry
36
+ if test ?f, entry
37
+ rac.cx.sys.rm_f entry
38
+ else
39
+ rac.cx.sys.rm_rf entry
40
+ end
41
+ end
42
+ }
43
+ end
44
+ end
45
+ end # class Rant::Generators::Clean
@@ -0,0 +1,122 @@
1
+
2
+ # directedrule.rb - "DirectedRule" generator for Rant.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ require 'rant/rantlib'
7
+
8
+ class Rant::Generators::DirectedRule
9
+ def self.rant_generate(rac, ch, args, &block)
10
+ unless args.size == 1
11
+ rac.abort_at(ch, "DirectedRule takes one arguments.")
12
+ end
13
+ h = args.first
14
+ if h.respond_to? :to_hash
15
+ h = h.to_hash
16
+ else
17
+ rac.abort_at(ch, "Argument has to be a hash.")
18
+ end
19
+ ts_h, dir_h = nil, nil
20
+ h.each { |k, v| v.respond_to?(:to_ary) ?
21
+ dir_h = { k => v } :
22
+ ts_h = { k => v }
23
+ }
24
+ unless dir_h
25
+ rac.abort_at(ch,
26
+ "Source directory argument has to be a list.")
27
+ end
28
+ target, source = nil, nil
29
+ ts_h.each { |target, source| }
30
+ target_dir, source_dirs = nil, nil
31
+ dir_h.each { |target_dir, source_dirs| }
32
+ if target_dir.respond_to? :to_str
33
+ target_dir = target_dir.to_str
34
+ else
35
+ rac.abort_at(ch, "String required as target directory.")
36
+ end
37
+ if source_dirs.respond_to? :to_ary
38
+ source_dirs = source_dirs.to_ary
39
+ elsif source_dirs.respond_to? :to_str
40
+ source_dirs = [source_dirs.to_str]
41
+ else
42
+ rac.abort_at(ch,
43
+ "List of strings or string required for source directories.")
44
+ end
45
+ target = ".#{target}" if Symbol === target
46
+ source = ".#{source}" if Symbol === source
47
+ if target.respond_to? :to_str
48
+ target = target.to_str
49
+ else
50
+ rac.abort_at(ch, "target has to be a string")
51
+ end
52
+ if source.respond_to? :to_str
53
+ source = source.to_str
54
+ else
55
+ rac.abort_at(ch, "source has to be a string or symbol")
56
+ end
57
+ blk = self.new(rac, ch, target_dir, source_dirs,
58
+ target, source, &block)
59
+ blk.define_hook
60
+ blk
61
+ end
62
+ def initialize(rac, ch, target_dir, source_dirs,
63
+ target, source, &block)
64
+ @rac = rac
65
+ @ch = ch
66
+ @source_dirs = source_dirs
67
+ @target_dir = target_dir
68
+ # target should be a string (file extension)
69
+ @target = target.sub(/^\./, '')
70
+ @target_rx = /#{Regexp.escape(target)}$/o
71
+ # source should be a string (file extension)
72
+ @source = source.sub(/^\./, '')
73
+ @esc_target_dir = Regexp.escape(target_dir)
74
+ @block = block
75
+ end
76
+ def call(name)
77
+ self[name]
78
+ end
79
+ def [](name)
80
+ #puts "rule for #{name} ?"
81
+ if name =~ /^#@esc_target_dir\//o && name =~ @target_rx
82
+ #puts " matches"
83
+ fn = File.basename(name)
84
+ src_fn = fn.sub_ext(@source)
85
+ #puts " source filename #{src_fn}"
86
+ src = nil
87
+ @source_dirs.each { |d|
88
+ path = File.join(d, src_fn)
89
+ #puts " #{path} exist?"
90
+ (src = path) && break if test(?e, path)
91
+ }
92
+ if src
93
+ [@rac.file(:__caller__ => @ch, name => src, &@block)]
94
+ else
95
+ nil
96
+ end
97
+ else
98
+ nil
99
+ end
100
+ end
101
+ def define_hook
102
+ @rac.resolve_hooks << self
103
+ end
104
+ def each_target &block
105
+ @rac.cx.sys["#@target_dir/*"].each { |entry|
106
+ yield entry if entry =~ @target_rx
107
+ }
108
+ end
109
+ def candidates
110
+ sources.map { |src|
111
+ File.join(@target_dir, File.basename(src).sub_ext(@target))
112
+ }
113
+ end
114
+ def sources
115
+ # TODO: returning a file list would be more efficient
116
+ cl = []
117
+ @source_dirs.each { |dir|
118
+ cl.concat(@rac.cx.sys["#{dir}/*.#@source"])
119
+ }
120
+ cl
121
+ end
122
+ end # class Rant::Generators::DirectedRule
@@ -0,0 +1,258 @@
1
+
2
+ require 'rant/rantlib'
3
+
4
+ class Rant::Generators::Package
5
+
6
+ class << self
7
+ def rant_generate(app, ch, args, &block)
8
+ if !args || args.empty?
9
+ self.new(:app => app, :__caller__ => ch, &block)
10
+ elsif args.size == 1
11
+ pkg_name = case args.first
12
+ when String: args.first
13
+ when Symbol: args.first.to_s
14
+ else
15
+ app.abort("Package takes only one additional " +
16
+ "argument, which should be a string or symbol.")
17
+ end
18
+ self.new(:app => app, :__caller__ => ch,
19
+ :name => pkg_name, &block)
20
+ else
21
+ app.abort(app.pos_text(file, ln),
22
+ "Package takes only one additional argument, " +
23
+ "which should be a string or symbol.")
24
+ end
25
+ end
26
+ end
27
+
28
+ # A hash containing all package information.
29
+ attr_reader :data
30
+ # Directory where packages go to. Defaults to "pkg".
31
+ attr_accessor :pkg_dir
32
+
33
+ def initialize(opts = {})
34
+ @rac = opts[:app] || Rant.rantapp
35
+ @pkg_dir = "pkg"
36
+ @pkg_dir_task = nil
37
+ @dist_dir_task = nil
38
+ @tar_task = nil
39
+ @zip_task = nil
40
+ @package_task = nil
41
+ name = opts[:name]
42
+ @ch = opts[:__caller__] || Rant::Lib.parse_caller_elem(caller[0])
43
+ unless name
44
+ # TODO: pos_text
45
+ @rac.warn_msg(@rac.pos_text(@ch[:file], @ch[:ln]),
46
+ "No package name given, using directory name.")
47
+ # use directory name as project name
48
+ name = File.split(Dir.pwd)[1]
49
+ # reset name if it contains a slash or a backslash
50
+ name = nil if name =~ /\/|\\/
51
+ end
52
+ @data = { "name" => name }
53
+
54
+ yield self if block_given?
55
+ end
56
+
57
+ def name
58
+ @data["name"]
59
+ end
60
+
61
+ def version
62
+ @data["version"]
63
+ end
64
+
65
+ def version=(str)
66
+ unless String === str
67
+ @rac.abort_at(@ch, "version has to be a String")
68
+ end
69
+ @data["version"] = str
70
+ end
71
+
72
+ def files
73
+ @data["files"]
74
+ end
75
+
76
+ def files=(list)
77
+ unless Array === list || ::Rant::FileList === List
78
+ if list.respond_to? :to_ary
79
+ list = list.to_ary
80
+ else
81
+ @rac.abort_at(@ch,
82
+ "files must be an Array or FileList")
83
+ end
84
+ end
85
+ @data["files"] = list
86
+ end
87
+
88
+ def validate_attrs(pkg_type = :general)
89
+ %w(name files).each { |a|
90
+ pkg_requires_attr a
91
+ }
92
+ end
93
+ private :validate_attrs
94
+
95
+ def pkg_requires_attr(attr_name)
96
+ unless @data[attr_name]
97
+ @rac.abort("Packaged defined: " +
98
+ @rac.pos_text(@ch[:file], @ch[:ln]),
99
+ "`#{attr_name}' attribute required")
100
+ end
101
+ end
102
+
103
+ def pkg_dir_task
104
+ return if @pkg_dir_task
105
+ if @dist_dir_task
106
+ # not ideal but should work: If only the gem task will
107
+ # be run, dist dir creation wouldn't be necessary
108
+ return @pkg_dir_task = @dist_dir_task
109
+ end
110
+ @pkg_dir_task = @rac.gen(
111
+ ::Rant::Generators::Directory, @pkg_dir)
112
+ end
113
+
114
+ def dist_dir_task
115
+ return if @dist_dir_task
116
+ pkg_name = pkg_dist_dir
117
+ dist_dir = pkg_dist_dir
118
+ @dist_dir_task = @rac.gen(Rant::Generators::Directory,
119
+ dist_dir => files) { |t|
120
+ # ensure to create new and empty destination directory
121
+ if Dir.entries(dist_dir).size > 2 # "." and ".."
122
+ @rac.sys.rm_rf(dist_dir)
123
+ @rac.sys.mkdir(dist_dir)
124
+ end
125
+ # evaluate directory structure first
126
+ dirs = []
127
+ fl = []
128
+ files.each { |e|
129
+ if test(?d, e)
130
+ dirs << e unless dirs.include? e
131
+ else # assuming e is a file
132
+ fl << e
133
+ dir = File.dirname(e)
134
+ dirs << dir unless dir == "." || dirs.include?(dir)
135
+ end
136
+ }
137
+ # create directory structure
138
+ dirs.each { |dir|
139
+ dest = File.join(dist_dir, dir)
140
+ @rac.sys.mkpath(dest) unless test(?d, dest)
141
+ }
142
+ # link or copy files
143
+ fl.each { |f|
144
+ dest = File.join(dist_dir, f)
145
+ @rac.sys.safe_ln(f, dest)
146
+ }
147
+ }
148
+ end
149
+
150
+ def tar_task(tname = :tar)
151
+ validate_attrs
152
+ # Create tar task first to ensure that a pending description
153
+ # is used for the tar task and not for the dist dir task.
154
+ pkg_name = tar_pkg_path
155
+ pkg_files = files
156
+ if tname
157
+ # shortcut task
158
+ @rac.task({:__caller__ => @ch, tname => pkg_name})
159
+ end
160
+ # actual tar-creating task
161
+ @tar_task = @rac.file(:__caller__ => @ch,
162
+ pkg_name => [pkg_dist_dir] + pkg_files) { |t|
163
+ @rac.sys.cd(@pkg_dir) {
164
+ @rac.sys %W(tar zcf #{tar_pkg_name} #{pkg_base_name})
165
+ }
166
+ }
167
+ dist_dir_task
168
+ end
169
+
170
+ def zip_task(tname = :zip)
171
+ validate_attrs
172
+ # Create zip task first to ensure that a pending description
173
+ # is used for the zip task and not for the dist dir task.
174
+ pkg_name = zip_pkg_path
175
+ pkg_files = files
176
+ if tname
177
+ # shortcut task
178
+ @rac.task({:__caller__ => @ch, tname => pkg_name})
179
+ end
180
+ # actual zip-creating task
181
+ @zip_task = @rac.file(:__caller__ => @ch,
182
+ pkg_name => [pkg_dist_dir] + pkg_files) { |t|
183
+ @rac.sys.cd(@pkg_dir) {
184
+ # zip options:
185
+ # y: store symlinks instead of referenced files
186
+ # r: recurse into directories
187
+ # q: quiet operation
188
+ @rac.sys %W(zip -yqr #{zip_pkg_name} #{pkg_base_name})
189
+ }
190
+ }
191
+ dist_dir_task
192
+ end
193
+
194
+ # Create a task which runs gem/zip/tar tasks.
195
+ def package_task(tname = :package)
196
+ def_tasks = [@tar_task, @zip_task].compact
197
+ if def_tasks.empty?
198
+ # take description for overall package task
199
+ pdesc = @rac.pop_desc
200
+ unless def_available_tasks
201
+ @rac.desc pdesc
202
+ @rac.warn_msg("No tools for packaging available (tar, zip):",
203
+ "Can't generate task `#{tname}'.")
204
+ return
205
+ end
206
+ @rac.desc pdesc
207
+ end
208
+ pre = []
209
+ pre << tar_pkg_path if @tar_task
210
+ pre << zip_pkg_path if @zip_task
211
+ pre << gem_pkg_path if @gem_task
212
+ @rac.task(:__caller__ => @ch, tname => pre)
213
+ end
214
+
215
+ # Returns true if at least one task was defined.
216
+ def def_available_tasks
217
+ defined = false
218
+ if Rant::Env.have_tar?
219
+ # we don't create shortcut tasks, hence nil as argument
220
+ self.tar_task(nil)
221
+ defined = true
222
+ end
223
+ if Rant::Env.have_zip?
224
+ self.zip_task(nil)
225
+ defined = true
226
+ end
227
+ defined
228
+ end
229
+
230
+ def pkg_base_name
231
+ unless name
232
+ @rac.abort(@rac.pos_text(@ch[:file], @ch[:ln]),
233
+ "`name' required for packaging")
234
+ end
235
+ version ? "#{name}-#{version}" : name
236
+ end
237
+
238
+ def tar_pkg_name
239
+ pkg_base_name + ".tar.gz"
240
+ end
241
+
242
+ def tar_pkg_path
243
+ pkg_dist_dir + ".tar.gz"
244
+ end
245
+
246
+ def zip_pkg_name
247
+ pkg_base_name + ".zip"
248
+ end
249
+
250
+ def zip_pkg_path
251
+ pkg_dist_dir + ".zip"
252
+ end
253
+
254
+ def pkg_dist_dir
255
+ @pkg_dir ? File.join(@pkg_dir, pkg_base_name) : pkg_base_name
256
+ end
257
+
258
+ end # class Rant::Generators::Package