rant 0.4.2 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
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
data/NEWS CHANGED
@@ -1,6 +1,20 @@
1
1
 
2
2
  = Rant NEWS
3
3
 
4
+ == Rant 0.4.4
5
+
6
+ Besides internal changes, this release is backwards compatible to
7
+ 0.4.2.
8
+
9
+ Fixes and minor improvements:
10
+ * Rant is Ruby 1.8.0 compatible now.
11
+ * Fixes for filelists.
12
+ * A fix for Directory generator (and thus dependent features).
13
+
14
+ New features:
15
+ * Optional recognition of file changes based on MD5 checksums.
16
+ Read doc/md5.rdoc[link:files/doc/md5_rdoc.html] for documentation.
17
+
4
18
  == Rant 0.4.2
5
19
 
6
20
  This is mainly a bugfix release and thus fully backwards compatible to
data/README CHANGED
@@ -16,6 +16,8 @@ Rant currently features:
16
16
  so you don't depend on an rant installation anymore.
17
17
  * Creating gzipped tar and zip archives -- without installing
18
18
  additional software.
19
+ * Optional recognition of file changes based on MD5 checksums instead
20
+ of file modification times.
19
21
  * Primitive support for compiling C# sources portably with csc, cscc
20
22
  and mcs.
21
23
  * Dependency checking for C/C++ source files.
@@ -28,7 +30,7 @@ basic example of rant usage:
28
30
  A file called +Rantfile+ contains the code:
29
31
 
30
32
  file "backup/data" => "data" do |t|
31
- sys.cp "data", t.name
33
+ sys.cp t.source, t.name
32
34
  end
33
35
 
34
36
  Running rant in the directory of this file:
@@ -39,7 +41,7 @@ Running rant in the directory of this file:
39
41
  will ensure that the "data" file in the "backup" directory is up to
40
42
  date.
41
43
 
42
- This document was written for version 0.4.2 of Rant. Most things
44
+ This document was written for version 0.4.4 of Rant. Most things
43
45
  described here will work for older/newer versions of Rant, but look at
44
46
  the README file in the Rant distribution you've installed for exact
45
47
  documentation of your Rant version.
@@ -49,9 +51,10 @@ documentation of your Rant version.
49
51
  The newest version of this document can be found at
50
52
  http://make.rubyforge.org.
51
53
 
52
- For further information, feature requests, bugreports or comments
53
- visit the
54
- {RubyForge site for Rant}[http://rubyforge.org/projects/make/].
54
+ For further information, feature requests, bugreports or comments join
55
+ the mailing list
56
+ {make-cafe}[http://rubyforge.org/mailman/listinfo/make-cafe] or visit
57
+ the {RubyForge site for Rant}[http://rubyforge.org/projects/make/].
55
58
 
56
59
  Also feel free to contact the author directly by sending an email to
57
60
  mailto:langstefan@gmx.at.
@@ -74,6 +77,8 @@ Advanced Rantfiles::
74
77
  read doc/advanced.rdoc[link:files/doc/advanced_rdoc.html]
75
78
  Packaging (creating zip/tgz archives)::
76
79
  read doc/package.rdoc[link:files/doc/package_rdoc.html]
80
+ Using MD5 checksums instead of file modification times::
81
+ read doc/md5.rdoc[link:files/doc/md5_rdoc.html]
77
82
  Compiling C/C++::
78
83
  read doc/c.rdoc[link:files/doc/c_rdoc.html]
79
84
  Using the Configure plugin::
@@ -204,7 +209,8 @@ Rant was tested on:
204
209
 
205
210
  System Ruby version
206
211
  =======================================================
207
- Linux 1.8.1
212
+ Linux 1.8.0
213
+ 1.8.1
208
214
  1.8.2
209
215
  1.8.3 preview 1
210
216
  1.9
@@ -215,7 +221,7 @@ Rant was tested on:
215
221
  It *should* run on most platforms where Ruby runs, but you never
216
222
  know...
217
223
 
218
- If you encounter problems with Rant on any platform (with Ruby 1.8.1
224
+ If you encounter problems with Rant on any platform (with Ruby 1.8.0
219
225
  or higher) please write a bugreport!
220
226
 
221
227
  === Why did you write another build tool?
data/Rantfile CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
2
  # Rantfile for Rant :)
3
3
 
4
+ import "md5"
4
5
  import %w(rubytest rubydoc rubypackage autoclean win32/rubycmdwrapper)
5
6
 
6
7
  task :default => :test
@@ -8,6 +9,10 @@ task :default => :test
8
9
  dist_files = sys["{bin,lib,test,doc,misc}/**/*"]
9
10
  dist_files.shun("html", "coverage")
10
11
  dist_files += sys["*"].no_dir.exclude("InstalledFiles", "Session.vim")
12
+
13
+ # remove when compiler stuff is getting useful...
14
+ dist_files.exclude "lib/rant/compiler*", "lib/rant/import/c/program.rb"
15
+
11
16
  rdoc_opts = %w(-S -c UTF-8 --title Rant --main README)
12
17
 
13
18
  gen RubyPackage, "rant" do |t|
@@ -103,6 +108,12 @@ gen RubyTest, :tall do |g|
103
108
  g.test_files = sys["test/**/test_*.rb"]
104
109
  end
105
110
 
111
+ task :t180 do |t|
112
+ # my installed testrb version doesn't work with ruby-1.8.0
113
+ sys.cd "test"
114
+ sys "ruby180 ts_all.rb"
115
+ end
116
+
106
117
  desc "Remove autogenerated files."
107
118
  gen AutoClean, :clean
108
119
  var[:clean].include %w(
data/doc/md5.rdoc ADDED
@@ -0,0 +1,49 @@
1
+
2
+ == Using MD5 checksums to detect file changes
3
+
4
+ Most build tools rely on the modification time of a file which is
5
+ updated usually when a program writes to the file. But this is not
6
+ always as accurate as one would like, e.g. when you edit a C source
7
+ file to simple change/add a comment, the compiler will probably
8
+ produce the same object file as before. The build tool recognizes that
9
+ the file modification time of the object file is newer than that of
10
+ the target program and rebuilds the target program.
11
+
12
+ With MD5 checksums instead of file modification times, the build tool
13
+ recognizes that the object file didn't change and thus the target
14
+ program doesn't need to be rebuilt.
15
+
16
+ Even worse is the case where the file modification time isn't updated
17
+ or is corrupted and the build tool thinks a source file hasn't changed
18
+ were in fact it has! In other words, with MD5 checksums, the build
19
+ tool recognizes when the *contents* of a file changes.
20
+
21
+ To enable this nice feature for your project, put this single line at
22
+ the top (before other +import+, +task+, +file+ or +gen+ statements) of
23
+ the Rantfile:
24
+
25
+ import "md5"
26
+
27
+ Note that Rant saves the checksums after a build in a file called
28
+ <tt>.rant.meta</tt>. If you remove this file, Rant looses all
29
+ information about the last builds, and so will rebuild all targets on
30
+ invocation.
31
+
32
+ If you want to switch back to modification time based builds, simply
33
+ remove the <tt>import "md5"</tt> statement and remove the file
34
+ <tt>.rant.meta</tt>.
35
+
36
+ == See also
37
+
38
+ Writing an Rantfile::
39
+ doc/rantfile.rdoc[link:files/doc/rantfile_rdoc.html]
40
+ Advanced Rantfiles::
41
+ doc/advanced.rdoc[link:files/doc/advanced_rdoc.html]
42
+ Support for C/C++::
43
+ doc/c.rdoc[link:files/doc/c_rdoc.html]
44
+ Packaging::
45
+ doc/package.rdoc[link:files/doc/package_rdoc.html]
46
+ Ruby project howto::
47
+ doc/rubyproject.rdoc[link:files/doc/rubyproject_rdoc.html]
48
+ Rant Overview::
49
+ README[link:files/README.html]
data/doc/rantfile.rdoc CHANGED
@@ -64,7 +64,7 @@ Running rant now:
64
64
  mytask
65
65
 
66
66
  Add prerequisites to create relations between tasks:
67
- task :first => [:t1, :t2] do
67
+ task :first => [:t1, :t2] do |t|
68
68
  puts t.name
69
69
  end
70
70
  task :t1 do |t|
@@ -0,0 +1,193 @@
1
+
2
+ # coregen.rb - Generators available in all Rantfiles.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ module Rant
7
+ module Generators
8
+ class Task
9
+ def self.rant_gen(rac, ch, args, &block)
10
+ unless args.size == 1
11
+ rac.abort("Task takes only one argument " +
12
+ "which has to be like one given to the " +
13
+ "`task' function")
14
+ end
15
+ rac.prepare_task(args.first, nil, ch) { |name,pre,blk|
16
+ rac.node_factory.new_custom(rac, name, pre, block)
17
+ }
18
+ end
19
+ end
20
+ class Directory
21
+ # Generate a task for making a directory path.
22
+ # Prerequisites can be given, which will be added as
23
+ # prerequistes for the _last_ directory.
24
+ #
25
+ # A special feature is used if you provide a block: The
26
+ # block will be called after complete directory creation.
27
+ # After the block execution, the modification time of the
28
+ # directory will be updated.
29
+ def self.rant_gen(rac, ch, args, &block)
30
+ case args.size
31
+ when 1
32
+ name, pre = rac.normalize_task_arg(args.first, ch)
33
+ self.task(rac, ch, name, pre, &block)
34
+ when 2
35
+ basedir = args.shift
36
+ if basedir.respond_to? :to_str
37
+ basedir = basedir.to_str
38
+ else
39
+ rac.abort_at(ch,
40
+ "Directory: basedir argument has to be a string.")
41
+ end
42
+ name, pre = rac.normalize_task_arg(args.first, ch)
43
+ self.task(rac, ch, name, pre, basedir, &block)
44
+ else
45
+ rac.abort_at(ch, "Directory takes one argument, " +
46
+ "which should be like one given to the `task' command.")
47
+ end
48
+ end
49
+
50
+ # Returns the task which creates the last directory
51
+ # element (and has all other necessary directories as
52
+ # prerequisites).
53
+ def self.task(rac, ch, name, prerequisites=[], basedir=nil, &block)
54
+ dirs = ::Rant::Sys.split_path(name)
55
+ if dirs.empty?
56
+ rac.abort_at(ch,
57
+ "Not a valid directory name: `#{name}'")
58
+ end
59
+ path = basedir
60
+ last_task = nil
61
+ task_block = nil
62
+ desc_for_last = rac.pop_desc
63
+ dirs.each { |dir|
64
+ pre = [path]
65
+ pre.compact!
66
+ if dir.equal?(dirs.last)
67
+ rac.cx.desc desc_for_last
68
+
69
+ # add prerequisites to pre
70
+ # if prerequisites is a FileList: there is
71
+ # only one save (no later removal) way to add
72
+ # an entry: with <<
73
+ dp = prerequisites.dup
74
+ pre.each { |elem| dp << elem }
75
+ pre = dp
76
+
77
+ task_block = block
78
+ end
79
+ path = path.nil? ? dir : File.join(path, dir)
80
+ last_task = rac.prepare_task({:__caller__ => ch,
81
+ path => pre}, task_block) { |name,pre,blk|
82
+ rac.node_factory.new_dir(rac, name, pre, blk)
83
+ }
84
+ }
85
+ last_task
86
+ end
87
+ end # class Directory
88
+ class SourceNode
89
+ def self.rant_gen(rac, ch, args)
90
+ unless args.size == 1
91
+ rac.abort_at(ch, "SourceNode takes one argument.")
92
+ end
93
+ if block_given?
94
+ rac.abort_at(ch, "SourceNode doesn't take a block.")
95
+ end
96
+ rac.prepare_task(args.first, nil, ch) { |name, pre, blk|
97
+ rac.node_factory.new_source(rac, name, pre, blk)
98
+ }
99
+ end
100
+ end
101
+ class Rule < ::Proc
102
+ # Generate a rule by installing an at_resolve hook for
103
+ # +rac+.
104
+ def self.rant_gen(rac, ch, args, &block)
105
+ unless args.size == 1
106
+ rac.abort_at(ch, "Rule takes only one argument.")
107
+ end
108
+ arg = args.first
109
+ target = nil
110
+ src_arg = nil
111
+ if Symbol === arg
112
+ target = ".#{arg}"
113
+ elsif arg.respond_to? :to_str
114
+ target = arg.to_str
115
+ elsif Regexp === arg
116
+ target = arg
117
+ elsif Hash === arg && arg.size == 1
118
+ arg.each_pair { |target, src_arg| }
119
+ src_arg = src_arg.to_str if src_arg.respond_to? :to_str
120
+ target = target.to_str if target.respond_to? :to_str
121
+ src_arg = ".#{src_arg}" if Symbol === src_arg
122
+ target = ".#{target}" if Symbol === target
123
+ else
124
+ rac.abort_at(ch, "Rule argument " +
125
+ "has to be a hash with one key-value pair.")
126
+ end
127
+ esc_target = nil
128
+ target_rx = case target
129
+ when String
130
+ esc_target = Regexp.escape(target)
131
+ /#{esc_target}$/
132
+ when Regexp
133
+ target
134
+ else
135
+ rac.abort_at(ch, "rule target has " +
136
+ "to be a string or regular expression")
137
+ end
138
+ src_proc = case src_arg
139
+ when String
140
+ unless String === target
141
+ rac.abort(ch, "rule target has to be a string " +
142
+ "if source is a string")
143
+ end
144
+ lambda { |name| name.sub(/#{esc_target}$/, src_arg) }
145
+ when Proc: src_arg
146
+ when nil: lambda { |name| [] }
147
+ else
148
+ rac.abort_at(ch, "rule source has to be " +
149
+ "String or Proc")
150
+ end
151
+ blk = self.new { |task_name|
152
+ if target_rx =~ task_name
153
+ have_src = true
154
+ src = src_proc[task_name]
155
+ if src.respond_to? :to_ary
156
+ src.each { |f|
157
+ if rac.resolve(f).empty? && !test(?e, f)
158
+ have_src = false
159
+ break
160
+ end
161
+ }
162
+ else
163
+ if rac.resolve(src).empty? && !test(?e, src)
164
+ have_src = false
165
+ end
166
+ end
167
+ if have_src
168
+ t = rac.file(:__caller__ => ch,
169
+ task_name => src_proc[task_name], &block)
170
+ t.project_subdir = rac.current_subdir
171
+ [t]
172
+ end
173
+ end
174
+ }
175
+ blk.target_rx = target_rx
176
+ rac.resolve_hooks << blk
177
+ nil
178
+ end
179
+ attr_accessor :target_rx
180
+ end # class Rule
181
+ class Action
182
+ def self.rant_gen(rac, ch, args, &block)
183
+ unless args.empty?
184
+ rac.warn_msg(rac.pos_text(ch[:file], ch[:ln]),
185
+ "Action doesn't take arguments.")
186
+ end
187
+ unless (rac[:tasks] || rac[:stop_after_load])
188
+ yield
189
+ end
190
+ end
191
+ end
192
+ end # module Generators
193
+ end # module Rant
@@ -42,6 +42,8 @@ module Rant::Generators::Archive
42
42
  end
43
43
  def rubyzip fn, files, opts = {:recurse => false}
44
44
  require 'rant/archive/rubyzip'
45
+ # rubyzip creates only a new file if fn doesn't exist
46
+ @rac.sys.rm_f fn if test ?e, fn
45
47
  @rac.cmd_msg "rubyzip #{fn}"
46
48
  Rant::Archive::Rubyzip::ZipFile.open fn,
47
49
  Rant::Archive::Rubyzip::ZipFile::CREATE do |z|
@@ -160,6 +160,14 @@ module Rant::Generators::Archive
160
160
  end
161
161
  # remove leading `./' relicts
162
162
  @res_files = fl.lazy_map! { |fn| fn.sub(/^\.\/(?=.)/,'') }
163
+ if defined?(@dist_path) && @dist_path
164
+ # Remove entries from the dist_path directory, which
165
+ # would create some sort of weird recursion.
166
+ #
167
+ # Normally, the Rantfile writer should care himself,
168
+ # but since I tapped into this trap frequently now...
169
+ @res_files.exclude(/^#{Regexp.escape @dist_path}/)
170
+ end
163
171
  @res_files.lazy_uniq!.lazy_sort!
164
172
  end
165
173
 
@@ -187,7 +195,7 @@ module Rant::Generators::Archive
187
195
  def define_manifest_task
188
196
  return @manifest_task if @manifest_task
189
197
  @manifest_task =
190
- @rac.gen ::Rant::Task, @manifest do |t|
198
+ @rac.gen ::Rant::Generators::Task, @manifest do |t|
191
199
  def t.each_target
192
200
  goto_task_home
193
201
  yield name
@@ -249,12 +257,12 @@ module Rant::Generators::Archive
249
257
  def define_task_for_dir(&block)
250
258
  return @pkg_task if @pkg_task
251
259
 
252
- get_files # set @res_files
253
260
  @dist_dirname = File.split(name).last
254
261
  @dist_dirname << "-#@version" if @version
255
262
  @dist_root, = File.split path
256
263
  @dist_path = (@dist_root == "." ?
257
264
  @dist_dirname : File.join(@dist_root, @dist_dirname))
265
+ get_files # set @res_files
258
266
 
259
267
  targ = {get_archive_path => [@dist_path]}
260
268
  #STDERR.puts "basedir: #{basedir}, fn: #@archive_path"
@@ -58,15 +58,24 @@ class Rant::Generators::AutoClean
58
58
  end
59
59
  }
60
60
  end
61
+ common = rac.var._get("__autoclean_common__")
62
+ if common
63
+ rac.rantfiles.each{ |rf|
64
+ sd = rf.project_subdir
65
+ common.each { |fn|
66
+ path = sd.empty? ? fn : File.join(sd, fn)
67
+ clean rac, path
68
+ }
69
+ }
70
+ end
71
+ t.goto_task_home
61
72
  end
62
73
  end
63
74
  def self.clean(rac, entry)
64
- if test ?e, entry
65
- if test ?f, entry
66
- rac.cx.sys.rm_f entry
67
- else
68
- rac.cx.sys.rm_rf entry
69
- end
70
- end
75
+ if test ?f, entry
76
+ rac.cx.sys.rm_f entry
77
+ elsif test ?e, entry
78
+ rac.cx.sys.rm_rf entry
79
+ end
71
80
  end
72
81
  end
@@ -91,7 +91,7 @@ class Rant::Generators::C::Dependencies
91
91
  f_task = tmp_rac.tasks[cf.to_str]
92
92
  deps = f_task ? f_task.prerequisites : nil
93
93
  if !deps or File.mtime(cf) > depfile_ts
94
- rac.cmd_msg "parsing #{cf}"
94
+ rac.cmd_msg "scanning #{cf}"
95
95
  std_includes, local_includes =
96
96
  ::Rant::C::Include.parse_includes(File.read(cf))
97
97
  deps = []
@@ -93,7 +93,7 @@ class Rant::Generators::DirectedRule
93
93
  # pre 0.3.7
94
94
  #[@rac.file(:__caller__ => @ch, name => src, &@block)]
95
95
  [@rac.prepare_task({name => src}, @block, @ch) { |name,pre,blk|
96
- ::Rant::AutoSubFileTask.new(@rac, name, pre, &blk)
96
+ @rac.node_factory.new_auto_subfile(@rac, name, pre, blk)
97
97
  }]
98
98
  else
99
99
  nil
@@ -105,7 +105,7 @@ class Rant::Generators::DirectedRule
105
105
  def define_hook
106
106
  @rac.resolve_hooks << self
107
107
  end
108
- def each_target &block
108
+ def each_target(&block)
109
109
  @rac.cx.sys["#@target_dir/*"].each { |entry|
110
110
  yield entry if entry =~ @target_rx
111
111
  }
@@ -0,0 +1,16 @@
1
+
2
+ # md5.rb - Use md5 checksums to recognize source changes.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ #require 'rant/import/signature/md5' #rant-import:uncomment
7
+ #require 'rant/import/metadata' #rant-import:uncomment
8
+ #require 'rant/import/nodes/signed' #rant-import:uncomment
9
+
10
+ module Rant
11
+ def self.init_import_md5(rac, *rest)
12
+ rac.import "signature/md5"
13
+ rac.import "metadata"
14
+ rac.import "nodes/signed"
15
+ end
16
+ end
@@ -0,0 +1,162 @@
1
+
2
+ # metadata.rb - Management of meta-information for Rant targets.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ module Rant
7
+
8
+ def self.init_import_metadata(rac, *rest)
9
+ mi = MetaData::Interface.new(rac)
10
+ rac.var._set("__metadata__", mi)
11
+ rac.at_return(&mi.method(:save))
12
+ rac.var._init("__autoclean_common__", []) << MetaData::META_FN
13
+ end
14
+
15
+ module MetaData
16
+
17
+ META_FN = ".rant.meta"
18
+
19
+ class Interface
20
+
21
+ def initialize(rac)
22
+ @rac = rac
23
+ # the keys in this hash are project directory names,
24
+ # the corresponding values are again hashes and their
25
+ # keys are target names
26
+ @store = {}
27
+ # just a set
28
+ @modified_dirs = {}
29
+ # just a set
30
+ @read_dirs = {}
31
+ end
32
+
33
+ # Fetch the meta value associated with the given key for
34
+ # target in dir. Note that the value will probably end in
35
+ # a newline. Very important is, that the +dir+ (third
36
+ # argument, relative to the projects root directory) has
37
+ # to be the current working directory! An example:
38
+ # project root directory: /home/foo/myproject
39
+ # dir: bar
40
+ # => Dir.pwd has to be: /home/foo/myproject/bar
41
+ #
42
+ # Returns nil only if the value doesn't exist.
43
+ def fetch(key, target, dir=@rac.current_subdir)
44
+ # first check if a value for the given key, target and
45
+ # dir already exists
46
+ dstore = @store[dir]
47
+ if dstore
48
+ tstore = dstore[target]
49
+ if tstore
50
+ val = tstore[key]
51
+ return val if val
52
+ end
53
+ end
54
+ # check if the meta file in dir was already read
55
+ unless @read_dirs.include? dir
56
+ read_meta_file_in_dir(dir)
57
+ end
58
+ tstore = @store[dir][target]
59
+ tstore[key] if tstore
60
+ end
61
+
62
+ # Set the key-value pair for the given target in dir. Note
63
+ # that if value.class is not String, the value will be
64
+ # replaced with a newline! key should also be a string and
65
+ # mustn't contain a newline.
66
+ #
67
+ # Returns nil.
68
+ def set(key, value, target, dir=@rac.current_subdir)
69
+ value = "\n" unless value.class == String
70
+ @modified_dirs[dir] ||= true
71
+ dstore = @store[dir]
72
+ unless dstore
73
+ @store[dir] = {target => {key => value}}
74
+ return
75
+ end
76
+ tstore = dstore[target]
77
+ if tstore
78
+ tstore[key] = value
79
+ else
80
+ dstore[target] = {key => value}
81
+ end
82
+ nil
83
+ end
84
+
85
+ # Assumes to be called from the projects root directory.
86
+ def save
87
+ @modified_dirs.each_key { |dir|
88
+ write_dstore(@store[dir], dir)
89
+ }
90
+ nil
91
+ end
92
+
93
+ private
94
+ # assumes that dir is already the current working
95
+ # directory
96
+ def read_meta_file_in_dir(dir)
97
+ #puts "in dir: #{dir}, pwd: #{Dir.pwd}"
98
+ @read_dirs[dir] = true
99
+ #fn = dir.empty? ? META_FN : File.join(dir, META_FN)
100
+ fn = META_FN
101
+ dstore = @store[dir]
102
+ @store[dir] = dstore = {} unless dstore
103
+ return unless File.exist? fn
104
+ open fn do |f|
105
+ # first line should only contain "Rant", later
106
+ # Rant versions can add version and other
107
+ # information
108
+ invalid_format(fn) unless f.readline == "Rant\n"
109
+ until f.eof?
110
+ target_name = f.readline.chomp!
111
+ dstore[target_name] = read_target_data(f)
112
+ end
113
+ end
114
+ rescue
115
+ invalid_format(fn)
116
+ end
117
+
118
+ def read_target_data(file)
119
+ h = {}
120
+ num_of_entries = file.readline.to_i
121
+ num_of_entries.times { |i|
122
+ key = file.readline.chomp!
123
+ value = ""
124
+ file.readline.to_i.times { |j|
125
+ value << file.readline
126
+ }
127
+ value.chomp!
128
+ h[key] = value
129
+ }
130
+ h
131
+ end
132
+
133
+ # assumes to be called from the projects root directory
134
+ def write_dstore(dstore, dir)
135
+ fn = dir.empty? ? META_FN : File.join(dir, META_FN)
136
+ target = sigs = key = value = lines = nil
137
+ open fn, "w" do |f|
138
+ f.puts "Rant"
139
+ dstore.each { |target, sigs|
140
+ f.puts target
141
+ f.puts sigs.size
142
+ sigs.each { |key, value|
143
+ f.puts key
144
+ lines = value.split(/\n/)
145
+ f.puts lines.size
146
+ f.puts lines
147
+ }
148
+ }
149
+ end
150
+ end
151
+
152
+ def invalid_format(fn)
153
+ raise Rant::Error, "The file `#{fn}' is used by " +
154
+ "Rant to store meta information, it is in an " +
155
+ "invalid state. Check that it doesn't contain " +
156
+ "important data, remove it and retry."
157
+ end
158
+
159
+ end
160
+
161
+ end # module MetaData
162
+ end # module Rant