rant 0.3.4 → 0.3.6

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.
@@ -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