commute 0.2.0.rc.2 → 0.3.0.pre

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