fixture_fox 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []