table-query 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,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