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.
- 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
|