Methods
public class
public instance
Constants
| CELL_PAD_SPACES | = | 4 |
Attributes
| columns | [RW] | An array of all column names present in this 2da table. |
| newline | [RW] |
What to use to set up newlines. Alternatively, specify the environ variable
NWN_LIB_2DA_NEWLINE with one of the following:
0 for windows newlines: \r\n 1 for unix newlines: \n 2 for caret return only: \r defaults to r\n. |
| rows | [RW] | An array of row arrays, without headers. |
Public class methods
Create a new, empty 2da table.
# File lib/nwn/twoda.rb, line 76 76: def initialize 77: @columns = [] 78: @rows = [] 79: @newline = "\r\n" 80: end
Parse a existing string containing a full 2da table. Returns a TwoDA::Table.
# File lib/nwn/twoda.rb, line 96 96: def self.parse bytes 97: obj = self.new 98: obj.parse bytes 99: obj 100: end
Creates a new Table object from a given IO source.
- file
- A IO object pointing to a 2da file.
# File lib/nwn/twoda.rb, line 85 85: def self.read_from io 86: self.parse io.read() 87: end
Public instance methods
Set a cell or row value.
- row
- The row to operate on (starts at 0)
- column
- Optional column name or index.
- value
- New value, either a full row, or a single value.
Examples:
TwoDA.get('portraits')[1, "BaseResRef"] = "hi"
TwoDA.get('portraits')[1] = %w{1 2 3 4 5 6}
# File lib/nwn/twoda.rb, line 201 201: def []= row, column = nil, value = nil 202: if value.nil? 203: value = column 204: raise ArgumentError, "Expected array for setting a whole row" unless value.is_a?(Array) 205: end 206: 207: if value.is_a?(Array) 208: raise ArgumentError, "Given array size does not match table columns (got: #{value.size}, want: #{self.columns.size})" unless value.size == self.columns.size 209: new_row = Row.new 210: new_row.concat(value.map {|x| x.to_s}) 211: 212: @rows[row] = new_row 213: 214: else 215: col = column_name_to_id column 216: @rows[row][col] = value 217: 218: end 219: end
Retrieve data by column.
- column
- The column to retrieve (name or id).
- row
- The row to retrieve (starts at 0), or nil for all rows.
# File lib/nwn/twoda.rb, line 226 226: def by_col column, row = nil 227: column = column_name_to_id column 228: raise ArgumentError, "column must not be nil." if column.nil? 229: row.nil? ? @rows.map {|v| v[column] } : (@rows[row.to_i].nil? ? nil : @rows[row.to_i][column]) 230: end
Retrieve data by row.
- row
- The row to retrieve (starts at 0)
- column
- The column to retrieve (name or id), or nil for all columns.
# File lib/nwn/twoda.rb, line 185 185: def by_row row, column = nil 186: column = column_name_to_id column 187: column.nil? ? @rows[row.to_i] : (@rows[row.to_i].nil? ? nil : @rows[row.to_i][column]) 188: end
Translate a column name to its array offset; will validate and raise an ArgumentError if the given argument is invalid or the column cannot be resolved.
# File lib/nwn/twoda.rb, line 236 236: def column_name_to_id column 237: case column 238: when String 239: @columns.index(column) or raise ArgumentError, "Not a valid column name: #{column}" 240: when Fixnum 241: column 242: when NilClass 243: nil 244: else 245: raise ArgumentError, "Invalid column type: #{column} as #{column.class}" 246: end 247: end
Parses a string that represents a valid 2da definition. Replaces any content this table may already have. This will cope with all misformatting in the same way that NWN1 itself does. NWN2 employs slightly different parsing rules, and may or may not be compatible in the fringe cases.
Will raise an ArgumentError if the given bytes do not contain a valid 2DA header, or the file is so badly misshaped that it will not ever be parsed correctly by NWN1.
# File lib/nwn/twoda.rb, line 112 112: def parse bytes 113: magic, *data = *bytes.split(/\r?\n/).map {|v| v.strip } 114: 115: raise ArgumentError, "Not valid 2da: No valid header found (got: #{magic[0,20].inspect}..)" if 116: magic !~ /^2DA\s+V2.0$/ 117: 118: # strip all empty lines; they are regarded as comments 119: data.reject! {|ln| ln.strip == ""} 120: 121: header = data.shift 122: 123: header = Shellwords.shellwords(header.strip) 124: data.map! {|line| 125: Shellwords.shellwords(line.strip) 126: } 127: 128: new_row_data = [] 129: 130: id_offset = 0 131: idx_offset = 0 132: data.each_with_index {|row, idx| 133: id = row.shift 134: 135: NWN.log_debug "Warning: invalid ID in line #{idx}: #{id.inspect}" if id !~ /^\d+$/ 136: 137: id = id.to_i + id_offset 138: 139: # Its an empty row - NWN strictly numbers by counted lines - then so do we. 140: while id > idx + idx_offset 141: NWN.log_debug "Warning: missing ID at #{id - id_offset}, fixing that for you." 142: idx_offset += 1 143: end 144: 145: # NWN automatically increments duplicate IDs - so do we. 146: while id < idx + idx_offset 147: NWN.log_debug "Warning: duplicate ID found at row #{idx} (id: #{id}); fixing that for you." 148: id_offset += 1 149: id += 1 150: end 151: 152: # NWN fills in missing columns with an empty value - so do we. 153: NWN.log_debug "Warning: row #{id} (real: #{id - id_offset}) misses " + 154: "#{header.size - row.size} columns at the end, fixed" if 155: row.size < header.size 156: 157: row << "" while row.size < header.size 158: 159: new_row_data << k_row = Row.new(row) 160: k_row.table = self 161: 162: k_row.map! {|cell| 163: cell = case cell 164: when nil; raise "Bug in parser: nil-value for cell" 165: when "****"; "" 166: else cell 167: end 168: } 169: 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 172: 173: k_row.pop while k_row.size > header.size 174: } 175: 176: @columns = header 177: @rows = new_row_data 178: end
Returns this table as a valid 2da to be written to a file.
# File lib/nwn/twoda.rb, line 250 250: def to_2da 251: ret = [] 252: 253: # Contains the maximum string length by each column, 254: # from which we can calulate the padding we need that 255: # things align properly. 256: id_cell_size = @rows.size.to_s.size + CELL_PAD_SPACES 257: max_cell_size_by_column = @columns.map {|col| 258: ([col] + by_col(col)).inject(0) {|max, cell| 259: cell = '"%s"' % cell if cell =~ /\s/ 260: cell.to_s.size > max ? cell.to_s.size : max 261: } + CELL_PAD_SPACES 262: } 263: 264: ret << "2DA V2.0" 265: ret << "" 266: 267: rv = [] 268: rv << " " * id_cell_size 269: @columns.each_with_index {|column, column_idx| 270: rv << column + " " * (max_cell_size_by_column[column_idx] - column.size) 271: } 272: ret << rv.join("").rstrip 273: 274: @rows.each_with_index {|row, row_idx| 275: rv = [] 276: rv << row_idx.to_s + " " * (id_cell_size - row_idx.to_s.size) 277: row.each_with_index {|cell, column_idx| 278: cell = "****" if cell == "" 279: cell = '"%s"' % cell if cell =~ /\s/ 280: rv << cell + " " * (max_cell_size_by_column[column_idx] - cell.size) 281: } 282: ret << rv.join("").rstrip 283: } 284: 285: # Append an empty newline. 286: ret << "" 287: 288: ret.join(case NWN.setting("2da_newline") 289: when "0", false 290: "\r\n" 291: when "1" 292: "\n" 293: when "2" 294: "\r" 295: when nil 296: @newline 297: end) 298: end
Dump this table to a IO object.
# File lib/nwn/twoda.rb, line 90 90: def write_to io 91: io.write(self.to_2da) 92: end