grid-csv 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .irbrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.3-p392@griddle.com --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in griddle.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 bjh
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,96 @@
1
+ Griddle
2
+ =======
3
+
4
+ **note:** someone has the gem name **griddle** registered already so I am calling the gem **grid-csv**.
5
+
6
+ This gem was created to help process CSV files that are not really the usual *[row, col]* data.
7
+ I have lost many hours of my life in the black hole known as the *coporate intranet*, where windows documents multiply at an obscene rate.
8
+ I came up with this idea to help someone handle this particular nastiness.
9
+
10
+ This is one for my **windows brothers**, of course if it is useful to anyone else I would be delighted.
11
+
12
+ On To Business
13
+ ==============
14
+
15
+ An example of the source material in question would be parsing Excel data that comes from a formatted template. One where the structure is more like a document than a database table.
16
+ The template in question may look like this
17
+
18
+ ![](data/example.png)
19
+
20
+ If you exported the Excel data as CSV it may look something like [this](data/po.csv)
21
+
22
+ Modus Operandi
23
+ ==============
24
+
25
+ The main idea to keep in mind is that you are not processing rows and columns of data.
26
+ You are cutting out **rectangles** or *grids* of data to process further.
27
+
28
+ Let's say you have to process a bunch of excel files that captured form data.
29
+ The template that was used has a section for the user's contact information.
30
+ Instead of ripping through rows and columns you can either:
31
+ - search for a known string and use that as the starting point to cut out a **rectangle**
32
+ - cut out a rectangle from a know location, *i.e.* the contact info starts at row 4, column 3 and is 1 column wide and 4 rows high
33
+
34
+ The hope is that you can access data in a more intuitive way than just doing a nested for loop and crossing your fingers.
35
+
36
+ But Why?
37
+ =======
38
+
39
+ Because Microsoft has empowered business people to do **terrible, terrible** things with data.
40
+
41
+ Small Examples
42
+ ==============
43
+
44
+ ```ruby
45
+ $: << File.expand_path(File.dirname(__FILE__) + './lib')
46
+ require 'griddle.rb'
47
+
48
+ grid = Griddle::DataGrid.new.populate_from_csv('po.csv')
49
+
50
+ # I know where the Vendor data is from looking at the Purchase Order
51
+ # so I cut a rectangle that contains that data
52
+ puts "<Vendor>"
53
+ candy = grid.cut(11, 1, 1, 5)
54
+ puts candy.to_s
55
+ puts
56
+
57
+ # the Ship To data might move down a line or two
58
+ # so search for it's location based off of it's column header
59
+ puts "<Ship To>"
60
+ shipto = grid.find("SHIP TO").last
61
+ puts "ship to header location: #{shipto}"
62
+ puts grid.cut(shipto.row+1, shipto.col, 1, 5).to_s
63
+ puts
64
+
65
+ # iterate over the line item rows returned in a Rectangle
66
+ puts "<line items>"
67
+ grid.cut(21, 1, 7, 2).each do |row|
68
+ puts row.join(":")
69
+ end
70
+ puts
71
+
72
+ # search for two different points and use them to make a Rectangle
73
+ puts "<rectangle from points>"
74
+ tl = grid.find("TAX RATE").last
75
+ br = grid.find("TOTAL").last
76
+ puts grid.cut_rectangle(Griddle::Rectangle.create(tl, br)).to_s
77
+
78
+
79
+ # try out the Point.move functionality
80
+ # use the previous Ship To example
81
+ puts
82
+ puts "<Ship To with move>"
83
+ shipto = grid.find("SHIP TO").last
84
+ shipto.move(down:1)
85
+ puts grid.cut(shipto.row, shipto.col, 1, 5).to_s
86
+
87
+
88
+ # show cash money info
89
+ puts
90
+ puts "<want mo money!>"
91
+ puts grid.cut(38, 6, 2, 4).to_s
92
+ ```
93
+
94
+
95
+ **NOTE:** This code was originally written to accompany the article [here](http://pregnantfist.tumblr.com/post/42406731034/eating-glue-or-how-to-parse-csv-like-a-pre-schooler),
96
+ It is no longer as up to date as the gem version but it does explain the **why** of it all.
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ # If you want to make this the default task
7
+ task default: :spec
8
+
9
+ desc 'load gem files into IRB'
10
+ task :console do
11
+ exec 'irb -Ilib -rgriddle'
12
+ end
Binary file
@@ -0,0 +1,47 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + './lib')
2
+ require 'griddle.rb'
3
+
4
+ grid = Griddle::DataGrid.new.populate_from_csv('po.csv')
5
+
6
+ # I know where the Vendor data is from looking at the Purchase Order
7
+ # so I cut a rectangle that contains that data
8
+ puts "<Vendor>"
9
+ candy = grid.cut(11, 1, 1, 5)
10
+ puts candy.to_s
11
+ puts
12
+
13
+ # the Ship To data might move down a line or two
14
+ # so search for it's location based off of it's column header
15
+ puts "<Ship To>"
16
+ shipto = grid.find("SHIP TO").last
17
+ puts "ship to header location: #{shipto}"
18
+ puts grid.cut(shipto.row+1, shipto.col, 1, 5).to_s
19
+ puts
20
+
21
+ # iterate over the line item rows returned in a Rectangle
22
+ puts "<line items>"
23
+ grid.cut(21, 1, 7, 2).each do |row|
24
+ puts row.join(":")
25
+ end
26
+ puts
27
+
28
+ # search for two different points and use them to make a Rectangle
29
+ puts "<rectangle from points>"
30
+ tl = grid.find("TAX RATE").last
31
+ br = grid.find("TOTAL").last
32
+ puts grid.cut_rectangle(Griddle::Rectangle.create(tl, br)).to_s
33
+
34
+
35
+ # try out the Point.move functionality
36
+ # use the previous Ship To example
37
+ puts
38
+ puts "<Ship To with move>"
39
+ shipto = grid.find("SHIP TO").last
40
+ shipto.move(down:1)
41
+ puts grid.cut(shipto.row, shipto.col, 1, 5).to_s
42
+
43
+
44
+ # show cash money info
45
+ puts
46
+ puts "<want mo money!>"
47
+ puts grid.cut(38, 6, 2, 4).to_s
@@ -0,0 +1,49 @@
1
+ [Company Name],,,PURCHASE ORDER,,,
2
+ [Company Slogan],,,,,DATE:,2/1/2013
3
+ ,,,,,P.O. #,[123456]
4
+ ,,,,,,
5
+ [Street Address],,,,,,
6
+ "[City, ST ZIP]",,,,,,
7
+ Phone: [000-000-0000],,,,,,
8
+ Fax: [000-000-0000],,,,,,
9
+ ,,,,,,
10
+ VENDOR,,,SHIP TO,,,
11
+ [Name],,,[Attn: Name],,,
12
+ [Company Name],,,[Company Name],,,
13
+ [Street Address],,,[Street Address],,,
14
+ "[City, ST ZIP]",,,"[City, ST ZIP]",,,
15
+ [Phone],,,[Phone],,,
16
+ ,,,,,,
17
+ REQUISITIONER,SHIP VIA,,F.O.B.,SHIPPING TERMS,,
18
+ ,,,,,,
19
+ ,,,,,,
20
+ ITEM #,DESCRIPTION,,,QTY,UNIT PRICE,TOTAL
21
+ [23423423],Product XYZ,,,15,150.00,"2,250.00"
22
+ [45645645],Product ABC,,,1,75.00,75.00
23
+ ,,,,,,0.00
24
+ ,,,,,,0.00
25
+ ,,,,,,0.00
26
+ ,,,,,,0.00
27
+ ,,,,,,0.00
28
+ ,,,,,,0.00
29
+ ,,,,,,0.00
30
+ ,,,,,,0.00
31
+ ,,,,,,0.00
32
+ ,,,,,,0.00
33
+ ,,,,,,0.00
34
+ ,,,,,,0.00
35
+ ,,,,,,0.00
36
+ ,,,,[42],SUBTOTAL,"$2,325.00"
37
+ Other Comments or Special Instructions,,,,,TAX RATE,6.875%
38
+ ,,,,,TAX,$159.84
39
+ ,,,,,S & H,$0.00
40
+ ,,,,,OTHER,$0.00
41
+ ,,,,,TOTAL,"$2,484.84"
42
+ ,,,,,,
43
+ ,,,,,,
44
+ ,,,,,,
45
+ ,,,,,,
46
+ ,,,Authorized by,,,Date
47
+ ,,,,,,
48
+ "If you have any questions about this purchase order, please contact",,,,,,
49
+ "[Name, Phone #, E-mail, Phone, Fax]",,,,,,
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'griddle/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "grid-csv"
8
+ spec.version = Griddle::VERSION
9
+ spec.authors = ["bjh"]
10
+ spec.email = ["fake@fake.com"]
11
+ spec.summary = %q{treat CSV like a grid}
12
+ spec.description = %q{access rectangles of data instead of rows and columns}
13
+ spec.homepage = "https://github.com/bjh/griddle"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "terminal-table"
22
+ spec.add_development_dependency "bundler", "~> 1.5"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ end
@@ -0,0 +1,4 @@
1
+ require 'griddle/version'
2
+ require 'griddle/point'
3
+ require 'griddle/rectangle'
4
+ require 'griddle/data_grid'
@@ -0,0 +1,105 @@
1
+ require 'csv'
2
+ require 'terminal-table'
3
+ require_relative 'point'
4
+
5
+ module Griddle
6
+ class DataGrid
7
+ include Enumerable
8
+ attr_accessor :case_sensitive, :offset, :grid
9
+
10
+ def self.from_csv(csv)
11
+ DataGrid.new.populate_from_csv(csv)
12
+ end
13
+
14
+ def initialize(start_counting_at_zero=false)
15
+ @offset = start_counting_at_zero ? 0 : 1
16
+ @grid = []
17
+ @case_sensitive = false
18
+ end
19
+
20
+ def to_rectangle
21
+ Rectangle.new(0, 0, width, height)
22
+ end
23
+
24
+ def width
25
+ grid[0].size
26
+ end
27
+
28
+ def height
29
+ grid.size
30
+ end
31
+
32
+ # Enumerable
33
+ def each(&block)
34
+ grid.each do |row|
35
+ block.call(row)
36
+ end
37
+ end
38
+
39
+ def to_s
40
+ Terminal::Table.new(rows: grid)
41
+ end
42
+
43
+ def populate_from_csv(csv_file_path)
44
+ CSV.foreach(csv_file_path) {|row| self.grid << row}
45
+ self
46
+ end
47
+
48
+ def populate_from_data(data)
49
+ self.grid = data
50
+ self
51
+ end
52
+
53
+ def to_regex(query)
54
+ return query if query.is_a? Regexp
55
+
56
+ if case_sensitive
57
+ /#{Regexp.escape(query)}/
58
+ else
59
+ /#{Regexp.escape(query)}/i
60
+ end
61
+ end
62
+
63
+ def find(what)
64
+ what = to_regex(what)
65
+ matches = []
66
+
67
+ grid.each_with_index do |row, row_index|
68
+ row.each_index.select {|n| what.match(row[n])}.each do |column|
69
+ matches << Point.new(*offset_up(row_index, column))
70
+ end
71
+ end
72
+
73
+ matches
74
+ end
75
+
76
+ def cut(top, left, width, height)
77
+ top, left = offset_down(top, left)
78
+ selection = []
79
+
80
+ for row in top...(top+height)
81
+ row_data = []
82
+
83
+ for col in left...(left+width)
84
+ row_data << grid[row][col]
85
+ end
86
+
87
+ selection << row_data
88
+ end
89
+
90
+ DataGrid.new.populate_from_data(selection)
91
+ end
92
+
93
+ def cut_rectangle(rectangle)
94
+ cut(rectangle.top, rectangle.left, rectangle.width, rectangle.height)
95
+ end
96
+
97
+ def offset_down(*args)
98
+ args.collect {|n| n - offset}
99
+ end
100
+
101
+ def offset_up(*args)
102
+ args.collect {|n| n + offset}
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,74 @@
1
+ require_relative 'rectangle'
2
+
3
+ module Griddle
4
+ class Point < Struct.new(:row, :col)
5
+ include Comparable
6
+
7
+ # TODO: not sure this is really necessary,
8
+ # it seemed like a good idea at the time
9
+ def <=>(point)
10
+ [row, col] <=> [point.row, point.col]
11
+ end
12
+
13
+ def to_s
14
+ "[#{row}, #{col}]"
15
+ end
16
+
17
+ def nil?
18
+ row.nil? && col.nil?
19
+ end
20
+
21
+ def zero?
22
+ row.zero? && col.zero?
23
+ end
24
+
25
+ def delta(point)
26
+ Point.new(
27
+ (self.row - point.row).abs,
28
+ (self.col - point.col).abs
29
+ )
30
+ end
31
+
32
+ # `point` is used to calculate the width and height
33
+ # of the new rectangle
34
+ def to_rectangle(point)
35
+ d = delta(point)
36
+
37
+ Rectangle.new(
38
+ row,
39
+ col,
40
+ d.col + 1,
41
+ d.row + 1
42
+ )
43
+ end
44
+
45
+ def move(directions)
46
+ directions.each_pair do |direction, amount|
47
+ amount = amount.to_i
48
+
49
+ case direction.to_sym
50
+ when :up
51
+ self.row -= amount
52
+ when :down
53
+ self.row += amount
54
+ when :left
55
+ self.col -= amount
56
+ when :right
57
+ self.col += amount
58
+ end
59
+ end
60
+ end
61
+
62
+ # some sugar
63
+ [:up, :down, :left, :right].each do |direction|
64
+ define_method direction do |amount|
65
+ move(direction => amount)
66
+ end
67
+ end
68
+ end
69
+
70
+ # # return NullPoint instead of nil
71
+ # NullPoint = Point.new(nil, nil)
72
+ # # for no apparent reason
73
+ # ZeroPoint = Point.new(0, 0)
74
+ end
@@ -0,0 +1,15 @@
1
+ module Griddle
2
+ class Rectangle < Struct.new(:top, :left, :width, :height)
3
+ # include Comparable
4
+
5
+ def self.create(top_left, bottom_right)
6
+ top_left.to_rectangle(bottom_right)
7
+ end
8
+
9
+ # TODO: what makes a rectangle less than another?
10
+ # the width, the position, the height? the size?
11
+ # def <=>(rectangle)
12
+ # [top, left, width, height] <=> [rectangle.top, rectangle.left, rectangle.width, rectangle.height]
13
+ # end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Griddle
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper.rb'
2
+ require 'griddle'
3
+
4
+ describe Griddle::DataGrid do
5
+ let(:grid) { Griddle::DataGrid.from_csv('data/po.csv') }
6
+
7
+ describe '#find' do
8
+ context 'using a regular expression' do
9
+ it 'uses the regexp to match against' do
10
+ expect(grid.find(/ship to/i)).to have(1).item
11
+ end
12
+ end
13
+
14
+ context 'when a single match is found' do
15
+ it 'returns an array with a single Point' do
16
+ expect(grid.find('SHIP TO')).to have(1).item
17
+ end
18
+
19
+ it 'the matched point has the row and column of the found item' do
20
+ p = grid.find('SHIP TO').last
21
+ expect(p.row).to eq 10
22
+ expect(p.col).to eq 4
23
+ end
24
+ end
25
+
26
+ context 'when multiple matches are found' do
27
+ it 'returns an array of Points' do
28
+ expect(grid.find('[Street Address]')).to have(3).items
29
+ end
30
+ end
31
+
32
+ context 'when a match is NOT found' do
33
+ it 'returns an empty array' do
34
+ expect(grid.find('SHAZAM!!!')).to have(0).items
35
+ end
36
+ end
37
+ end
38
+
39
+ describe '#width' do
40
+ it 'returns the width of the data grid' do
41
+ r = grid.cut(11, 1, 1, 5).to_rectangle
42
+ expect(r.width).to eq 1
43
+ end
44
+ end
45
+
46
+ describe '#height' do
47
+ it 'the height of the data grid' do
48
+ r = grid.cut(11, 1, 1, 5).to_rectangle
49
+ expect(r.height).to eq 5
50
+ end
51
+ end
52
+
53
+ describe '#to_rectangle' do
54
+ it 'returns a rectangle with the width and height of the data' do
55
+ r = grid.cut(11, 1, 1, 5).to_rectangle
56
+ expect(r.width).to eq 1
57
+ expect(r.height).to eq 5
58
+ end
59
+ end
60
+
61
+ describe '#cut' do
62
+ it 'returns a matrix/grid the size of the passed in parameters' do
63
+ data = grid.cut(11, 1, 1, 5)
64
+ expect(data.grid).to have(5).items
65
+ end
66
+ end
67
+
68
+ describe '#offset_up' do
69
+ it 'adds one to every value passed in' do
70
+ expect(grid.offset_up(4, 4)).to eq [5, 5]
71
+ end
72
+ end
73
+
74
+ describe '#offset_down' do
75
+ it 'subtract one from every value passed in' do
76
+ expect(grid.offset_down(4, 4)).to eq [3, 3]
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper.rb'
2
+ require 'griddle'
3
+
4
+ describe Griddle::Point do
5
+ let(:point) { Griddle::Point.new(10, 10) }
6
+
7
+ describe '#delta' do
8
+ context 'when the points have the same values' do
9
+ it 'returns a point with [0,0]' do
10
+ p2 = Griddle::Point.new(10, 10)
11
+ expect(point.delta(p2).zero?).to be_true
12
+ end
13
+ end
14
+
15
+ context 'when the points have the different values' do
16
+ it 'returns a point with the difference' do
17
+ p2 = Griddle::Point.new(3, 3)
18
+ expect(point.delta(p2)).to eq Griddle::Point.new(7, 7)
19
+ end
20
+ end
21
+ end
22
+
23
+ # make sure the spaceship was implemented properly
24
+ describe '<=>' do
25
+ context 'when lhs == rhs' do
26
+ it 'returns true' do
27
+ rhs = Griddle::Point.new(10, 10)
28
+ expect(point == rhs).to be_true
29
+ end
30
+ end
31
+
32
+ context 'when lhs > rhs' do
33
+ it 'returns true' do
34
+ rhs = Griddle::Point.new(9, 0)
35
+ expect(point > rhs).to be_true
36
+ end
37
+ end
38
+
39
+ context 'when lhs < rhs' do
40
+ it 'returns true' do
41
+ rhs = Griddle::Point.new(11, 11)
42
+ expect(point < rhs).to be_true
43
+ end
44
+ end
45
+ end
46
+
47
+ describe 'movement methods' do
48
+ it 'can go up' do
49
+ expect(point).to respond_to :up
50
+ end
51
+
52
+ it 'can go down' do
53
+ expect(point).to respond_to :down
54
+ end
55
+
56
+ it 'can go left' do
57
+ expect(point).to respond_to :left
58
+ end
59
+
60
+ it 'can go right' do
61
+ expect(point).to respond_to :right
62
+ end
63
+
64
+ describe "#move" do
65
+ it "moves up" do
66
+ point.move(up: 2)
67
+ expect(point.row).to eq(8)
68
+ end
69
+
70
+ it "moves down" do
71
+ point.move(:down => 4)
72
+ expect(point.row).to eq(14)
73
+ end
74
+
75
+ it "moves up and right" do
76
+ point.move(up:5, right:3)
77
+ expect(point.row).to eq(5)
78
+ expect(point.col).to eq(13)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grid-csv
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - bjh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: terminal-table
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.5'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.5'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: access rectangles of data instead of rows and columns
79
+ email:
80
+ - fake@fake.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - .rspec
87
+ - .rvmrc
88
+ - Gemfile
89
+ - Gemfile.lock
90
+ - LICENSE.txt
91
+ - README.markdown
92
+ - Rakefile
93
+ - data/example.png
94
+ - data/griddle.rb
95
+ - data/po.csv
96
+ - griddle.gemspec
97
+ - lib/griddle.rb
98
+ - lib/griddle/data_grid.rb
99
+ - lib/griddle/point.rb
100
+ - lib/griddle/rectangle.rb
101
+ - lib/griddle/version.rb
102
+ - spec/lib/data_grid_spec.rb
103
+ - spec/lib/point_spec.rb
104
+ - spec/spec_helper.rb
105
+ homepage: https://github.com/bjh/griddle
106
+ licenses:
107
+ - MIT
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ segments:
119
+ - 0
120
+ hash: 434941054811727487
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ segments:
128
+ - 0
129
+ hash: 434941054811727487
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 1.8.23
133
+ signing_key:
134
+ specification_version: 3
135
+ summary: treat CSV like a grid
136
+ test_files:
137
+ - spec/lib/data_grid_spec.rb
138
+ - spec/lib/point_spec.rb
139
+ - spec/spec_helper.rb