conformist 0.0.3 → 0.1.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,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