activefacts 0.7.3 → 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/Manifest.txt +24 -2
- data/Rakefile +25 -3
- data/bin/afgen +1 -1
- data/bin/cql +13 -2
- data/css/offline.css +3 -0
- data/css/orm2.css +24 -0
- data/css/print.css +8 -0
- data/css/style-print.css +357 -0
- data/css/style.css +387 -0
- data/download.html +85 -0
- data/examples/CQL/Address.cql +3 -3
- data/examples/CQL/Blog.cql +13 -14
- data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
- data/examples/CQL/Death.cql +3 -2
- data/examples/CQL/Genealogy.cql +13 -11
- data/examples/CQL/Marriage.cql +2 -2
- data/examples/CQL/Metamodel.cql +136 -93
- data/examples/CQL/MultiInheritance.cql +2 -2
- data/examples/CQL/OilSupply.cql +14 -10
- data/examples/CQL/Orienteering.cql +22 -19
- data/examples/CQL/PersonPlaysGame.cql +3 -2
- data/examples/CQL/SchoolActivities.cql +4 -2
- data/examples/CQL/SimplestUnary.cql +1 -1
- data/examples/CQL/SubtypePI.cql +6 -7
- data/examples/CQL/Warehousing.cql +16 -19
- data/examples/CQL/unit.cql +584 -0
- data/examples/index.html +276 -0
- data/examples/intro.html +497 -0
- data/examples/local.css +20 -0
- data/index.html +96 -0
- data/lib/activefacts/api/concept.rb +48 -46
- data/lib/activefacts/api/constellation.rb +43 -23
- data/lib/activefacts/api/entity.rb +2 -2
- data/lib/activefacts/api/instance.rb +6 -2
- data/lib/activefacts/api/instance_index.rb +5 -0
- data/lib/activefacts/api/value.rb +8 -2
- data/lib/activefacts/api/vocabulary.rb +15 -10
- data/lib/activefacts/cql/CQLParser.treetop +109 -88
- data/lib/activefacts/cql/Concepts.treetop +32 -10
- data/lib/activefacts/cql/Context.treetop +34 -0
- data/lib/activefacts/cql/Expressions.treetop +9 -9
- data/lib/activefacts/cql/FactTypes.treetop +30 -31
- data/lib/activefacts/cql/Language/English.treetop +50 -0
- data/lib/activefacts/cql/LexicalRules.treetop +2 -1
- data/lib/activefacts/cql/Terms.treetop +117 -0
- data/lib/activefacts/cql/ValueTypes.treetop +152 -0
- data/lib/activefacts/cql/compiler.rb +1718 -0
- data/lib/activefacts/cql/parser.rb +124 -57
- data/lib/activefacts/generate/absorption.rb +1 -1
- data/lib/activefacts/generate/cql.rb +111 -100
- data/lib/activefacts/generate/cql/html.rb +5 -5
- data/lib/activefacts/generate/oo.rb +3 -3
- data/lib/activefacts/generate/ordered.rb +51 -19
- data/lib/activefacts/generate/ruby.rb +10 -8
- data/lib/activefacts/generate/sql/mysql.rb +14 -10
- data/lib/activefacts/generate/sql/server.rb +29 -24
- data/lib/activefacts/input/cql.rb +9 -1264
- data/lib/activefacts/input/orm.rb +213 -200
- data/lib/activefacts/persistence/columns.rb +11 -10
- data/lib/activefacts/persistence/index.rb +15 -18
- data/lib/activefacts/persistence/reference.rb +17 -17
- data/lib/activefacts/persistence/tables.rb +50 -51
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +79 -8
- data/lib/activefacts/vocabulary/metamodel.rb +183 -114
- data/spec/absorption_ruby_spec.rb +99 -0
- data/spec/absorption_spec.rb +3 -4
- data/spec/api/constellation.rb +1 -1
- data/spec/api/entity_type.rb +3 -1
- data/spec/api/instance.rb +4 -2
- data/spec/api/roles.rb +8 -6
- data/spec/api_spec.rb +1 -2
- data/spec/cql/context_spec.rb +71 -0
- data/spec/cql/samples_spec.rb +154 -0
- data/spec/cql/unit_spec.rb +375 -0
- data/spec/cql_cql_spec.rb +31 -21
- data/spec/cql_mysql_spec.rb +70 -0
- data/spec/cql_parse_spec.rb +15 -9
- data/spec/cql_ruby_spec.rb +27 -13
- data/spec/cql_sql_spec.rb +42 -16
- data/spec/cql_symbol_tables_spec.rb +2 -3
- data/spec/cqldump_spec.rb +7 -7
- data/spec/helpers/file_matcher.rb +39 -0
- data/spec/norma_cql_spec.rb +20 -12
- data/spec/norma_ruby_spec.rb +6 -3
- data/spec/norma_sql_spec.rb +6 -3
- data/spec/norma_tables_spec.rb +6 -4
- data/spec/spec_helper.rb +27 -8
- data/status.html +69 -0
- data/why.html +60 -0
- metadata +34 -11
- data/lib/activefacts/cql/DataTypes.treetop +0 -81
- data/spec/cql_unit_spec.rb +0 -330
@@ -0,0 +1,99 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts tests: Compare column lists created by aborption and by generated Ruby.
|
3
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'stringio'
|
7
|
+
require 'activefacts/vocabulary'
|
8
|
+
require 'activefacts/support'
|
9
|
+
require 'activefacts/input/orm'
|
10
|
+
require 'activefacts/persistence'
|
11
|
+
require 'activefacts/generate/ruby'
|
12
|
+
|
13
|
+
include ActiveFacts
|
14
|
+
|
15
|
+
describe "Column lists from absorption compared with Ruby's" do
|
16
|
+
ABSORPTION_RUBY_FAILURES = {
|
17
|
+
"Metamodel" => "Overlaps with ActiveFacts Metamodel",
|
18
|
+
"MetamodelTerms" => "Overlaps with ActiveFacts Metamodel",
|
19
|
+
"ServiceDirector" => "Lacks standard AutoTimestamp class"
|
20
|
+
}
|
21
|
+
|
22
|
+
# Generate and return the Ruby for the given vocabulary
|
23
|
+
def ruby(vocabulary)
|
24
|
+
output = StringIO.new
|
25
|
+
@dumper = ActiveFacts::Generate::RUBY.new(vocabulary.constellation, "sql")
|
26
|
+
@dumper.generate(output)
|
27
|
+
output.rewind
|
28
|
+
output.read
|
29
|
+
end
|
30
|
+
|
31
|
+
pattern = ENV["AFTESTS"] || "*"
|
32
|
+
Dir["examples/norma/#{pattern}.orm"].each do |orm_file|
|
33
|
+
expected_file = orm_file.sub(%r{examples/norma/(.*).orm\Z}, 'examples/ruby/\1.rb')
|
34
|
+
actual_file = orm_file.sub(%r{examples/norma/(.*).orm\Z}, 'spec/actual/\1.rb')
|
35
|
+
|
36
|
+
it "should load #{orm_file} and generate relational composition and Ruby with matching column names" do
|
37
|
+
vocabulary = ActiveFacts::Input::ORM.readfile(orm_file)
|
38
|
+
|
39
|
+
# Get the list of tables from the relational composition:
|
40
|
+
absorption_tables = vocabulary.tables.sort_by(&:name)
|
41
|
+
absorption_table_names = absorption_tables.map{|at| at.name}
|
42
|
+
|
43
|
+
# Build the Ruby and eval it:
|
44
|
+
ruby_text = ruby(vocabulary)
|
45
|
+
File.open(actual_file, "w") { |f| f.write ruby_text }
|
46
|
+
|
47
|
+
broken = ABSORPTION_RUBY_FAILURES[File.basename(orm_file, ".orm")]
|
48
|
+
eval_it = lambda { Object.send :eval, ruby_text }
|
49
|
+
if broken
|
50
|
+
pending(broken) {
|
51
|
+
lambda {
|
52
|
+
eval_it.call
|
53
|
+
}.should_not raise_error
|
54
|
+
}
|
55
|
+
else
|
56
|
+
lambda {
|
57
|
+
eval_it.call
|
58
|
+
}.should_not raise_error
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get a list of table classes in the new module, sorted by name
|
62
|
+
mod = eval(vocabulary.name)
|
63
|
+
ruby_tables = mod.constants.map{|n|
|
64
|
+
c = mod.const_get(n)
|
65
|
+
c.class == Class && c.is_table ? c : nil
|
66
|
+
}.compact.sort_by{|c|
|
67
|
+
c.basename
|
68
|
+
}
|
69
|
+
ruby_table_names = ruby_tables.map{|c| c.basename}
|
70
|
+
|
71
|
+
# Assert that the list of tables is the same:
|
72
|
+
tables = lambda { ruby_table_names.should == absorption_table_names }
|
73
|
+
if broken
|
74
|
+
pending { tables.call }
|
75
|
+
else
|
76
|
+
tables.call
|
77
|
+
end
|
78
|
+
|
79
|
+
# So we get to see the full differences, figure them here and assert them to be empty:
|
80
|
+
diffs = {}
|
81
|
+
ruby_tables.each{|rt|
|
82
|
+
next unless rt.is_entity_type
|
83
|
+
absorption_table = absorption_tables.select{|at| at.name == rt.basename}[0]
|
84
|
+
absorption_columns = absorption_table.columns.map{|c| c.name("").downcase}.sort
|
85
|
+
ruby_columns = rt.columns.map{|c| c.gsub(/\./,'').downcase}.sort
|
86
|
+
missing = absorption_columns - ruby_columns
|
87
|
+
extra = ruby_columns - absorption_columns
|
88
|
+
unless missing.empty? and extra.empty?
|
89
|
+
diffs[rt.basename] = missing.map{|m| "-"+m} + extra.map{|e| '+'+e}
|
90
|
+
end
|
91
|
+
}
|
92
|
+
diffs.should == {}
|
93
|
+
|
94
|
+
# Clean up:
|
95
|
+
Object.send :remove_const, vocabulary.name.to_sym
|
96
|
+
File.delete(actual_file) # It succeeded, we don't need the file.
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/spec/absorption_spec.rb
CHANGED
@@ -2,8 +2,7 @@
|
|
2
2
|
# ActiveFacts tests: Test the relational absorption by compiling CQL fragments.
|
3
3
|
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
4
|
#
|
5
|
-
|
6
|
-
require 'treetop'
|
5
|
+
|
7
6
|
require 'activefacts/support'
|
8
7
|
require 'activefacts/api/support'
|
9
8
|
require 'activefacts/input/cql'
|
@@ -75,8 +74,8 @@ describe "Absorption" do
|
|
75
74
|
cql = test[:cql]
|
76
75
|
expected_tables = test[:tables]
|
77
76
|
it "should #{should}" do
|
78
|
-
@compiler = ActiveFacts::
|
79
|
-
@vocabulary = @compiler.
|
77
|
+
@compiler = ActiveFacts::CQL::Compiler.new(cql, should)
|
78
|
+
@vocabulary = @compiler.vocabulary
|
80
79
|
|
81
80
|
# puts cql
|
82
81
|
|
data/spec/api/constellation.rb
CHANGED
@@ -50,7 +50,7 @@ describe "A Constellation instance" do
|
|
50
50
|
identified_by :name, :family_name # REVISIT: want a way to role_alias :name, :given_name
|
51
51
|
supertypes SurrogateId
|
52
52
|
|
53
|
-
has_one :family_name, :Name
|
53
|
+
has_one :family_name, :class => Name
|
54
54
|
end
|
55
55
|
end
|
56
56
|
@constellation = ActiveFacts::API::Constellation.new(Mod)
|
data/spec/api/entity_type.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
# ActiveFacts tests: Entity classes in the Runtime API
|
3
3
|
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
4
|
#
|
5
|
+
require 'activefacts/api'
|
6
|
+
|
5
7
|
describe "Entity Type class definitions" do
|
6
8
|
before :each do
|
7
9
|
Object.send :remove_const, :Mod if Object.const_defined?("Mod")
|
@@ -13,7 +15,7 @@ describe "Entity Type class definitions" do
|
|
13
15
|
end
|
14
16
|
class Person < LegalEntity
|
15
17
|
identified_by :name
|
16
|
-
has_one :name, Name
|
18
|
+
has_one :name, :class => Name
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
data/spec/api/instance.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
# ActiveFacts tests: Value instances in the Runtime API
|
3
3
|
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
4
|
#
|
5
|
+
require 'activefacts/api'
|
6
|
+
|
5
7
|
describe "An instance of every type of Concept" do
|
6
8
|
before :each do
|
7
9
|
Object.send :remove_const, :Mod if Object.const_defined?("Mod")
|
@@ -45,7 +47,7 @@ describe "An instance of every type of Concept" do
|
|
45
47
|
@role_names.map do |role_name|
|
46
48
|
%Q{
|
47
49
|
has_one :#{role_name}
|
48
|
-
one_to_one :one_#{role_name}, #{role_name.to_s.camelcase(true)}}
|
50
|
+
one_to_one :one_#{role_name}, :class => #{role_name.to_s.camelcase(true)}}
|
49
51
|
end*""
|
50
52
|
}
|
51
53
|
end
|
@@ -55,7 +57,7 @@ describe "An instance of every type of Concept" do
|
|
55
57
|
@role_names.map do |role_name|
|
56
58
|
%Q{
|
57
59
|
has_one :#{role_name}
|
58
|
-
one_to_one :one_#{role_name}, #{role_name.to_s.camelcase(true)}}
|
60
|
+
one_to_one :one_#{role_name}, :class => #{role_name.to_s.camelcase(true)}}
|
59
61
|
end*""
|
60
62
|
}
|
61
63
|
end
|
data/spec/api/roles.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
# ActiveFacts tests: Roles of concept classes in the Runtime API
|
3
3
|
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
4
|
#
|
5
|
+
require 'activefacts/api'
|
6
|
+
|
5
7
|
describe "Roles" do
|
6
8
|
before :each do
|
7
9
|
Object.send :remove_const, :Mod if Object.const_defined?("Mod")
|
@@ -15,16 +17,16 @@ describe "Roles" do
|
|
15
17
|
end
|
16
18
|
class Contract
|
17
19
|
identified_by :first, :second
|
18
|
-
has_one :first, LegalEntity
|
19
|
-
has_one :second, LegalEntity
|
20
|
+
has_one :first, :class => LegalEntity
|
21
|
+
has_one :second, :class => LegalEntity
|
20
22
|
end
|
21
23
|
class Person < LegalEntity
|
22
24
|
# identified_by # No identifier needed, inherit from superclass
|
23
25
|
# New identifier:
|
24
26
|
identified_by :family, :given
|
25
|
-
has_one :family, Name
|
26
|
-
has_one :given, Name
|
27
|
-
has_one :related_to, LegalEntity
|
27
|
+
has_one :family, :class => Name
|
28
|
+
has_one :given, :class => Name
|
29
|
+
has_one :related_to, :class => LegalEntity
|
28
30
|
end
|
29
31
|
end
|
30
32
|
# print "concept: "; p Mod.concept
|
@@ -79,7 +81,7 @@ describe "Roles" do
|
|
79
81
|
module Mod
|
80
82
|
class FamilyName < Name
|
81
83
|
value_type
|
82
|
-
one_to_one :patriarch, Person
|
84
|
+
one_to_one :patriarch, :class => Person
|
83
85
|
end
|
84
86
|
end
|
85
87
|
r = Mod::FamilyName.roles(:patriarch)
|
data/spec/api_spec.rb
CHANGED
@@ -2,10 +2,9 @@
|
|
2
2
|
# ActiveFacts tests: Run the API tests
|
3
3
|
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
4
|
#
|
5
|
-
base = File.dirname(__FILE__)+"/"
|
6
|
-
require 'rubygems'
|
7
5
|
require 'activefacts/api'
|
8
6
|
|
7
|
+
base = File.dirname(__FILE__)+"/"
|
9
8
|
require base+'api/constellation'
|
10
9
|
require base+'api/entity_type'
|
11
10
|
require base+'api/instance'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts CQL Business Context Note tests
|
3
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'activefacts/support'
|
7
|
+
require 'activefacts/api/support'
|
8
|
+
require 'activefacts/cql/parser'
|
9
|
+
|
10
|
+
describe "Business Context Notes" do
|
11
|
+
# (according_to people ',')? (because / as_opposed_to / so_that / to_avoid) discussion (',' as_agreed_by)? s
|
12
|
+
Prefix = %q{
|
13
|
+
Person is written as Person;
|
14
|
+
}
|
15
|
+
Notes = [
|
16
|
+
# Constraints:
|
17
|
+
[ 'each Person occurs one time in Person is employed, Person is unemployed (because it can be no other way!);',
|
18
|
+
[["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :presence, [["Person"]], [1, 1], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"unemployed"}]]], [nil, "because", "it can be no other way!", []]]]]
|
19
|
+
],
|
20
|
+
[ 'each Person occurs one time in Person is employed, Person is unemployed (as opposed to blah!);',
|
21
|
+
[["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :presence, [["Person"]], [1, 1], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"unemployed"}]]], [nil, "as_opposed_to", " blah!", []]]]]
|
22
|
+
],
|
23
|
+
[ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (so that blah);',
|
24
|
+
[["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [nil, "so_that", " blah", []]]]]
|
25
|
+
],
|
26
|
+
[ 'Person is good credit risk only if Person is employed (to avoid lending to people who can\'t repay);',
|
27
|
+
[["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :subset, [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"good"}, {:word=>"credit"}, {:word=>"risk"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]]], [nil, "to_avoid", " lending to people who can't repay", []]]]]
|
28
|
+
],
|
29
|
+
[ 'Person is good credit risk if and only if Person is employed (to avoid lending to people who can\'t repay);',
|
30
|
+
[["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :equality, [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"good"}, {:word=>"credit"}, {:word=>"risk"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]]], [nil, "to_avoid", " lending to people who can't repay", []]]]]
|
31
|
+
],
|
32
|
+
# Entity and Fact types
|
33
|
+
[ 'Foo is identified by Bar [independent] where Foo has one Bar (so that we have an id);',
|
34
|
+
[["Person", [:value_type, "Person", [], [], [], []]], ["Foo", [:entity_type, [], {:roles=>[["Bar"]]}, ["independent"], [[:fact_clause, [], [{:word=>"Foo"}, {:word=>"has"}, {:word=>"Bar", :quantifier=>[1, 1]}], [nil, "so_that", " we have an id", []]]]]]]
|
35
|
+
],
|
36
|
+
[ 'Foo has one Bar (so that we have an id), Bar is of one Foo (because we need that);',
|
37
|
+
[["Person", [:value_type, "Person", [], [], [], []]], [nil, [:fact_type, [[:fact_clause, [], [{:word=>"Foo"}, {:word=>"has"}, {:word=>"Bar", :quantifier=>[1, 1]}], [nil, "so_that", " we have an id", []]], [:fact_clause, [], [{:word=>"Bar"}, {:word=>"is"}, {:word=>"of"}, {:word=>"Foo", :quantifier=>[1, 1]}], [nil, "because", "we need that", []]]], []]]]
|
38
|
+
],
|
39
|
+
# REVISIT: No context notes on quantifiers yet
|
40
|
+
# As agreed by:
|
41
|
+
[ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (so that blah, as agreed by Jim);',
|
42
|
+
[["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [nil, "so_that", " blah", [nil, ["Jim"]]]]]]
|
43
|
+
],
|
44
|
+
# REVISIT: Populate an "as agreed by" with a date
|
45
|
+
[ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (so that blah, as agreed on 29 March by Jim);',
|
46
|
+
[["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [nil, "so_that", " blah", ["29 March", ["Jim"]]]]]]
|
47
|
+
],
|
48
|
+
# According to:
|
49
|
+
[ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (according to jim, so that blah);',
|
50
|
+
[["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [["jim"], "so_that", " blah", []]]]]
|
51
|
+
],
|
52
|
+
]
|
53
|
+
|
54
|
+
before :each do
|
55
|
+
@parser = ActiveFacts::CQL::Parser.new
|
56
|
+
end
|
57
|
+
|
58
|
+
Notes.each do |c|
|
59
|
+
source, ast = *c
|
60
|
+
it "should parse #{source.inspect}" do
|
61
|
+
#debugger
|
62
|
+
result = @parser.parse_all(Prefix+source, :definition)
|
63
|
+
|
64
|
+
puts @parser.failure_reason unless result
|
65
|
+
result.should_not be_nil
|
66
|
+
result.map{|d| d.value}.should == ast if ast
|
67
|
+
# Uncomment this to see what should replace "nil" in the cases above:
|
68
|
+
#puts result.map{|d| d.value}.inspect unless ast
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts tests: Test the CQL parser by looking at its parse trees.
|
3
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'activefacts/support'
|
7
|
+
require 'activefacts/api/support'
|
8
|
+
require 'activefacts/input/cql'
|
9
|
+
|
10
|
+
describe "Sample data" do
|
11
|
+
SamplePrefix = %q{
|
12
|
+
vocabulary V;
|
13
|
+
|
14
|
+
CompanyName is written as String;
|
15
|
+
Company is identified by its Name;
|
16
|
+
Person is identified by its Name where Person is called PersonName;
|
17
|
+
Directorship is where
|
18
|
+
Company is directed by Person;
|
19
|
+
}
|
20
|
+
|
21
|
+
Samples = [
|
22
|
+
[ # A simple ValueType instance
|
23
|
+
"CompanyName 'Microsoft';",
|
24
|
+
[{:facts=>[], :instances=>["CompanyName 'Microsoft'"]}]
|
25
|
+
],
|
26
|
+
[ # Re-assert the same instance
|
27
|
+
"CompanyName 'Microsoft'; CompanyName 'Microsoft';",
|
28
|
+
[{:facts=>[], :instances=>["CompanyName 'Microsoft'"]}]
|
29
|
+
],
|
30
|
+
[ # The same instance, but in a named population
|
31
|
+
"example: CompanyName 'Microsoft';",
|
32
|
+
[{:facts=>[], :instances=>["CompanyName 'Microsoft'"]}]
|
33
|
+
],
|
34
|
+
[ # A simply-identified EntityType instance
|
35
|
+
"Company 'Microsoft';",
|
36
|
+
[{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
|
37
|
+
],
|
38
|
+
[ # Re-assert the same instance
|
39
|
+
"Company 'Microsoft'; Company 'Microsoft';",
|
40
|
+
[{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
|
41
|
+
],
|
42
|
+
[ # The same instance in a named population
|
43
|
+
"example: Company 'Microsoft';",
|
44
|
+
[{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
|
45
|
+
],
|
46
|
+
[ # The Company instance asserted with an explicit identifying fact
|
47
|
+
"Company has CompanyName 'Microsoft';",
|
48
|
+
[{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
|
49
|
+
],
|
50
|
+
[ # The Company instance asserted with an joined identifying instance
|
51
|
+
"Company has CompanyName, CompanyName 'Microsoft';",
|
52
|
+
[{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
|
53
|
+
],
|
54
|
+
[ # The same, with an explicit identifying instance join
|
55
|
+
"CompanyName 'Microsoft', Company has CompanyName;",
|
56
|
+
[{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
|
57
|
+
],
|
58
|
+
[ # A simple fact instance with two simply-identified entities
|
59
|
+
"Company 'Microsoft' is directed by Person 'Gates';",
|
60
|
+
[{:facts=>["Company has CompanyName 'Microsoft'", "Company is directed by Person", "Person is called PersonName 'Gates'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'", "Directorship where Company is directed by Person", "Person is identified by PersonName where Person is called PersonName 'Gates'", "PersonName 'Gates'"]}]
|
61
|
+
],
|
62
|
+
[ # Same with an explicit joined fact
|
63
|
+
"Company 'Microsoft' is directed by Person, Person is called PersonName 'Gates';",
|
64
|
+
[{:facts=>["Company has CompanyName 'Microsoft'", "Company is directed by Person", "Person is called PersonName 'Gates'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'", "Directorship where Company is directed by Person", "Person is identified by PersonName where Person is called PersonName 'Gates'", "PersonName 'Gates'"]}]
|
65
|
+
],
|
66
|
+
[ # Same with explicitly joined facts and instances
|
67
|
+
"Company is directed by Person, Person is called PersonName, PersonName 'Gates', Company has CompanyName, CompanyName 'Microsoft';",
|
68
|
+
[{:facts=>["Company has CompanyName 'Microsoft'", "Company is directed by Person", "Person is called PersonName 'Gates'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'", "Directorship where Company is directed by Person", "Person is identified by PersonName where Person is called PersonName 'Gates'", "PersonName 'Gates'"]}]
|
69
|
+
],
|
70
|
+
[ # Same in a named population
|
71
|
+
"example: Company is directed by Person, Person is called PersonName, PersonName 'Gates', Company has CompanyName, CompanyName 'Microsoft';",
|
72
|
+
[{:facts=>["Company has CompanyName 'Microsoft'", "Company is directed by Person", "Person is called PersonName 'Gates'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'", "Directorship where Company is directed by Person", "Person is identified by PersonName where Person is called PersonName 'Gates'", "PersonName 'Gates'"]}]
|
73
|
+
],
|
74
|
+
]
|
75
|
+
|
76
|
+
# REVISIT: This code does a better job than verbalise. Consider incorporating it?
|
77
|
+
def instance_name(i)
|
78
|
+
if i.is_a?(ActiveFacts::Metamodel::Fact)
|
79
|
+
fact = i
|
80
|
+
reading = fact.fact_type.preferred_reading
|
81
|
+
reading_roles = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role }
|
82
|
+
role_values_in_reading_order = fact.all_role_value.sort_by{|rv| reading_roles.index(rv.role) }
|
83
|
+
instance_verbalisations = role_values_in_reading_order.map do |rv|
|
84
|
+
next nil unless v = rv.instance.value
|
85
|
+
v.to_s
|
86
|
+
end
|
87
|
+
return reading.expand([], false, instance_verbalisations)
|
88
|
+
# REVISIT: Include the instance_names of all role players
|
89
|
+
end
|
90
|
+
|
91
|
+
if i.concept.is_a?(ActiveFacts::Metamodel::ValueType)
|
92
|
+
return "#{i.concept.name} #{i.value}"
|
93
|
+
end
|
94
|
+
|
95
|
+
if i.concept.fact_type # An instance of an objectified fact type
|
96
|
+
return "#{i.concept.name} where #{instance_name(i.fact)}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# It's an entity that's not an objectified fact type
|
100
|
+
# REVISIT: If it has a simple identifier, there's no need to fully verbalise the identifying facts
|
101
|
+
pi = i.concept.preferred_identifier
|
102
|
+
identifying_role_refs = pi.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
|
103
|
+
return "#{i.concept.name}" +
|
104
|
+
" is identified by " +
|
105
|
+
identifying_role_refs.map do |rr|
|
106
|
+
[ (l = rr.leading_adjective) ? l+"-" : nil,
|
107
|
+
rr.role_name || rr.role.concept.name,
|
108
|
+
(t = rr.trailing_adjective) ? l+"-" : nil
|
109
|
+
].compact*""
|
110
|
+
end * " and " +
|
111
|
+
" where " +
|
112
|
+
identifying_role_refs.map do |rr| # Go through the identifying roles and emit the facts that define them
|
113
|
+
instance_role = i.concept.all_role.detect{|r| r.fact_type == rr.role.fact_type}
|
114
|
+
identifying_fact = i.all_role_value.detect{|rv| rv.fact.fact_type == rr.role.fact_type}.fact
|
115
|
+
#counterpart_role = (rr.role.fact_type.all_role.to_a-[instance_role])[0]
|
116
|
+
#identifying_instance = counterpart_role.all_role_value.detect{|rv| rv.fact == identifying_fact}.instance
|
117
|
+
instance_name(identifying_fact)
|
118
|
+
end*", "
|
119
|
+
end
|
120
|
+
|
121
|
+
def instance_data(populations)
|
122
|
+
populations = @vocabulary.constellation.Population
|
123
|
+
populations.keys.sort.map do |popname|
|
124
|
+
popvalue = populations[popname]
|
125
|
+
{
|
126
|
+
:instances => popvalue.all_instance.map { |i| instance_name(i) }.sort,
|
127
|
+
:facts => popvalue.all_fact.map { |fact| instance_name(fact) }.sort
|
128
|
+
}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
Samples.each do |c|
|
133
|
+
source, expected = *Array(c)
|
134
|
+
it "should handle #{source.inspect}" do
|
135
|
+
@text = SamplePrefix+source
|
136
|
+
@vocabulary = ActiveFacts::Input::CQL.readstring(@text)
|
137
|
+
result = instance_data(@vocabulary)
|
138
|
+
|
139
|
+
if expected
|
140
|
+
result[0].should == expected[0]
|
141
|
+
else
|
142
|
+
pending "#{source}:\n\t#{result.inspect}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should de-duplicate #{source.inspect}" do
|
147
|
+
# Make sure you don't get anything duplicated
|
148
|
+
@text = SamplePrefix+source+source
|
149
|
+
@vocabulary = ActiveFacts::Input::CQL.readstring(@text)
|
150
|
+
result = instance_data(@vocabulary)
|
151
|
+
result[0].should == expected[0]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|