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.
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