activefacts 0.7.0 → 0.7.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.
- data/README.rdoc +0 -3
- data/Rakefile +7 -5
- data/bin/afgen +5 -2
- data/bin/cql +3 -2
- data/examples/CQL/Genealogy.cql +3 -3
- data/examples/CQL/Metamodel.cql +10 -7
- data/examples/CQL/MultiInheritance.cql +2 -1
- data/examples/CQL/OilSupply.cql +4 -4
- data/examples/CQL/Orienteering.cql +2 -2
- data/lib/activefacts.rb +2 -1
- data/lib/activefacts/api.rb +21 -2
- data/lib/activefacts/api/concept.rb +52 -39
- data/lib/activefacts/api/constellation.rb +8 -6
- data/lib/activefacts/api/entity.rb +41 -37
- data/lib/activefacts/api/instance.rb +5 -3
- data/lib/activefacts/api/numeric.rb +28 -21
- data/lib/activefacts/api/role.rb +29 -43
- data/lib/activefacts/api/standard_types.rb +8 -3
- data/lib/activefacts/api/support.rb +4 -4
- data/lib/activefacts/api/value.rb +9 -3
- data/lib/activefacts/api/vocabulary.rb +17 -7
- data/lib/activefacts/cql.rb +10 -7
- data/lib/activefacts/cql/CQLParser.treetop +6 -0
- data/lib/activefacts/cql/Concepts.treetop +32 -26
- data/lib/activefacts/cql/DataTypes.treetop +6 -0
- data/lib/activefacts/cql/Expressions.treetop +6 -0
- data/lib/activefacts/cql/FactTypes.treetop +6 -0
- data/lib/activefacts/cql/Language/English.treetop +9 -3
- data/lib/activefacts/cql/LexicalRules.treetop +6 -0
- data/lib/activefacts/cql/Rakefile +8 -0
- data/lib/activefacts/cql/parser.rb +4 -2
- data/lib/activefacts/generate/absorption.rb +20 -28
- data/lib/activefacts/generate/cql.rb +28 -16
- data/lib/activefacts/generate/cql/html.rb +327 -321
- data/lib/activefacts/generate/null.rb +7 -3
- data/lib/activefacts/generate/oo.rb +19 -15
- data/lib/activefacts/generate/ordered.rb +457 -461
- data/lib/activefacts/generate/ruby.rb +12 -4
- data/lib/activefacts/generate/sql/server.rb +42 -10
- data/lib/activefacts/generate/text.rb +7 -3
- data/lib/activefacts/input/cql.rb +55 -28
- data/lib/activefacts/input/orm.rb +32 -22
- data/lib/activefacts/persistence.rb +5 -0
- data/lib/activefacts/persistence/columns.rb +66 -32
- data/lib/activefacts/persistence/foreignkey.rb +29 -5
- data/lib/activefacts/persistence/index.rb +57 -25
- data/lib/activefacts/persistence/reference.rb +65 -30
- data/lib/activefacts/persistence/tables.rb +28 -17
- data/lib/activefacts/support.rb +8 -0
- data/lib/activefacts/version.rb +7 -1
- data/lib/activefacts/vocabulary.rb +4 -2
- data/lib/activefacts/vocabulary/extensions.rb +12 -10
- data/lib/activefacts/vocabulary/metamodel.rb +24 -23
- data/spec/api/autocounter.rb +2 -2
- data/spec/api/entity_type.rb +2 -2
- data/spec/api/instance.rb +61 -30
- data/spec/api/roles.rb +9 -9
- data/spec/cql_parse_spec.rb +1 -0
- data/spec/norma_tables_spec.rb +3 -3
- metadata +8 -4
@@ -1,8 +1,11 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Standard types extensions.
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
# These extensions add ActiveFacts Concept and Instance behaviour into base Ruby value classes,
|
8
|
+
# and allow any Class to become an Entity.
|
6
9
|
#
|
7
10
|
require 'date'
|
8
11
|
|
@@ -37,6 +40,8 @@ class NilClass #:nodoc:
|
|
37
40
|
end
|
38
41
|
|
39
42
|
class Class
|
43
|
+
# Make this Class into a Concept and if necessary its module into a Vocabulary.
|
44
|
+
# The parameters are the names (Symbols) of the identifying roles.
|
40
45
|
def identified_by *args
|
41
46
|
raise "not an entity type" if respond_to? :value_type # Don't make a ValueType into an EntityType
|
42
47
|
include ActiveFacts::API::Entity
|
@@ -1,8 +1,8 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Runtime API.
|
3
|
+
# Various additions or patches to Ruby built-in classes, and some global support methods
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
7
7
|
|
8
8
|
class Symbol #:nodoc:
|
@@ -21,7 +21,7 @@ class String #:nodoc:
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def snakecase
|
24
|
-
gsub(/([
|
24
|
+
gsub(/([a-z])([A-Z])/,'\1_\2').downcase
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Value module (mixins for ValueType classes and instances)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
6
|
#
|
5
7
|
# The methods of this module are added to Value type classes.
|
6
8
|
#
|
@@ -13,6 +15,7 @@ module ActiveFacts
|
|
13
15
|
|
14
16
|
# Value instance methods:
|
15
17
|
def initialize(*args) #:nodoc:
|
18
|
+
args[0] = args[0].__getobj__ if RoleProxy === args[0]
|
16
19
|
super(args)
|
17
20
|
end
|
18
21
|
|
@@ -59,7 +62,10 @@ module ActiveFacts
|
|
59
62
|
def identifying_role_values(*args) #:nodoc:
|
60
63
|
# If the single arg is the correct class or a subclass, use it directly
|
61
64
|
#puts "#{basename}.identifying_role_values#{args.inspect}"
|
62
|
-
|
65
|
+
if (args.size == 1 and (arg = args[0]).is_a?(self.class)) # No secondary supertypes allowed for value types
|
66
|
+
arg = arg.__getobj__ if RoleProxy === arg
|
67
|
+
return arg
|
68
|
+
end
|
63
69
|
new(*args)
|
64
70
|
end
|
65
71
|
|
@@ -1,15 +1,21 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Vocabulary module (mixin for any Module that contains classes having Concept mixed in)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
6
|
#
|
5
7
|
# The methods of this module are extended into any module that contains
|
6
8
|
# a Concept class (Entity type or Value type).
|
7
9
|
#
|
8
10
|
module ActiveFacts
|
9
11
|
module API
|
12
|
+
# Vocabulary is a mixin that adds methods to any Module which has any Concept classes (ValueType or EntityType).
|
13
|
+
# A Vocabulary knows all the Concept classes including forward-referenced ones,
|
14
|
+
# and can resolve the forward references when the class is finally defined.
|
15
|
+
# Construction of a Constellation requires a Vocabuary as argument.
|
10
16
|
module Vocabulary
|
11
17
|
# With a parameter, look up a concept class by name.
|
12
|
-
# Without, return the hash of all concepts in this vocabulary
|
18
|
+
# Without, return the hash (keyed by the class' basename) of all concepts in this vocabulary
|
13
19
|
def concept(name = nil)
|
14
20
|
@concept ||= {}
|
15
21
|
return @concept unless name
|
@@ -61,6 +67,7 @@ module ActiveFacts
|
|
61
67
|
}*"\n\t"
|
62
68
|
end
|
63
69
|
|
70
|
+
=begin
|
64
71
|
# Create or find an instance of klass in constellation using value to identify it
|
65
72
|
def adopt(klass, constellation, value) #:nodoc:
|
66
73
|
puts "Adopting #{ value.verbalise rescue value.class.to_s+' '+value.inspect} as #{klass} into constellation #{constellation.object_id}"
|
@@ -68,20 +75,21 @@ module ActiveFacts
|
|
68
75
|
path = "unknown"
|
69
76
|
# Create a value instance we can hack if the value isn't already in this constellation
|
70
77
|
if (c = constellation)
|
71
|
-
if klass
|
78
|
+
if value.is_a?(klass) # Right class?
|
79
|
+
value = value.__getobj__ if RoleProxy === value
|
72
80
|
vc = value.constellation rescue nil
|
73
81
|
if (c == vc) # Right constellation?
|
74
82
|
# Already right class, in the right constellation
|
75
83
|
path = "right constellation, right class, just use it"
|
76
84
|
else
|
77
85
|
# We need a new object from our constellation, so copy the value.
|
78
|
-
if klass.respond_to?(:
|
86
|
+
if klass.respond_to?(:identifying_role_names)
|
79
87
|
# Make a new entity having only the identifying roles set.
|
80
88
|
# Someone will complain that this is wrong, and all functional role values should also
|
81
89
|
# be cloned, and I'm listening... but not there yet. Why just those?
|
82
90
|
cloned = c.send(
|
83
91
|
:"#{klass.basename}",
|
84
|
-
*klass.
|
92
|
+
*klass.identifying_role_names.map{|role| value.send(role) }
|
85
93
|
)
|
86
94
|
path = "wrong constellation, right class, cloned entity"
|
87
95
|
else
|
@@ -99,7 +107,8 @@ module ActiveFacts
|
|
99
107
|
end
|
100
108
|
else
|
101
109
|
# This object's not in a constellation
|
102
|
-
if klass
|
110
|
+
if value.is_a?(klass) # Right class?
|
111
|
+
value = value.__getobj__ if RoleProxy === value
|
103
112
|
if vc = value.constellation rescue nil
|
104
113
|
raise "REVISIT: Assigning to #{self.class.basename}.#{role_name} with constellation=#{c.inspect}: Can't dis-associate object from its constellation #{vc.object_id} yet"
|
105
114
|
end
|
@@ -114,6 +123,7 @@ module ActiveFacts
|
|
114
123
|
# print "#{path}"; puts ", adopted as #{value.verbalise rescue value.inspect}"
|
115
124
|
value
|
116
125
|
end
|
126
|
+
=end
|
117
127
|
|
118
128
|
end
|
119
129
|
end
|
data/lib/activefacts/cql.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts CQL loader.
|
3
|
+
# Use Polyglot to patch things so you can *require* a CQL file and have it define a Ruby module.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
6
|
#
|
5
7
|
require 'rubygems'
|
6
8
|
require 'polyglot'
|
@@ -9,12 +11,13 @@ require 'activefacts/input/cql'
|
|
9
11
|
require 'activefacts/generate/ruby'
|
10
12
|
|
11
13
|
module ActiveFacts
|
12
|
-
#
|
14
|
+
# This class has a load method for Polyglot to tell it how to _require_ a CQL file.
|
15
|
+
# The CQL file is parsed to a vocabulary constellation, which is generated
|
16
|
+
# to Ruby code and eval'd, making the generated classes available.
|
17
|
+
# To make this Loader available, simply
|
18
|
+
# require 'activefacts/cql'
|
13
19
|
class CQLLoader
|
14
|
-
|
15
|
-
# The CQL file is parsed to a vocabulary constellation, which is generated
|
16
|
-
# to Ruby code and eval'd, making the generated classes available.
|
17
|
-
def self.load(file)
|
20
|
+
def self.load(file) #:nodoc:
|
18
21
|
debug "Loading #{file}" do
|
19
22
|
vocabulary = ActiveFacts::Input::CQL.readfile(file)
|
20
23
|
|
@@ -1,3 +1,9 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts CQL Parser.
|
3
|
+
# Parse rules relating to Concept definitions.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
1
7
|
module ActiveFacts
|
2
8
|
module CQL
|
3
9
|
grammar Concepts
|
@@ -22,8 +28,8 @@ module ActiveFacts
|
|
22
28
|
|
23
29
|
rule basetype
|
24
30
|
is s
|
25
|
-
|
26
|
-
|
31
|
+
# independency?
|
32
|
+
identification
|
27
33
|
{ def supers; [] end
|
28
34
|
def identifier; identification.value; end
|
29
35
|
}
|
@@ -31,8 +37,8 @@ module ActiveFacts
|
|
31
37
|
|
32
38
|
rule subtype
|
33
39
|
subtype_prefix
|
34
|
-
|
35
|
-
|
40
|
+
# independency?
|
41
|
+
supertype_list ident:identification?
|
36
42
|
{
|
37
43
|
def supers; supertype_list.value; end
|
38
44
|
def identifier; ident.empty? ? nil : ident.value; end
|
@@ -41,7 +47,7 @@ module ActiveFacts
|
|
41
47
|
|
42
48
|
# REVISIT: This doesn't work, and I don't know why.
|
43
49
|
rule independency
|
44
|
-
|
50
|
+
('independent' S / 'separate' S / 'partitioned' S)
|
45
51
|
end
|
46
52
|
|
47
53
|
rule supertype_list
|
@@ -54,12 +60,12 @@ module ActiveFacts
|
|
54
60
|
end
|
55
61
|
|
56
62
|
rule identification
|
57
|
-
|
58
|
-
identified_by its s id s
|
59
|
-
|
60
|
-
|
63
|
+
# REVISIT: Consider distinguishing "-Id" from just "Id", and not prepending the entity type name if no "-"
|
64
|
+
identified_by its s id s # Reference Mode
|
65
|
+
{ def value; {:mode => id.text_value }; end }
|
66
|
+
/
|
61
67
|
identified_by role_list
|
62
|
-
|
68
|
+
{ def value; {:roles => role_list.roles }; end }
|
63
69
|
end
|
64
70
|
|
65
71
|
# Identified by roles... also used for constraints, beware
|
@@ -75,24 +81,24 @@ module ActiveFacts
|
|
75
81
|
# We can't tell which word is an adjective and which is a concept here.
|
76
82
|
# The concept might be forward-referenced, but not if adjectives are used.
|
77
83
|
# REVISIT: This accepts double-adjective expressions (three words) but they don't work elsewhere
|
78
|
-
|
79
|
-
|
80
|
-
|
84
|
+
rule lead_adj
|
85
|
+
role_player_id '-'
|
86
|
+
end
|
81
87
|
|
82
|
-
|
83
|
-
|
84
|
-
|
88
|
+
rule trail_adj
|
89
|
+
'-' role_player_id
|
90
|
+
end
|
85
91
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
rule role_player
|
93
|
+
l:lead_adj? tail:(s role_player_id)+ s t:trail_adj?
|
94
|
+
{
|
95
|
+
def value
|
96
|
+
(l.empty? ? [] : [l.role_player_id.text_value]) +
|
97
|
+
tail.elements.map{|e| e.role_player_id.text_value } +
|
98
|
+
(t.empty? ? [] : [t.role_player_id.text_value])
|
99
|
+
end
|
100
|
+
}
|
101
|
+
end
|
96
102
|
|
97
103
|
rule role_player_id
|
98
104
|
!(role_list_sep / quantifier) id
|
@@ -1,3 +1,9 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts CQL Parser.
|
3
|
+
# Parse rules the English syntax of CQL.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
1
7
|
module ActiveFacts
|
2
8
|
module CQL
|
3
9
|
grammar Language
|
@@ -17,9 +23,9 @@ module ActiveFacts
|
|
17
23
|
rule quantifier
|
18
24
|
each s { def value; [1, nil]; end }
|
19
25
|
/ some s { def value; nil; end }
|
20
|
-
|
21
|
-
|
22
|
-
|
26
|
+
# REVISIT: "Some" means that any prior occurrence of this role player is to be ignored; this is a new occurrence
|
27
|
+
# "that" on the other hand means that this role player was *previously* designated using "some".
|
28
|
+
# These distinctions are lost here
|
23
29
|
/ that s { def value; nil; end }
|
24
30
|
/ one s { def value; [1, 1]; end }
|
25
31
|
/ no s { def value; [0, 0]; end }
|
@@ -1,3 +1,11 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts CQL Parser.
|
3
|
+
# A Rakefile to run Treetop when the ActiveFacts gem is installed.
|
4
|
+
# This isn't mandatory but makes it much faster to start the parser.
|
5
|
+
# Delete the generated files during parser development.
|
6
|
+
#
|
7
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
8
|
+
#
|
1
9
|
task :default do
|
2
10
|
pattern = File.dirname(__FILE__) + '**/*.treetop'
|
3
11
|
files = Dir[pattern]
|
@@ -1,6 +1,8 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts CQL Parser.
|
3
|
+
# The parser turns CQL strings into abstract syntax trees ready for semantic analysis.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
6
|
#
|
5
7
|
require 'rubygems'
|
6
8
|
require 'treetop'
|
@@ -1,17 +1,27 @@
|
|
1
1
|
#
|
2
|
-
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Absorption generator.
|
3
4
|
#
|
4
|
-
# Copyright (c)
|
5
|
-
# Author: Clifford Heath.
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
7
7
|
require 'activefacts/persistence'
|
8
8
|
|
9
9
|
module ActiveFacts
|
10
10
|
module Generate
|
11
|
+
# Emit the absorption (Relational summary) for vocabulary.
|
12
|
+
# Not currently working, it relies on the old relational composition code.
|
13
|
+
# Invoke as
|
14
|
+
# afgen --absorption[=options] <file>.cql"
|
15
|
+
# Options are comma or space separated:
|
16
|
+
# * no_columns Don't emit the columns
|
17
|
+
# * dependent Show Concepts that are not tables as well
|
18
|
+
# * paths Show the references paths through which each column was defined
|
19
|
+
# * no_identifier Don't show the identified_by columns for an EntityType
|
20
|
+
|
11
21
|
class ABSORPTION
|
12
22
|
include Metamodel
|
13
23
|
|
14
|
-
def initialize(vocabulary, *options)
|
24
|
+
def initialize(vocabulary, *options) #:nodoc:
|
15
25
|
@vocabulary = vocabulary
|
16
26
|
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
17
27
|
@no_columns = options.include? "no_columns"
|
@@ -20,7 +30,7 @@ module ActiveFacts
|
|
20
30
|
@no_identifier = options.include? "no_identifier"
|
21
31
|
end
|
22
32
|
|
23
|
-
def generate(out = $>)
|
33
|
+
def generate(out = $>) #:nodoc:
|
24
34
|
no_absorption = 0
|
25
35
|
single_absorption_vts = 0
|
26
36
|
single_absorption_ets = 0
|
@@ -31,42 +41,24 @@ module ActiveFacts
|
|
31
41
|
# Don't dump imported (base) ValueTypes:
|
32
42
|
next if ValueType === o && !o.supertype
|
33
43
|
show(o)
|
34
|
-
|
35
|
-
case o.absorption_paths.size
|
36
|
-
when 0; no_absorption += 1
|
37
|
-
when 1
|
38
|
-
if ValueType === o
|
39
|
-
single_absorption_vts += 1
|
40
|
-
else
|
41
|
-
single_absorption_ets += 1
|
42
|
-
end
|
43
|
-
else
|
44
|
-
if ValueType === o
|
45
|
-
multi_absorption_vts += 1
|
46
|
-
else
|
47
|
-
multi_absorption_ets += 1
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
44
|
end
|
52
|
-
puts "#{no_absorption} concepts have no absorption paths, #{single_absorption_vts}/#{single_absorption_ets} value/entity types have only one path, #{multi_absorption_vts}/#{multi_absorption_ets} have more than one"
|
53
45
|
end
|
54
46
|
|
55
|
-
def show concept
|
56
|
-
return unless concept.
|
47
|
+
def show concept #:nodoc:
|
48
|
+
return unless concept.is_table || @dependent
|
57
49
|
|
58
50
|
print "#{concept.name}"
|
59
|
-
print " (#{concept.tentative ? "tentatively " : ""}#{concept.
|
51
|
+
print " (#{concept.tentative ? "tentatively " : ""}#{concept.is_table ? "in" : ""}dependent)" if @dependent
|
60
52
|
|
61
53
|
if !@no_identifier && concept.is_a?(EntityType)
|
62
54
|
print " is identified by:\n\t#{
|
63
|
-
concept.
|
55
|
+
"REVISIT" # concept.references_from.to_s
|
64
56
|
}"
|
65
57
|
end
|
66
58
|
print "\n"
|
67
59
|
|
68
60
|
unless @no_columns
|
69
|
-
puts "#{ concept.
|
61
|
+
puts "#{ concept.references_from.map do |role_ref|
|
70
62
|
"\t#{role_ref.column_name(".")}\n"
|
71
63
|
end*"" }"
|
72
64
|
end
|