commute 0.2.0.rc.2 → 0.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.todo +28 -12
  2. data/README.md +0 -1
  3. data/commute.gemspec +4 -5
  4. data/lib/commute/common/basic_auth.rb +10 -9
  5. data/lib/commute/common/caching.rb +208 -0
  6. data/lib/commute/common/chemicals.rb +47 -24
  7. data/lib/commute/common/eventmachine.rb +68 -0
  8. data/lib/commute/common/synchrony.rb +42 -0
  9. data/lib/commute/common/typhoeus.rb +64 -0
  10. data/lib/commute/core/api.rb +42 -29
  11. data/lib/commute/core/builder.rb +4 -15
  12. data/lib/commute/core/context.rb +156 -15
  13. data/lib/commute/core/http.rb +124 -0
  14. data/lib/commute/core/layer.rb +187 -0
  15. data/lib/commute/core/sequence.rb +83 -132
  16. data/lib/commute/core/stack.rb +63 -72
  17. data/lib/commute/core/status.rb +45 -0
  18. data/lib/commute/core/util/event_emitter.rb +58 -0
  19. data/lib/commute/core/util/path.rb +37 -0
  20. data/lib/commute/core/util/stream.rb +141 -0
  21. data/lib/commute/extensions/crud.rb +88 -0
  22. data/lib/commute/extensions/param.rb +20 -0
  23. data/lib/commute/extensions/url.rb +53 -0
  24. data/lib/commute/version.rb +1 -1
  25. data/spec/commute/common/caching_spec.rb +158 -0
  26. data/spec/commute/common/eventmachine_spec.rb +74 -0
  27. data/spec/commute/common/typhoeus_spec.rb +67 -0
  28. data/spec/commute/core/api_spec.rb +3 -1
  29. data/spec/commute/core/builder_spec.rb +8 -8
  30. data/spec/commute/core/http_spec.rb +39 -0
  31. data/spec/commute/core/layer_spec.rb +81 -0
  32. data/spec/commute/core/sequence_spec.rb +36 -150
  33. data/spec/commute/core/stack_spec.rb +33 -83
  34. data/spec/commute/core/util/event_emitter_spec.rb +35 -0
  35. data/spec/commute/core/util/path_spec.rb +29 -0
  36. data/spec/commute/core/util/stream_spec.rb +90 -0
  37. data/spec/commute/extensions/url_spec.rb +76 -0
  38. data/spec/spec_helper.rb +3 -1
  39. metadata +61 -48
  40. data/examples/gist_api.rb +0 -71
  41. data/examples/highrise_task_api.rb +0 -59
  42. data/examples/pastie_api.rb +0 -18
  43. data/lib/commute/aspects/caching.rb +0 -37
  44. data/lib/commute/aspects/crud.rb +0 -41
  45. data/lib/commute/aspects/pagination.rb +0 -16
  46. data/lib/commute/aspects/url.rb +0 -57
  47. data/lib/commute/common/cache.rb +0 -43
  48. data/lib/commute/common/conditional.rb +0 -27
  49. data/lib/commute/common/em-synchrony_adapter.rb +0 -29
  50. data/lib/commute/common/em_http_request_adapter.rb +0 -57
  51. data/lib/commute/common/typhoeus_adapter.rb +0 -40
  52. data/lib/commute/common/xml.rb +0 -7
  53. data/lib/commute/core/commuter.rb +0 -116
  54. data/lib/commute/core/processors/code_status_processor.rb +0 -40
  55. data/lib/commute/core/processors/hook.rb +0 -14
  56. data/lib/commute/core/processors/request_builder.rb +0 -26
  57. data/lib/commute/core/processors/sequencer.rb +0 -46
  58. data/lib/commute/core/request.rb +0 -58
  59. data/lib/commute/core/response.rb +0 -18
  60. data/spec/commute/aspects/caching_spec.rb +0 -12
  61. data/spec/commute/aspects/url_spec.rb +0 -61
  62. data/spec/commute/core/commuter_spec.rb +0 -64
  63. data/spec/commute/core/processors/code_status_processor_spec.rb +0 -5
  64. data/spec/commute/core/processors/hook_spec.rb +0 -25
  65. data/spec/commute/core/processors/request_builder_spec.rb +0 -25
  66. data/spec/commute/core/processors/sequencer_spec.rb +0 -33
@@ -1,16 +1,29 @@
1
- require 'commute/core/commuter'
1
+ require 'commute/core/layer'
2
2
 
3
3
  module Commute
4
4
 
5
- # Internal: A Sequence is an ordered list of processors, that all
6
- # work together to incrementally process a Commuter.
5
+ # Internal: A Sequence is an ordered list of Layers.
6
+ # It is a thin layer around an Array that enables it to
7
+ # add/remove/replace Layers more easily.
7
8
  #
8
- class Sequence
9
+ # Examples
10
+ #
11
+ # Sequence.new do
12
+ # append Commute::Common::Auth::Basic, as: :auth
13
+ # append Commute::Common::Json::Render, before: :auth
14
+ # replace :auth, Commute::Common::Auth::OAuth
15
+ # end
16
+ #
17
+ class Sequence < Array
9
18
 
10
- # Public: Initializes a sequence without processors.
11
- def initialize &alter
12
- @processors = []
13
- @index = {}
19
+ # Internal: Name of this Sequence.
20
+ attr_reader :name
21
+
22
+ # Public: Initializes a sequence without layers.
23
+ # Alters the Sequence if a block is given.
24
+ def initialize name, &alter
25
+ super()
26
+ @name = name
14
27
  self.alter &alter if block_given?
15
28
  end
16
29
 
@@ -30,151 +43,89 @@ module Commute
30
43
  return self
31
44
  end
32
45
 
33
- # Returns an duplicate collection of processors.
34
- def processors
35
- @processors.clone
36
- end
46
+ # Alias Array#insert.
47
+ alias :index_insert :insert
37
48
 
38
- # Internal: Processes a commuter with al processors in the sequence.
49
+ # Public: Appends a layer to a the list of layers.
39
50
  #
40
- # When a commuter provides parameters for a processor through the
41
- # parameters method, given the processor id. Then those parameters
42
- # are passed as an argument to the call method of the processor (if applicable).
51
+ # options - Optional parameters for layer naming and placement (default: {}):
52
+ # :as - The name of the layer in the sequence (optional).
53
+ # :after - A reference layer id after which the processor needs to be appended.
43
54
  #
44
- # Returns Nothing.
45
- def call commuter, options = {}
46
- call_one 0, commuter
47
- end
48
-
49
- def call_one index, commuter
50
- # Get the processor using the index.
51
- processor = @processors[index]
52
- # Immediately return if no processor is found.
53
- return nil unless processor
54
- # Identify the processor.
55
- # TODO: not very performant
56
- processor_id = @index.key processor
57
- # Immediately jumpt to the next processor if this one is disabled.
58
- if commuter.enabled? processor_id
59
- # Get the parameters.
60
- parameters = commuter.parameters(processor_id) if processor_id
61
- # Calling the processor.
62
- if parameters
63
- processor.call commuter, parameters
64
- else
65
- processor.call commuter
66
- end
67
- end
68
- # Call self for the next processor.
69
- call_next = Proc.new { call_one index + 1, commuter }
70
- if commuter.delayed?
71
- commuter.wait &call_next
72
- else
73
- call_next.call
74
- end
55
+ # Returns the appended processor.
56
+ def append callable, options = {}
57
+ # Create a layer.
58
+ layer = Layer.new(callable, options[:as])
59
+ # Index to insert at.
60
+ index = (options[:after] && layer_index(options[:after])+1) || -1
61
+ # Append the layer.
62
+ index_insert index, layer
63
+ # Return the appended layer.
64
+ layer
75
65
  end
76
66
 
77
- # Public: Appends a processor to a the list of processors.
67
+ # Public: Inserts a layer to a the list of layers.
78
68
  #
79
- # options - Optional parameters for processor naming and placement (default: {}):
80
- # :as - The name of the processor in the sequence (optional).
81
- # :after - A reference processor id after which the processor needs to be appended.
69
+ # options - Optional parameters for layer naming and placement (default: {}):
70
+ # :as - The name of the layer in the sequence (optional).
71
+ # :after - A reference layer id before which the processor needs to be inserted.
82
72
  #
83
73
  # Returns the appended processor.
84
- def append processor, options = {}
85
- # When a reference is given, append after it.
86
- if options[:after]
87
- @processors.insert id_index(options[:after]) + 1, processor
88
- else
89
- @processors << processor
90
- end
91
- # Identify the processor using different methods.
92
- id = identify processor, options[:as]
93
- # Add the processor to the index for direct access purposes.
94
- @index[id] = processor if id
95
- # Return the appended processor.
96
- processor
97
- end
98
-
99
- # TODO
100
- def insert processor, options = {}
101
- # When a reference is given, insert before it.
102
- if options[:before]
103
- @processors.insert id_index(options[:before]) - 1, processor
104
- else
105
- @processors.unshift processor
106
- end
107
- # Identify the processor using different methods.
108
- id = identify processor, options[:as]
109
- # Add the processor to the index for direct access purposes.
110
- @index[id] = processor if id
111
- # Return the appended processor.
112
- processor
74
+ def insert callable, options = {}
75
+ # Create a layer.
76
+ layer = Layer.new(callable, options[:as])
77
+ # Index to insert at.
78
+ index = (options[:before] && layer_index(options[:before])) || 0
79
+ # Append the layer.
80
+ index_insert index, layer
81
+ # Return the appended layer.
82
+ layer
113
83
  end
114
84
 
115
- # TODO TEST
116
- def replace processor_id
117
- processor = at processor_id
118
- raise "processor does not exist #{processor_id}" unless processor
119
- replacement = yield processor
120
- @processors[@processors.index processor] = replacement
121
- @index[processor_id] = replacement
122
- end
123
-
124
- # Public: Removes a processor from the sequence.
85
+ # Public: Removes a layer from the sequence.
125
86
  #
126
- # When a processor object is given, the processor is removed. When its
127
- # Symbol identifier is given, the associated processor is removed.
87
+ # name - The name of the layer to be removed.
128
88
  #
129
- # Returns the removed processor.
130
- def remove processor
131
- if processor.is_a?(Symbol)
132
- processor_id = processor
133
- processor = at(processor)
134
- @processors.delete processor
135
- @index.delete processor_id
136
- else
137
- # Remove from list of processors.
138
- @processors.delete processor
139
- # Remove from index (if necessary).
140
- @index.delete_if { |id, pr| pr == processor}
141
- end
142
- # Return the deleted processor.
143
- processor
89
+ # Returns the removed layer.
90
+ def remove name
91
+ # Remove the layer.
92
+ delete_at layer_index(name)
144
93
  end
145
94
 
146
- # Public
147
- # Returns the processor with the given id.
95
+ # Public: Replaces a layer in the sequence.
148
96
  #
149
- # id - The Symbol identifier of a processor.
150
- def at id
151
- @index[id]
97
+ # name - The name of the layer to be replaced.
98
+ # layer - The callable to replace it with.
99
+ #
100
+ # Returns the new layer.
101
+ def replace name, callable
102
+ self[layer_index name] = Layer.new callable, name
152
103
  end
153
104
 
154
- # Internal: Clones the sequence.
155
- def dup
156
- Sequence.new.tap do |sequence|
157
- sequence.processors = @processors.clone
158
- sequence.index = @index.clone
159
- end
105
+ # Internal: Lazily adds the sequence (as a Layer)
106
+ # with this name to the router path.
107
+ #
108
+ # Lazily here means that when this sequence is added as a Layer
109
+ # it is not necessarely complete yet. Layers could be appended or
110
+ # removed later on. Therefor, the sequence is fetched from
111
+ # the current context.
112
+ #
113
+ def call router, request, options = {}
114
+ context = request.http.context
115
+ router << context.stack.sequence(name).reject { |layer|
116
+ !layer.callable?(context)
117
+ }.each
118
+ router.succ.call router, request
160
119
  end
161
120
 
162
121
  private
163
122
 
164
- def identify processor, override = nil
165
- if override
166
- override
167
- else
168
- processor.class.instance_variable_get :@id
169
- end
170
- end
171
-
172
- def id_index id
173
- @processors.index at(id)
123
+ # Internal: Returns the index of a layer.
124
+ #
125
+ # name - The name of the layer.
126
+ #
127
+ def layer_index name
128
+ index { |layer| layer.name == name }
174
129
  end
175
-
176
- protected
177
-
178
- attr_writer :processors, :index
179
130
  end
180
131
  end
@@ -1,76 +1,61 @@
1
1
  require 'commute/core/sequence'
2
+ require 'commute/core/util/path'
2
3
 
3
4
  module Commute
4
5
 
5
6
  # Internal: A stack is a collection of sequences with
6
7
  # A main sequence uses as a starting point for processing.
7
8
  #
8
- # When the stack is called with a processable, it gets
9
+ # When the stack is called with a request, it gets
9
10
  # processed by the main sequence. That main sequence
10
11
  # can easily "embed" other sequences by adding them as
11
- # a processer (a sequence is also a processor).
12
+ # a layer (a sequence is also a layer (composite)).
12
13
  #
13
14
  # Example:
14
15
  #
15
- # stack = Stack.new do |stack, sequence|
16
- # stack.sequence(:calculation) do
17
- # append Proc.new { |n| n.data += 1 }
18
- # append Proc.new { |n| n.data *= 2 }
16
+ # stack = Stack.new do
17
+ # sequence(:request_body) do
18
+ # append Commute::Common::Json::Render
19
+ # append Commute::Common::Auth::Basic
19
20
  # end
20
21
  #
21
- # sequence.append stack.sequencer(:calculation)
22
- # sequence.append Proc.new { |p| p.process { |n| "The result is #{n}" }}
22
+ # sequence.append sequence(:request_body)
23
23
  # end
24
24
  #
25
- # stack.call Processable.new(context, 1)
26
- # # => "The result is 4"
25
+ # request = stack.call Request.new(context) do |response, status|
26
+ # # Process the response.
27
+ # end
28
+ # request.write data
29
+ # request.end
27
30
  #
28
31
  class Stack
29
32
 
30
- # Public: Constructs a stack with a main sequence.
31
- #
32
- # sequence - Sequence used as main sequence.
33
- #
34
- # Yields
35
- # 1. The Stack itself. (If the arity of the block is 2)
36
- # 2. a new sequence to use a main sequence when no
37
- # sequence is explicitely given.
33
+ # Internal: A Router that routes http request to the next layer.
38
34
  #
39
- # Raises an error if neither is given.
35
+ # The router wraps the Http Request in a Layer Request and
36
+ # attaches the correct handler to the response event.
40
37
  #
41
- # Returns a Stack with a main sequence.
42
- def initialize sequence = nil, &main
43
- @sequences = {}
44
- @main = if sequence
45
- sequence
46
- else
47
- if main.arity == 1
48
- Sequence.new &main
49
- else
50
- sequence = Sequence.new
51
- yield self, sequence
52
- sequence
38
+ class Router < Path
39
+
40
+ # Public: Call the next layer.
41
+ #
42
+ # http - Http Request to send to the next layer on the Path.
43
+ #
44
+ # Yields a response and a status.
45
+ # Returns the Layer Request.
46
+ def call http, &on_response
47
+ Layer::Request.new(http).tap do |request|
48
+ request.on :response, &on_response
49
+ succ.call self, request
53
50
  end
54
51
  end
55
52
  end
56
53
 
57
- # Public: Adds a sequence to the stack.
58
- #
59
- # sequence - The Sequence to be added.
60
- # name - The name of the sequence.
61
- #
62
- # Returns the Sequence that was added.
63
- def add sequence, name
64
- @sequences[name] = sequence
65
- end
66
-
67
- # Public: Get a sequence from the stack.
68
- #
69
- # name - The name of the wanted Sequence.
70
- #
71
- # Returns the asked sequence.
72
- def get name
73
- @sequences[name]
54
+ # Public: Constructs a stack with a main sequence.
55
+ # Akter the Stack if a block is given.
56
+ def initialize &alter
57
+ @sequences = {}
58
+ self.alter &alter if block_given?
74
59
  end
75
60
 
76
61
  # Public: Manipulate an either existing or new sequence.
@@ -84,25 +69,10 @@ module Commute
84
69
  #
85
70
  # Returns the Sequence.
86
71
  def sequence name = nil, &alter
87
- if name
88
- @sequences[name] || add(Sequence.new(&alter), name)
89
- else
90
- @main
91
- end
92
- end
93
-
94
- # Public: Creates a processor that lazily fetches a named sequence
95
- # on a call.
96
- #
97
- # name - The name of the sequence this sequencer stands in for.
98
- #
99
- # Returns a Sequencer
100
- def sequencer name = nil
101
- if name
102
- Sequencer.new.here(name)
103
- else
104
- Sequencer.new
105
- end
72
+ name ||= :main
73
+ sequence = @sequences[name] || add(Sequence.new name)
74
+ sequence.alter &alter if block_given?
75
+ sequence
106
76
  end
107
77
 
108
78
  # Internal: Calls the stack aka call the main sequence.
@@ -110,8 +80,9 @@ module Commute
110
80
  # processable - The thing to process.
111
81
  #
112
82
  # Returns the processable
113
- def call processable
114
- @main.call processable
83
+ def call request, &on_response
84
+ router = Router.new([sequence].each)
85
+ router.call request, &on_response
115
86
  end
116
87
 
117
88
  # Public: Alters the stack.
@@ -124,19 +95,39 @@ module Commute
124
95
  if alter.arity == 0
125
96
  instance_eval &alter
126
97
  else
127
- yield self, @main
98
+ yield self
128
99
  end
129
100
  # Return the Stack.
130
101
  return self
131
102
  end
132
103
 
133
- # Internal: Clones the sequence.
104
+ # Internal: Duplicates the stack.
134
105
  def dup
135
- Stack.new(@main.dup).tap do |stack|
106
+ Stack.new.tap do |stack|
136
107
  stack.sequences = Hash[@sequences.map { |k,v| [k, v.dup] }]
137
108
  end
138
109
  end
139
110
 
111
+ private
112
+
113
+ # Internal: Adds a sequence to the stack.
114
+ #
115
+ # sequence - The Sequence to be added.
116
+ #
117
+ # Returns the Sequence that was added.
118
+ def add sequence
119
+ @sequences[sequence.name] = sequence
120
+ end
121
+
122
+ # Internal: Get a sequence from the stack.
123
+ #
124
+ # name - The name of the wanted Sequence.
125
+ #
126
+ # Returns the asked sequence.
127
+ def get name
128
+ @sequences[name]
129
+ end
130
+
140
131
  protected
141
132
 
142
133
  attr_writer :sequences
@@ -0,0 +1,45 @@
1
+ module Commute
2
+
3
+ # Public: A Generic status object that can represent
4
+ # either a positive or negative status.
5
+ #
6
+ # When the status is negative, an additional error
7
+ # detail can be provided.
8
+ #
9
+ # Examples
10
+ #
11
+ # Status.new false, { reason: 'No access' }
12
+ # Status.ok # Same as Status.new true
13
+ #
14
+ class Status
15
+
16
+ # Public: Creates a positive status.
17
+ #
18
+ def self.ok
19
+ self.new
20
+ end
21
+
22
+ # Public: Creates a new status object.
23
+ #
24
+ # success - Whether the status is positive or not (default: true).
25
+ # error - Optional error detail (can be anything).
26
+ #
27
+ def initialize success = true, error = nil
28
+ @success = success
29
+ end
30
+
31
+ # Public: Check if a status is a success.
32
+ #
33
+ # Returns true if the status is positive.
34
+ def success?
35
+ @success
36
+ end
37
+
38
+ # Public: Check if a status is a fail.
39
+ #
40
+ # Returns true if the status is negative.
41
+ def fail?
42
+ !success?
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,58 @@
1
+ module Commute
2
+
3
+ # Internal: A Simple EventEmitter modelled after node.js' EventEmitter.
4
+ # More info: http://nodejs.org/api/events.html
5
+ #
6
+ # Include this module in any model that wants to send events to its listeners.
7
+ #
8
+ # Listen to events using `on(:event)`. In the model, notify listeners
9
+ # by calling `emit(:event, ...)` with some arguments.
10
+ module EventEmitter
11
+
12
+ # Internal: Initializes an empty handler hash.
13
+ def initialize *args
14
+ super *args
15
+ @handlers = Hash.new { |h, k| h[k] = [] }
16
+ end
17
+
18
+ # Internal: Subscribe a handler to an event.
19
+ #
20
+ # event - The name of the event to listen for.
21
+ # handler - The proc to call when this event occurs.
22
+ #
23
+ # Returns Nothing.
24
+ def on event, &handler
25
+ @handlers[event] << handler
26
+ nil
27
+ end
28
+
29
+ # Internal: Subscribe a handler to an event and
30
+ # unsuscribe it when the event fired once.
31
+ #
32
+ def once event, &handler
33
+ on event do |*args, &block|
34
+ handler.call *args, &block
35
+ remove event, handler
36
+ end
37
+ end
38
+
39
+ # Internal: Remove an event handler.
40
+ def remove event, handler
41
+ @handlers[event].delete handler
42
+ end
43
+
44
+ protected
45
+
46
+ # Internal: Emit an event to all handlers.
47
+ #
48
+ # event - The name of the event.
49
+ # args - Arguments to send to the listeners.
50
+ #
51
+ # Returns the notified handlers.
52
+ def emit event, *args
53
+ @handlers[event].each do |handler|
54
+ handler.call *args
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,37 @@
1
+ module Commute
2
+
3
+ # Internal: A Path is that wraps some enums and
4
+ # returns there values in the order that you added them.
5
+ #
6
+ class Path
7
+
8
+ # Internal: Initialize the path with a main route.
9
+ #
10
+ # main - An enumerable to start the path with.
11
+ #
12
+ def initialize main
13
+ @path = []
14
+ self << main
15
+ end
16
+
17
+ # Internal: Push a route on the path (detour).
18
+ #
19
+ # route - An enumerable to walk first before continuing.
20
+ def << route
21
+ @path << route
22
+ end
23
+
24
+ # Internal: Figures out the successor in the path.
25
+ #
26
+ # Returns the next stop on the path.
27
+ def succ
28
+ begin
29
+ return nil if @path.empty?
30
+ @path.last.next
31
+ rescue StopIteration
32
+ @path.pop
33
+ retry
34
+ end
35
+ end
36
+ end
37
+ end