nwn-lib 0.4.6 → 0.4.7

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.
data/lib/nwn/res.rb CHANGED
@@ -11,13 +11,15 @@ module NWN
11
11
 
12
12
  # Create a new index to +filename+, optionally specifying +io+.
13
13
  def self.new_from filename, io = nil
14
+ FileTest.exists?(filename) or raise Errno::ENOENT unless io
15
+
14
16
  filename = File.expand_path(filename)
15
17
  base = File.basename(filename).split(".")[0..-2].join(".").downcase
16
- ext = File.extname(filename)[1..-1].downcase
18
+ ext = File.extname(filename)[1..-1].downcase rescue ""
17
19
  res_type = NWN::Resources::Extensions[ext] or raise ArgumentError,
18
20
  "Not a valid extension: #{ext.inspect} (while packing #{filename})"
19
21
 
20
- ContentObject.new(base, res_type, io || filename, 0, io.size || File.stat(filename).size)
22
+ ContentObject.new(base, res_type, io || filename, 0, io ? io.size : File.stat(filename).size)
21
23
  end
22
24
 
23
25
  def initialize resref, res_type, io = nil, offset = nil, size = nil
@@ -39,7 +41,7 @@ module NWN
39
41
  # and do it yourself, observing ContentObject#offset and ContentObject#size.
40
42
  def get
41
43
  if @io.respond_to?(:read)
42
- @io.seek(@offset) if @offset
44
+ @io.seek(@offset ? @offset : 0)
43
45
  d = @io.read(self.size)
44
46
  raise IOError,
45
47
  "not enough data available while reading #{self.filename}" if
@@ -52,7 +54,7 @@ module NWN
52
54
 
53
55
  # Get the canonical filename of this object.
54
56
  def filename
55
- @resref + "." + self.extension
57
+ @resref + "." + (self.extension || "unknown-#{@res_type}")
56
58
  end
57
59
 
58
60
  # Get the extension of this object.
data/lib/nwn/scripting.rb CHANGED
@@ -33,7 +33,7 @@ module NWN::Gff::Scripting
33
33
  fn, hash = $satisfy_loaded[object.object_id]
34
34
  if fn
35
35
  if hash != object.hash
36
- File.open(fn, "w") {|f|
36
+ File.open(fn, "wb") {|f|
37
37
  NWN::Gff.write(f, NWN::Gff.guess_file_format(fn), object)
38
38
  }
39
39
  log "saved #{object.to_s} -> #{fn}"
@@ -103,7 +103,7 @@ module NWN::Gff::Scripting
103
103
  fn = what.shift
104
104
  io = case fn
105
105
  when String
106
- IO.read(fn)
106
+ File.new(fn, "r")
107
107
  when IO
108
108
  fn
109
109
  else
data/lib/nwn/settings.rb CHANGED
@@ -1,3 +1,15 @@
1
+ module NWN
2
+
3
+ # This writes a debug message to stderr if the environment
4
+ # variable +NWN_LIB_DEBUG+ is set to non-nil or $DEBUG is
5
+ # true (ruby -d).
6
+ def self.log_debug msg
7
+ return false unless ENV['NWN_LIB_DEBUG'] || $DEBUG
8
+ $stderr.puts "(nwn-lib debug) %s: %s" % [caller[0].to_s, msg]
9
+ true
10
+ end
11
+ end
12
+
1
13
  if ENV['NWN_LIB_2DA_LOCATION'] && ENV['NWN_LIB_2DA_LOCATION'] != ""
2
14
  NWN::TwoDA::Cache.setup ENV['NWN_LIB_2DA_LOCATION']
3
15
  end
data/lib/nwn/twoda.rb CHANGED
@@ -109,9 +109,6 @@ module NWN
109
109
  # Will raise an ArgumentError if the given +bytes+ do
110
110
  # not contain a valid 2DA header, or the file is so badly
111
111
  # misshaped that it will not ever be parsed correctly by NWN1.
112
- #
113
- # Will complain about everything mismatching it finds
114
- # on $stderr if $DEBUG is set (-d).
115
112
  def parse bytes
116
113
  magic, *data = *bytes.split(/\r?\n/).map {|v| v.strip }
117
114
 
@@ -135,27 +132,27 @@ module NWN
135
132
  data.each_with_index {|row, idx|
136
133
  id = row.shift
137
134
 
138
- $stderr.puts "Warning: invalid ID in line #{idx}: #{id.inspect}" if $DEBUG && $id !~ /^\d+$/
135
+ NWN.log_debug "Warning: invalid ID in line #{idx}: #{id.inspect}" if $id !~ /^\d+$/
139
136
 
140
137
  id = id.to_i + id_offset
141
138
 
142
139
  # Its an empty row - NWN strictly numbers by counted lines - then so do we.
143
140
  while id > idx + idx_offset
144
- $stderr.puts "Warning: missing ID at #{id - id_offset}, fixing that for you." if $DEBUG
141
+ NWN.log_debug "Warning: missing ID at #{id - id_offset}, fixing that for you."
145
142
  idx_offset += 1
146
143
  end
147
144
 
148
145
  # NWN automatically increments duplicate IDs - so do we.
149
146
  while id < idx + idx_offset
150
- $stderr.puts "Warning: duplicate ID found at row #{idx} (id: #{id}); fixing that for you." if $DEBUG
147
+ NWN.log_debug "Warning: duplicate ID found at row #{idx} (id: #{id}); fixing that for you."
151
148
  id_offset += 1
152
149
  id += 1
153
150
  end
154
151
 
155
152
  # NWN fills in missing columns with an empty value - so do we.
156
- $stderr.puts "Warning: row #{id} (real: #{id - id_offset}) misses " +
153
+ NWN.log_debug "Warning: row #{id} (real: #{id - id_offset}) misses " +
157
154
  "#{header.size - row.size} columns at the end, fixed" if
158
- row.size < header.size if $DEBUG
155
+ row.size < header.size
159
156
 
160
157
  row << "" while row.size < header.size
161
158
 
@@ -170,8 +167,8 @@ module NWN
170
167
  end
171
168
  }
172
169
 
173
- $stderr.puts "Warning: row #{idx} has too many cells (has #{k_row.size}, want <= #{header.size})" if
174
- $DEBUG && k_row.size > header.size
170
+ NWN.log_debug "Warning: row #{idx} has too many cells (has #{k_row.size}, want <= #{header.size})" if
171
+ k_row.size > header.size
175
172
 
176
173
  k_row.pop while k_row.size > header.size
177
174
  }
@@ -10,7 +10,7 @@ list += o['Equip_ItemList'].field_value if o['Equip_ItemList']
10
10
  list += o['ItemList'].field_value if o['ItemList']
11
11
  log "#{list.size} items found."
12
12
  list.each_with_index {|item, index|
13
- File.open(fname = "item_#{index}.uti", "w") {|file|
13
+ File.open(fname = "item_#{index}.uti", "wb") {|file|
14
14
  file.write item.to_gff("UTI")
15
15
  log "Written item: #{item['Tag'].field_value} as #{fname}"
16
16
  }
data/scripts/reformat_2da CHANGED
@@ -4,6 +4,6 @@
4
4
  ARGV.each {|f|
5
5
  log "Working on #{f} .."
6
6
  t = TwoDA::Table.parse(IO.read(f))
7
- File.open(f, "w") {|n| n.puts t.to_2da }
7
+ File.open(f, "wb") {|n| n.puts t.to_2da }
8
8
  log "done."
9
9
  }
@@ -0,0 +1,36 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "nwn-dsl" do
4
+ it_should_behave_like "bin helper"
5
+
6
+ it "runs empty scripts" do
7
+ t = Tempfile.new(@tmp)
8
+ t.close
9
+ run(t.path)
10
+ end
11
+
12
+ it "runs want/need scripts" do
13
+ t = Tempfile.new(@tmp)
14
+ t.write("need ARGV.shift, :bic")
15
+ t.close
16
+ run_bin(t.path, WELLFORMED_GFF_PATH) do |i,o,e|
17
+ e = e.read
18
+ o = o.read
19
+ e.should == "#{t.path}: satisfied #{WELLFORMED_GFF_PATH} -> <NWN::Gff::Struct BIC/V3.2, 129 fields>\n"
20
+ o.should == ""
21
+ end
22
+ end
23
+
24
+ it "logs to stderr" do
25
+ t = Tempfile.new(@tmp)
26
+ t.write("log 'hey'")
27
+ t.close
28
+ run_bin(t.path, WELLFORMED_GFF_PATH) do |i,o,e|
29
+ e = e.read
30
+ o = o.read
31
+ e.should == "#{t.path}: hey\n"
32
+ o.should == ""
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,118 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "nwn-erf" do
4
+ it_should_behave_like "bin helper"
5
+
6
+ # Create temporary testcase files.
7
+ before do
8
+ @target = File.join(@tmp, "nwn-lib-spec-target.erf")
9
+
10
+ @tmp0s = []
11
+ @tmp1s = []
12
+ for x in %w{aaa.tga bbbb.erf ccc.bmp 44.txt} do
13
+ File.open(File.join(@tmp, x), "w") do |fn|
14
+ fn.write(x)
15
+ end
16
+ @tmp0s << File.join(@tmp, x)
17
+ end
18
+ for x in %w{bbbbbbbbbbbbbbbbbbbb.bmp} do
19
+ File.open(File.join(@tmp, x), "w") do |fn|
20
+ fn.write(x)
21
+ end
22
+ @tmp1s << File.join(@tmp, x)
23
+ end
24
+ @tmp0s.sort!
25
+ @tmp1s.sort!
26
+ end
27
+
28
+ after do
29
+ @tmp0s.each {|f|
30
+ File.unlink(f.path) rescue nil
31
+ }
32
+ @tmp1s.each {|f|
33
+ File.unlink(f.path) rescue nil
34
+ }
35
+ File.unlink(@target) rescue nil
36
+ end
37
+
38
+ it "packs -0" do
39
+ run("-0", "-c", "-f", @target, *@tmp0s)
40
+ end
41
+
42
+ it "fails to pack -0 with 1.1 filenames" do
43
+ run_fail("-0", "-c", "-f", @target, *(@tmp0s + @tmp1s))
44
+ end
45
+
46
+ it "packs -1" do
47
+ run("-1", "-c", "-f", @target, *(@tmp0s + @tmp1s))
48
+ end
49
+
50
+ it "lists -0" do
51
+ run("-c", "-f", @target, *@tmp0s)
52
+ run_bin("-l", "-f", @target) do |i,o,e|
53
+ o = o.read
54
+ o.split(/\n/).sort.should == @tmp0s.map {|x| File.basename(x) }
55
+ end
56
+ end
57
+
58
+ it "lists -1" do
59
+ run("-1", "-c", "-f", @target, *@tmp1s)
60
+ run_bin("-l", "-f", @target) do |i,o,e|
61
+ o = o.read
62
+ o.split(/\n/).sort.should == @tmp1s.map {|x| File.basename(x) }
63
+ end
64
+ end
65
+
66
+ it "extracts -0" do
67
+ run("-c", "-f", @target, *@tmp0s)
68
+ @tmp0s.each {|f| File.unlink(f) }
69
+ run("-v", "-x", "-f", @target)
70
+ @tmp0s.each {|f| FileTest.exists?(f).should == true }
71
+ @tmp0s.each {|f| IO.read(f).should == File.basename(f) }
72
+ end
73
+
74
+ it "extracts -1" do
75
+ workon = @tmp0s + @tmp1s
76
+ run("-1", "-c", "-f", @target, *workon)
77
+ workon.each {|f| File.unlink(f) }
78
+ run("-v", "-x", "-f", @target)
79
+ workon.each {|f| FileTest.exists?(f).should == true }
80
+ workon.each {|f| IO.read(f).should == File.basename(f) }
81
+ end
82
+
83
+ it "creates haks" do
84
+ run("-H", "-c", "-f", @target, *@tmp0s)
85
+ IO.read(@target).should =~ /^HAK/
86
+ end
87
+
88
+ it "creates mods" do
89
+ run("-M", "-c", "-f", @target, *@tmp0s)
90
+ IO.read(@target).should =~ /^MOD/
91
+ end
92
+
93
+ it "creates erfs (by default)" do
94
+ run("-E", "-c", "-f", @target, *@tmp0s)
95
+ IO.read(@target).should =~ /^ERF/
96
+ run("-c", "-f", @target, *@tmp0s)
97
+ IO.read(@target).should =~ /^ERF/
98
+ end
99
+
100
+ it "adds files from existing archives" do
101
+ run("-c", "-f", @target, *(@tmp0s[1 .. -1]))
102
+ run("-a", "-f", @target, @tmp0s[0])
103
+ run_bin("-l", "-f", @target) do |i,o,e|
104
+ o = o.read
105
+ o.split(/\n/).sort.should == @tmp0s.map {|x| File.basename(x) }
106
+ end
107
+ end
108
+
109
+ it "removes files from existing archives" do
110
+ run("-c", "-f", @target, *@tmp0s)
111
+ run("-r", "-f", @target, File.basename(@tmp0s[0]))
112
+ run_bin("-l", "-f", @target) do |i,o,e|
113
+ o = o.read
114
+ o.split(/\n/).sort.should == @tmp0s[1 .. -1].map {|x| File.basename(x) }
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,90 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "nwn-gff" do
4
+ it_should_behave_like "bin helper"
5
+
6
+ NWN::Gff::FileFormats.each do |in_format|
7
+ inf = in_format.to_s
8
+
9
+ NWN::Gff::FileFormats.each do |out_format|
10
+ otf = out_format.to_s
11
+
12
+ it "converts #{inf} to #{otf}" do
13
+ # prepare the temp file
14
+ t = Tempfile.new(@tmp)
15
+ t.close
16
+
17
+ run("-lg", "-i", WELLFORMED_GFF_PATH, "-k", inf, "-o", t.path)
18
+ run("-l", inf, "-i", t.path, "-k", otf)
19
+ t.unlink
20
+ end
21
+ end
22
+ end
23
+
24
+ it "supports none GNU-style backup" do
25
+ t = Tempfile.new(@tmp)
26
+ t.close
27
+ run("-b", "none", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
28
+ FileTest.exists?(t.path).should == true
29
+ FileTest.exists?(t.path + "~").should == false
30
+ end
31
+
32
+ it "supports numbered GNU-style backup" do
33
+ t = Tempfile.new(@tmp)
34
+ t.close
35
+
36
+ run("-b", "numbered", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
37
+ FileTest.exists?(t.path).should == true
38
+ FileTest.exists?(t.path + "~0").should == true
39
+ FileTest.exists?(t.path + "~1").should == false
40
+
41
+ run("-b", "numbered", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
42
+ FileTest.exists?(t.path).should == true
43
+ FileTest.exists?(t.path + "~0").should == true
44
+ FileTest.exists?(t.path + "~1").should == true
45
+ FileTest.exists?(t.path + "~2").should == false
46
+
47
+ File.unlink(t.path + "~0")
48
+ File.unlink(t.path + "~1")
49
+ end
50
+
51
+ it "supports existing with no numbered backups GNU-style backup" do
52
+ t = Tempfile.new(@tmp)
53
+ t.close
54
+
55
+ FileTest.exists?(t.path + "~").should == false
56
+
57
+ run("-b", "existing", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
58
+ FileTest.exists?(t.path).should == true
59
+ FileTest.exists?(t.path + "~").should == true
60
+
61
+ File.unlink(t.path + "~")
62
+
63
+ FileUtils.cp(t.path, t.path + "~0")
64
+ run("-b", "existing", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
65
+ FileTest.exists?(t.path).should == true
66
+ FileTest.exists?(t.path + "~").should == false
67
+ FileTest.exists?(t.path + "~0").should == true
68
+ FileTest.exists?(t.path + "~1").should == true
69
+ FileTest.exists?(t.path + "~2").should == false
70
+
71
+ File.unlink( t.path + "~0")
72
+ File.unlink( t.path + "~1")
73
+ end
74
+
75
+
76
+ it "supports simple GNU-style backup" do
77
+ t = Tempfile.new(@tmp)
78
+ t.close
79
+
80
+ FileTest.exists?(t.path + "~").should == false
81
+
82
+ run("-b", "simple", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
83
+ FileTest.exists?(t.path).should == true
84
+ FileTest.exists?(t.path + "~").should == true
85
+
86
+ File.unlink(t.path + "~")
87
+ end
88
+
89
+
90
+ end
data/spec/erf_spec.rb CHANGED
@@ -1,39 +1,10 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper')
2
2
 
3
- WELLFORMED_ERF = ([
4
- "HAK", "V1.0",
5
- locstr_count = 1, locstr_size = 14,
6
- entry_count = 3,
7
- offset_to_locstr = 160,
8
- offset_to_keys = offset_to_locstr + locstr_size,
9
- offset_to_res = offset_to_locstr + locstr_size + entry_count * 24,
10
-
11
- 100, 126, # year, dayofyear
12
- 0xdeadbeef, "" #description strref, 116 bytes 0-padding
13
- ].pack("a4 a4 VV VV VV VV V a116") + [
14
- 0, 6, "abcdef" # one locstr
15
- ].pack("V V a*") + [
16
- "resref", 0, 10, 0, # keylist: resref.txt, id = 0
17
- "help", 1, 1, 0, # keylist: help.bmp, id = 1
18
- "yegods", 2, 4, 0, # keylist: yegods.wav, id = 2
19
- ].pack("a16 V v v" * entry_count) + [
20
- offset_to_res + entry_count * 8, 6, # offset, size
21
- offset_to_res + entry_count * 8 + 6, 4, # offset, size
22
- offset_to_res + entry_count * 8 + 6 + 4, 6, # offset, size
23
- ].pack("II" * entry_count) + [
24
- "resref", "help", "yegods"
25
- ].pack("a* a* a*")).freeze
26
-
27
-
28
-
29
- describe "Erf::Erf" do
30
-
3
+ describe "Erf::Erf", :shared => true do
31
4
  def wellformed_verify binary, expect_locstr = true
32
- t = nil
33
5
  t = Erf::Erf.new(StringIO.new binary)
34
6
 
35
7
  t.file_type.should == "HAK"
36
- t.file_version.should == "V1.0"
37
8
  if expect_locstr
38
9
  t.localized_strings.should == {0 => "abcdef"}
39
10
  else
@@ -56,32 +27,78 @@ describe "Erf::Erf" do
56
27
  end
57
28
 
58
29
  it "reads wellformed ERF containers" do
59
- wellformed_verify WELLFORMED_ERF
30
+ wellformed_verify @erf
60
31
  end
61
32
 
62
33
  it "reproduces correct ERF binary data" do
63
- t = Erf::Erf.new(StringIO.new WELLFORMED_ERF)
34
+ t = Erf::Erf.new(StringIO.new @erf)
64
35
  io = StringIO.new
65
36
  t.write_to(io)
66
37
  io.seek(0)
67
- proc {
68
- wellformed_verify io.read
69
- }.should_not raise_error IOError
38
+ n = io.read
39
+ wellformed_verify n
40
+ n.should == @erf
70
41
  end
71
42
 
72
- it "does not read ERF with locstr_size = 0 and locstr_count > 0" do
73
- b = WELLFORMED_ERF.dup
74
- b[12,4] = [0].pack("V")
75
- proc {
76
- wellformed_verify b
77
- }.should raise_error IOError
43
+ it "reads ERF with locstr_size = 0 and locstr_count > 0" do
44
+ @erf[12,4] = [0].pack("V")
45
+ wellformed_verify @erf, false
78
46
  end
79
47
 
80
48
  it "reads ERF with locstr_size > 0 and locstr_count = 0" do
81
- b = WELLFORMED_ERF.dup
82
- b[8,4] = [0].pack("V")
83
- proc {
84
- wellformed_verify b, false
85
- }.should_not raise_error IOError
49
+ @erf[8,4] = [0].pack("V")
50
+ wellformed_verify @erf, false
51
+ end
52
+ end
53
+
54
+ describe "Erf V1.0" do
55
+ before do
56
+ @erf = WELLFORMED_ERF.dup
57
+ end
58
+
59
+ it_should_behave_like "Erf::Erf"
60
+
61
+ it "accepts valid filenames" do
62
+ t = Erf::Erf.new(StringIO.new @erf)
63
+ t.add_file("a" * 1 + ".txt", StringIO.new("blargh"))
64
+ t.add_file("a" * 16 + ".txt", StringIO.new("blargh"))
65
+ end
66
+
67
+ it "fails on invalid filenames" do
68
+ t = Erf::Erf.new(StringIO.new @erf)
69
+ proc { t.add_file("a" * 0 + ".txt", StringIO.new("blargh")) }.should raise_error ArgumentError
70
+ proc { t.add_file("a" * 17 + ".txt", StringIO.new("blargh")) }.should raise_error ArgumentError
71
+ end
72
+
73
+ it "returns unknown for unknown-N file types" do
74
+ @erf[174 + 16 + 4, 2] = [9995].pack("v")
75
+ t = Erf::Erf.new(StringIO.new @erf)
76
+ t.content[0].filename.should == "resref.unknown-9995"
77
+ end
78
+ end
79
+
80
+ describe "Erf V1.1" do
81
+ before do
82
+ @erf = WELLFORMED_ERF_11.dup
83
+ end
84
+
85
+ it_should_behave_like "Erf::Erf"
86
+
87
+ it "accepts valid filenames" do
88
+ t = Erf::Erf.new(StringIO.new @erf)
89
+ t.add_file("a" * 1 + ".txt", StringIO.new("blargh"))
90
+ t.add_file("a" * 32 + ".txt", StringIO.new("blargh"))
91
+ end
92
+
93
+ it "fails on invalid filenames" do
94
+ t = Erf::Erf.new(StringIO.new @erf)
95
+ proc { t.add_file("a" * 0 + ".txt", StringIO.new("blargh")) }.should raise_error ArgumentError
96
+ proc { t.add_file("a" * 33 + ".txt", StringIO.new("blargh")) }.should raise_error ArgumentError
97
+ end
98
+
99
+ it "returns unknown for unknown-N file types" do
100
+ @erf[174 + 32 + 4, 2] = [9995].pack("v")
101
+ t = Erf::Erf.new(StringIO.new @erf)
102
+ t.content[0].filename.should == "resref.unknown-9995"
86
103
  end
87
104
  end