table-query 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +68 -0
- data/Rakefile +11 -0
- data/example/calendar-jp/calendar-jp-query.rb +20 -0
- data/example/calendar-jp/calendar-jp-schema.rb +33 -0
- data/example/calendar-jp/calendar-jp.csv +365 -0
- data/example/simple/simple-query.rb +6 -0
- data/example/simple/simple-schema.rb +17 -0
- data/example/simple/simple.csv +6 -0
- data/lib/table-query.rb +26 -0
- data/lib/table-query/field.rb +4 -0
- data/lib/table-query/query-schema.rb +96 -0
- data/lib/table-query/query.rb +54 -0
- data/lib/table-query/schema-loader.rb +92 -0
- data/lib/table-query/table-schema.rb +34 -0
- data/lib/table-query/table.rb +61 -0
- data/lib/table-query/type.rb +121 -0
- data/lib/table-query/version.rb +4 -0
- data/table-query.gemspec +21 -0
- data/test/spec_field.rb +24 -0
- data/test/spec_query-schema.rb +42 -0
- data/test/spec_query.rb +35 -0
- data/test/spec_schema-loader.rb +15 -0
- data/test/spec_table-schema.rb +18 -0
- data/test/spec_table.rb +27 -0
- data/test/spec_type.rb +97 -0
- data/test/table/alphabet-schema.rb +14 -0
- data/test/table/alphabet.csv +26 -0
- data/test/table/calendar-schema.rb +29 -0
- data/test/table/calendar.csv +365 -0
- metadata +105 -0
@@ -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
|
data/lib/table-query.rb
ADDED
@@ -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,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
|