fabulator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +41 -0
  3. data/README.rdoc +61 -0
  4. data/Rakefile +54 -0
  5. data/lib/fabulator.rb +5 -0
  6. data/lib/fabulator/action.rb +46 -0
  7. data/lib/fabulator/action_lib.rb +438 -0
  8. data/lib/fabulator/context.rb +39 -0
  9. data/lib/fabulator/core.rb +8 -0
  10. data/lib/fabulator/core/actions.rb +514 -0
  11. data/lib/fabulator/core/actions/choose.rb +167 -0
  12. data/lib/fabulator/core/actions/for_each.rb +105 -0
  13. data/lib/fabulator/core/actions/variables.rb +52 -0
  14. data/lib/fabulator/core/constraint.rb +117 -0
  15. data/lib/fabulator/core/filter.rb +41 -0
  16. data/lib/fabulator/core/group.rb +123 -0
  17. data/lib/fabulator/core/parameter.rb +128 -0
  18. data/lib/fabulator/core/state.rb +91 -0
  19. data/lib/fabulator/core/state_machine.rb +153 -0
  20. data/lib/fabulator/core/transition.rb +164 -0
  21. data/lib/fabulator/expr.rb +37 -0
  22. data/lib/fabulator/expr/axis.rb +133 -0
  23. data/lib/fabulator/expr/axis_descendent_or_self.rb +26 -0
  24. data/lib/fabulator/expr/bin_expr.rb +178 -0
  25. data/lib/fabulator/expr/context.rb +368 -0
  26. data/lib/fabulator/expr/for_expr.rb +74 -0
  27. data/lib/fabulator/expr/function.rb +52 -0
  28. data/lib/fabulator/expr/if_expr.rb +22 -0
  29. data/lib/fabulator/expr/let_expr.rb +17 -0
  30. data/lib/fabulator/expr/literal.rb +39 -0
  31. data/lib/fabulator/expr/node.rb +216 -0
  32. data/lib/fabulator/expr/node_logic.rb +99 -0
  33. data/lib/fabulator/expr/parser.rb +1470 -0
  34. data/lib/fabulator/expr/path_expr.rb +49 -0
  35. data/lib/fabulator/expr/predicates.rb +45 -0
  36. data/lib/fabulator/expr/statement_list.rb +96 -0
  37. data/lib/fabulator/expr/step.rb +43 -0
  38. data/lib/fabulator/expr/unary_expr.rb +30 -0
  39. data/lib/fabulator/expr/union_expr.rb +21 -0
  40. data/lib/fabulator/template.rb +9 -0
  41. data/lib/fabulator/template/context.rb +51 -0
  42. data/lib/fabulator/template/parse_result.rb +153 -0
  43. data/lib/fabulator/template/parser.rb +17 -0
  44. data/lib/fabulator/template/standard_tags.rb +95 -0
  45. data/lib/fabulator/template/taggable.rb +88 -0
  46. data/lib/fabulator/version.rb +14 -0
  47. data/test/test_fabulator.rb +17 -0
  48. data/test/test_helper.rb +24 -0
  49. data/xslt/form.xsl +2161 -0
  50. 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