bmg 0.23.3 → 0.23.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b89960dcbdf37657c8c0dea35e8f735c71175e61363a122b5a249e4a3bd232e
4
- data.tar.gz: 93f21f3f85bf21466a280b67e6cf339ae1498ed165cf40fcbd38ac88bd1f2970
3
+ metadata.gz: cb0b47d8dbd6924eb1f4fb7e773635a5d1fc83fc09b09c60ead3ac913bbd2051
4
+ data.tar.gz: ad0919fc045b59b5299254d0f476b31390069b6d39e086334cd55790eedf56e0
5
5
  SHA512:
6
- metadata.gz: 043731637cf8c164225db38e9f47c2d9bf68a35598ea37947dede59c031c99e550f0dcfe5ac20503942d0a43b5036749b9d158140f59d1e463594a2fe20a1236
7
- data.tar.gz: ad41145f3411f1e4cf28e293ef53faee512c9808cbc0e7a091a2da3366b665dadea628eacbf684e5c24d1ea4c3c97df8c5e783f59597d6a385119a14f8acab8c
6
+ metadata.gz: 1fa7c3dfa036762e82dd86275c522af811eba747901f288e8b1804388ddee0094350374795527b0884cf8d5355eea649f9c3ce4a66ec7c9742eeaef5fff81a16
7
+ data.tar.gz: 5cee0487c9e70df6a049791ef48d93fda5ff516d769b7f8d25f9ea6f29d9d780bec8aebca78a8e88e435113acde6860fc1c7dbbbb2c9bb7f70edf88d22bfe739
data/README.md CHANGED
@@ -22,6 +22,7 @@ further down this README.
22
22
  * [Connecting to SQL databases](#connecting-to-sql-databases)
23
23
  * [Reading data files](#reading-data-files-json-csv-yaml-text-xls--xlsx)
24
24
  * [Connecting to Redis databases](#connecting-to-redis-databases)
25
+ * [The Generator](#the-generator)
25
26
  * [Your own relations](#your-own-relations)
26
27
  * [The Database abstraction](#the-database-abstraction)
27
28
  * [List of supported operators](#supported-operators)
@@ -238,6 +239,42 @@ and is optional.
238
239
  The redis relvars support basic algorithms for insert/update/delete.
239
240
  No optimization is currently supported.
240
241
 
242
+ ### The Generator
243
+
244
+ Somewhat similar to PostgreSQL's `generate_series`, Bmg supports the generation
245
+ of relations. Unlike PostgreSQL though, the result is a Relation that can be
246
+ "chained" as any other relation, not a sequence of scalars :
247
+
248
+ ```ruby
249
+ r = Bmg
250
+ .generate(1, 10, :step => 2, :as => :index)
251
+ .restrict(:index => 5)
252
+ ```
253
+
254
+ If you don't specify the `:as` option, the attribute name will be `:i`.
255
+
256
+ The step can be negative :
257
+
258
+ ```ruby
259
+ Bmg.generate(10, 1, :step => -2, :as => :index)
260
+ ```
261
+
262
+ The step can also be computed :
263
+
264
+ ```ruby
265
+ Bmg.generate(1, 100, :step => ->(current){ current * 2 })
266
+ ```
267
+
268
+ Last but not least, you can use any ordinal data type. For instance, the
269
+ following code will work and generate all months from May to December 2025
270
+ (under the assumption that ActiveRecord's `next_month` is available).
271
+
272
+ ```ruby
273
+ min = Date.new(2025,5,1)
274
+ max = Date.new(2025,12,1)
275
+ Bmg.generate(min, max, :step => ->(current){ current.next_month })
276
+ ```
277
+
241
278
  ### Your own relations
242
279
 
243
280
  As noted earlier, Bmg has a simple relation interface where you only have to
@@ -353,6 +390,7 @@ r.transform(:to_s) # all-attrs transformation
353
390
  r.transform(&:to_s) # similar, but Proc-driven
354
391
  r.transform(:foo => :upcase, ...) # specific-attrs tranformation
355
392
  r.transform([:to_s, :upcase]) # chain-transformation
393
+ r.undress() # convert all values to a scalar (number, string, bool, object, array)
356
394
  r.ungroup([:a, :b, ...]) # ungroup relation-valued attributes within parent tuple
357
395
  r.ungroup(:a) # shortcut over ungroup([:a])
358
396
  r.union(right) # set union
data/lib/bmg/algebra.rb CHANGED
@@ -184,6 +184,15 @@ module Bmg
184
184
  end
185
185
  protected :_transform
186
186
 
187
+ def undress(options = {})
188
+ _undress self.type.undress(options), options
189
+ end
190
+
191
+ def _undress(type, options)
192
+ Operator::Undress.new(type, self, options)
193
+ end
194
+ protected :_undress
195
+
187
196
  def ungroup(attrs)
188
197
  _ungroup self.type.ungroup(attrs), attrs
189
198
  end
@@ -0,0 +1,68 @@
1
+ module Bmg
2
+ module Operator
3
+ #
4
+ # Generator operator.
5
+ #
6
+ # Generates a relation. Most inspired by PostgreSQL's generate_series.
7
+ #
8
+ class Generator
9
+ include Operator::Zeroary
10
+
11
+ DEFAULT_OPTIONS = {
12
+ :as => :i,
13
+ :step => 1,
14
+ }
15
+
16
+ def initialize(type, from, to, options = {})
17
+ options = { step: options } unless options.is_a?(Hash)
18
+ @type = type
19
+ @from = from
20
+ @to = to
21
+ @options = DEFAULT_OPTIONS.merge(options)
22
+ raise ArgumentError, "from, to and step must be defined" if from.nil? || to.nil? || step.nil?
23
+ end
24
+ attr_reader :from, :to, :options
25
+
26
+ def each
27
+ return to_enum unless block_given?
28
+
29
+ current = from
30
+ as = options[:as]
31
+
32
+ until overflowed?(current)
33
+ yield({ as => current })
34
+ current = next_of(current)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def step
41
+ @step ||= options[:step]
42
+ end
43
+
44
+ def positive_step?
45
+ !step.is_a?(Numeric) || step > 0
46
+ end
47
+
48
+ def next_of(current)
49
+ step.is_a?(Proc) ? step.call(current) : current + step
50
+ end
51
+
52
+ def overflowed?(current)
53
+ positive_step? ? current > to : current < to
54
+ end
55
+
56
+ protected ### inspect
57
+
58
+ def operands
59
+ []
60
+ end
61
+
62
+ def args
63
+ [ from, to, options ]
64
+ end
65
+
66
+ end # class Generator
67
+ end # module Operator
68
+ end # module Bmg
@@ -0,0 +1,24 @@
1
+ module Bmg
2
+ module Operator
3
+ module Zeroary
4
+ include Operator
5
+
6
+ def bind(binding)
7
+ dup
8
+ end
9
+
10
+ protected
11
+
12
+ attr_accessor :operand
13
+
14
+ def _visit(parent, visitor)
15
+ visitor.call(self, parent)
16
+ end
17
+
18
+ def operands
19
+ []
20
+ end
21
+
22
+ end # module Zeroary
23
+ end # module Operator
24
+ end # module Bmg
@@ -0,0 +1,66 @@
1
+ module Bmg
2
+ module Operator
3
+ #
4
+ # Undress operator.
5
+ #
6
+ # Transform all values to keep only integer, strings & booleans
7
+ #
8
+ class Undress
9
+ include Operator::Unary
10
+
11
+ DEFAULT_OPTIONS = {}
12
+
13
+ def initialize(type, operand, options = {})
14
+ @type = type
15
+ @operand = operand
16
+ @options = DEFAULT_OPTIONS.merge(options)
17
+ end
18
+
19
+ protected
20
+
21
+ attr_reader :options
22
+
23
+ public
24
+
25
+ def each
26
+ return to_enum unless block_given?
27
+
28
+ @operand.each do |tuple|
29
+ yield undress(tuple)
30
+ end
31
+ end
32
+
33
+ def to_ast
34
+ [ :transform, operand.to_ast, transformation.dup ]
35
+ end
36
+
37
+ protected ### inspect
38
+
39
+ def args
40
+ [ ]
41
+ end
42
+
43
+ private
44
+
45
+ def undress(value)
46
+ case value
47
+ when ->(v) { v.respond_to?(:undress) }
48
+ value.undress
49
+ when Hash
50
+ value.each_with_object({}) do |(k,v), undressed|
51
+ undressed[k] = undress(v)
52
+ end
53
+ when Relation
54
+ Relation.new value.map{|tuple| undress(tuple) }
55
+ when Date, Time
56
+ value.iso8601
57
+ when Array
58
+ value.map{|v| undress(v) }
59
+ else
60
+ value
61
+ end
62
+ end
63
+
64
+ end # class Undress
65
+ end # module Operator
66
+ end # module Bmg
data/lib/bmg/operator.rb CHANGED
@@ -17,15 +17,20 @@ module Bmg
17
17
 
18
18
  def inspect
19
19
  str = "(#{self.class.name.split('::').last.downcase}\n"
20
- str << operands.map{|op| op.inspect.gsub(/^/m, " ") }.join("\n")
21
- str << "\n"
22
- str << args.map{|a| a.inspect.gsub(/^/m, " ") }.join("\n")
20
+ unless operands.empty?
21
+ str << operands.map{|op| op.inspect.gsub(/^/m, " ") }.join("\n")
22
+ str << "\n"
23
+ end
24
+ unless args.empty?
25
+ str << args.map{|a| a.inspect.gsub(/^/m, " ") }.join("\n")
26
+ end
23
27
  str << ")"
24
28
  str
25
29
  end
26
30
 
27
31
  end # module Operator
28
32
  end # module Bmg
33
+ require_relative 'operator/shared/zeroary'
29
34
  require_relative 'operator/shared/unary'
30
35
  require_relative 'operator/shared/binary'
31
36
  require_relative 'operator/shared/nary'
@@ -48,6 +53,9 @@ require_relative 'operator/restrict'
48
53
  require_relative 'operator/rxmatch'
49
54
  require_relative 'operator/summarize'
50
55
  require_relative 'operator/transform'
56
+ require_relative 'operator/undress'
51
57
  require_relative 'operator/ungroup'
52
58
  require_relative 'operator/union'
53
59
  require_relative 'operator/unwrap'
60
+
61
+ require_relative 'operator/generator'
data/lib/bmg/relation.rb CHANGED
@@ -2,17 +2,13 @@ module Bmg
2
2
  module Relation
3
3
  include Enumerable
4
4
  include Algebra
5
+ extend Factory
5
6
 
6
7
  def self.new(operand, type = Type::ANY)
7
8
  raise ArgumentError, "Missing type" if type.nil?
8
9
  operand.is_a?(Relation) ? operand : Bmg.in_memory(operand, type)
9
10
  end
10
11
 
11
- def self.empty(type = Type::ANY)
12
- raise ArgumentError, "Missing type" if type.nil?
13
- Relation::Empty.new(type)
14
- end
15
-
16
12
  def bind(binding)
17
13
  self
18
14
  end
@@ -0,0 +1,53 @@
1
+ module Bmg
2
+ module Factory
3
+ def empty(type = Type::ANY)
4
+ raise ArgumentError, "Missing type" if type.nil?
5
+ Relation::Empty.new(type)
6
+ end
7
+
8
+ def mutable(enumerable, type = Type::ANY)
9
+ Relation::InMemory::Mutable.new(type, enumerable).spied(Bmg.main_spy)
10
+ end
11
+
12
+ def in_memory(enumerable, type = Type::ANY)
13
+ Relation::InMemory.new(type, enumerable).spied(Bmg.main_spy)
14
+ end
15
+
16
+ def generate(from, to, options = {})
17
+ type = Type::ANY.with_attrlist([options[:as] || Operator::Generator::DEFAULT_OPTIONS[:as]])
18
+ Operator::Generator.new(type, from, to, options)
19
+ end
20
+
21
+ def text_file(path, options = {}, type = Type::ANY)
22
+ Reader::TextFile.new(type, path, options).spied(Bmg.main_spy)
23
+ end
24
+
25
+ def csv(path, options = {}, type = Type::ANY)
26
+ Reader::Csv.new(type, path, options).spied(Bmg.main_spy)
27
+ end
28
+
29
+ def json_file(path, options = {}, type = Type::ANY)
30
+ in_memory(path.load.map{|tuple| TupleAlgebra.symbolize_keys(tuple) })
31
+ end
32
+
33
+ def json(*args, &bl)
34
+ json_file(*args, &bl)
35
+ end
36
+
37
+ def yaml_file(path, options = {}, type = Type::ANY)
38
+ in_memory(path.load.map{|tuple| TupleAlgebra.symbolize_keys(tuple) })
39
+ end
40
+
41
+ def yaml(*args, &bl)
42
+ yaml_file(*args, &bl)
43
+ end
44
+
45
+ def excel_file(path, options = {}, type = Type::ANY)
46
+ Reader::Excel.new(type, path, options).spied(Bmg.main_spy)
47
+ end
48
+
49
+ def excel(*args, &bl)
50
+ excel_file(*args, &bl)
51
+ end
52
+ end
53
+ end
@@ -49,6 +49,11 @@ module Bmg
49
49
  with.inject(tuple){|dup,on|
50
50
  transform_tuple(dup, on)
51
51
  }
52
+ when Type
53
+ unless with.knows_types?
54
+ raise ArgumentError, "Heading must specify the attribute types`"
55
+ end
56
+ transform_tuple(tuple, with.heading)
52
57
  else
53
58
  raise ArgumentError, "Unexpected transformation `#{with.inspect}`"
54
59
  end
@@ -78,6 +83,18 @@ module Bmg
78
83
  with.call(value)
79
84
  when Hash
80
85
  with[value]
86
+ when Type
87
+ unless with.knows_types?
88
+ raise ArgumentError, "Heading must specify the attribute types`"
89
+ end
90
+ case value
91
+ when Hash
92
+ transform_tuple(value, with.heading)
93
+ when Enumerable
94
+ Relation.new(value.map{|t| transform_tuple(t, with.heading) })
95
+ else
96
+ raise ArgumentError, "Unexpected value `#{value.inspect}`"
97
+ end
81
98
  else
82
99
  raise ArgumentError, "Unexpected transformation `#{with.inspect}`"
83
100
  end
data/lib/bmg/support.rb CHANGED
@@ -3,3 +3,4 @@ require_relative 'support/tuple_transformer'
3
3
  require_relative 'support/keys'
4
4
  require_relative 'support/ordering'
5
5
  require_relative 'support/output_preferences'
6
+ require_relative 'support/factory'
data/lib/bmg/type.rb CHANGED
@@ -9,6 +9,10 @@ module Bmg
9
9
 
10
10
  ANY = Type.new
11
11
 
12
+ def self.for_heading(heading)
13
+ Type.new.with_heading(heading)
14
+ end
15
+
12
16
  public ## type checking
13
17
 
14
18
  attr_writer :typechecked
@@ -41,6 +45,22 @@ module Bmg
41
45
  }
42
46
  end
43
47
 
48
+ public ## heading
49
+
50
+ attr_accessor :heading
51
+ protected :heading=
52
+
53
+ def with_heading(heading)
54
+ dup.tap{|x|
55
+ x.heading = heading
56
+ x.attrlist = heading.keys
57
+ }
58
+ end
59
+
60
+ def knows_types?
61
+ !@heading.nil?
62
+ end
63
+
44
64
  public ## attrlist
45
65
 
46
66
  attr_accessor :attrlist
@@ -201,6 +221,17 @@ module Bmg
201
221
  self
202
222
  end
203
223
 
224
+ def minus(other)
225
+ union_compatible!(other, "Minus")
226
+ dup.tap{|x|
227
+ # 1. attributes do not change
228
+ # 2. predicate does not change, we can't strengthen it in any way but it
229
+ # does not become weaker either
230
+ # 3. we can safely keep all existing keys, but it's not obvious we can
231
+ # gain some new ones
232
+ }
233
+ end
234
+
204
235
  def not_matching(right, on)
205
236
  join_compatible!(right, on) if typechecked? && knows_attrlist?
206
237
  self
@@ -278,6 +309,12 @@ module Bmg
278
309
  }
279
310
  end
280
311
 
312
+ def undress(options)
313
+ dup.tap{|x|
314
+ x.predicate = Predicate.tautology
315
+ }
316
+ end
317
+
281
318
  def ungroup(attrlist)
282
319
  known_attributes!(attrlist) if typechecked? && knows_attrlist?
283
320
  dup.tap{|x|
@@ -296,17 +333,6 @@ module Bmg
296
333
  }
297
334
  end
298
335
 
299
- def minus(other)
300
- union_compatible!(other, "Minus")
301
- dup.tap{|x|
302
- # 1. attributes do not change
303
- # 2. predicate does not change, we can't strengthen it in any way but it
304
- # does not become weaker either
305
- # 3. we can safely keep all existing keys, but it's not obvious we can
306
- # gain some new ones
307
- }
308
- end
309
-
310
336
  def unwrap(attrlist)
311
337
  known_attributes!(attrlist) if typechecked? && knows_attrlist?
312
338
  dup.tap{|x|
data/lib/bmg/version.rb CHANGED
@@ -2,7 +2,7 @@ module Bmg
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 23
5
- TINY = 3
5
+ TINY = 5
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
@@ -0,0 +1,200 @@
1
+ module Bmg
2
+ module Writer
3
+ class Text
4
+ include Writer
5
+
6
+ TupleLike = lambda{|t| t.is_a?(Hash) || (defined?(OpenStruct) && t.is_a?(OpenStruct)) }
7
+ RelationLike = lambda{|r| r.is_a?(Relation) || (r.is_a?(Enumerable) && r.all?{|t| TupleLike === t }) }
8
+
9
+ module Utils
10
+
11
+ def max(x, y)
12
+ return y if x.nil?
13
+ return x if y.nil?
14
+ x > y ? x : y
15
+ end
16
+ end
17
+ include Utils
18
+
19
+ class Cell
20
+ include Utils
21
+
22
+ def initialize(renderer, value)
23
+ @renderer = renderer
24
+ @value = value
25
+ end
26
+
27
+ def min_width
28
+ @min_width ||= rendering_lines.inject(0) do |maxl,line|
29
+ max(maxl,line.size)
30
+ end
31
+ end
32
+
33
+ def rendering_lines(size = nil)
34
+ if size.nil?
35
+ text_rendering.split(/\n/)
36
+ elsif @value.is_a?(Numeric)
37
+ rendering_lines(nil).map{|l| "%#{size}s" % l}
38
+ else
39
+ rendering_lines(nil).map{|l| "%-#{size}s" % l}
40
+ end
41
+ end
42
+
43
+ def text_rendering
44
+ @text_rendering ||= case (value = @value)
45
+ when NilClass
46
+ "[nil]"
47
+ when Symbol
48
+ value.inspect
49
+ when Float
50
+ (@renderer.text_options[:float_format] || "%.3f") % value
51
+ when Hash
52
+ value.inspect
53
+ when RelationLike
54
+ @renderer.render(value, "")
55
+ when Array
56
+ array_rendering(value)
57
+ when Time, DateTime
58
+ value.to_s
59
+ else
60
+ value.to_s
61
+ end
62
+ end
63
+
64
+ def array_rendering(value)
65
+ if TupleLike === value.first
66
+ @renderer.render(value, "")
67
+ elsif value.empty?
68
+ "[]"
69
+ else
70
+ values = value.map{|x| Cell.new(x).text_rendering}
71
+ if values.inject(0){|memo,s| memo + s.size} < 20
72
+ "[" + values.join(", ") + "]"
73
+ else
74
+ "[" + values.join(",\n ") + "]"
75
+ end
76
+ end
77
+ end
78
+
79
+ end # class Cell
80
+
81
+ class Row
82
+ include Utils
83
+
84
+ def initialize(renderer, values)
85
+ @renderer = renderer
86
+ @cells = values.map{|v| Cell.new(renderer, v) }
87
+ end
88
+
89
+ def min_widths
90
+ @cells.map{|cell| cell.min_width}
91
+ end
92
+
93
+ def rendering_lines(sizes = min_widths)
94
+ nb_lines = 0
95
+ by_cell = @cells.zip(sizes).map do |cell,size|
96
+ lines = cell.rendering_lines(size)
97
+ nb_lines = max(nb_lines, lines.size)
98
+ lines
99
+ end
100
+ grid = (0...nb_lines).map do |line_i|
101
+ "| " + by_cell.zip(sizes).map{|cell_lines, size|
102
+ cell_lines[line_i] || " "*size
103
+ }.join(" | ") + " |"
104
+ end
105
+ grid.empty? ? ["| |"] : grid
106
+ end
107
+
108
+ end # class Row
109
+
110
+ class Table
111
+ include Utils
112
+
113
+ def initialize(renderer, records, attributes)
114
+ @renderer = renderer
115
+ @header = Row.new(renderer, attributes.map(&:to_s))
116
+ @rows = records.map{|r| Row.new(renderer, r) }
117
+ end
118
+ attr_reader :renderer, :header, :rows
119
+
120
+ def sizes
121
+ @sizes ||= rows.inject(header.min_widths) do |memo,row|
122
+ memo.zip(row.min_widths).map{|x,y| max(x,y)}
123
+ end
124
+ end
125
+
126
+ def sep
127
+ @sep ||= '+-' << sizes.map{|s| '-' * s}.join('-+-') << '-+'
128
+ end
129
+
130
+ def each_line(pretty = renderer.text_options[:pretty])
131
+ if pretty && trim = renderer.text_options[:trim_at]
132
+ each_line(false) do |line|
133
+ yield(line[0..trim])
134
+ end
135
+ else
136
+ yield(sep)
137
+ yield(header.rendering_lines(sizes).first)
138
+ yield(sep)
139
+ rows.each do |row|
140
+ row.rendering_lines(sizes).each do |line|
141
+ yield(line)
142
+ end
143
+ end
144
+ yield(sep)
145
+ end
146
+ end
147
+
148
+ def each
149
+ return to_enum unless block_given?
150
+ each_line do |line|
151
+ yield(line.strip << "\n")
152
+ end
153
+ end
154
+
155
+ def to_s
156
+ each.each_with_object(""){|line,buf| buf << line}
157
+ end
158
+
159
+ end # class Table
160
+
161
+
162
+ DEFAULT_OPTIONS = {
163
+ }
164
+
165
+ def initialize(text_options = {}, output_preferences = nil)
166
+ @text_options = DEFAULT_OPTIONS.merge(text_options)
167
+ @output_preferences = OutputPreferences.dress(output_preferences)
168
+ end
169
+ attr_reader :text_options, :output_preferences
170
+
171
+ def call(relation, output = "")
172
+ each_line(relation) do |str|
173
+ output << str
174
+ end
175
+ output
176
+ end
177
+ alias :render :call
178
+
179
+ private
180
+
181
+ def each_line(relation, &bl)
182
+ input = relation
183
+ input = [input.to_hash] if TupleLike === input
184
+ relation = input.to_a
185
+ attrs = relation.inject([]){|memo,t| (memo | t.keys) }
186
+ records = relation.map{|t| attrs.map{|a| t[a]} }
187
+ table = Table.new(self, records, attrs)
188
+ table.each(&bl)
189
+ end
190
+
191
+ end # class Text
192
+ end # module Writer
193
+ module Relation
194
+
195
+ def to_text(options = {}, preferences = {}, output = "")
196
+ Writer::Text.new(options, preferences).render(self, output)
197
+ end
198
+
199
+ end # module Relation
200
+ end # module Bmg
data/lib/bmg/writer.rb CHANGED
@@ -25,4 +25,5 @@ module Bmg
25
25
 
26
26
  end # module Writer
27
27
  end # module Bmg
28
+ require_relative 'writer/text'
28
29
  require_relative 'writer/csv'
data/lib/bmg.rb CHANGED
@@ -4,51 +4,6 @@ require 'forwardable'
4
4
  require 'set'
5
5
  module Bmg
6
6
 
7
- def mutable(enumerable, type = Type::ANY)
8
- Relation::InMemory::Mutable.new(type, enumerable).spied(main_spy)
9
- end
10
- module_function :mutable
11
-
12
- def in_memory(enumerable, type = Type::ANY)
13
- Relation::InMemory.new(type, enumerable).spied(main_spy)
14
- end
15
- module_function :in_memory
16
-
17
- def text_file(path, options = {}, type = Type::ANY)
18
- Reader::TextFile.new(type, path, options).spied(main_spy)
19
- end
20
- module_function :text_file
21
-
22
- def csv(path, options = {}, type = Type::ANY)
23
- Reader::Csv.new(type, path, options).spied(main_spy)
24
- end
25
- module_function :csv
26
-
27
- def json(path, options = {}, type = Type::ANY)
28
- in_memory(path.load.map{|tuple| TupleAlgebra.symbolize_keys(tuple) })
29
- end
30
- module_function :json
31
-
32
- def yaml(path, options = {}, type = Type::ANY)
33
- in_memory(path.load.map{|tuple| TupleAlgebra.symbolize_keys(tuple) })
34
- end
35
- module_function :yaml
36
-
37
- def excel(path, options = {}, type = Type::ANY)
38
- Reader::Excel.new(type, path, options).spied(main_spy)
39
- end
40
- module_function :excel
41
-
42
- def main_spy
43
- @main_spy
44
- end
45
- module_function :main_spy
46
-
47
- def main_spy=(spy)
48
- @main_spy = spy
49
- end
50
- module_function :main_spy=
51
-
52
7
  require_relative 'bmg/version'
53
8
  require_relative 'bmg/error'
54
9
  require_relative 'bmg/support'
@@ -69,6 +24,19 @@ module Bmg
69
24
 
70
25
  require_relative 'bmg/database'
71
26
 
27
+ def main_spy
28
+ @main_spy
29
+ end
30
+ module_function :main_spy
31
+
32
+ def main_spy=(spy)
33
+ @main_spy = spy
34
+ end
35
+ module_function :main_spy=
36
+
37
+ # Add all factory methods
38
+ extend Factory
39
+
72
40
  # Deprecated
73
41
  Leaf = Relation::InMemory
74
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bmg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.3
4
+ version: 0.23.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-26 00:00:00.000000000 Z
11
+ date: 2025-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: predicate
@@ -128,6 +128,20 @@ dependencies:
128
128
  - - ">="
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: activesupport
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
131
145
  description: Bmg is Alf's relational algebra for ruby, but much simpler and lighter
132
146
  than Alf itself
133
147
  email: blambeau@gmail.com
@@ -153,6 +167,7 @@ files:
153
167
  - lib/bmg/operator/autowrap.rb
154
168
  - lib/bmg/operator/constants.rb
155
169
  - lib/bmg/operator/extend.rb
170
+ - lib/bmg/operator/generator.rb
156
171
  - lib/bmg/operator/group.rb
157
172
  - lib/bmg/operator/image.rb
158
173
  - lib/bmg/operator/join.rb
@@ -167,8 +182,10 @@ files:
167
182
  - lib/bmg/operator/shared/binary.rb
168
183
  - lib/bmg/operator/shared/nary.rb
169
184
  - lib/bmg/operator/shared/unary.rb
185
+ - lib/bmg/operator/shared/zeroary.rb
170
186
  - lib/bmg/operator/summarize.rb
171
187
  - lib/bmg/operator/transform.rb
188
+ - lib/bmg/operator/undress.rb
172
189
  - lib/bmg/operator/ungroup.rb
173
190
  - lib/bmg/operator/union.rb
174
191
  - lib/bmg/operator/unwrap.rb
@@ -296,6 +313,7 @@ files:
296
313
  - lib/bmg/summarizer/value_by.rb
297
314
  - lib/bmg/summarizer/variance.rb
298
315
  - lib/bmg/support.rb
316
+ - lib/bmg/support/factory.rb
299
317
  - lib/bmg/support/keys.rb
300
318
  - lib/bmg/support/ordering.rb
301
319
  - lib/bmg/support/output_preferences.rb
@@ -305,6 +323,7 @@ files:
305
323
  - lib/bmg/version.rb
306
324
  - lib/bmg/writer.rb
307
325
  - lib/bmg/writer/csv.rb
326
+ - lib/bmg/writer/text.rb
308
327
  - lib/bmg/writer/xlsx.rb
309
328
  - lib/bmg/xlsx.rb
310
329
  - tasks/gem.rake
@@ -313,7 +332,7 @@ homepage: http://github.com/enspirit/bmg
313
332
  licenses:
314
333
  - MIT
315
334
  metadata: {}
316
- post_install_message:
335
+ post_install_message:
317
336
  rdoc_options: []
318
337
  require_paths:
319
338
  - lib
@@ -329,7 +348,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
329
348
  version: '0'
330
349
  requirements: []
331
350
  rubygems_version: 3.3.27
332
- signing_key:
351
+ signing_key:
333
352
  specification_version: 4
334
353
  summary: Bmg is Alf's successor.
335
354
  test_files: []