activefacts 1.3.0 → 1.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4e9276d073a9b3444de848bf033b76f373784a51
4
- data.tar.gz: 0ab5602a9298cc84ce688168a3635bd7f4d976a8
3
+ metadata.gz: f4c7be7ca9e116d36232a6e9c0bcf28f0f4a63eb
4
+ data.tar.gz: a3c13e8e437aa028760eff29cd05df07b0017a91
5
5
  SHA512:
6
- metadata.gz: a984577de5c0aa667d5975f70b35c76f2bb4ab65555fc6a19ca0b7a8bae635cd77fb6d3fbac3959f9f9d76bc53c81846214940ea5109d3e58c6ae5b06197c6c5
7
- data.tar.gz: 169adf1025e3c1bb46fa58fd45e79d05c2eb45e4ffb069c25bec1c10b786f3dddca32c97f54556353bd48fb9580e2becc279f5a11185373857af4dff64694635
6
+ metadata.gz: 49878c205424a5534e811af19bf09ebe0e3c8aefbd3127a1ee0695d3a5c77eec2beb74715455ff32d7bde613e8a1eff8241dc35ba77a79915770e1ee68c60ba0
7
+ data.tar.gz: 4396ee06e34a48236e858274010356123f66847686a04763b4b4992283ae30f8dd6b0d96e7086803339a97f9ffbc2c158d2563a8840a288c330623f11680de8b
data/Manifest.txt CHANGED
@@ -71,10 +71,13 @@ lib/activefacts/cql/compiler/shared.rb
71
71
  lib/activefacts/cql/compiler/value_type.rb
72
72
  lib/activefacts/cql/nodes.rb
73
73
  lib/activefacts/cql/parser.rb
74
+ lib/activefacts/dependency_analyser.rb
74
75
  lib/activefacts/generate/absorption.rb
76
+ lib/activefacts/generate/composition.rb
75
77
  lib/activefacts/generate/cql.rb
76
78
  lib/activefacts/generate/dm.rb
77
79
  lib/activefacts/generate/help.rb
80
+ lib/activefacts/generate/helpers/inject.rb
78
81
  lib/activefacts/generate/helpers/oo.rb
79
82
  lib/activefacts/generate/helpers/ordered.rb
80
83
  lib/activefacts/generate/helpers/rails.rb
@@ -85,9 +88,14 @@ lib/activefacts/generate/records.rb
85
88
  lib/activefacts/generate/ruby.rb
86
89
  lib/activefacts/generate/sql/mysql.rb
87
90
  lib/activefacts/generate/sql/server.rb
91
+ lib/activefacts/generate/stats.rb
88
92
  lib/activefacts/generate/rails/schema.rb
89
93
  lib/activefacts/generate/rails/models.rb
90
94
  lib/activefacts/generate/text.rb
95
+ lib/activefacts/generate/topics.rb
96
+ lib/activefacts/generate/traits/oo.rb
97
+ lib/activefacts/generate/traits/ordered.rb
98
+ lib/activefacts/generate/traits/ruby.rb
91
99
  lib/activefacts/generate/transform/surrogate.rb
92
100
  lib/activefacts/generate/version.rb
93
101
  lib/activefacts/input/cql.rb
@@ -107,6 +115,7 @@ lib/activefacts/vocabulary.rb
107
115
  lib/activefacts/vocabulary/extensions.rb
108
116
  lib/activefacts/vocabulary/metamodel.rb
109
117
  lib/activefacts/vocabulary/verbaliser.rb
118
+ lib/activefacts/vocabulary/query_evaluator.rb
110
119
  script/txt2html
111
120
  spec/cql/comparison_spec.rb
112
121
  spec/cql/contractions_spec.rb
@@ -31,6 +31,7 @@ Rotation Setting is written as String restricted to {'left', 'right'};
31
31
  Scale is written as Unsigned Integer(32);
32
32
  Subscript is written as Unsigned Integer(16);
33
33
  Text is written as String(256);
34
+ Topic Name is written as Name;
34
35
  Transaction Phase is written as String restricted to {'assert', 'commit'};
35
36
  X is written as Signed Integer(32);
36
37
  Y is written as Signed Integer(32);
@@ -165,6 +166,10 @@ Subset Constraint is a kind of Set Constraint;
165
166
  Subset Constraint covers one subset-Role Sequence;
166
167
  Subset Constraint covers one superset-Role Sequence;
167
168
 
169
+ Topic is identified by its Name;
170
+ Concept belongs to at most one Topic,
171
+ Topic contains Concept;
172
+
168
173
  Unit is identified by Concept where
169
174
  Unit is an instance of one Concept;
170
175
  Ephemera URL provides Unit coefficient,
@@ -56,6 +56,15 @@ module ActiveFacts
56
56
  extend language_module
57
57
  end
58
58
 
59
+ # Mark any new Concepts as belonging to this topic
60
+ def topic_flood
61
+ @constellation.Concept.each do |key, concept|
62
+ next if concept.topic
63
+ trace :topic, "Colouring #{concept.describe} with #{@topic.topic_name}"
64
+ concept.topic = @topic
65
+ end
66
+ end
67
+
59
68
  def compile input
60
69
  include_language
61
70
 
@@ -75,7 +84,14 @@ module ActiveFacts
75
84
  ast.vocabulary = @vocabulary
76
85
  value = compile_definition ast
77
86
  trace :definition, "Compiled to #{value.is_a?(Array) ? value.map{|v| v.verbalise}*', ' : value.verbalise}" if value
78
- @vocabulary = value if ast.is_a?(Compiler::Vocabulary)
87
+ if value.is_a?(ActiveFacts::Metamodel::Topic)
88
+ topic_flood if @topic
89
+ @topic = value
90
+ elsif ast.is_a?(Compiler::Vocabulary)
91
+ topic_flood if @topic
92
+ @vocabulary = value
93
+ @topic = @constellation.Topic(@vocabulary.name)
94
+ end
79
95
  rescue => e
80
96
  # Augment the exception message, but preserve the backtrace
81
97
  start_line = @string.line_of(node.interval.first)
@@ -86,6 +102,7 @@ module ActiveFacts
86
102
  raise ne
87
103
  end
88
104
  end
105
+ topic_flood if @topic
89
106
  end
90
107
  raise failure_reason unless ok
91
108
  vocabulary
@@ -96,12 +113,18 @@ module ActiveFacts
96
113
  saved_block = @block
97
114
  saved_string = @string
98
115
  saved_input_length = @input_length
116
+ saved_topic = @topic
99
117
  old_filename = @filename
100
118
  @filename = File.dirname(old_filename)+'/'+file+'.cql'
101
119
 
102
120
  # REVISIT: Save and use another @vocabulary for this file?
103
121
  File.open(@filename) do |f|
104
- ok = parse_all(f.read, nil, &@block)
122
+ topic_flood if @topic
123
+ @topic = @constellation.Topic(File.basename(@filename, '.cql'))
124
+ trace :import, "Importing #{@filename} as #{@topic.topic_name}" do
125
+ ok = parse_all(f.read, nil, &@block)
126
+ end
127
+ @topic = saved_topic
105
128
  end
106
129
 
107
130
  rescue => e
@@ -47,8 +47,16 @@ module ActiveFacts
47
47
 
48
48
  class Constraint < Definition
49
49
  def initialize context_note, enforcement, clauses_lists = []
50
- if context_note.is_a?(Treetop::Runtime::SyntaxNode)
50
+ if context_note.is_a?(Treetop::Runtime::SyntaxNode) && !context_note.empty?
51
51
  context_note = context_note.empty? ? nil : context_note.ast
52
+ else
53
+ context_note = nil # Perhaps a context note got attached to one of the clauses. Steal it.
54
+ clauses_lists.detect do |clauses_list|
55
+ if c = clauses_list.last.context_note
56
+ context_note = c
57
+ clauses_list.last.context_note = nil
58
+ end
59
+ end
52
60
  end
53
61
  @context_note = context_note
54
62
  @enforcement = enforcement
@@ -323,6 +323,7 @@ module ActiveFacts
323
323
  :max_frequency => 1,
324
324
  :is_preferred_identifier => true # (prefer || !!@fact_type.entity_type)
325
325
  )
326
+ pc.concept.topic = @fact_type.concept.topic
326
327
  trace :constraint, "Made new fact type implicit PC GUID=#{pc.concept.guid} #{pc.name} min=nil max=1 over #{rs.describe}"
327
328
  elsif pc
328
329
  trace :constraint, "Will rely on existing UC GUID=#{pc.concept.guid} #{pc.name} to be used as PI over #{rs.describe}"
@@ -116,7 +116,11 @@ module ActiveFacts
116
116
  end
117
117
 
118
118
  def compile
119
- @constellation.Vocabulary @name
119
+ if @constellation.Vocabulary.size > 0
120
+ @constellation.Topic @name
121
+ else
122
+ @constellation.Vocabulary @name
123
+ end
120
124
  end
121
125
 
122
126
  def to_s
@@ -0,0 +1,182 @@
1
+ module ActiveFacts
2
+ class DependencyAnalyser
3
+ def initialize enumerable, &block
4
+ @enumerable = enumerable
5
+ analyse_precursors &block
6
+ end
7
+
8
+ def analyse_precursors &block
9
+ @precursors = {}
10
+ @enumerable.each do |item|
11
+ @precursors[item] = block.call(item)
12
+ end
13
+ end
14
+
15
+ def analyse_precursors_transitive
16
+ all_precursors = proc do |item|
17
+ p = @precursors[item]
18
+ all =
19
+ p + p.map do |precursor|
20
+ p.include?(precursor) ? [] : all_precursors.call(precursor)
21
+ end.flatten
22
+ all.uniq
23
+ end
24
+
25
+ @precursors_transitive = {}
26
+ @enumerable.each do |item|
27
+ @precursors_transitive[item] = all_precursors.call(item)
28
+ end
29
+ end
30
+
31
+ def analyse_followers
32
+ @followers = Hash.new{|h, k| h[k] = [] }
33
+ @enumerable.each do |item|
34
+ @precursors[item].each do |precursor|
35
+ @followers[precursor] << item
36
+ end
37
+ end
38
+ end
39
+
40
+ def analyse_chasers
41
+ analyse_precursors_transitive unless @precursors_transitive
42
+ analyse_followers unless @followers
43
+
44
+ # A follower is an object with us as a precursor, that has no new precursors of its own
45
+ @chasers = {}
46
+ @enumerable.each do |item|
47
+ @chasers[item] =
48
+ @enumerable.select do |follower|
49
+ @precursors[follower].include?(item) and
50
+ (@precursors_transitive[follower] - @precursors_transitive[item] - [item]).size == 0
51
+ end
52
+ end
53
+ end
54
+
55
+ def tsort &block
56
+ analyse_precursors unless @precursors
57
+ emitted = {}
58
+ pass = 0
59
+ until emitted.size == @enumerable.size
60
+ next_items = []
61
+ blocked =
62
+ @enumerable.inject({}) do |hash, item|
63
+ next hash if emitted[item]
64
+ blockers = item.precursors.select{|precursor| !emitted[precursor]}
65
+ if blockers.size > 0
66
+ hash[item] = blockers
67
+ else
68
+ next_items << item
69
+ end
70
+ hash
71
+ end
72
+ return blocked if next_items.size == 0 # Cannot make progress
73
+ # puts "PASS #{pass += 1}"
74
+ next_items.each do |item|
75
+ block.call(item)
76
+ emitted[item] = true
77
+ end
78
+ end
79
+ nil
80
+ end
81
+
82
+ def each &b
83
+ if block_given?
84
+ @enumerable.each { |item| yield item}
85
+ else
86
+ @enumerable
87
+ end
88
+ end
89
+
90
+ def precursors item = nil, &b
91
+ analyse_precursors unless @precursors
92
+ if item
93
+ if block_given?
94
+ Array(@precursors[item]).each { |precursor| yield precursor, item }
95
+ else
96
+ Array(@precursors[item])
97
+ end
98
+ else
99
+ @enumerable.each do |item|
100
+ precursors(item, &b)
101
+ end
102
+ end
103
+ end
104
+
105
+ def precursors_transitive item, &b
106
+ analyse_precursors_transitive unless @precursors_transitive
107
+ if item
108
+ if block_given?
109
+ Array(@precursors_transitive[item]).each { |precursor| yield precursor, item }
110
+ else
111
+ Array(@precursors_transitive[item])
112
+ end
113
+ else
114
+ @enumerable.each do |item|
115
+ precursors_transitive(item, &b)
116
+ end
117
+ end
118
+ end
119
+
120
+ def followers item = nil, &b
121
+ analyse_followers unless @followers
122
+ if item
123
+ if block_given?
124
+ Array(@followers[item]).each { |follower| yield follower, item }
125
+ else
126
+ Array(@followers[item])
127
+ end
128
+ else
129
+ @enumerable.each do |item|
130
+ followers(item, &b)
131
+ end
132
+ end
133
+ end
134
+
135
+ def chasers item, &b
136
+ analyse_chasers unless @chasers
137
+ if item
138
+ if block_given?
139
+ Array(@chasers[item]).each { |follower| yield follower, item }
140
+ else
141
+ Array(@chasers[item])
142
+ end
143
+ else
144
+ @enumerable.each do |item|
145
+ follower(item, &b)
146
+ end
147
+ end
148
+ end
149
+
150
+ # Compute the page rank of the objects
151
+ # If used, the block shold return the starting weight
152
+ def page_rank damping = 0.85, &weight
153
+ weight ||= proc {|item| 1.0}
154
+
155
+ @total = 0
156
+ @rank = {}
157
+ @enumerable.each do |item|
158
+ @total +=
159
+ (@rank[item] = weight.call(item) * 1.0)
160
+ end
161
+ # Normalize:
162
+ @enumerable.each do |item|
163
+ @rank[item] /= @total
164
+ end
165
+
166
+ 50.times do |iteration|
167
+ @enumerable.each do |item|
168
+ links = (precursors(item) + followers(item)).uniq
169
+ linked_rank = links.map do |l|
170
+ onward_links = (precursors(l) + followers(l)).uniq || @enumerable.size
171
+ @rank[l] / onward_links.size
172
+ end.inject(&:+) || 0
173
+ @rank[item] = (1.0-damping) + damping*linked_rank
174
+ end
175
+ end
176
+
177
+ @rank
178
+ end
179
+
180
+ end
181
+ end
182
+
@@ -0,0 +1,118 @@
1
+ #
2
+ # ActiveFacts Generators.
3
+ # Generate a Relational Composition (for activefacts/composition).
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ require 'activefacts/vocabulary'
8
+ require 'activefacts/generate/helpers/inject'
9
+ require 'activefacts/persistence'
10
+ require 'activefacts/generate/traits/ruby'
11
+
12
+ module ActiveFacts
13
+ module Generate
14
+ # afgen --composition[=options] <file>.cql
15
+ # Options are comma or space separated:
16
+ class Composition #:nodoc:
17
+ private
18
+ include Persistence
19
+
20
+ def initialize(vocabulary, *options)
21
+ @vocabulary = vocabulary
22
+ @vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
23
+ @underscore = options.include?("underscore") ? "_" : ""
24
+ end
25
+
26
+ def puts s
27
+ @out.puts s
28
+ end
29
+
30
+ public
31
+ def generate(out = $>) #:nodoc:
32
+ @out = out
33
+
34
+ tables_emitted = {}
35
+
36
+ puts "require '#{@vocabulary.name}'"
37
+ puts "require 'activefacts/composition'"
38
+ puts "\n#{@vocabulary.name}_ER = ActiveFacts::Composition.new(#{@vocabulary.name}) do"
39
+ @vocabulary.tables.each do |table|
40
+ puts " composite :\"#{table.name.gsub(' ',@underscore)}\" do"
41
+
42
+ pk = table.identifier_columns
43
+ identity_column = pk[0] if pk[0].is_auto_assigned
44
+
45
+ fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
46
+ fk_columns = table.columns.select do |column|
47
+ column.references[0].is_simple_reference
48
+ end
49
+
50
+ columns =
51
+ table.columns.map do |column|
52
+ [column, column.references.map{|r| r.to_names }]
53
+ end.sort_by do |column, refnames|
54
+ refnames
55
+ end
56
+ previous_flattening = []
57
+ ref_prefix = []
58
+ columns.each do |column, refnames|
59
+ ref_prefix = column.references[0...previous_flattening.size]
60
+ # Pop back. Not a succinct algorithm, but easy to check
61
+ while previous_flattening.size > ref_prefix.size
62
+ previous_flattening.pop
63
+ puts ' '+' '*previous_flattening.size+"end\n"
64
+ end
65
+ while ref_prefix.size > 0 and previous_flattening != ref_prefix
66
+ previous_flattening.pop
67
+ ref_prefix.pop
68
+ puts ' '+' '*previous_flattening.size+"end\n"
69
+ end
70
+ loop do
71
+ ref = column.references[ref_prefix.size]
72
+ if ref.is_self_value
73
+ # REVISIT: I think these should be 'insert :value, :as => "XYZ"'
74
+ role_name = "value".snakecase
75
+ reading = "Intrinsic value of #{role_name}"
76
+ elsif ref.is_to_objectified_fact
77
+ # REVISIT: It's ugly to have to handle these special cases here
78
+ role_name = ref.to.name.words.snakecase
79
+ reading = ref.from_role.link_fact_type.default_reading
80
+ else
81
+ if ref.is_unary && ref.is_from_objectified_fact && ref != column.references.last
82
+ # Use the name of the objectification on the path to other absorbed fact types:
83
+ role_name = ref.to_role.fact_type.entity_type.name.words.snakecase
84
+ else
85
+ role_name = ref.to_role.preferred_role_name
86
+ end
87
+ # puts ">>>>> #{ref.inspect}: #{role_name} <<<<<<"
88
+ reading = ref.fact_type.default_reading
89
+ end
90
+ if ref == column.references.last
91
+ # REVISIT: Avoid the "as" here when the value is implied by the role_name:
92
+ puts ' '+' '*ref_prefix.size+"nest :#{role_name}, :as => \"#{column.name}\"\t\t# #{reading}"
93
+ break
94
+ else
95
+ puts ' '+' '*ref_prefix.size+"flatten :#{role_name} do\t\t# #{reading}"
96
+ ref_prefix.push ref
97
+ end
98
+ end
99
+ previous_flattening = ref_prefix
100
+ end
101
+
102
+ while previous_flattening.size > 0
103
+ previous_flattening.pop
104
+ puts ' '+' '*previous_flattening.size+"end\n"
105
+ end
106
+ puts " end\n\n"
107
+
108
+ tables_emitted[table] = true
109
+
110
+ end
111
+ puts "end\n"
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+
118
+ ActiveFacts::Registry.generator('composition', ActiveFacts::Generate::Composition)