fixture_fox 0.1.1

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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +7 -0
  7. data/README.md +36 -0
  8. data/Rakefile +6 -0
  9. data/TODO +79 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/doc/diagram.drawio +1 -0
  13. data/examples/1-1-subrecord.fox +15 -0
  14. data/examples/1-1-subrecord.sql +22 -0
  15. data/examples/N-M.fox +25 -0
  16. data/examples/N-M.sql +34 -0
  17. data/examples/anchors.sql +13 -0
  18. data/examples/anchors.yml +4 -0
  19. data/examples/anchors1.fox +4 -0
  20. data/examples/anchors2.fox +6 -0
  21. data/examples/array.fox +84 -0
  22. data/examples/array.sql +53 -0
  23. data/examples/base.fox +81 -0
  24. data/examples/base.sql +52 -0
  25. data/examples/empty.fox +8 -0
  26. data/examples/empty.sql +33 -0
  27. data/examples/include/schema-included.fox +23 -0
  28. data/examples/inherit.fox +26 -0
  29. data/examples/inherit.sql +28 -0
  30. data/examples/kind.fox +17 -0
  31. data/examples/kind.sql +31 -0
  32. data/examples/link.fox +35 -0
  33. data/examples/link.sql +32 -0
  34. data/examples/root.fox +22 -0
  35. data/examples/schema-fragment-1.fox +9 -0
  36. data/examples/schema-fragment-2.fox +21 -0
  37. data/examples/schema-include.fox +10 -0
  38. data/examples/schema-indent.fox +29 -0
  39. data/examples/schema.fox +29 -0
  40. data/examples/schema.sql +33 -0
  41. data/examples/types.fox +8 -0
  42. data/examples/types.sql +17 -0
  43. data/examples/views.fox +15 -0
  44. data/examples/views.sql +38 -0
  45. data/exe/fox +178 -0
  46. data/fixture_fox.gemspec +37 -0
  47. data/lib/fixture_fox/analyzer.rb +371 -0
  48. data/lib/fixture_fox/anchor.rb +93 -0
  49. data/lib/fixture_fox/ast.rb +176 -0
  50. data/lib/fixture_fox/error.rb +23 -0
  51. data/lib/fixture_fox/hash_parser.rb +111 -0
  52. data/lib/fixture_fox/idr.rb +62 -0
  53. data/lib/fixture_fox/line.rb +217 -0
  54. data/lib/fixture_fox/parser.rb +153 -0
  55. data/lib/fixture_fox/token.rb +173 -0
  56. data/lib/fixture_fox/tokenizer.rb +78 -0
  57. data/lib/fixture_fox/version.rb +3 -0
  58. data/lib/fixture_fox.rb +216 -0
  59. metadata +227 -0
@@ -0,0 +1,78 @@
1
+ require 'pathname'
2
+
3
+ module FixtureFox
4
+ class Tokenizer
5
+ # Minimum path to source file. Note that this is a ruby file if the source
6
+ # was inline
7
+ attr_reader :file
8
+
9
+ # Array of tokenized lines
10
+ attr_reader :lines
11
+
12
+ def initialize(file_or_text)
13
+ @lines = []
14
+
15
+ # FIXME: Why @dev_proc ?
16
+ if file_or_text =~ /\s/
17
+ if caller[0] =~ /^(.*):(\d+):in `.*/
18
+ file, @lineno = $1, $2.to_i - 1
19
+ @file = Pathname.new(Pathname.new(file).expand_path).relative_path_from(Pathname.getwd).to_s
20
+ @dev_proc = lambda { |&block| StringIO.open(file_or_text, &block) }
21
+ else
22
+ raise "Oops"
23
+ end
24
+ else
25
+ @lineno = 0
26
+ @file = file_or_text
27
+ @dev_proc = lambda { |&block| File.open(@file, "r", &block) }
28
+ end
29
+ end
30
+
31
+ def call
32
+ # Read source
33
+ source = @dev_proc.call { |dev|
34
+ dev.readlines.take_while { |l| l !~ /^__END__\s*$/ }
35
+ }
36
+
37
+ # Find initial indent
38
+ while !source.empty? && source.first =~ /^\s*(?:#.*)?$/
39
+ source.shift
40
+ @lineno += 1
41
+ end
42
+ !source.empty? or return []
43
+ @initial_indent = source.first[/\A */].size
44
+
45
+ while !source.empty?
46
+ line = source.shift
47
+ @lineno += 1
48
+
49
+ # Ignore blank lines and comments
50
+ next if line =~ /^\s*(?:#.*)?$/
51
+
52
+ # Find indent
53
+ indent = line[/\A */].size - @initial_indent
54
+ indent >= 0 or error("Illegal indent")
55
+
56
+ # Strip prefixed and suffixed blanks
57
+ line.strip!
58
+
59
+ # Create
60
+ if line =~ /^@/
61
+ @lines << DirectiveLine.new(@file, @lineno, @initial_indent, indent, line)
62
+ else
63
+ @lines << Line.new(@file, @lineno, @initial_indent, indent, line)
64
+ end
65
+ end
66
+
67
+ @lines
68
+ end
69
+
70
+ def dump(long: false)
71
+ @lines.each { |l| puts l.to_s(long: long) }
72
+ end
73
+
74
+ def error(msg)
75
+ raise ParseError.new(@file, @lineno, @initial_indent, msg)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ module FixtureFox
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,216 @@
1
+
2
+ require "pg_graph"
3
+
4
+ require "fixture_fox/version"
5
+ require "fixture_fox/error.rb"
6
+ require "fixture_fox/token.rb"
7
+ require "fixture_fox/line.rb"
8
+ require "fixture_fox/ast.rb"
9
+ require "fixture_fox/idr.rb"
10
+ require "fixture_fox/tokenizer.rb"
11
+ require "fixture_fox/parser.rb"
12
+ require "fixture_fox/hash_parser.rb"
13
+ require "fixture_fox/analyzer.rb"
14
+ require "fixture_fox/anchor.rb"
15
+
16
+ module FixtureFox
17
+ # Fox is compiled incrementally so you can add some files, parse them into an
18
+ # AST, inspect the result through #idr, and then add some more files. The #idr
19
+ # can also be regenerated by executing #call with a different set of seed table IDs
20
+ class Fox
21
+ # The type of the database (PgGraph::Type::Database object)
22
+ attr_reader :type
23
+
24
+ # Name of first source file
25
+ attr_reader :file
26
+
27
+ # Names of all source files except included files. FIXME: Not maintained - always []
28
+ attr_reader :files
29
+
30
+ # Tokenized lines. Note that this doesn't include lines from included
31
+ # files
32
+ attr_reader :lines
33
+
34
+ # The AST object. The AST object is a snapshot and grows as more files are
35
+ # parsed
36
+ attr_reader :ast
37
+
38
+ # The Analyzer object. The analyzer object is recalculated after new files
39
+ # are added
40
+ attr_reader :analyzer
41
+
42
+ # The IDR object. It is reset every time a new file is added
43
+ def idr() @idr || generate end
44
+
45
+ # List of tables with records (PgGraph::Type::Table)
46
+ def tables()
47
+ @analyzer.data_tables
48
+ end
49
+
50
+ # Default schema. Defaults to "public"
51
+ attr_reader :schema
52
+
53
+ # Map from qualified table name to max ID for that table. This is the same
54
+ # as the number of records in the table if the IDs wasn't seeded with a
55
+ # start value in #initialize
56
+ attr_reader :ids
57
+
58
+ # Anchors object. #anchors is modified as more and more files are added
59
+ attr_reader :anchors
60
+
61
+ # List of anchors defined by the sources
62
+ def defined_anchors()
63
+ @analyzer.defined_anchors.values
64
+ end
65
+
66
+ # List of external anchors referenced by the sources. FIXME: Unused
67
+ def referenced_anchors()
68
+ @analyzer.referenced_anchors.values
69
+ end
70
+
71
+ def parsed?() !@ast.nil? end
72
+ def assigned?() !@analyzer.nil? && @analyzer.assigned? end
73
+ def checked?() assigned? && @analyzer.checked? end
74
+ def analyzed?() assigned? && checked? end
75
+ def generated?() !@idr.nil? end # TODO: Rephrase in terms of Analyzer#generated?
76
+
77
+ # Returns true if the Fox object is frozen and no more files can be added
78
+ def frozen?() @frozen end
79
+
80
+ # Freeze the Fox object. After this no more files can be added
81
+ def freeze!() @frozen = true end
82
+
83
+ def initialize(type, files = [], schema: nil, ids: nil, anchors: nil)
84
+ constrain type, PgGraph::Type
85
+ constrain files, [String]
86
+ constrain ids, NilClass, String => Integer
87
+ constrain anchors, Anchors, [Hash], NilClass
88
+ @type = type
89
+ @files = []
90
+ @schema = schema || "public"
91
+ @ids = ids || {}
92
+
93
+ if anchors.is_a?(Array)
94
+ @anchors = Anchors.new(type, anchors)
95
+ else
96
+ @anchors = anchors || Anchors.new(type)
97
+ end
98
+
99
+ @lines = []
100
+ @ast = nil
101
+ @analyzer = nil
102
+ @idr = nil
103
+ @data = nil
104
+ @frozen = false
105
+ if !files.empty?
106
+ compile(files)
107
+ assign_types # Analyze as far as possible without requiring anchors & ids
108
+ freeze!
109
+ end
110
+ end
111
+
112
+ # Note: Doesn't dup the Ast or other internal data structures except ids
113
+ # and anchors
114
+ def dup
115
+ generate if !generated?
116
+ Fox.new(type)
117
+ end
118
+
119
+ def compile(*file_or_texts)
120
+ !frozen? or raise Error, "Frozen Fox object"
121
+ @analyzer = nil
122
+ @idr = nil
123
+ @data = nil
124
+ @ast ||= Ast.new
125
+ Array(file_or_texts).flatten.each { |source|
126
+ source, lines = tokenize(source)
127
+ parse(source, lines)
128
+ }
129
+ end
130
+
131
+ def tokenize(source)
132
+ # puts "tokenize(#{source.inspect})"
133
+ tokenizer = Tokenizer.new(source)
134
+ @file ||= tokenizer.file
135
+ @files.append tokenizer.file
136
+ lines = tokenizer.call
137
+ @lines.append(*lines)
138
+ [source, lines]
139
+ end
140
+
141
+ def parse(source, lines)
142
+ # puts "parse(#{source.inspect})"
143
+ parser = Parser.new(source, lines, schema: @schema)
144
+ @ast = parser.call(@ast)
145
+ parser.anchor_files.each { |file| @anchors.merge!(Anchors.load(@type, file)) }
146
+ end
147
+
148
+ def analyze(anchors: nil)
149
+ # puts "analyze"
150
+ constrain anchors, Anchors, NilClass
151
+ assign_types if !assigned?
152
+ check_types(anchors: anchors) if !checked?
153
+ end
154
+
155
+ def assign_types
156
+ # puts "assign_types"
157
+ @analyzer = Analyzer.new(@type, @ast, ids: ids, anchors: anchors)
158
+ @ids = @analyzer.ids
159
+ @anchors = @analyzer.anchors
160
+ @analyzer.assign_types
161
+ end
162
+
163
+ def check_types(anchors: nil)
164
+ # puts "check_types"
165
+ constrain anchors, Anchors, NilClass
166
+ assign_types if !assigned?
167
+ @analyzer.check_types(anchors: anchors)
168
+ end
169
+
170
+ def generate(anchors: nil, ids: nil)
171
+ # puts "generate"
172
+ constrain anchors, Anchors, NilClass
173
+ analyze(anchors: anchors) if !analyzed?
174
+ @idr = @analyzer.generate(ids: ids)
175
+ end
176
+
177
+ # PgGraph::Data::Database object of the Idr object
178
+ def data(anchors: nil, ids: nil)
179
+ constrain anchors, Anchors, NilClass
180
+ generate(anchors: anchors, ids: ids) if !generated?
181
+ @data = PgGraph::Data.new(type, idr.to_h)
182
+ end
183
+
184
+ def write_state(file)
185
+ self.class.write_state(ids, anchors, file)
186
+ end
187
+
188
+ def to_yaml
189
+ data.to_yaml.to_yaml
190
+ end
191
+
192
+ def to_sql(format: :psql, ids: nil, delete: :all)
193
+ data.to_sql(format: format, ids: ids || {}, delete: delete, files: files)
194
+ end
195
+
196
+ def self.read_ids(file) YAML.load(IO.read(file)) end
197
+ def self.write_ids(ids, file) IO.write(file, YAML.dump(ids)) end
198
+
199
+ def self.read_anchors(type, file) Anchors.load(type, file) end
200
+ def self.write_anchors(anchors, file) anchors.save(file) end
201
+
202
+ def self.read_state(type, file)
203
+ if File.size?(file)
204
+ state = YAML.load(IO.read(file))
205
+ [state[:ids], Anchors.new(type, state[:anchors])]
206
+ else
207
+ [nil, nil]
208
+ end
209
+ end
210
+
211
+ def self.write_state(ids, anchors, file)
212
+ IO.write(file, YAML.dump({ ids: ids, anchors: anchors.to_yaml }))
213
+ end
214
+ end
215
+ end
216
+
metadata ADDED
@@ -0,0 +1,227 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fixture_fox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Claus Rasmussen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-03-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pg
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-inflector
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: indented_io
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: boolean
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: developer_exceptions
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: constrain
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: shellopts
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 2.0.6
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 2.0.6
111
+ - !ruby/object:Gem::Dependency
112
+ name: pg_graph
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '='
116
+ - !ruby/object:Gem::Version
117
+ version: 0.1.0
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '='
123
+ - !ruby/object:Gem::Version
124
+ version: 0.1.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description: fixture_fox gem
140
+ email:
141
+ - claus.l.rasmussen@gmail.com
142
+ executables:
143
+ - fox
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - ".gitignore"
148
+ - ".rspec"
149
+ - ".ruby-version"
150
+ - ".travis.yml"
151
+ - Gemfile
152
+ - README.md
153
+ - Rakefile
154
+ - TODO
155
+ - bin/console
156
+ - bin/setup
157
+ - doc/diagram.drawio
158
+ - examples/1-1-subrecord.fox
159
+ - examples/1-1-subrecord.sql
160
+ - examples/N-M.fox
161
+ - examples/N-M.sql
162
+ - examples/anchors.sql
163
+ - examples/anchors.yml
164
+ - examples/anchors1.fox
165
+ - examples/anchors2.fox
166
+ - examples/array.fox
167
+ - examples/array.sql
168
+ - examples/base.fox
169
+ - examples/base.sql
170
+ - examples/empty.fox
171
+ - examples/empty.sql
172
+ - examples/include/schema-included.fox
173
+ - examples/inherit.fox
174
+ - examples/inherit.sql
175
+ - examples/kind.fox
176
+ - examples/kind.sql
177
+ - examples/link.fox
178
+ - examples/link.sql
179
+ - examples/root.fox
180
+ - examples/schema-fragment-1.fox
181
+ - examples/schema-fragment-2.fox
182
+ - examples/schema-include.fox
183
+ - examples/schema-indent.fox
184
+ - examples/schema.fox
185
+ - examples/schema.sql
186
+ - examples/types.fox
187
+ - examples/types.sql
188
+ - examples/views.fox
189
+ - examples/views.sql
190
+ - exe/fox
191
+ - fixture_fox.gemspec
192
+ - lib/fixture_fox.rb
193
+ - lib/fixture_fox/analyzer.rb
194
+ - lib/fixture_fox/anchor.rb
195
+ - lib/fixture_fox/ast.rb
196
+ - lib/fixture_fox/error.rb
197
+ - lib/fixture_fox/hash_parser.rb
198
+ - lib/fixture_fox/idr.rb
199
+ - lib/fixture_fox/line.rb
200
+ - lib/fixture_fox/parser.rb
201
+ - lib/fixture_fox/token.rb
202
+ - lib/fixture_fox/tokenizer.rb
203
+ - lib/fixture_fox/version.rb
204
+ homepage: http://www.nowhere.com/
205
+ licenses: []
206
+ metadata:
207
+ homepage_uri: http://www.nowhere.com/
208
+ post_install_message:
209
+ rdoc_options: []
210
+ require_paths:
211
+ - lib
212
+ required_ruby_version: !ruby/object:Gem::Requirement
213
+ requirements:
214
+ - - ">="
215
+ - !ruby/object:Gem::Version
216
+ version: 2.3.0
217
+ required_rubygems_version: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ requirements: []
223
+ rubygems_version: 3.2.26
224
+ signing_key:
225
+ specification_version: 4
226
+ summary: fixture_fox gem
227
+ test_files: []