conformist 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ module Conformist
2
+ class Builder
3
+ def self.call schema, enumerable
4
+ columns = schema.columns
5
+ columns.inject HashStruct.new do |hash, column|
6
+ hash.merge column.name => column.values_in(enumerable)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,26 +1,23 @@
1
1
  module Conformist
2
2
  class Column
3
- attr_reader :name, :indexes, :preprocessor
3
+ attr_accessor :name, :indexes, :preprocessor
4
4
 
5
5
  def initialize name, *indexes, &preprocessor
6
- @name, @indexes, @preprocessor = name, indexes, preprocessor
6
+ self.name = name
7
+ self.indexes = indexes
8
+ self.preprocessor = preprocessor
7
9
  end
8
10
 
9
- # Map column(s) into a single value, strips whitespace and performs
10
- # any preprocessing. Takes a +row+ argument that should behave as an
11
- # array-like object.
12
- #
13
- # Example:
14
- #
15
- # row = ['Hello', 'World']
16
- # column = Column.new :first, 0
17
- # column.value_in row # => 'Hello'
18
- #
19
- # Returns preprocessed value.
20
- def values_in row
21
- values = indexes.map { |index| row[index] ? row[index].strip : nil }
22
- values = values.first if values.size == 1
23
- if preprocessor.respond_to? :call
11
+ def values_in array
12
+ values = array.values_at(*indexes).map do |value|
13
+ if value.respond_to? :strip
14
+ value.strip
15
+ else
16
+ value
17
+ end
18
+ end
19
+ values = values.first if values.size == 1
20
+ if preprocessor
24
21
  preprocessor.call values
25
22
  else
26
23
  values
@@ -0,0 +1,33 @@
1
+ module Conformist
2
+ class HashStruct
3
+ extend Forwardable
4
+
5
+ attr_accessor :attributes
6
+
7
+ def_delegators :attributes, :[], :[]=, :fetch, :key?
8
+
9
+ def initialize attributes = {}
10
+ self.attributes = attributes
11
+ end
12
+
13
+ def merge other
14
+ self.class.new.tap do |instance|
15
+ instance.attributes = attributes.merge other
16
+ end
17
+ end
18
+
19
+ def == other
20
+ other.class == self.class && attributes == other.attributes
21
+ end
22
+
23
+ protected
24
+
25
+ def respond_to_missing? method, include_private
26
+ key?(method) || super
27
+ end
28
+
29
+ def method_missing method, *args, &block
30
+ fetch(method) { super }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,66 @@
1
+ module Conformist
2
+ module Schema
3
+ def self.included base
4
+ base.send :include, InstanceExtensions
5
+ base.send :include, Methods
6
+ end
7
+
8
+ def self.extended base
9
+ base.extend ClassExtensions
10
+ base.extend Methods
11
+ end
12
+
13
+ module ClassExtensions
14
+ def inherited base
15
+ base.builder = builder.dup
16
+ base.columns = columns.dup
17
+ end
18
+
19
+ def load *args
20
+ raise "``#{self.class}.load` has been removed, use something like `#{self.class}.conform(file).each(&block)` instead (#{caller.first})"
21
+ end
22
+ end
23
+
24
+ module InstanceExtensions
25
+ def initialize super_schema = nil, &block
26
+ if super_schema
27
+ self.builder = super_schema.builder.dup
28
+ self.columns = super_schema.columns.dup
29
+ end
30
+ if block
31
+ instance_eval &block
32
+ end
33
+ end
34
+ end
35
+
36
+ module Methods
37
+ def builder
38
+ @builder ||= Builder
39
+ end
40
+
41
+ def builder= value
42
+ @builder = value
43
+ end
44
+
45
+ def column *args, &block
46
+ columns << Column.new(*args, &block)
47
+ end
48
+
49
+ def columns
50
+ @columns ||= []
51
+ end
52
+
53
+ def columns= value
54
+ @columns = value
55
+ end
56
+
57
+ def conform enumerables
58
+ Enumerator.new do |yielder|
59
+ enumerables.each do |enumerable|
60
+ yielder.yield builder.call(self, enumerable)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,3 +1,3 @@
1
1
  module Conformist
2
- VERSION = '0.0.3'
2
+ VERSION = '0.1.0'
3
3
  end
data/test/helper.rb CHANGED
@@ -1,13 +1,4 @@
1
- require 'minitest/autorun'
2
-
3
1
  require 'conformist'
4
- require 'definitions/acma'
5
- require 'definitions/fcc'
6
-
7
- def stub_row
8
- ('a'..'d').to_a
9
- end
2
+ require 'minitest/autorun'
10
3
 
11
- def fixture file
12
- File.expand_path "../fixtures/#{file}", __FILE__
13
- end
4
+ include Conformist
@@ -0,0 +1,15 @@
1
+ class ACMA
2
+ extend Conformist
3
+
4
+ column :name, 11 do |value|
5
+ value.match(/[A-Z]+$/)[0].upcase
6
+ end
7
+ column :callsign, 1
8
+ column :latitude, 15
9
+ end
10
+
11
+ class ACMA::Digital < ACMA
12
+ column :signal_type do
13
+ 'digital'
14
+ end
15
+ end
@@ -1,8 +1,6 @@
1
1
  class FCC
2
- include Conformist::Base
3
-
4
- option :col_sep => '|'
5
-
2
+ extend Conformist
3
+
6
4
  column :name, 10, 11 do |values|
7
5
  "#{values[0].upcase}, #{values[-1]}"
8
6
  end
@@ -0,0 +1,9 @@
1
+ require 'helper'
2
+
3
+ class BuilderTest < MiniTest::Unit::TestCase
4
+ def test_included
5
+ assert_raises RuntimeError do
6
+ Class.new { include Base }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ require 'helper'
2
+
3
+ class BuilderTest < MiniTest::Unit::TestCase
4
+ def test_call
5
+ column = MiniTest::Mock.new
6
+ column.expect :name, :a
7
+ column.expect :values_in, [1], [Array]
8
+ definition = MiniTest::Mock.new
9
+ definition.expect :columns, [column]
10
+ assert_equal HashStruct.new({:a => [1]}), Builder.call(definition, [])
11
+ end
12
+ end
@@ -1,6 +1,10 @@
1
1
  require 'helper'
2
2
 
3
3
  class ColumnTest < MiniTest::Unit::TestCase
4
+ def stub_row
5
+ ('a'..'d').to_a
6
+ end
7
+
4
8
  def test_name
5
9
  column = Conformist::Column.new :foo
6
10
  assert_equal :foo, column.name
@@ -0,0 +1,48 @@
1
+ require 'helper'
2
+
3
+ class HashStructTest < MiniTest::Unit::TestCase
4
+ def test_initialize
5
+ assert_equal({:a => 1}, HashStruct.new({:a => 1}).attributes)
6
+ assert_empty HashStruct.new.attributes
7
+ end
8
+
9
+ def test_delegates
10
+ hash = HashStruct.new
11
+ assert hash.respond_to?(:[])
12
+ assert hash.respond_to?(:[]=)
13
+ assert hash.respond_to?(:fetch)
14
+ assert hash.respond_to?(:key?)
15
+ end
16
+
17
+ def test_equality
18
+ hash1 = HashStruct.new :a => 1
19
+ hash2 = HashStruct.new :a => 1
20
+ hash3 = MiniTest::Mock.new
21
+ hash3.expect :attributes, {:a => 1}
22
+ hash3.expect :class, MiniTest::Mock
23
+ assert_equal hash1, hash2
24
+ refute_equal hash1, hash3
25
+ end
26
+
27
+ def test_merge
28
+ hash1 = HashStruct.new
29
+ hash2 = hash1.merge :a => 1
30
+ refute_equal hash1.object_id, hash2.object_id
31
+ refute_equal 1, hash1[:a]
32
+ assert_equal 1, hash2[:a]
33
+ end
34
+
35
+ def test_readers_with_method_missing
36
+ hash = HashStruct.new :a => 1, :c_d => 1
37
+ assert_equal 1, hash.a
38
+ assert_equal 1, hash.c_d
39
+ end
40
+
41
+ if respond_to? :respond_to_missing? # Compatible with 1.9
42
+ def test_readers_with_respond_to_missing
43
+ hash = HashStruct.new :a => 1, :c_d => 1
44
+ assert hash.respond_to?(:a)
45
+ assert hash.respond_to?(:c_d)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,74 @@
1
+ require 'helper'
2
+
3
+ class SchemaTest < MiniTest::Unit::TestCase
4
+ def test_initialize_with_instance
5
+ parent = Class.new { include Schema }.new.tap { |d| d.columns = [0] }
6
+ child1 = Class.new { include Schema }.new(parent).tap { |d| d.columns << 1 }
7
+ child2 = Class.new { include Schema }.new(parent).tap { |d| d.columns << 2 }
8
+ assert_equal [0], parent.columns
9
+ assert_equal [0, 1], child1.columns
10
+ assert_equal [0, 2], child2.columns
11
+ end
12
+
13
+ def test_initialize_with_block
14
+ anonymous = Class.new { include Schema }.new do
15
+ column :a, 0
16
+ column :b, 1
17
+ end
18
+ assert_equal 2, anonymous.columns.size
19
+ end
20
+
21
+ def test_builder_reader
22
+ assert_equal Builder, Class.new { extend Schema }.builder
23
+ end
24
+
25
+ def test_builder_writer
26
+ definition = Class.new { extend Schema }
27
+ definition.builder = Object
28
+ assert_equal Object, definition.builder
29
+ end
30
+
31
+ def test_columns_reader
32
+ assert_empty Class.new { extend Schema }.columns
33
+ end
34
+
35
+ def test_columns_writer
36
+ definition = Class.new { extend Schema }
37
+ definition.columns = [1]
38
+ assert_equal [1], definition.columns
39
+ end
40
+
41
+ def test_column
42
+ definition = Class.new { extend Schema }
43
+ definition.column :a, 0
44
+ definition.column :b, 1
45
+ assert_equal 2, definition.columns.size
46
+ end
47
+
48
+ def test_conform_returns_enumerable
49
+ definition = Class.new { extend Schema }
50
+ assert definition.conform([]).respond_to?(:each)
51
+ assert definition.conform([]).respond_to?(:map)
52
+ end
53
+
54
+ def test_conform_calls_builders_call_method
55
+ definition = Class.new { extend Schema }
56
+ definition.builder = lambda { |definition, value| value }
57
+ assert_equal [2, 4], definition.conform([1, 2]).map { |value| value * 2 }
58
+ end
59
+
60
+ def test_inheritance
61
+ parent = Class.new { extend Schema }.tap { |d| d.columns = [0] }
62
+ child1 = Class.new(parent).tap { |d| d.columns << 1 }
63
+ child2 = Class.new(parent).tap { |d| d.columns << 2 }
64
+ assert_equal [0], parent.columns
65
+ assert_equal [0, 1], child1.columns
66
+ assert_equal [0, 2], child2.columns
67
+ end
68
+
69
+ def test_load
70
+ assert_raises RuntimeError do
71
+ Class.new { extend Schema }.load
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,20 @@
1
+ require 'helper'
2
+
3
+ class ConformistTest < MiniTest::Unit::TestCase
4
+ def test_extended
5
+ definition = Class.new { extend Conformist }
6
+ assert definition.respond_to?(:builder)
7
+ assert definition.respond_to?(:columns)
8
+ assert definition.respond_to?(:conform)
9
+ end
10
+
11
+ def test_foreach
12
+ assert_raises RuntimeError do
13
+ Conformist.foreach
14
+ end
15
+ end
16
+
17
+ def test_new
18
+ assert Conformist.new.class.include?(Schema)
19
+ end
20
+ end
@@ -0,0 +1,72 @@
1
+ require 'csv'
2
+ require 'helper'
3
+ require 'schemas/acma'
4
+ require 'schemas/fcc'
5
+
6
+ class IntegrationTest < MiniTest::Unit::TestCase
7
+ def open_csv filename, options = {}
8
+ path = File.expand_path "../../fixtures/#{filename}", __FILE__
9
+ if CSV.method(:open).arity == -3 # 1.8 CSV
10
+ CSV.open path, 'r', options[:col_sep]
11
+ else
12
+ CSV.open path, options
13
+ end
14
+ end
15
+
16
+ def test_class_with_csv
17
+ enumerable = ACMA.conform open_csv('acma.csv')
18
+ last = enumerable.to_a.last
19
+ assert_equal HashStruct.new(:name=>'CRAFERS', :callsign=>'ADS10', :latitude=>'34 58 57S'), last
20
+ end
21
+
22
+ def test_inherited_class_with_csv
23
+ enumerable = ACMA::Digital.conform open_csv('acma.csv')
24
+ last = enumerable.to_a.last
25
+ assert_equal HashStruct.new(:name=>'CRAFERS', :callsign=>'ADS10', :latitude=>'34 58 57S', :signal_type => 'digital'), last
26
+ end
27
+
28
+ def test_class_with_psv
29
+ enumerable = FCC.conform open_csv('fcc.txt', :col_sep => '|')
30
+ last = enumerable.to_a.last
31
+ assert_equal HashStruct.new(:name => 'LOS ANGELES, CA', :callsign => 'KVTU-LP', :latitude => '34 13 38.00 N', :signtal_type => 'digital'), last
32
+ end
33
+
34
+ def test_instance_with_array_of_arrays
35
+ data = Array.new.tap do |d|
36
+ d << ['NSW', 'New South Wales', 'Sydney']
37
+ d << ['VIC', 'Victoria', 'Melbourne']
38
+ d << ['QLD', 'Queensland', 'Brisbane']
39
+ end
40
+ definition = Conformist.new do
41
+ column :state, 0, 1 do |values|
42
+ "#{values.first}, #{values.last}"
43
+ end
44
+ column :capital, 2
45
+ end
46
+ enumerable = definition.conform data
47
+ last = enumerable.to_a.last
48
+ assert_equal HashStruct.new(:state => 'QLD, Queensland', :capital => 'Brisbane'), last
49
+ end
50
+
51
+ def test_inherited_instance_with_array_of_arrays
52
+ data = Array.new.tap do |d|
53
+ d << ['NSW', 'New South Wales', 'Sydney']
54
+ d << ['VIC', 'Victoria', 'Melbourne']
55
+ d << ['QLD', 'Queensland', 'Brisbane']
56
+ end
57
+ parent = Conformist.new do
58
+ column :state, 0, 1 do |values|
59
+ "#{values.first}, #{values.last}"
60
+ end
61
+ column :capital, 2
62
+ end
63
+ child = Conformist.new parent do
64
+ column :country do
65
+ 'Australia'
66
+ end
67
+ end
68
+ enumerable = child.conform data
69
+ last = enumerable.to_a.last
70
+ assert_equal HashStruct.new(:state => 'QLD, Queensland', :capital => 'Brisbane', :country => 'Australia'), last
71
+ end
72
+ end