fabulator 0.0.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/History.txt +4 -0
- data/Manifest.txt +41 -0
- data/README.rdoc +61 -0
- data/Rakefile +54 -0
- data/lib/fabulator.rb +5 -0
- data/lib/fabulator/action.rb +46 -0
- data/lib/fabulator/action_lib.rb +438 -0
- data/lib/fabulator/context.rb +39 -0
- data/lib/fabulator/core.rb +8 -0
- data/lib/fabulator/core/actions.rb +514 -0
- data/lib/fabulator/core/actions/choose.rb +167 -0
- data/lib/fabulator/core/actions/for_each.rb +105 -0
- data/lib/fabulator/core/actions/variables.rb +52 -0
- data/lib/fabulator/core/constraint.rb +117 -0
- data/lib/fabulator/core/filter.rb +41 -0
- data/lib/fabulator/core/group.rb +123 -0
- data/lib/fabulator/core/parameter.rb +128 -0
- data/lib/fabulator/core/state.rb +91 -0
- data/lib/fabulator/core/state_machine.rb +153 -0
- data/lib/fabulator/core/transition.rb +164 -0
- data/lib/fabulator/expr.rb +37 -0
- data/lib/fabulator/expr/axis.rb +133 -0
- data/lib/fabulator/expr/axis_descendent_or_self.rb +26 -0
- data/lib/fabulator/expr/bin_expr.rb +178 -0
- data/lib/fabulator/expr/context.rb +368 -0
- data/lib/fabulator/expr/for_expr.rb +74 -0
- data/lib/fabulator/expr/function.rb +52 -0
- data/lib/fabulator/expr/if_expr.rb +22 -0
- data/lib/fabulator/expr/let_expr.rb +17 -0
- data/lib/fabulator/expr/literal.rb +39 -0
- data/lib/fabulator/expr/node.rb +216 -0
- data/lib/fabulator/expr/node_logic.rb +99 -0
- data/lib/fabulator/expr/parser.rb +1470 -0
- data/lib/fabulator/expr/path_expr.rb +49 -0
- data/lib/fabulator/expr/predicates.rb +45 -0
- data/lib/fabulator/expr/statement_list.rb +96 -0
- data/lib/fabulator/expr/step.rb +43 -0
- data/lib/fabulator/expr/unary_expr.rb +30 -0
- data/lib/fabulator/expr/union_expr.rb +21 -0
- data/lib/fabulator/template.rb +9 -0
- data/lib/fabulator/template/context.rb +51 -0
- data/lib/fabulator/template/parse_result.rb +153 -0
- data/lib/fabulator/template/parser.rb +17 -0
- data/lib/fabulator/template/standard_tags.rb +95 -0
- data/lib/fabulator/template/taggable.rb +88 -0
- data/lib/fabulator/version.rb +14 -0
- data/test/test_fabulator.rb +17 -0
- data/test/test_helper.rb +24 -0
- data/xslt/form.xsl +2161 -0
- metadata +182 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
module Fabulator
|
2
|
+
module Core
|
3
|
+
class Group < Fabulator::Action
|
4
|
+
attr_accessor :name, :params, :tags
|
5
|
+
|
6
|
+
namespace Fabulator::FAB_NS
|
7
|
+
|
8
|
+
has_select
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@params = [ ]
|
12
|
+
@constraints = [ ]
|
13
|
+
@filters = [ ]
|
14
|
+
@required_params = [ ]
|
15
|
+
@tags = [ ]
|
16
|
+
end
|
17
|
+
|
18
|
+
def compile_xml(xml, context)
|
19
|
+
super
|
20
|
+
xml.each_element do |e|
|
21
|
+
next unless e.namespaces.namespace.href == FAB_NS
|
22
|
+
|
23
|
+
case e.name
|
24
|
+
when 'param':
|
25
|
+
v = Parameter.new.compile_xml(e,@context)
|
26
|
+
@params << v
|
27
|
+
@required_params = @required_params + v.names if v.required?
|
28
|
+
when 'group':
|
29
|
+
v = Group.new.compile_xml(e,@context)
|
30
|
+
@params << v
|
31
|
+
@required_params = @required_params + v.required_params.collect{ |n| (@name + '/' + n).gsub(/\/+/, '/') }
|
32
|
+
when 'constraint':
|
33
|
+
@constraints << Constraint.new.compile_xml(e,@context)
|
34
|
+
when 'filter':
|
35
|
+
@filters << Filter.new.compile_xml(e,@context)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def apply_filters(context)
|
42
|
+
filtered = [ ]
|
43
|
+
|
44
|
+
@context.with(context) do |ctx|
|
45
|
+
|
46
|
+
self.get_context(ctx).each do |root|
|
47
|
+
@params.each do |param|
|
48
|
+
@filters.each do |f|
|
49
|
+
filtered = filtered + f.apply_filter(ctx.with_root(root))
|
50
|
+
end
|
51
|
+
filtered = filtered + param.apply_filters(ctx.with_root(root))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
filtered.uniq
|
56
|
+
end
|
57
|
+
|
58
|
+
def apply_constraints(context)
|
59
|
+
res = { :missing => [], :invalid => [], :valid => [], :messages => [] }
|
60
|
+
passed = [ ]
|
61
|
+
failed = [ ]
|
62
|
+
@context.with(context) do |ctx|
|
63
|
+
self.get_context(ctx).each do |root|
|
64
|
+
@params.each do |param|
|
65
|
+
@constraints.each do |c|
|
66
|
+
r = c.test_constraint(ctx.with_root(root))
|
67
|
+
passed += r[0]
|
68
|
+
failed += r[1]
|
69
|
+
end
|
70
|
+
p_res = param.apply_constraints(ctx.with_root(root))
|
71
|
+
res[:messages] += p_res[:messages]
|
72
|
+
failed += p_res[:invalid]
|
73
|
+
passed += p_res[:valid]
|
74
|
+
res[:missing] += p_res[:missing]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
res[:invalid] = failed.uniq
|
79
|
+
res[:valid] = (passed - failed).uniq
|
80
|
+
res[:messages].uniq!
|
81
|
+
res[:missing] = (res[:missing] - passed).uniq
|
82
|
+
res
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def get_context(context)
|
87
|
+
return [ context.root ] if @select.nil?
|
88
|
+
ret = [ ]
|
89
|
+
context.in_context do |ctx|
|
90
|
+
ret = @select.run(ctx)
|
91
|
+
end
|
92
|
+
ret
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_constraints(context)
|
96
|
+
passed = [ ]
|
97
|
+
failed = [ ]
|
98
|
+
@context.with(context) do |ctx|
|
99
|
+
roots = self.get_context(ctx)
|
100
|
+
roots.each do |root|
|
101
|
+
@params.each do |param|
|
102
|
+
p_ctx = param.get_context(ctx.with_root(root))
|
103
|
+
if !p_ctx.nil? && !p_ctx.empty?
|
104
|
+
p_ctx.each do |p|
|
105
|
+
@constraints.each do |c|
|
106
|
+
r = c.test_constraint(ctx.with_root(p))
|
107
|
+
passed += r[0]
|
108
|
+
failed += r[1]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
if failed.empty?
|
116
|
+
return [ passed.uniq, [] ]
|
117
|
+
else
|
118
|
+
return [ (passed - failed).uniq, failed ]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Fabulator
|
2
|
+
module Core
|
3
|
+
class Parameter < Fabulator::Action
|
4
|
+
attr_accessor :name
|
5
|
+
|
6
|
+
namespace Fabulator::FAB_NS
|
7
|
+
|
8
|
+
attribute :name, :eval => false, :static => true
|
9
|
+
attribute :required, :static => true, :default => 'false'
|
10
|
+
|
11
|
+
def required?
|
12
|
+
@required
|
13
|
+
end
|
14
|
+
|
15
|
+
def param_names
|
16
|
+
[ @name ]
|
17
|
+
end
|
18
|
+
|
19
|
+
def compile_xml(xml, context)
|
20
|
+
super
|
21
|
+
|
22
|
+
@constraints = [ ]
|
23
|
+
@filters = [ ]
|
24
|
+
|
25
|
+
case @required.downcase
|
26
|
+
when 'yes':
|
27
|
+
@required = true
|
28
|
+
when 'true':
|
29
|
+
@required = true
|
30
|
+
when 'no':
|
31
|
+
@required = false
|
32
|
+
when 'false':
|
33
|
+
@required = false
|
34
|
+
end
|
35
|
+
|
36
|
+
xml.each_element do |e|
|
37
|
+
next unless e.namespaces.namespace.href == FAB_NS
|
38
|
+
case e.name
|
39
|
+
when 'constraint':
|
40
|
+
@constraints << Constraint.new.compile_xml(e, @context)
|
41
|
+
when 'filter':
|
42
|
+
@filters << Filter.new.compile_xml(e, @context)
|
43
|
+
when 'value':
|
44
|
+
@constraints << Constraint.new.compile_xml(e, @context)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_context(context)
|
51
|
+
context = [ context ] unless context.is_a?(Array)
|
52
|
+
context.collect{ |c| c.traverse_path(@name) }.flatten
|
53
|
+
end
|
54
|
+
|
55
|
+
def apply_filters(context)
|
56
|
+
filtered = [ ]
|
57
|
+
@context.with(context) do |ctx|
|
58
|
+
@filters.each do |f|
|
59
|
+
self.get_context(ctx).each do |cc|
|
60
|
+
filtered = filtered + f.run(ctx.with_root(cc))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
filtered
|
65
|
+
end
|
66
|
+
|
67
|
+
def apply_constraints(context)
|
68
|
+
res = { :missing => [], :invalid => [], :valid => [], :messages => [] }
|
69
|
+
ctx = @context.merge(context)
|
70
|
+
items = self.get_context(ctx)
|
71
|
+
#name = context.attribute(FAB_NS, 'name')
|
72
|
+
if items.empty?
|
73
|
+
if required?
|
74
|
+
res[:missing] = [ (ctx.root.path + '/' + @name).gsub(/\/+/, '/') ]
|
75
|
+
end
|
76
|
+
elsif @constraints.empty? # make sure something exists
|
77
|
+
res[:valid] = items
|
78
|
+
elsif @all_constraints
|
79
|
+
@constraints.each do |c|
|
80
|
+
items.each do |item|
|
81
|
+
r = c.test_constraint(ctx.with_root(i))
|
82
|
+
res[:valid] += r[0]
|
83
|
+
if !r[1].empty?
|
84
|
+
res[:invalid] += r[1]
|
85
|
+
res[:messages] += r[1].collect{ |i| c.error_message(i) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
else
|
90
|
+
items.each do |item|
|
91
|
+
passed = @constraints.select {|c| c.test_constraint(ctx.with_root(item))[1].empty? }
|
92
|
+
if passed.empty?
|
93
|
+
res[:invalid] << item
|
94
|
+
res[:messages] += @constraints.collect { |c| c.error_message(item) }
|
95
|
+
else
|
96
|
+
res[:valid] << item
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
return res
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_constraints(context)
|
105
|
+
@context.with(context) do |ctx|
|
106
|
+
me = ctx.traverse_path(@name)
|
107
|
+
return [ [ me.collect{ |m| m.path } ], [] ] if @constraints.empty?
|
108
|
+
paths = [ [], [] ]
|
109
|
+
if @all_constraints
|
110
|
+
@constraints.each do |c|
|
111
|
+
p = c.test_constraints(ctx, me)
|
112
|
+
paths[0] += p[0]
|
113
|
+
paths[1] += p[1]
|
114
|
+
end
|
115
|
+
return [ (paths[0] - paths[1]).uniq, paths[1].uniq ]
|
116
|
+
else
|
117
|
+
@constraints.each do |c|
|
118
|
+
p = c.test_constraints(ctx, me)
|
119
|
+
paths[0] += p[0]
|
120
|
+
paths[1] += p[1]
|
121
|
+
end
|
122
|
+
return [ paths[0].uniq, (paths[1] - paths[0]).uniq ]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Fabulator
|
2
|
+
module Core
|
3
|
+
class State < Fabulator::Action
|
4
|
+
attr_accessor :name, :transitions
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@transitions = []
|
8
|
+
@pre_actions = nil
|
9
|
+
@post_actions = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
namespace Fabulator::FAB_NS
|
13
|
+
attribute :name, :static => true
|
14
|
+
|
15
|
+
|
16
|
+
def compile_xml(xml, ctx)
|
17
|
+
super
|
18
|
+
|
19
|
+
inheriting = !@transitions.empty?
|
20
|
+
xml.each_element do |e|
|
21
|
+
next unless e.namespaces.namespace.href == FAB_NS
|
22
|
+
case e.name
|
23
|
+
when 'goes-to':
|
24
|
+
if inheriting
|
25
|
+
target = e.attributes.get_attribute_ns(FAB_NS, 'view').value
|
26
|
+
tags = (e.attributes.get_attribute_ns(FAB_NS, 'tag').value rescue '').split(/\s+/)
|
27
|
+
old = @transitions.collect{ |t| t.state == target && (tags.empty? || !(tags & t.tags).empty?)}
|
28
|
+
if old.empty?
|
29
|
+
@transitions << Transition.new.compile_xml(e,@context)
|
30
|
+
else
|
31
|
+
old.each do |t|
|
32
|
+
t.compile_xml(e,@context)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
@transitions << Transition.new.compile_xml(e, @context)
|
37
|
+
end
|
38
|
+
when 'before':
|
39
|
+
ActionLib.with_super(@pre_actions) do
|
40
|
+
t = @context.compile_actions(e)
|
41
|
+
@pre_actions = t if @pre_actions.nil? || !t.is_noop?
|
42
|
+
end
|
43
|
+
when 'after':
|
44
|
+
ActionLib.with_super(@post_actions) do
|
45
|
+
t = @context.compile_actions(e)
|
46
|
+
@post_actions = t if @post_actions.nil? || !t.is_noop?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def states
|
54
|
+
@transitions.map { |t| t.state }.uniq
|
55
|
+
end
|
56
|
+
|
57
|
+
def select_transition(context,params)
|
58
|
+
# we need hypthetical variables here :-/
|
59
|
+
best_match = nil
|
60
|
+
@context.with(context) do |ctx|
|
61
|
+
best_match = nil
|
62
|
+
@transitions.each do |t|
|
63
|
+
res = t.validate_params(ctx,params)
|
64
|
+
if res[:missing].empty? && res[:messages].empty? && res[:unknown].empty? && res[:invalid].empty?
|
65
|
+
res[:transition] = t
|
66
|
+
end
|
67
|
+
if best_match.nil? || res[:score] > best_match[:score]
|
68
|
+
best_match = res
|
69
|
+
best_match[:transition] = t
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return best_match
|
74
|
+
end
|
75
|
+
|
76
|
+
def run_pre(context)
|
77
|
+
# do queries, denials, assertions in the order given
|
78
|
+
ctx = context.class.new(@context, context)
|
79
|
+
@pre_actions.run(ctx) unless @pre_actions.nil?
|
80
|
+
return []
|
81
|
+
end
|
82
|
+
|
83
|
+
def run_post(context)
|
84
|
+
# do queries, denials, assertions in the order given
|
85
|
+
ctx = context.class.new(@context, context)
|
86
|
+
@post_actions.run(ctx) unless @post_actions.nil?
|
87
|
+
return []
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'xml/libxml'
|
3
|
+
|
4
|
+
module Fabulator
|
5
|
+
FAB_NS='http://dh.tamu.edu/ns/fabulator/1.0#'
|
6
|
+
RDFS_NS = 'http://www.w3.org/2000/01/rdf-schema#'
|
7
|
+
RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
|
8
|
+
RDFA_NS = 'http://dh.tamu.edu/ns/fabulator/rdf/1.0#'
|
9
|
+
|
10
|
+
class StateChangeException < Exception
|
11
|
+
end
|
12
|
+
|
13
|
+
module Core
|
14
|
+
|
15
|
+
class StateMachine
|
16
|
+
attr_accessor :states, :missing_params, :errors, :namespaces, :updated_at
|
17
|
+
attr_accessor :state
|
18
|
+
|
19
|
+
def compile_xml(xml, context = nil, callbacks = { })
|
20
|
+
# /statemachine/states
|
21
|
+
@states ||= { }
|
22
|
+
@state = 'start'
|
23
|
+
|
24
|
+
if xml.is_a?(String)
|
25
|
+
xml = LibXML::XML::Document.string xml
|
26
|
+
end
|
27
|
+
|
28
|
+
if context.nil?
|
29
|
+
@context = Fabulator::Expr::Context.new.merge(xml.root)
|
30
|
+
else
|
31
|
+
@context = context.merge(xml.root)
|
32
|
+
end
|
33
|
+
|
34
|
+
ActionLib.with_super(@actions) do
|
35
|
+
p_actions = @context.compile_actions(xml.root)
|
36
|
+
@actions = p_actions if @actions.nil? || !p_actions.is_noop?
|
37
|
+
end
|
38
|
+
|
39
|
+
xml.root.each_element do |child|
|
40
|
+
next unless child.namespaces.namespace.href == FAB_NS
|
41
|
+
case child.name
|
42
|
+
when 'view':
|
43
|
+
nom = (child.attributes.get_attribute_ns(FAB_NS, 'name').value rescue nil)
|
44
|
+
if !@states[nom].nil?
|
45
|
+
@states[nom].compile_xml(child, @context)
|
46
|
+
else
|
47
|
+
@states[nom] = State.new.compile_xml(child, @context)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if @states.empty?
|
53
|
+
s = State.new
|
54
|
+
s.name = 'start'
|
55
|
+
@states['start'] = s
|
56
|
+
end
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def clone
|
61
|
+
YAML::load( YAML::dump( self ) )
|
62
|
+
end
|
63
|
+
|
64
|
+
def namespaces
|
65
|
+
@context.ns
|
66
|
+
end
|
67
|
+
|
68
|
+
def init_context(c)
|
69
|
+
@context.root = c.root
|
70
|
+
begin
|
71
|
+
@actions.run(@context)
|
72
|
+
rescue Fabulator::StateChangeException => e
|
73
|
+
@state = e
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def context
|
78
|
+
{ :data => @context.root, :state => @state }
|
79
|
+
end
|
80
|
+
|
81
|
+
def fabulator_context
|
82
|
+
@context
|
83
|
+
end
|
84
|
+
|
85
|
+
def context=(c)
|
86
|
+
if c.is_a?(Fabulator::Expr::Context)
|
87
|
+
@context = c
|
88
|
+
elsif c.is_a?(Fabulator::Expr::Node)
|
89
|
+
@context.root = c
|
90
|
+
elsif c.is_a?(Hash)
|
91
|
+
@context.root = c[:data]
|
92
|
+
@state = c[:state]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def run(params)
|
97
|
+
current_state = @states[@state]
|
98
|
+
return if current_state.nil?
|
99
|
+
# select transition
|
100
|
+
# possible get some errors
|
101
|
+
# run transition, and move to new state as needed
|
102
|
+
@context.in_context do |ctx|
|
103
|
+
self.run_transition(current_state.select_transition(@context, params))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def run_transition(best_transition)
|
108
|
+
return if best_transition.nil? || best_transition.empty?
|
109
|
+
current_state = @states[@state]
|
110
|
+
t = best_transition[:transition]
|
111
|
+
@missing_params = best_transition[:missing]
|
112
|
+
@errors = best_transition[:messages]
|
113
|
+
if @missing_params.empty? && @errors.empty?
|
114
|
+
@state = t.state
|
115
|
+
# merge valid and context
|
116
|
+
best_transition[:valid].sort_by { |a| a.path.length }.each do |item|
|
117
|
+
p = item.path.gsub(/^[^:]+::/, '').split('/') - [ '' ]
|
118
|
+
n = @context.traverse_path(p, true).first
|
119
|
+
n.prune
|
120
|
+
n.copy(item)
|
121
|
+
end
|
122
|
+
# run_post of state we're leaving
|
123
|
+
begin
|
124
|
+
current_state.run_post(@context)
|
125
|
+
t.run(@context)
|
126
|
+
# run_pre for the state we're going to
|
127
|
+
new_state = @states[@state]
|
128
|
+
new_state.run_pre(@context) if !new_state.nil?
|
129
|
+
rescue Fabulator::StateChangeException => e # catch state change
|
130
|
+
new_state = @states[e]
|
131
|
+
begin
|
132
|
+
if !new_state.nil?
|
133
|
+
@state = new_state.name
|
134
|
+
new_state.run_pre(@context)
|
135
|
+
end
|
136
|
+
rescue Fabulator::StateChangeException => e
|
137
|
+
new_state = @states[e]
|
138
|
+
retry
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def data
|
145
|
+
@context.root
|
146
|
+
end
|
147
|
+
|
148
|
+
def state_names
|
149
|
+
(@states.keys.map{ |k| @states[k].states }.flatten + @states.keys).uniq
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|