nwn-lib 0.3.6 → 0.4.0

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.
@@ -0,0 +1,7 @@
1
+ if ENV['NWN_LIB_INFER_DATA_FILE'] && ENV['NWN_LIB_INFER_DATA_FILE'] != ""
2
+ NWN::Gff.load_struct_defaults(ENV['NWN_LIB_INFER_DATA_FILE'])
3
+ end
4
+
5
+ if ENV['NWN_LIB_2DA_LOCATION'] && ENV['NWN_LIB_2DA_LOCATION'] != ""
6
+ NWN::TwoDA::Cache.setup ENV['NWN_LIB_2DA_LOCATION']
7
+ end
@@ -2,7 +2,42 @@ require 'shellwords'
2
2
 
3
3
  module NWN
4
4
  module TwoDA
5
+
6
+ # A Row is simply an Array with some helpers.
7
+ # It wraps a data row in a TwoDA::Table.
8
+ #
9
+ # You can access Table columns in a row by simply
10
+ # calling a method with the same name.
11
+ #
12
+ # For example (spells.2da):
13
+ #
14
+ # table.rows.select {|x| x.Wiz_Sorc == "9" }
15
+ #
16
+ # selects all level 9 arcane spells.
17
+ class Row < Array
18
+ attr_accessor :table
19
+
20
+ # Returns the id of this row.
21
+ def ID
22
+ @table.rows.index(self)
23
+ end
24
+
25
+ def method_missing meth, *args
26
+ if idx = @table.columns.index(meth.to_s.downcase) || idx = @table.columns.index(meth.to_s)
27
+ if meth.to_s =~ /=$/
28
+ self[idx] = args.shift or raise ArgumentError,
29
+ "Need a paramenter for assignments .."
30
+ else
31
+ self[idx]
32
+ end
33
+ else
34
+ super
35
+ end
36
+ end
37
+ end
38
+
5
39
  class Table
40
+ CELL_PAD_SPACES = 4
6
41
 
7
42
  # An array of all column names present in this 2da table.
8
43
  attr_accessor :columns
@@ -68,8 +103,16 @@ module NWN
68
103
  end
69
104
 
70
105
  # [1..-1]: Strip off the ID
71
- data[row[0].to_i] = row = row[1..-1]
72
-
106
+ data[row[0].to_i] = row = Row.new(row[1..-1])
107
+ row.table = self
108
+
109
+ row.map! {|cell|
110
+ cell = case cell
111
+ when nil; nil
112
+ when "****"; ""
113
+ else cell
114
+ end
115
+ }
73
116
  raise ArgumentError,
74
117
  "Row #{idx} does not have the appropriate amount of cells (has: #{row.size}, want: #{header.size}) (while parsing #{row[0,3].join(' ')})." if
75
118
  row.size != header.size
@@ -86,7 +129,7 @@ module NWN
86
129
  # [+column+] The column to retrieve (name or id), or nil for all columns.
87
130
  def by_row row, column = nil
88
131
  column = column_name_to_id column
89
- column.nil? ? @rows[row.to_i] : @rows[row.to_i][column]
132
+ column.nil? ? @rows[row.to_i] : (@rows[row.to_i].nil? ? nil : @rows[row.to_i][column])
90
133
  end
91
134
 
92
135
 
@@ -97,7 +140,7 @@ module NWN
97
140
  def by_col column, row = nil
98
141
  column = column_name_to_id column
99
142
  raise ArgumentError, "column must not be nil." if column.nil?
100
- row.nil? ? @rows.map {|v| v[column] } : @rows[row.to_i][column]
143
+ row.nil? ? @rows.map {|v| v[column] } : (@rows[row.to_i].nil? ? nil : @rows[row.to_i][column])
101
144
  end
102
145
 
103
146
 
@@ -120,15 +163,70 @@ module NWN
120
163
  # Returns this table as a valid 2da to be written to a file.
121
164
  def to_2da
122
165
  ret = []
166
+
167
+ # Contains the maximum string length by each column,
168
+ # from which we can calulate the padding we need that
169
+ # things align properly.
170
+ id_cell_size = @rows.size.to_s.size + CELL_PAD_SPACES
171
+ max_cell_size_by_column = @columns.map {|col|
172
+ ([col] + by_col(col)).inject(0) {|max, cell|
173
+ cell.to_s.size > max ? cell.to_s.size : max
174
+ } + CELL_PAD_SPACES
175
+ }
176
+
123
177
  ret << "2DA V2.0"
124
178
  ret << ""
125
- ret << " " + @columns.join(" ")
126
- @rows.each_with_index {|row, idx|
127
- ret << [idx].concat(row).join(" ")
179
+
180
+ rv = []
181
+ rv << " " * id_cell_size
182
+ @columns.each_with_index {|column, column_idx|
183
+ rv << column + " " * (max_cell_size_by_column[column_idx] - column.size)
184
+ }
185
+ ret << rv.join("").rstrip
186
+
187
+ @rows.each_with_index {|row, row_idx|
188
+ rv = []
189
+ rv << row_idx.to_s + " " * (id_cell_size - row_idx.to_s.size)
190
+ row.each_with_index {|cell, column_idx|
191
+ cell = "****" if cell == ""
192
+ rv << cell + " " * (max_cell_size_by_column[column_idx] - cell.size)
193
+ }
194
+ ret << rv.join("").rstrip
128
195
  }
129
196
  ret.join("\r\n")
130
197
  end
198
+ end
199
+
200
+ # This is a simple 2da cache.
201
+ module Cache
202
+ @_cache = {}
203
+ @_roots = []
204
+
205
+ # Set the file system path spec where all 2da files reside.
206
+ # Call this on application startup.
207
+ # path spec is a colon-separated list of pathes, just like $PATH.
208
+ def self.setup root_directories
209
+ @_roots = root_directories.split(':').compact.reject {|x| "" == x.strip }
210
+ end
211
+
212
+ # Get the 2da file with the given name. +name+ is without extension.
213
+ def self.get(name)
214
+ raise Exception,
215
+ "You need to set up the cache first through the environment variable NWN_LIB_2DA_LOCATION." unless
216
+ @_roots.size > 0
217
+ @_cache[name.downcase] ||= read_2da(name.downcase)
218
+ end
219
+
220
+ def self.read_2da name # :nodoc:
221
+ @_roots.each {|root|
222
+ file = root + '/' + name + '.2da'
223
+ next unless FileTest.exists?(file)
224
+ return Table.parse(IO.read(file))
225
+ }
226
+ raise Errno::ENOENT, name + ".2da"
227
+ end
131
228
 
229
+ private_class_method :read_2da
132
230
  end
133
231
  end
134
232
  end
@@ -1,17 +1,288 @@
1
1
  require 'yaml'
2
2
 
3
+ # See http://www.taguri.org/ for the exact meaning of this.
4
+ NWN::YAML_DOMAIN = "nwn-lib.elv.es,2008-12"
5
+
6
+ class Array
7
+ attr_accessor :to_yaml_style
8
+ end
9
+ class Hash
10
+ attr_accessor :to_yaml_style
11
+ end
12
+
3
13
  class Hash
4
14
  # Replacing the to_yaml function so it'll serialize hashes sorted (by their keys)
5
15
  # Original function is in /usr/lib/ruby/1.8/yaml/rubytypes.rb
6
- def to_yaml( opts = {} )
7
- YAML::quick_emit( object_id, opts ) do |out|
8
- out.map( taguri, to_yaml_style ) do |map|
9
- sort.each do |k, v|
10
- map.add( k, v )
16
+ def to_yaml(opts = {})
17
+ YAML::quick_emit(nil, opts) do |out|
18
+ out.map(taguri, to_yaml_style) do |map|
19
+ if keys.map {|v| v.class }.size > 0
20
+ each do |k, v|
21
+ map.add(k, v)
22
+ end
23
+ else
24
+ sort.each do |k, v|
25
+ map.add(k, v)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ module NWN::Gff::Struct
34
+ def to_yaml_type
35
+ "!#{NWN::YAML_DOMAIN}/struct"
36
+ end
37
+
38
+ def to_yaml(opts = {})
39
+ YAML::quick_emit(nil, opts) do |out|
40
+ out.map(taguri, to_yaml_style) do |map|
41
+ # Inline certain structs that are small enough.
42
+ map.style = :inline if self.size <= 1 &&
43
+ self.values.select {|x|
44
+ NWN::Gff::YAMLNonInlineableFields.index(x['type'])
45
+ }.size == 0
46
+
47
+ map.add('__' + 'data_type', @data_type) if @data_type
48
+ map.add('__' + 'data_version', @data_version) if @data_version && !can_infer_data_version?
49
+ map.add('__' + 'struct_id', @struct_id) if @struct_id && !can_infer_struct_id?
50
+
51
+ reject {|k, v|
52
+ # Dont emit fields that would result in their default values anyways.
53
+ ENV['NWN_LIB_CLEAR_KNOWN_VALUES'] && v.can_infer_type? &&
54
+ v.can_infer_str_ref? && v.can_infer_value?
55
+ }.sort.each {|k,v|
56
+ map.add(k,v)
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ module NWN::Gff::Field
64
+
65
+ def to_yaml(opts = {})
66
+
67
+ if !ENV['NWN_LIB_DONT_COMPACT_LIST_STRUCTS'] && field_type == :list && can_compact_as_list?
68
+ YAML::quick_emit(nil, opts) do |out|
69
+ out.seq("!", to_yaml_style) do |seq|
70
+ field_value.each {|item|
71
+ struct_id_of_item = NWN::Gff.get_struct_defaults_for(item.path, '__struct_id')
72
+
73
+ calf = get_compact_as_list_field
74
+ case calf
75
+ when Array
76
+ style = NWN::Gff.get_struct_defaults_for(item.path, '__inline')
77
+
78
+ non_inferrable = (item.keys.reject {|x|
79
+ item[x].can_infer_type? && item[x].can_infer_value?
80
+ } - calf).sort
81
+ raise NWN::Gff::GffError, "cannot compact list struct at #{item.path}, would " +
82
+ "LOSE fields: #{non_inferrable.inspect}" if non_inferrable.size > 0
83
+
84
+ ar = calf.map {|ik| item[ik] || NWN::Gff.get_struct_default_value(item.path, ik) }
85
+ missing = ar.map {|x| x.nil? ? calf[ar.index(x)] : nil }.compact
86
+
87
+ raise NWN::Gff::GffError, "cannot compact list-struct at #{item.path}, does not " +
88
+ "have all compactable field values set or are inferrable (missing fields: #{missing.inspect})." if
89
+ missing.size > 0
90
+
91
+ ar.to_yaml_style = :inline if style
92
+
93
+ ar.unshift(item.struct_id) if struct_id_of_item == 'inline'
94
+ seq.add(ar)
95
+ else
96
+ isv = NWN::Gff.get_struct_defaults_for(self.path, '__inline')
97
+ seq.style = :inline if isv.nil? || isv === true
98
+
99
+ seq.add(item.struct_id) if struct_id_of_item == 'inline'
100
+ seq.add(item[calf])
101
+ end
102
+ }
103
+ end
104
+ end
105
+
106
+ elsif !ENV['NWN_LIB_DONT_COMPACT_FIELDS'] && can_compact_print?
107
+ field_value_as_compact.to_yaml(opts)
108
+
109
+ else
110
+ YAML::quick_emit(nil, opts) do |out|
111
+ out.map(taguri, to_yaml_style) do |map|
112
+ map.style = :inline unless NWN::Gff::YAMLNonInlineableFields.index(self['type'])
113
+ map.add('type', self['type']) unless can_infer_type?
114
+ map.add('str_ref', self['str_ref']) unless can_infer_str_ref?
115
+ map.add('value', self['value']) unless can_infer_value?
11
116
  end
12
117
  end
13
118
  end
14
119
  end
15
120
  end
16
121
 
122
+ # This parses the struct and extends all fields with their proper type.
123
+ YAML.add_domain_type(NWN::YAML_DOMAIN,'struct') {|t,hash|
124
+ struct = {}.taint
125
+ struct.extend(NWN::Gff::Struct)
126
+
127
+ # The metadata
128
+ struct.struct_id = hash.delete('__struct_id')
129
+ struct.data_type = hash.delete('__data_type')
130
+ struct.data_version = hash.delete('__data_version')
131
+ struct.data_version ||= NWN::Gff::Struct::DEFAULT_DATA_VERSION
132
+
133
+ if struct.struct_id.nil? && s_id = NWN::Gff.get_struct_defaults_for(struct.path, '__struct_id')
134
+ struct.struct_id = s_id.to_i
135
+ elsif struct.struct_id.nil?
136
+ raise NWN::Gff::GffError, "Cannot infer implicit struct_id for struct at #{struct.path}."
137
+ end
138
+
139
+ hash.each {|label,element|
140
+ label.freeze
141
+ element = case element
142
+ when Hash # already uncompacted or a compacted exolocstr
143
+ default_type = NWN::Gff.get_struct_default_type(struct.path, label)
144
+ raise NWN::Gff::GffError,
145
+ "Cannot parse compacted hash with no contents and no infer data at #{struct.path}/#{label}." if
146
+ element.size == 0 && default_type == nil
147
+
148
+ # It has only got numbers as key, we *assume* its a cexoloc.
149
+ # Thats okay, because type inferration will catch it later and bite us. Hopefully.
150
+ if element.keys.select {|x| x.to_s !~ /^(str_ref|\d+)$/}.size == 0
151
+ element = {
152
+ 'type' => :cexolocstr,
153
+ 'str_ref' => element.delete('str_ref') || NWN::Gff::Field::DEFAULT_STR_REF,
154
+ 'value' => element,
155
+ }
156
+ end
157
+
158
+ element
159
+
160
+ when Array # compacted struct-list
161
+ element = {
162
+ 'type' => :list,
163
+ 'value' => element,
164
+ }
165
+ path = struct.data_type + "/" + label
166
+ unpack_struct_element = NWN::Gff.get_struct_defaults_for(path, '__compact')
167
+ if unpack_struct_element
168
+ # If it doesn't have unpack data, we need to assume its a compacted list itself.
169
+ # Sorry.
170
+ # Hope this wont bite anyone later on.
171
+
172
+ #raise NWN::Gff::GffError,
173
+ # "Cannot unpack compacted struct list at #{path}, no infer data available." unless
174
+ # unpack_struct_element
175
+
176
+ unpack_struct_element_struct_id = NWN::Gff.get_struct_defaults_for(path, "__struct_id")
177
+
178
+ unpack_struct_elements = [unpack_struct_element].flatten.freeze
179
+
180
+ unpack_struct_element_types = unpack_struct_elements.map {|unpack_struct_element|
181
+ raise NWN::Gff::GffError, "While unpacking #{path}: " +
182
+ "#{unpack_struct_element} is not a field-naime, dummy." unless
183
+ unpack_struct_element.is_a?(String)
184
+
185
+ unpack_struct_element_type =
186
+ NWN::Gff.get_struct_default_type(path, unpack_struct_element)
187
+
188
+ unpack_struct_element_type
189
+ }.freeze
190
+
191
+ last_struct_id = -1
192
+ element['value'].map! {|kv|
193
+ st = {}
194
+ st.extend(NWN::Gff::Struct)
195
+ st.struct_id = case unpack_struct_element_struct_id
196
+ when 'iterative'
197
+ last_struct_id += 1
198
+ when 'inline'
199
+ kv.shift
200
+ when Fixnum
201
+ unpack_struct_element_struct_id
202
+ else
203
+ kv['__struct_id'] || raise(NWN::Gff::GffError,
204
+ "dont know how to handle struct_id #{unpack_struct_element_struct_id} at #{label} = #{kv.inspect}")
205
+ end
206
+ st.data_type = path
207
+
208
+ unpack_struct_elements.each_with_index {|use, index|
209
+ case kv
210
+ when Hash
211
+ if !kv[use]
212
+ uset, usev = unpack_struct_element_types[index],
213
+ NWN::Gff.get_struct_default_value(path, use)
214
+ else
215
+ uset = kv[use]['type']
216
+ usev = kv[use]['value']
217
+ end
218
+ when Array
219
+ uset = unpack_struct_element_types[index]
220
+ usev = kv[index]
221
+ if usev.is_a?(Hash)
222
+ uset, usev = usev['type'], usev['value']
223
+ end
224
+ when String, Numeric, Symbol
225
+ uset = unpack_struct_element_types[index]
226
+ usev = kv
227
+ else
228
+ raise "Dont know how to unpack list-struct element: #{kv.inspect}"
229
+ end
230
+
231
+ raise NWN::Gff::GffError, "Cannot infer type of #{path}/#{use} (got: #{uset.inspect})" unless
232
+ uset && NWN::Gff::Types.index(uset)
233
+ raise NWN::Gff::GffError, "Cannot get or infer value of #{path}/#{use} (got: #{usev.inspect})" unless
234
+ usev && (usev.is_a?(String) || usev.is_a?(Numeric) || usev.is_a?(Symbol))
235
+
236
+ # Figure out the default value (which we'll need to re-add?) if
237
+ # it was filtered while outputting.
238
+ #kv[index] = NWN::Gff.get_struct_default_value(path, use) if !kv[index]
239
+ #raise NWN::Gff::GffError, "field present in compacted struct-list, and cant " +
240
+ # "infer default value at #{use}/#{index} (#{kv.inspect})" if !kv[index]
241
+
242
+ el = st[use] = {
243
+ 'label' => use,
244
+ 'type' => uset,
245
+ 'value' => usev
246
+ }
247
+ el.extend(NWN::Gff::Field)
248
+ el.extend_meta_classes
249
+ el.parent = st
250
+ }
251
+ st
252
+ }
253
+ end
254
+ element
255
+
256
+ when Numeric, String # compacted scalar
257
+ {'value' => element}
258
+
259
+ else
260
+ fail "Don't know how to un-compact /#{label}: #{element.inspect}, klass #{element.class.to_s}"
261
+ end
262
+
263
+ element.extend(NWN::Gff::Field)
264
+ element.field_label = label
265
+ element.parent = struct
266
+ element.str_ref ||= NWN::Gff::Field::DEFAULT_STR_REF if element.respond_to?('str_ref=')
267
+
268
+ infer_field_type = NWN::Gff.get_struct_default_type(struct.path, element.field_label)
269
+
270
+ if element.field_type && infer_field_type && infer_field_type != element.field_type
271
+ raise NWN::Gff::GffError, "/#{label} has field_type #{element.field_type.inspect}, but infer data says #{infer_field_type.inspect}."
272
+
273
+ elsif element.field_type.nil? && infer_field_type.nil?
274
+ raise NWN::Gff::GffError, "Cannot infer implicit type for /#{label} while parsing struct-id #{struct.struct_id}."
275
+
276
+ elsif element.field_type.nil? && infer_field_type
277
+ element.field_type = infer_field_type
278
+ end
279
+
280
+
281
+ element.extend_meta_classes
282
+
283
+ struct[label] = element.taint
284
+ }
17
285
 
286
+ NWN::Gff.__yaml_postparse nil, struct if struct.data_type
287
+ struct
288
+ }