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