table-query 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ require 'table-query'
2
+
3
+ path = File.join(File.dirname(__FILE__), "simple.csv")
4
+ table = TableQuery.table(path)
5
+ p table.number_to_char(1).entries # => [:a, :b, :c]
6
+ p table.char_to_number(:b).entries # => [1, 3]
@@ -0,0 +1,17 @@
1
+ # data fields schema
2
+ table.define do
3
+ field :number, :int
4
+ field :char, :symbol
5
+ end
6
+
7
+ # query schema bthat named "number_to_char"
8
+ query.define(:number_to_char) do
9
+ input :number
10
+ output :char
11
+ end
12
+
13
+ # query schema that named "char_to_number"
14
+ query.define(:char_to_number) do
15
+ input :char
16
+ output :number
17
+ end
@@ -0,0 +1,6 @@
1
+ 1,a
2
+ 1,b
3
+ 1,c
4
+ 2,a
5
+ 2,c
6
+ 3,b
@@ -0,0 +1,26 @@
1
+ require 'csv'
2
+ require 'date'
3
+
4
+ require 'table-query/field'
5
+ require 'table-query/query'
6
+ require 'table-query/type'
7
+ require 'table-query/table-schema'
8
+ require 'table-query/query-schema'
9
+ require 'table-query/schema-loader'
10
+ require 'table-query/table'
11
+
12
+ # TableQuery provides table definition schema and the query.
13
+ # You can get table data by easy way.
14
+ module TableQuery
15
+ # Create a table.
16
+ #
17
+ # @param path [String]
18
+ # table data file path
19
+ # @param options [Hash]
20
+ # table options
21
+ # @return [Table]
22
+ # table
23
+ def self.table(path, options={})
24
+ Table.new(path, options)
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ module TableQuery
2
+ # Field represents table fields.
3
+ Field = Struct.new(:name, :type, :pos, :value, :options)
4
+ end
@@ -0,0 +1,96 @@
1
+ module TableQuery
2
+ # QuerySchema defines query's input and output field relation.
3
+ class QuerySchema
4
+ # Define a new query schema.
5
+ #
6
+ # @param name [Symbol]
7
+ # query name
8
+ # @return [QuerySchema]
9
+ # new query schema
10
+ def self.define(name, &b)
11
+ new(name).tap {|x| x.instance_eval(&b)}
12
+ end
13
+
14
+ # query name
15
+ # @return [Symbol]
16
+ attr_reader :name
17
+
18
+ # input field list
19
+ # @return [Array<Field>]
20
+ attr_reader :inputs
21
+
22
+ # output field list
23
+ # @return [Array<Field>]
24
+ attr_reader :outputs
25
+
26
+ # preset value list
27
+ # @return [Array<Field>]
28
+ attr_reader :values
29
+
30
+ # Create a query schema object.
31
+ #
32
+ # @param name [Symbol]
33
+ # query name
34
+ def initialize(name)
35
+ @name = name
36
+ @inputs = []
37
+ @outputs = []
38
+ @values = []
39
+ @table = nil
40
+ end
41
+
42
+ # Define an input field.
43
+ #
44
+ # @param name [Symbol]
45
+ # field name
46
+ # @param options [Hash]
47
+ # input options
48
+ # @return [void]
49
+ def input(name, options={})
50
+ @inputs << Field.new(name, nil, nil, nil, options)
51
+ end
52
+
53
+ # Define an output field.
54
+ #
55
+ # @param name [Symbol]
56
+ # field name
57
+ # @param options [Hash]
58
+ # output options
59
+ # @return [void]
60
+ def output(name, options={})
61
+ @outputs << Field.new(name, nil, nil, nil, options)
62
+ end
63
+
64
+ # Define pre set value of the query.
65
+ #
66
+ # @param name [Symbol]
67
+ # field name
68
+ # @param val [Object]
69
+ # table data
70
+ # @param options [Hash]
71
+ # value options
72
+ # @return [void]
73
+ def value(name, val, options={})
74
+ @values << Field.new(name, nil, nil, val, options)
75
+ end
76
+
77
+ # Set target table for query.
78
+ #
79
+ # @param table [Table]
80
+ # query target table
81
+ # @return [void]
82
+ # @api private
83
+ def set_table(table)
84
+ @table = table
85
+ @table.table_schema.fields.each do |field|
86
+ (@inputs + @outputs + @values).each do |item|
87
+ if field.name == item.name
88
+ item.pos = field.pos
89
+ item.type = @table.types[field.type]
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
@@ -0,0 +1,54 @@
1
+ module TableQuery
2
+ # Query is a class for providing query interfaces. This is enumerable, so you
3
+ # can access results by various enumeration methods.
4
+ class Query
5
+ include Enumerable
6
+
7
+ # target table
8
+ # @return [Table]
9
+ attr_reader :table
10
+
11
+ # query schema
12
+ # @return [QuerySchema]
13
+ attr_reader :query_schema
14
+
15
+ # query values
16
+ # @return [Array<Object>]
17
+ attr_reader :values
18
+
19
+ # Create a query interface.
20
+ def initialize(table, query_schema, vals)
21
+ @table = table
22
+ @query_schema = query_schema
23
+ @values = vals
24
+ end
25
+
26
+ # This is an enumeration method. If block is not given, then return an
27
+ # +Enumerator+ object.
28
+ def each
29
+ if block_given?
30
+ vals = []
31
+ @query_schema.inputs.each_with_index do |input, i|
32
+ vals << input.type.convert_from_ruby(@values[i])
33
+ end
34
+ @table.csv.each do |row|
35
+ if @query_schema.inputs.zip(vals).all? {|input, val| row[input.pos] == val} and
36
+ @query_schema.values.all? {|value| row[value.pos] == value.type.convert_from_ruby(value.value)}
37
+ outputs = []
38
+ cond = @query_schema.outputs.each do |output|
39
+ val = row[output.pos]
40
+ if output.options[:empty] == :ignore and val == ""
41
+ break :break
42
+ else
43
+ outputs << output.type.convert_to_ruby(val)
44
+ end
45
+ end
46
+ yield *outputs unless cond == :break
47
+ end
48
+ end
49
+ else
50
+ return Enumerator.new(self)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,92 @@
1
+ module TableQuery
2
+ # SchemaLoader provides functions to load schema file and evaluation context.
3
+ class SchemaLoader
4
+ # VariableType is a class for +type+ special variable.
5
+ # @api private
6
+ class VariableType
7
+ attr_reader :value
8
+
9
+ def initialize
10
+ @value = {}
11
+ end
12
+
13
+ def define(name, &b)
14
+ @value[name] = Type.define(name, &b)
15
+ end
16
+ end
17
+
18
+ # VariableTable is a class for +table+ special variable.
19
+ # @api private
20
+ class VariableTable
21
+ attr_reader :value
22
+
23
+ def define(&b)
24
+ @value = TableSchema.define(&b)
25
+ end
26
+ end
27
+
28
+ # VariableQuery is a class for +query+ special variable.
29
+ # @api private
30
+ class VariableQuery
31
+ attr_reader :value
32
+
33
+ def initialize
34
+ @value = {}
35
+ end
36
+
37
+ def define(name, &b)
38
+ @value[name] = QuerySchema.define(name, &b)
39
+ end
40
+ end
41
+
42
+ # Evaluation context for schema definition.
43
+ class Context
44
+ attr_reader :type
45
+ attr_reader :table
46
+ attr_reader :query
47
+
48
+ # Create a context.
49
+ def initialize
50
+ @type = VariableType.new
51
+ @table = VariableTable.new
52
+ @query = VariableQuery.new
53
+ end
54
+
55
+ # Return the context binding.
56
+ def binding
57
+ Kernel.binding
58
+ end
59
+ end
60
+
61
+ # Load schema file and evaluate.
62
+ #
63
+ # @param src [String]
64
+ # schema source
65
+ # @param filename [String]
66
+ # filename
67
+ def self.load(src, filename)
68
+ new(src, filename).eval
69
+ end
70
+
71
+ # Create a new loader.
72
+ #
73
+ # @param src [String]
74
+ # schema source
75
+ # @param filename [String]
76
+ # filename
77
+ def initialize(src, filename)
78
+ @src = src
79
+ @filename = filename
80
+ @context = Context.new
81
+ end
82
+
83
+ # Evaluate schema file.
84
+ #
85
+ # @return [Array<Array<Type>, TableSchema, Array<QuerySchema>>]
86
+ # schema contents
87
+ def eval
88
+ @context.binding.eval(@src, @filename)
89
+ [@context.type.value, @context.table.value, @context.query.value]
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,34 @@
1
+ module TableQuery
2
+ # TableSchema defines table fields.
3
+ class TableSchema
4
+ # Define a new table schema.
5
+ #
6
+ # @param b [Proc]
7
+ # schema definition
8
+ # @return [TableSchema]
9
+ # table schema
10
+ def self.define(&b)
11
+ new.tap {|x| x.instance_eval(&b)}
12
+ end
13
+
14
+ # table field list
15
+ # @return [Array<Field>]
16
+ attr_reader :fields
17
+
18
+ # Create a new table schema.
19
+ def initialize
20
+ @fields = []
21
+ end
22
+
23
+ # Add a table field.
24
+ #
25
+ # @param name [Symbol]
26
+ # field name
27
+ # @param type [Symbol]
28
+ # field type name
29
+ # @return [void]
30
+ def field(name, type)
31
+ @fields << Field.new(name, type, @fields.size, nil, {})
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,61 @@
1
+ module TableQuery
2
+ # Table is a class for loading table data/schema files and providing
3
+ # interfaces for query.
4
+ class Table
5
+ # table field type list
6
+ attr_reader :types
7
+
8
+ # table schema
9
+ attr_reader :table_schema
10
+
11
+ # table query schema list
12
+ attr_reader :query_schemas
13
+
14
+ # @api private
15
+ attr_reader :csv
16
+
17
+ # Create a new table.
18
+ #
19
+ # @param path [String]
20
+ # table data file path
21
+ # @param options [Hash]
22
+ # table options
23
+ def initialize(path, options={})
24
+ @path = path
25
+ @csv = CSV.read(path)
26
+
27
+ eval_schema_file
28
+ setup_query_schemas
29
+ end
30
+
31
+ # Return schema filename.
32
+ #
33
+ # @return [String]
34
+ # schema filename
35
+ def schema_path
36
+ name = "%s-schema.rb" % File.basename(@path, ".*")
37
+ File.join(File.dirname(@path), name)
38
+ end
39
+
40
+ private
41
+
42
+ # Evaluate schema file.
43
+ def eval_schema_file
44
+ src = File.read(schema_path)
45
+ types, @table_schema, @query_schemas = SchemaLoader.load(src, schema_path)
46
+ standard_types = Hash[STANDARD_TYPES.map{|type| [type.name, type]}]
47
+ @types = standard_types.merge(types)
48
+ end
49
+
50
+ # Setup query schemas to access the table.
51
+ def setup_query_schemas
52
+ @query_schemas.each do |name, schema|
53
+ schema.set_table self
54
+ singleton = class << self; self; end
55
+ singleton.send(:define_method, name) do |*vals|
56
+ Query.new(self, schema, vals)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,121 @@
1
+ module TableQuery
2
+ # Type is a class for data conversion.
3
+ class Type
4
+ # Define new type with the block.
5
+ #
6
+ # @return [Type]
7
+ # type object
8
+ def self.define(name, &b)
9
+ new(name).tap {|x| x.instance_eval(&b)}
10
+ end
11
+
12
+ # Return the type name.
13
+ attr_reader :name
14
+
15
+ # Create new type.
16
+ #
17
+ # @param name [Symbol]
18
+ # type name
19
+ def initialize(name)
20
+ @name = name
21
+ end
22
+
23
+ # Define a converter data to ruby object.
24
+ #
25
+ # @param b [Proc]
26
+ # table data to ruby object converter
27
+ # @return [void]
28
+ def to_ruby(&b)
29
+ @to_ruby = b
30
+ end
31
+
32
+ # Define a converter ruby object to data.
33
+ #
34
+ # @param b [Proc]
35
+ # ruby object to table data converter
36
+ # @return [void]
37
+ def from_ruby(&b)
38
+ @from_ruby = b
39
+ end
40
+
41
+ # Convert the data to ruby object.
42
+ #
43
+ # @param val [String]
44
+ # the data
45
+ # @return [Object]
46
+ # ruby object
47
+ def convert_to_ruby(val)
48
+ @to_ruby.call(val)
49
+ end
50
+
51
+ # Convert the ruby object to data.
52
+ #
53
+ # @param val [Object]
54
+ # ruby object
55
+ # @return [String]
56
+ # data
57
+ def convert_from_ruby(val)
58
+ @from_ruby.call(val)
59
+ end
60
+
61
+ # field type +bool+
62
+ BOOL = Type.define(:bool) {
63
+ to_ruby {|val| val == "true"}
64
+ from_ruby {|val| val.to_s}
65
+ }
66
+
67
+ # field type +int+
68
+ INT = Type.define(:int) {
69
+ to_ruby {|val| val.to_i}
70
+ from_ruby {|val| val.to_s}
71
+ }
72
+
73
+ # field type +float+
74
+ FLOAT = Type.define(:float) {
75
+ to_ruby {|val| val.to_f}
76
+ from_ruby {|val| val.to_s}
77
+ }
78
+
79
+ # field type +string+
80
+ STRING = Type.define(:string) {
81
+ to_ruby {|val| val}
82
+ from_ruby {|val| val.to_s}
83
+ }
84
+
85
+ # field type +symbol+
86
+ SYMBOL = Type.define(:symbol) {
87
+ to_ruby {|val| val.to_sym}
88
+ from_ruby {|val| val.to_s}
89
+ }
90
+
91
+ # field type +time+
92
+ TIME = Type.define(:time) {
93
+ to_ruby {|val| DateTime.parse(val)}
94
+ from_ruby {|val| val.strftime("%Y-%m-%d %H:%M:%S")}
95
+ }
96
+
97
+ # field type +date+
98
+ DATE = Type.define(:date) {
99
+ to_ruby {|val| Date.parse(val)}
100
+ from_ruby {|val| val.strftime("%Y-%m-%d")}
101
+ }
102
+ end
103
+
104
+ # standard type list that includes
105
+ # - bool
106
+ # - int
107
+ # - float
108
+ # - string
109
+ # - symbol
110
+ # - time
111
+ # - date
112
+ STANDARD_TYPES = [
113
+ Type::BOOL,
114
+ Type::INT,
115
+ Type::FLOAT,
116
+ Type::STRING,
117
+ Type::SYMBOL,
118
+ Type::TIME,
119
+ Type::DATE
120
+ ]
121
+ end