Created
February 14, 2013 03:23
-
-
Save zerowidth/4950374 to your computer and use it in GitHub Desktop.
Script to parse a text table and convert to a reasonable output. Demonstration of an idiomatic ruby script for a friend.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "pp" | |
def convert_to_offsets(widths) | |
offsets = [] | |
position = 0 | |
widths.each.with_index do |width, index| | |
offsets << (position...(width+position)) # range does not include the end | |
position += width + 1 # assume spacing of 1 character | |
end | |
offsets | |
end | |
def extract_values(line, offsets) | |
offsets.map { |range| line[range].strip } | |
end | |
def read_table(table) | |
lines = table.split "\n" | |
fields = lines[0].split /\s+/ | |
widths = lines[1].split(" ").map(&:length) | |
offsets = convert_to_offsets widths | |
lines[2..-1].map do |line| | |
values = extract_values line, offsets | |
Hash[*fields.zip(values).flatten] | |
end | |
end | |
# this reads from the bottom of the file, after the __END__: | |
DATA.read.split("***\n").each do |table| | |
puts "-" * 20 | |
data = read_table table | |
data.each do |row| | |
# do a bit o' massaging with the data before displaying it: | |
port_id = row.delete "Port" | |
row["PortID"] = port_id | |
row["Module"], row["PortNum"] = port_id.split("/").map(&:to_i) | |
pp row | |
end | |
end | |
__END__ | |
Port Name Status Vlan Duplex Speed Type | |
----- -------------------- ---------- ---------- ------ ----- ------------ | |
1/1 LcacdcC1-CU08 7/12 connected trunk full 1000 1000Base SX | |
1/2 disable 1 full 1000 1000Base SX | |
3/1 not used? to 8/12 connected trunk full 1000 1000BaseSX | |
*** | |
Port Name Status Vlan Duplex Speed Type | |
----- ----------------- ---------- ------------ ------ ----- ------------ | |
1/1 Lcac1-CU08 7/12 connected trunk full 1000 1000BaseSX | |
1/2 disable 1 full 1000 1000 BaseSX | |
3/1 nosed? to 8/12 connected trunk full 1000 1000 BaseSX |
Aaaand because I appear to have nothing better to do, let's go so far overboard we can't see the boat anymore. Here's an example of that script extracted into a class and documented with TomDoc.
# Assumes ActiveSupport (Rails) is loaded, for String#underscore
require "ostruct"
class TextTable
# Public: return a list of fields for this table
attr_reader :fields
# Public: return a list of values for each row of this table
attr_reader :row_values
# Create a new TextTable from a textual representation of a table
#
# table - a string containing the table data, e.g.
#
# Field 1 Field 2
# --------- -------
# value 1 asdf
# value 2 jkl
#
def initialize(table)
@lines = table.split "\n"
@fields = @lines[0].split /\s+/
@pretty_fields = @fields.map(&:underscore)
offsets = convert_widths_to_offsets(@lines[1].split(" ").map(&:length))
@row_values = @lines[2..-1].map do |line|
extract_values line, offsets
end
end
# Public: return a list of rows from this table
#
#
# The fields are converted to underscored names, e.g. "Field 1" would become
# "field_1".
#
# Example:
# text_table.rows
# # => [ #<OpenStruct field_1="value 1", field_2="asdf">, ... ]
#
# Returns OpenStructs for each row, with the table fields as accessors.
def rows
@row_values.map do |values|
OpenStruct.new Hash[*@pretty_fields.zip(values).flatten]
end
end
# Public: return a Hash for each row from this table
#
# Example:
#
# text_table.row_hashes
# # => [ {"Field 1" => "value 1", "Field 2" => "asdf"}, ... ]
#
# Returns an array of hashes representing each row, without field name
# conversion.
def row_hashes
@row_values.map do |values|
Hash[*@fields.zip(values).flatten]
end
end
# Internal: convert a list of widths to a list of offsets
#
# widths - an Array of column widths
#
# Assumes a spacing of one character between fields.
#
# Example:
#
# convert_widths_to_offsets [5, 5, 10]
# # => [0..4, 6..10, 12..21]
#
# Returns a list of offset Ranges.
def convert_widths_to_offsets(widths)
offsets = []
position = 0
widths.each.with_index do |width, index|
offsets << (position...(width+position))
position += width + 1
end
offsets
end
# Internal: extract the values from a line
#
# line - the input line of a table row
# offsets - an array of Ranges for each field's offsets
#
# Returns a list of values, whitespace stripped.
def extract_values(line, offsets)
offsets.map { |range| line[range].strip }
end
end
And the script:
require "pp"
DATA.read.split("***\n").each do |input|
puts "-" * 20
table = TextTable.new input
table.rows.each do |row|
pp row
end
end
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I would wrap this up in a class if this needed to be a reusable component. Additionally, for example, if using this within a rails app (
String#underscore
is available), I'd do something like:so the field names are ruby style, that is,
"port_num"
instead of"PortNum"
I'd also consider returning an
OpenStruct
instead of a hash: